Secret Management Operations on the MCP Surface — Observation, Orchestration, and Permanent Exclusions

Proposed

ontoref
ADR-017 established that the daemon is structurally excluded from credential resolution: it has no age private key and cannot decrypt sops files. This is load-bearing — any actor with MCP access cann

Context

ADR-017 established that the daemon is structurally excluded from credential resolution: it has no age private key and cannot decrypt sops files. This is load-bearing — any actor with MCP access cannot use the daemon as a credential amplifier. The existing MCP surface has one secret-related tool: ontoref_vault_status (read-only metadata: vault_id, recipient_count, access_sops_present, recipient_groups). The workspace toolchain (secrets.just + provisioning/workspace/tools/secrets.nu) exposes a full secret management lifecycle: gen, new, make, list, pending, show, edit, rekey. The question arose when staging plaintext credential files (*.sops.yaml.template) was added to the workspace workflow, and when CI/CD and agent automation scenarios began requiring machine-readable discovery of pending and existing secrets. Two failure modes emerge if the wrong boundary is drawn: (1) exposing decrypt operations via MCP turns the daemon into a plaintext exfiltration channel — any actor with daemon access and the right MCP token can trigger decryption of any credential file; (2) exposing no operations at all forces agents and CI to shell out to just directly, bypassing the actor authorization model entirely and producing invisible, unaudited secret access.

Decision

Secret management operations on the MCP surface are partitioned into three permanent tiers. Tier 1 — Permanent Exclusions (never on MCP surface): any operation that produces plaintext credential values in a response payload (secret-show, decrypt-to-stdout, raw sops --decrypt), age private key material (.kage content or derived values), and staged plaintext template content (*.sops.yaml.template). These exclusions are structural and cannot be lifted by actor configuration or policy override. Tier 2 — Read-Only Observation (permitted for any authorized actor): ontoref_vault_status (already exists), secret names list (encrypted filenames, no values), pending secret discovery (*.sops.yaml.template filenames without corresponding *.sops.yaml). Tier 2 tools invoke the secrets engine as a read-only subprocess and return structured metadata only. Tier 3 — Orchestrated Mutating Operations (permitted only for actor=admin, requires subprocess isolation, mandatory audit log): secret-gen, secret-make, secret-new, secret-rekey. These invoke the secrets.nu subprocess with the workspace environment variables; the daemon never holds key material — the subprocess reads .kage from the filesystem path configured in the project. Every Tier 3 invocation appends an entry to the vault access log (logs/access.jsonl) recording: actor, operation, secret name, timestamp, exit code. The daemon never buffers subprocess output that contains plaintext — stdout of any secret subprocess is streamed to the caller's MCP response only after the daemon confirms the response payload type is metadata (exit code, names, status), never raw subprocess stdout when the underlying command is decrypt-capable.

Constraints

  • Hard No MCP tool may return plaintext credential values in its response payload — secret-show and any decrypt-capable operation are permanently excluded from the MCP surface
  • Hard No MCP tool may return age private key content, sops DEK material, or staged template plaintext in its response payload
  • Hard Tier 3 mutating tools (secret-gen, secret-make, secret-new, secret-rekey) must require actor=admin; agent and developer actors must receive Unauthorized before the subprocess is spawned
  • Hard Every Tier 3 subprocess invocation must append an entry to logs/access.jsonl before returning — a missing log entry for a completed operation is a constraint violation
  • Hard The daemon must not forward raw subprocess stdout to the MCP response caller when the invoked secrets.nu subcommand is decrypt-capable (show, edit); for Tier 3 commands, only structured metadata (exit code, names changed, audit entry written) may be returned
  • Hard Every MCP tool in category credentials must declare its tier (1|2|3) as a metadata field at registration time; tools without a declared tier fail daemon startup

Alternatives considered

  • No expansion of MCP surface (Tier 1 only, current state)rejected: Forces agents and CI pipelines to shell out to just secret-* outside the actor authorization model. Pending secret discovery, vault compliance checks, and automated secret rotation all become invisible to the audit trail. The automation use case is real and growing; refusing it produces shadow workflows rather than preventing them.
  • Full exposure including decrypt-to-response (no Tier 1 exclusions)rejected: secret-show and any decrypt-capable operation as MCP tools turns the daemon into a plaintext credential proxy. Any actor with admin token and network access to the loopback daemon can extract credential values without a trace in the vault's own audit log (daemon access log is separate). The structural exclusion of ADR-017 would be effectively nullified — the daemon would hold no key but could produce plaintext on demand.
  • Daemon-as-proxy: daemon acquires .kage path, resolves credentials in-processrejected: Breaks the load-bearing invariant of ADR-017: 'the daemon cannot be used as a credential amplifier regardless of MCP actor permissions'. In-process resolution means the daemon holds plaintext during the operation lifecycle. Any memory disclosure (core dump, debug interface, language runtime reflection) exposes key material. The subprocess model keeps key material in a child process with a narrower attack surface and no persistence in daemon memory.
  • Separate secret-management daemon with its own MCP serverrejected: Adds operational complexity (two daemons to manage, two auth models to align) without meaningful security gain over subprocess isolation. The tier model achieves the same separation of concerns within the existing daemon's actor authorization infrastructure. A separate daemon would need its own loopback trust model — re-solving a solved problem.

Related ADRs

ADR-017 · ADR-019 · ADR-015 · ADR-005

Was this useful? Rate it
Got something to add? Tell me what you think, what you'd suggest, or whether we should keep exploring this topic.
· reads

We use cookies to help this site function, understand service usage, and support marketing efforts. Cookie Policy for more info.