> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ironrun.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuring Approved Commands and Secrets in ironrun.yml

> ironrun.yml defines approved commands, secret references, timeouts, and security constraints. Learn every field with real-world YAML examples.

`ironrun.yml` is the single YAML file that controls what commands agents can run and which secrets they receive. Every command the agent can call via `run_sealed` must appear in this file, along with any secret references it needs. Humans write and maintain the policy; agents can only propose additions with `propose_command`, which does nothing until a human approves it with `ironrun approve`.

## Version 2 policy (current)

Version 2 policies use the encrypted environment vault for all secret storage. Secrets are referenced by name — the same name you used when you ran `ironrun add` or `ironrun import`. Storing or rotating a value never requires a policy edit.

```yaml theme={null}
version: "2"
environment_set: active
require_agent_leases: true
allow_proposals: true

commands:
  - id: dev
    argv: [npm, run, dev]
    ttl: 0
    secrets: [DATABASE_URL, STRIPE_SECRET_KEY]

  - id: test
    argv: [npm, test]
    ttl: 120s
    secrets: [DATABASE_URL, STRIPE_SECRET_KEY]

  - id: build
    argv: [npm, run, build]
    ttl: 120s
```

Run `ironrun setup` to generate a starter v2 policy for your project. It detects your stack and pre-fills commands from your existing `.env` key names.

## Version 1 policy

Version 1 policies use a top-level `provider:` field and reference secrets by provider path in each command's `env:` block. They remain fully supported. Use `ironrun migrate` to preview a transactional migration to v2.

```yaml theme={null}
version: "1"
provider: 1password

commands:
  - id: test
    argv: [npm, test]
    ttl: 120s
    env:
      DATABASE_URL: "op://Engineering/staging-db/url"

  - id: deploy
    argv: [./scripts/deploy.sh, production]
    ttl: 10m
    no_network: false
    env:
      FLY_API_TOKEN: "op://Engineering/fly/token"
```

## Field reference

### Top-level fields

<ParamField path="version" type="string" required>
  Policy schema version. Use `"2"` for new policies. `"1"` remains supported for existing policies.
</ParamField>

<ParamField path="provider" type="string">
  Where secrets come from. Required for v1 policies. See [Secret Providers](/concepts/secret-providers) for the full list of supported values and reference formats.

  Examples: `1password`, `envfile:~/.secrets/myapp.env`, `doppler`, `vault`, `infisical`, `env`, `passthrough`
</ParamField>

<ParamField path="environment_set" type="string">
  Set to `"active"` to use the encrypted vault managed by `ironrun env` and `ironrun new`. This is the default for v2 policies.
</ParamField>

<ParamField path="require_agent_leases" type="boolean">
  When `true`, agents must call `request_lease` and wait for human approval before `run_sealed` will execute any command. Useful for sensitive or production environments. Defaults to `false` for backwards compatibility.
</ParamField>

<ParamField path="allow_proposals" type="boolean">
  When `true`, agents can call `propose_command` to stage a new command for human approval. The command is not runnable until a human runs `ironrun approve <id>`. Defaults to `false`.
</ParamField>

<ParamField path="audit_log" type="string">
  Path to the audit log file. Defaults to `$XDG_STATE_HOME/ironrun/audit.log`. Set to `off` to disable. You can also override this with the `IRONRUN_AUDIT_LOG` environment variable.
</ParamField>

<ParamField path="seccomp_default" type="boolean">
  Whether to apply the seccomp syscall denylist to all commands by default on Linux. Defaults to `true`. Override per command with the `seccomp` field. Set to `false` globally with `IRONRUN_SECCOMP=off`.
</ParamField>

### Command fields

<ParamField path="commands[].id" type="string" required>
  The name the agent uses to call this command via `run_sealed` (e.g. `command_id: "deploy"`). Must be unique within the policy. Use only letters, digits, `-`, and `_`.
</ParamField>

<ParamField path="commands[].argv" type="string[]" required>
  The exact binary and arguments to run, as a YAML list. No shell expansion, no pipes, no globs, no `$VAR` substitution. `["npm", "test"]` runs `npm test` directly — there is no shell in between.

  ```yaml theme={null}
  argv: [go, test, ./...]           # ✓ correct
  argv: ["bash", "-c", "go test"]   # ✗ denied at runtime — no shells
  ```
</ParamField>

<ParamField path="commands[].ttl" type="string">
  Kill the command after this duration. Accepts Go duration strings: `30s`, `10m`, `2h`. Set to `0` for no timeout (use with care for long-running dev servers).
</ParamField>

<ParamField path="commands[].max_bytes" type="integer">
  Cap the combined stdout + stderr at this many bytes. Output beyond the limit is dropped and the agent receives a truncation notice. Example: `10485760` for 10 MB.
</ParamField>

<ParamField path="commands[].no_network" type="boolean">
  Block outbound network access for this command's child process. On Linux, uses a network namespace. On macOS, uses `sandbox-exec`. Defaults to `false`.
</ParamField>

<ParamField path="commands[].seccomp" type="boolean">
  Apply the seccomp syscall denylist to this command on Linux. Overrides `seccomp_default`. Set to `false` to allow syscalls that the default filter blocks, such as `ptrace` for a debugger.
</ParamField>

<ParamField path="commands[].workdir" type="string">
  Run the command from this directory instead of the project root. Accepts relative paths (relative to the policy file location) or absolute paths.
</ParamField>

<ParamField path="commands[].env" type="object">
  **(v1 policies)** Map of environment variable name → provider reference. The reference format depends on the top-level `provider:` field. See [Secret Providers](/concepts/secret-providers).

  ```yaml theme={null}
  env:
    DATABASE_URL: "op://Engineering/prod-db/url"
    STRIPE_KEY:   "op://Engineering/stripe/secret_key"
  ```
</ParamField>

<ParamField path="commands[].secrets" type="string[]">
  **(v2 policies)** List of encrypted environment entry names to inject for this command. These are the names you used when running `ironrun add KEY` or `ironrun import .env` — not provider paths.

  ```yaml theme={null}
  secrets: [DATABASE_URL, STRIPE_SECRET_KEY]
  ```
</ParamField>

## Important: argv is a literal list, not a shell line

<Warning>
  `argv` is a literal argument list. There is no shell between ironrun and your binary. This means you **cannot** use shell features: no pipes (`|`), no redirects (`>`), no variable expansion (`$VAR`), no globs (`*.js`), and no shell keywords (`&&`, `||`).

  If your command needs a shell, wrap it in a script file:

  ```yaml theme={null}
  # ✓ correct — call the script directly
  - id: deploy
    argv: [./scripts/deploy.sh]

  # ✗ wrong — will be denied at runtime
  - id: deploy
    argv: [bash, -c, ./scripts/deploy.sh]
  ```
</Warning>

This is a security feature: an injected secret value can never be re-expanded, piped somewhere unexpected, or exfiltrated through shell substitution.

## Linting and validation

```bash theme={null}
ironrun lint      # security review: flags shell argv, missing ttl, secrets with open egress, etc.
ironrun validate  # parses the policy and lists commands; does not check provider auth
```

Use `ironrun lint` regularly. It catches common mistakes like a deploy command that injects secrets but has `no_network: false` and no `ttl`.

Use `ironrun doctor` for a full end-to-end check: policy parses, provider CLI is installed and authenticated, redactor self-test passes, and every command's binary resolves on PATH.

## Realistic example

Here is a complete v1 policy for a service with 1Password-stored credentials:

```yaml theme={null}
version: "1"
provider: 1password

commands:
  # Unit tests — no secrets needed
  - id: test
    argv: [go, test, ./...]
    ttl: 10m
    max_bytes: 10485760   # 10 MB cap

  # Build the binary — no secrets needed
  - id: build
    argv: [go, build, -o, bin/api, ./cmd/api]
    ttl: 5m

  # Deploy to production — injects secrets below agent visibility
  - id: deploy
    argv: [./scripts/deploy.sh]
    ttl: 30m
    no_network: false   # deploy needs outbound access
    env:
      DEPLOY_TOKEN:   "op://prod/deploy/token"
      DATABASE_URL:   "op://prod/db/url"
      AWS_ACCESS_KEY: "op://prod/aws/access_key_id"
      AWS_SECRET_KEY: "op://prod/aws/secret_access_key"

  # Integration tests against staging
  - id: integration-test
    argv: [go, test, -tags=integration, ./...]
    ttl: 20m
    env:
      TEST_API_KEY: "op://dev/api/test_key"
```

And the equivalent v2 policy using the encrypted vault:

```yaml theme={null}
version: "2"
environment_set: active
require_agent_leases: false
allow_proposals: true

commands:
  - id: test
    argv: [go, test, ./...]
    ttl: 10m
    max_bytes: 10485760

  - id: build
    argv: [go, build, -o, bin/api, ./cmd/api]
    ttl: 5m

  - id: deploy
    argv: [./scripts/deploy.sh]
    ttl: 30m
    secrets: [DEPLOY_TOKEN, DATABASE_URL, AWS_ACCESS_KEY, AWS_SECRET_KEY]

  - id: integration-test
    argv: [go, test, -tags=integration, ./...]
    ttl: 20m
    secrets: [TEST_API_KEY]
```
