# 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.

```yaml
config:
  security:
    deny:  ["*"]         # nothing is directly callable...
    allow: ["db *"]      # ...except commands under the db profile
    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:

```yaml
# allow-list: expose only db
security:
  deny:  ["*"]
  allow: ["db *"]

# deny-list: expose everything except one command
security:
  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. |

```bash
# expose only db, imposed from the environment
export AUX4_SECURITY='{"deny":["*"],"allow":["db *"]}'

aux4 db query                              # runs
aux4 other                                 # Command not found

# a param cannot loosen an imposed policy
aux4 other --security '{"allow":["*"]}'    # still Command not found
```

The `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:

```json
{
  "name": "report",
  "execute": ["aux4 db query --sql '...'", "echo done"]
}
```

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.
