Skip to content

73. Named mint privilege levels under agent roles

Date: 2026-07-19

Status

Accepted

Context

The mint maps each agent role to a single hardcoded permission set (ADR 0007). Every token minted for a role gets the same ceiling. The threat model establishes least privilege as a cross-cutting principle (see security-threat-model.md), but the current model cannot differentiate between a phase that only needs read access and one that needs write — both receive write-level tokens.

#2821 (human-gated permission adjustments) and the least-privilege topic (#5312) depend on a general mechanism for minting tokens at differentiated privilege levels within a role. Without this mechanism, downstream work risks encoding use-case-specific designs instead of a reusable model.

Decision

Role levels

Each role defines an ordered set of named privilege levels. Each level's permissions are a superset of all preceding levels. The first level is always read — generally read-only — and is the default when no level is requested.

The read level for each built-in role is derived by taking the role's current permission set and downgrading all *:write permissions to their read counterparts (e.g., contents: write becomes contents: read). The write level is defined as the full permission set currently granted by each built-in role. For roles where write is not explicitly defined, requesting write falls back to read automatically.

Mint API

Token requests accept an optional level field:

json
{"role": "coder", "level": "write"}

When level is omitted, the mint defaults to read. The mint validates that the requested level exists for the role and returns an error for unknown levels.

Custom roles

CUSTOM_ROLE_PERMISSIONS continues to accept the current JSON shape — a flat role-to-permissions map — interpreted as the read permissions for each role:

json
{"my-role": {"contents": "read", "issues": "write"}}

An alternate shape specifies multiple named levels per role. The mint auto-detects the format per role by checking whether the role's value contains a levels key (reserved for format discrimination — role definitions must not use levels as a permission name):

json
{"my-role": {"levels": {"read": {"contents": "read"}, "write": {"contents": "read", "issues": "write"}}}}

Mixed format is allowed within a single CUSTOM_ROLE_PERMISSIONS value — some roles may use the flat format while others use the multi-level format. The mint detects the format independently for each role.

Clients

mintclient and CLI paths that mint tokens accept an optional level parameter, defaulting to read when unset. The harness passes the level derived from the privilege_levels configuration (see below) to the mint client for each phase.

Acquisition semantics

The harness is the sole caller that selects privilege levels. It determines the level for each run phase from the privilege_levels configuration and requests the corresponding token from the mint before that phase begins. Agents and scripts within a phase do not choose or escalate their own level — they receive the token the harness minted for their phase.

Harness privilege_levels flag

Harness YAML supports a privilege_levels field mapping run phases to levels:

yaml
privilege_levels:
  pre_script: write
  runtime: read
  post_script: write

A default key specifies the level for phases not explicitly listed. Phases not shown above — such as validation_loop — inherit the runtime level, since they execute within the runtime phase and share its security context.

When privilege_levels is omitted entirely, the harness behaves as if:

yaml
privilege_levels:
  default: write

This preserves backward compatibility — existing harnesses continue to receive write-level tokens for all phases.

Consequences

  • Agents running in the LLM sandbox can receive read-only tokens, reducing blast radius if the sandbox is compromised.
  • The mint API defaults to read when level is omitted, producing narrower tokens than the current behavior. Non-harness callers that omit level — including reconcileMintToken, the mint-token CLI, and e2e test helpers — will receive read-level tokens instead of write-level tokens. Migration of these callers is coordinated in #2823 and #2826.
  • CUSTOM_ROLE_PERMISSIONS supports both the existing flat format and the new multi-level format — including mixed format within a single value — without a breaking change.
  • Harnesses that omit privilege_levels default to write for all phases, so existing harness configurations are unaffected — the harness requests write from the mint, preserving current token scope.
  • Implementation of role levels in the mint, clients, and harness is tracked in #2823 and #2826.
  • #2821 (human-gated permission adjustments) is unblocked and can build on this mechanism.