Command patterns and sudo
A token can run only the commands you list for it. Each allowed command rule is compared against the command line your pipeline submits, not the command that ends up running as root, so a command that starts with sudo needs sudo in its rule too.
Getting a privileged command through takes two separate permissions: a rule that allows the command line, and authorization to elevate on the server. They live in different places and follow opposite conventions, which is the most common reason an automated deployment stalls here.
Allowed commands are configured per token. See API access token and Service tokens.
How a command rule is matched
- The rule is compared against the command line you submit, from beginning to end. A partial match is not enough.
*is the only wildcard. It stands for any run of characters.- If the command line chains several commands with
&&,||,|,;, or a redirection, each part is matched separately and every part must be allowed by some rule. - Quotes are removed and extra spacing is collapsed before the comparison, so
echo "hello world"is matched asecho hello world, andecho hello(two spaces) is matched asecho hello. - If nothing matches, the request is rejected with
api_token_acl_not_allowedand nothing runs on the server.
A trailing wildcard needs an argument
A rule ending in * requires at least one argument, because the space before the wildcard is part of the rule.
| Rule | Allows | Does not allow |
|---|---|---|
docker | docker | docker ps |
docker * | docker ps, docker compose up -d | docker (no arguments) |
systemctl status * | systemctl status nginx | systemctl restart nginx |
To allow both the bare command and its arguments, add two rules: docker and docker *.
Username and group on a rule
Each rule also pins which system user and group may run it.
| Token type | Username left empty | Username * | Username deploy |
|---|---|---|---|
| API access token | Only the token owner | Any user | Only deploy |
| Service token | Any user | Any user | Only deploy |
A service token belongs to an application, not to a person, so there is no owner to fall back on. Leaving the username empty on a service token rule allows any system user. Set it explicitly when the rule should be pinned to one account.
Write the rule exactly as you run it
Because the rule is compared against the line you submit, not the command that runs as root, a rule for a privileged command has to contain sudo.
| Command you run | Rule that allows it | Rule that does not |
|---|---|---|
sudo systemctl restart nginx | sudo systemctl restart nginx | systemctl restart nginx |
sudo -n systemctl restart nginx | sudo -n systemctl restart nginx | sudo systemctl restart nginx |
sudo docker ps | sudo docker * | docker * |
sudo X and sudo -n X are different command lines, so each needs its own rule. Register the line your pipeline actually runs: if a job adds -n for non-interactive use or -u to run as another account, that flag is part of the line being compared.
Why wildcards with sudo are dangerous
A rule of sudo * grants unrestricted root access. Wildcards match across quoting, so that rule also allows sudo bash -c "rm -rf /". sudo bash -c * and sudo sh -c * have the same effect.
List the exact privileged commands instead:
sudo systemctl restart nginx
sudo systemctl reload nginxCommand rules and sudo policies are opposites
Alpacon has a second place where command patterns are written: a sudo policy, which decides who may elevate on which servers. The two look alike, but they are checked at different moments against different text, so the same command needs the opposite pattern in each.
| Allowed command rule | Sudo policy | |
|---|---|---|
| Applies to | one token | Alpacon users on selected servers |
| Checked | before the command is sent to the server | when the server asks Alpacon to approve the elevation |
| Compared against | the line as you wrote it, sudo included | the command that runs as root, with sudo already removed |
Pattern for sudo systemctl restart nginx | sudo systemctl restart nginx | systemctl restart nginx |
A leading sudo in the pattern | required | rejected when you save the policy |
| On no match | request rejected, nothing runs | elevation refused, sudo exits with an error |
Saving a sudo policy whose pattern starts with sudo fails with sudo_policy_pattern_has_sudo_prefix. That is deliberate: such a pattern could never match anything, and rejecting it as you type is better than a silent denial weeks later.
See sudo with MFA for how sudo policies are created and used.
What happens when a token runs sudo
Passing the allowed-command rule only gets the command delivered. Elevation is decided again on the server, at the moment sudo runs.
Service tokens. Once the command clears its allowed-command rule, elevation is decided automatically from the command’s assessed risk: allowed at every level below Critical, refused at Critical with SUDO_RISK_DENIED. A command that could not be assessed is allowed too, so an outage in risk assessment does not stall the pipeline. Nothing is queued for a reviewer, because an unattended pipeline cannot answer an approval prompt. Every automatic elevation is recorded in Sudo as a Low-risk command grant, attributed to the application that owns the token.
Sudo policies do not apply to a service token. A sudo policy targets Alpacon users, and a service token is not one. Writing a policy will not change the outcome above.
Administrators can tighten this. A workspace can require refusal at a lower risk level than Critical. It cannot make a token wait for a human reviewer.
There is no sudo permission to grant. No resource offers a sudo action in the token scope catalog, so elevation is not something you enable on the application’s role or on the token’s scopes. The allowed-command entry and the command’s assessed risk decide it.
API access tokens are different. A personal API access token cannot elevate on its own. Run the command inside a work session that includes the sudo scope and is covered by a sudo policy, or move the workload to a service token.
On self-hosted deployments, elevation from a token is not enabled by default and sudo is refused with SUDO_COMMAND_NOT_AUTHORIZED. Ask your administrator whether it is enabled for your deployment.
Denial messages
When elevation is refused, the terminal prints Alpacon denied this sudo command (CODE). and sudo exits with a non-zero status.
| Code | Meaning | What to do |
|---|---|---|
SUDO_RISK_DENIED | The command was assessed Critical, or above the level your workspace allows. | Split it into narrower commands, or run it through a work session with approval. |
SUDO_COMMAND_NOT_AUTHORIZED | Elevation from a token is not enabled on this deployment. | Self-hosted default. Ask your administrator. |
SUDO_NO_WORKSESSION_POLICY | No sudo policy matched this command. | Use a service token, or run inside a work session covered by a sudo policy. |
SUDO_POLICY_MFA_REQUIRED | A sudo policy matched but requires MFA, which an unattended job cannot complete. | Bind the policy to a work session that allows the MFA prompt to be skipped. |
WORK_SESSION_SCOPE_NOT_ALLOWED | The work session does not include the sudo scope. | Request the sudo scope when creating the session. |
A rejection that happens before the command is sent is a different failure: the API responds with api_token_acl_not_allowed and nothing reaches the server. That means no allowed-command rule matched the line—go back to Write the rule exactly as you run it.
When elevation is not available
If your deployment refuses token elevation, or the command is assessed Critical, there are three ways forward.
- Remove the need for sudo. Give the account the token runs as direct control of what it manages—a systemd user unit for the service, or membership in the group that owns the files. Nothing is elevated, so nothing has to be approved. This is the narrowest option and the one to try first.
- Run inside a work session. A work session with the sudo scope keeps the approval and audit trail intact. The trade-off is that a person has to request the session, so the deployment is no longer fully unattended.
- Grant the command in the host’s own sudoers file. The job keeps running unattended, but that host’s elevation is decided outside Alpacon: those commands do not appear in Sudo and no policy or approval applies to them. Treat this as a last resort, and only for a host you have deliberately excluded from Alpacon’s privileged-access controls.