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 as echo hello world, and echo hello (two spaces) is matched as echo hello.
  • If nothing matches, the request is rejected with api_token_acl_not_allowed and 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.

RuleAllowsDoes not allow
dockerdockerdocker ps
docker *docker ps, docker compose up -ddocker (no arguments)
systemctl status *systemctl status nginxsystemctl 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 typeUsername left emptyUsername *Username deploy
API access tokenOnly the token ownerAny userOnly deploy
Service tokenAny userAny userOnly 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 runRule that allows itRule that does not
sudo systemctl restart nginxsudo systemctl restart nginxsystemctl restart nginx
sudo -n systemctl restart nginxsudo -n systemctl restart nginxsudo systemctl restart nginx
sudo docker pssudo 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 nginx

Command 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 ruleSudo policy
Applies toone tokenAlpacon users on selected servers
Checkedbefore the command is sent to the serverwhen the server asks Alpacon to approve the elevation
Compared againstthe line as you wrote it, sudo includedthe command that runs as root, with sudo already removed
Pattern for sudo systemctl restart nginxsudo systemctl restart nginxsystemctl restart nginx
A leading sudo in the patternrequiredrejected when you save the policy
On no matchrequest rejected, nothing runselevation 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.

CodeMeaningWhat to do
SUDO_RISK_DENIEDThe 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_AUTHORIZEDElevation from a token is not enabled on this deployment.Self-hosted default. Ask your administrator.
SUDO_NO_WORKSESSION_POLICYNo sudo policy matched this command.Use a service token, or run inside a work session covered by a sudo policy.
SUDO_POLICY_MFA_REQUIREDA 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_ALLOWEDThe 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.
Last updated: