Security Policy
When several packages are installed into one CLI — locally, or in a cloud deployment that bundles many packages — you often want to expose only a curated subset of commands while keeping the rest available as internal building blocks. The security policy controls exactly this.
It answers two questions about every command:
- Callability — can it be invoked directly? (allow / ask / deny)
- Visibility — is it listed in help and autocomplete? (a denied command is hidden)
A key property: deny blocks direct invocation only. A command that is exposed can still call a denied command from its own execute steps, so denied commands remain usable building blocks rather than dead ends.
A runtime decision
The policy is imposed by whoever runs the CLI. It is never declared inside a package — a package cannot grant itself exposure. This is what makes it a real boundary: in the cloud the platform sets the policy and the running package cannot see or change it.
1config:
2 security:
3 deny: ["*"] # nothing is directly callable...
4 allow: ["db *"] # ...except commands under the db profile
5 ask: ["deploy *"] # prompt before running (denied when non-interactive)Pattern notation
Patterns are globs matched against the command path as typed after aux4, space-joined and without the aux4 prefix:
| Pattern | Matches |
|---|---|
| * | everything |
| db * | every command under the db profile, at any depth |
| db query | exactly that one command |
| ai agent config * | everything under the nested ai agent config path |
* matches any run of characters, spaces included.
How a decision is made
Most specific match wins. The pattern with the fewest wildcards (longest literal prefix) decides; on a tie, deny beats ask beats allow (fail closed).
This gives two natural idioms with the same mechanism:
1# allow-list: expose only db
2security:
3 deny: ["*"]
4 allow: ["db *"]
5
6# deny-list: expose everything except one command
7security:
8 deny: ["db secret"]- deny: ["*"] + allow: ["db *"] → only db commands are callable; everything else is denied and hidden.
- deny: ["db secret"] + allow: ["db *"] → db is exposed, but db secret is hidden — the exact deny is more specific than the db * allow.
Routers stay navigable. A profile that leads to an allowed command is kept visible so you can reach it. With deny: ["*"] + allow: ["grp *"], the grp router is still listed and aux4 grp one runs, while unrelated siblings are hidden.
deny hides
A denied command is:
- left out of aux4 --help, aux4 --help --json, and shell autocomplete, and
- reported as Command not found if invoked directly.
There is no separate "hidden" list — hiding follows from being denied. (The command-level private flag remains available for the separate case of a command that is callable but not listed.)
Sources and precedence
A policy can be tightened but never loosened by a lower-trust source — otherwise the boundary would be worthless. So security is resolved with the precedence env → config → param, the reverse of aux4's normal "a param overrides config" rule.
| Source | How to set it | Strength |
|---|---|---|
| AUX4_SECURITY environment variable | a JSON string | Authoritative. Inherited by subprocess shell-outs and unreachable from a param — the real boundary, used in the cloud. |
| Config file | the security: key, read with --configFile / --config | Protects the top-level call, but does not propagate to a subprocess shell-out. Best for local use. |
| Parameter | --security '{...}' or --security.allow "db *" | Only consulted when neither of the above defines a policy. |
1# expose only db, imposed from the environment
2export AUX4_SECURITY='{"deny":["*"],"allow":["db *"]}'
3
4aux4 db query # runs
5aux4 other # Command not found
6
7# a param cannot loosen an imposed policy
8aux4 other --security '{"allow":["*"]}' # still Command not foundThe security parameter is reserved: it never reaches the command being run and never forwards through value(*) / object(*), so a package can neither read nor re-broadcast the policy it is running under.
Internal calls and the pipe form
Internal command-to-command calls are exempt from the block, so an exposed command keeps working even when it depends on denied commands:
1{
2 "name": "report",
3 "execute": ["aux4 db query --sql '...'", "echo done"]
4}Here report may be allowed while db query is denied — report still runs.
This exemption applies to the in-process form (aux4 other with no shell metacharacters). A call that is piped or redirected (aux4 other | jq) shells out to a fresh top-level process and is re-evaluated by the policy. Keep internal calls in the plain form to stay exempt.