MAQPNADocs

Kill switch and revocation#

The kill switch stops an agent, a session, a user's agents, a token, a namespace or everything. A revocation is one kill-switch entry: what is revoked, by whom, why, until when. Revocations reach the MAQPNA gateway in two ways, and the operator acts on the sessions themselves. Code: internal/controller/revocation_controller.go, session_governance.go, cmd/maqpna-gateway/revocation.go, configsync.go, token_vault.go, pkg/revocation.

Path Created by Effective at the gateway
Declarative AgentRevocation (kubectl, GitOps, maqpna kill --emit-yaml) Within about 1–3 seconds with configSync (polls the ConfigMap every second); 60–90 seconds with plain kubelet volume refresh
Break-glass POST /v1/revocations (maqpna kill) At once on the receiving replica; on other replicas within stateBackend.pollMillis (Postgres)

Actions: block (default; calls are refused), suspend (the operator also suspends matching sessions), terminate (the operator fails them, deletes the sandbox and the token Secret, and the gateway revokes the user's vault tokens for cluster-wide user matches).

Sequence#

sequenceDiagram
    autonumber
    actor SRE as On-call engineer
    participant GW as Gateway (replica A)
    participant GW2 as Gateway (replica B)
    participant DB as Postgres state
    participant K as Kubernetes API
    participant Op as Operator
    participant Ag as Agent sandbox
    participant L as Audit ledger
    SRE->>GW: maqpna kill -n team-a --agent coder --terminate<br/>POST /v1/revocations
    GW->>GW: role killswitch or admin, add break-glass entry
    GW->>DB: journal revocations (or fsynced file)
    GW->>L: admin record revocation.create
    GW-->>SRE: 201 id breakglass/hex, mirrored team-a/breakglass-hex
    GW->>K: create AgentRevocation breakglass-hex (mirror)
    DB-->>GW2: poll, entry visible
    Ag->>GW2: tools/call
    GW2->>L: deny, rule revocation/id, ext.revocation
    GW2-->>Ag: -32001 reason revoked
    K-->>Op: AgentRevocation event
    Op->>K: render maqpna-revocations ConfigMap
    Op->>K: AgentSession Failed (Revoked), delete Sandbox and token Secret
    Op->>K: AgentRevocation status affectedSessions, appliedAt

Where the check runs#

flowchart TD
    R["Request with a verified token"] --> L{"Revocation list readable?"}
    L -- no --> F["deny · revocation_list_unavailable<br/>/readyz 503"]
    L -- yes --> M{"Entry matches?<br/>namespace and every non-empty matcher:<br/>sessions, agents, users globs, jtis exact,<br/>or all"}
    M -- yes --> D["deny · revoked<br/>policy revocation/id"]
    M -- no --> C["continue the pipeline"]
    C --> AW{"Held for approval?"}
    AW -- yes --> RC["re-check after the wait"]
    RC --> M
    AW -- no --> FW["forward"]

Step by step#

  1. Declare or break glass. - Declarative: an AgentRevocation with match {sessions, agents, users, jtis, all} (at least one), action, reason and optional expiresAt. It matches only its own namespace, except in the gateway namespace (--gateway-namespace, default maqpna-system), where it is cluster-wide and all: true is the global kill. - Break-glass: POST /v1/revocations with {namespace?, sessions?, agents?, users?, jtis?, all?, action?, reason?, expiresAt? | ttlSeconds?}, role killswitch or admin. With OIDC, createdBy is the verified username. The entry ID is breakglass/<16 hex digits>.
  2. Render. The revocation reconciler writes every non-expired revocation to the ConfigMap maqpna-revocations, key revocations.json (entries sorted by <namespace>/<name>; an empty namespace means cluster-wide), sets affectedSessions, appliedAt and Ready, and requeues at the next expiry (at least every 30 seconds).
  3. Deliver. With gateway.configSync (on by default in the chart), the gateway reads the ConfigMap from the Kubernetes API every pollMillis (1000) and writes the file atomically; the reloader picks it up within policyReloadSeconds (2).
  4. Persist break-glass entries. They are fsynced to revocationsJournalPath or the shared Postgres journal revocations, so they survive restarts, and, with revocationMirror.enabled (on in the chart), mirrored to an AgentRevocation named breakglass-<id> (label maqpna.com/breakglass=true) so the operator and every replica apply them.
  5. Match. An entry matches a token when it has not expired, its namespace matches, and either all is true or every non-empty list matches (AND across fields, OR within one). sessions, agents and users are globs; jtis are exact and also match tokens whose maqpna_parent_jti is listed (exchanged tokens). For agent-to-agent (A2A) calls, every agent of the delegation chain and the callee are checked.
  6. Enforce at the gateway. Right after token verification on /mcp (POST, GET and DELETE), /llm, /a2a and browser egress, and again after an approval wait. MCP: JSON-RPC -32001, {reason: "revoked", policy: "revocation/<id>"}; /llm: HTTP 403, type maqpna_revoked. Each denial is audited with rule=revocation/<id> and ext.revocation=<id>.
  7. Act on sessions. The session reconciler watches revocations and applies the strictest matching action before minting any token or registering any release: terminate fails the session (Revoked) and deletes its sandbox and token Secret; suspend sets spec.suspend=true; block keeps it Revoked=True with nothing minted.
  8. Revoke delegated tokens. Every policyReloadSeconds, the gateway revokes at the provider, and deletes, the token-vault accounts of users matched by an active cluster-wide terminate revocation with all or a user-only match, unless tokenVault.keepOnKillSwitch.
  9. Notify and lift. revocation.created goes to notifiers. Deleting the AgentRevocation (or DELETE /v1/revocations/{id} for break-glass entries) lifts it; a suspend leaves spec.suspend=true until you resume.

What you see#

The maqpna kill help, from cmd/maqpna/testdata/help-kill.golden:

Kill switch: revoke sessions, agents, users or tokens at the gateway (break-glass)

Usage:
  maqpna kill --gateway URL [-n NS] [--agent A]... [--session S]... [--user U]... [--jti J]... [--all] --reason TEXT [--suspend|--terminate] [--ttl 1h] [--emit-yaml] [--yes]

Flags of kill:
  --agent string              agent name or glob (repeatable)
  --all                       every session in scope (with -n: the namespace; without: the whole cluster)
  --emit-yaml                 print the AgentRevocation manifest (pipe to kubectl apply -f -)
  --gateway string            gateway base URL (env MAQPNA_GATEWAY_URL; default: the context's gateway)
  --gateway-namespace string  gateway namespace (cluster-wide revocations live there) (default "maqpna-system")
  --json                      print the gateway response as JSON
  --jti string                token ID (repeatable)
  -n, --namespace string      namespace (default: every namespace)
  --name string               AgentRevocation name for --emit-yaml (default kill-<timestamp>)
  --oidc-token-file string    file holding an OIDC access token for the admin API (env MAQPNA_OIDC_TOKEN_FILE); wins over --token
  --reason string             reason (recorded in the audit ledger and session events)
  --session string            session name or glob (repeatable)
  --suspend                   also suspend matching sessions
  --terminate                 also terminate matching sessions (sandbox and token deleted)
  --token string              static admin token (env MAQPNA_ADMIN_TOKEN; dev/break-glass)
  --ttl duration              revocation lifetime (0 = until removed)
  --user string               on-behalf-of user or glob (repeatable)
  -y, --yes                   do not ask for confirmation (required when stdin is not a terminal)

Output: -o table|json|yaml, --jq EXPR

Examples:
  # Revoke one agent in a namespace and terminate its sessions
  maqpna kill --gateway "$GW" -n team-a --agent coder --reason "INC-123" --terminate
  # The same as a manifest, for GitOps or when the gateway is unreachable
  maqpna kill -n team-a --agent coder --reason "INC-123" --terminate --emit-yaml | kubectl apply -f -

After a kill, the CLI prints revoked (terminate, namespace team-a): breakglass/<id> and mirrored to AgentRevocation team-a/breakglass-<id> (formats from cmd/maqpna/kill.go). maqpna revocations list prints ID SOURCE ACTION NAMESPACE EXPIRES REASON, with source file or breakglass and * for cluster-wide entries. See maqpna kill, maqpna revocations list and maqpna revocations delete.

Failure modes#

Situation Behaviour
Revocation file missing, unreadable or invalid Every /mcp and /llm call denied, revocation_list_unavailable; /readyz 503; maqpna_gateway_revocation_list_ok 0; alert MaqpnaRevocationListUnavailable
One invalid entry or a duplicate ID The whole file is rejected (fail closed as above)
Gateway unreachable Use --emit-yaml and kubectl apply; the operator still acts on sessions
Mirror disabled and action not block The CLI warns that the operator cannot suspend or terminate, and suggests applying an AgentRevocation too
Slow propagation Alert MaqpnaRevocationLag; maqpna_gateway_configsync_errors_total counts failures