MCP Security Model

Authentication, authorization, plaintext contract, identity boundary, and audit semantics for the SikkerKey MCP server.

The MCP server is a management-plane tool. It cannot read plaintext secret values, cannot authenticate as a machine identity, and every operation is authenticated, scoped, and audited. This page is the contract.

Surface

Management plane only. The MCP server's tools cover machine and AI-agent identity, projects, secret metadata, rotation schedules, access policies, canaries, audit log, alerts, webhooks, IP allowlist, support, and trash. There is no tool that returns the plaintext content of a stored secret, and no tool that authenticates as a machine identity.

The runtime read surface (SDK, CLI) is a separate trust class bound to machine identities. AI agents cannot reach it.

Authentication

Every call from the MCP server to SikkerKey is signed with the AI agent's Ed25519 private key. The signed payload is:

{method}:{path}:{timestamp}:{nonce}:{bodyHash}

The four signing inputs sit in headers (X-Agent-Id, X-Timestamp, X-Nonce, X-Signature) and are validated server-side on every request:

  • Timestamp window: requests outside ±5 minutes of server time are refused.
  • Nonce: each nonce is one-shot. Replays inside the timestamp window are caught at insert time.
  • Body hash: SHA-256 of the request body is part of the signed payload. Tampering with the body invalidates the signature.

The agent's private key never leaves the machine running the MCP server. There are no API keys, bearer tokens, or sessions to leak. There is no shared secret with the server. Even compromising the SikkerKey database does not let an attacker forge a request, since only the public half of the keypair is stored.

Identity boundary

A SikkerKey vault has two identity classes:

MachinesAI Agents
Tablemachinesai_agents
Authenticates against/v1/secret/..., /v1/secrets/.../v1/ai/...
Reads plaintextyes (subject to grants)never
Has per-secret grantsyesno (scope-based)
Has per-project allowlistimplicit (project memberships)explicit
Bootstrapdashboard or enrollment tokendashboard only

The two tables are physically distinct. The machine-auth lookup queries machines only; an AI agent's id is invisible to it. The AI-auth lookup queries ai_agents only; a machine's id is invisible to it. There is no fallback or cross-table fallback path.

A compromised AI agent gives the attacker the agent's scopes. It does not give the attacker any way to authenticate as a machine and read stored secrets.

Authorization

Every AI agent holds a flat set of scopes granted at provisioning time. Scopes are validated against an allowlist on the server. Each route declares the scope it requires, and the agent's request is rejected with HTTP 403 if the scope isn't present.

Vault-level scopes

ScopeUnlocks
machines.readList machines, read machine name history.
machines.writeApprove / deny / revoke / rename machines. Issue bootstrap tokens.
aiagents.readList AI agents, read AI-agent detail and name history.
aiagents.writeApprove / deny / disable / enable / revoke / rename AI agents.
enrollment.readList enrollment tokens.
enrollment.writeCreate / revoke enrollment tokens. Render CI/CD templates.
audit.readQuery the audit log, export CSV, read stats and usage.
alerts.readList enabled alert actions, list webhooks.
alerts.writeConfigure alert actions and webhooks.
ipallowlist.readList IP allowlist entries.
ipallowlist.writeAdd / remove / enable / disable IP allowlist.
trash.readList soft-deleted secrets.
trash.writeRestore or purge soft-deleted secrets.
team.readList team members and pending invites.
team.writeInvite, remove, configure permissions.
support.writeOpen and reply to support tickets (read access included).

Project-context scopes

These scopes' routes carry a {projectId} path parameter and additionally consult the agent's project allowlist.

ScopeUnlocks
projects.readList projects, read permissions.
projects.writeCreate / update / delete projects.
projects.secrets.readList secret metadata, read versions, dynamic-secret schedules, temporary-secret status.
projects.secrets.writeCreate / update / rotate / rollback / delete secrets. Manage dynamic-rotation schedules. Create temporary secrets.
projects.machines.readList machines attached to a project, view per-machine grants.
projects.machines.writeAttach machines to projects, configure per-secret grants.
projects.policies.readList policies, read bindings, view canaries.
projects.policies.writeCreate / update / delete policies and bindings. Plant / configure canaries. Unfreeze projects.

Project allowlist

In addition to scopes, an agent can be configured with an explicit project allowlist. If the list is non-empty, project-context routes are filtered to those project ids. The agent gets HTTP 403 on project-context operations against any project not in the allowlist.

If the list is empty, project-context routes apply to every project the vault owner has, including projects created in the future.

When an agent with a non-empty allowlist creates a new project, the new project is automatically added to its allowlist so it can manage what it just created.

Privilege escalation guard

The MCP surface deliberately omits two operations that the dashboard exposes:

  • PUT /ai-agents/{id}/scopes (replace an agent's scope set)
  • PUT /ai-agents/{id}/allowlist (replace an agent's project allowlist)

Exposing either would let an agent grant itself, or a peer, scopes or projects it doesn't already hold. Both remain dashboard-only.

Plaintext contract

The MCP server is read-blind on stored secret values. No tool returns the plaintext content of an existing secret.

Read side

Tool actionWhat it returns
manage_secrets.listid, name, type, fieldNames schema, note, version, createdAt, updatedAt. No value.
manage_secrets.getSame as one list row. No value.
manage_secrets.versionsVersion numbers and timestamps. No values.
manage_secrets.rollbackid, restoredVersion, newVersion. No values.
manage_secrets.dynamic_getSchedule config, last/next rotation timestamps. No values.

Write side

Write actions accept plaintext as input. The input is encrypted server-side with envelope encryption (a per-secret AES-256-GCM data key wrapped by a per-project master key, which is itself encrypted by the in-memory server unseal key). The response carries only metadata.

Tool actionWhat you supplyWhat you get back
manage_secrets.createname, value (plaintext), optional fieldNamesid, name
manage_secrets.update_valuesecretId, value (plaintext)id, new version
manage_secrets.rotatesecretId, length, charset, optional fieldsid, new version. Value is generated server-side; AI never sees it.
manage_secrets.dynamic_createprojectId, name, intervalSecondsid, name. Initial value generated server-side.
manage_secrets.dynamic_updatesecretId, schedule fieldsmetadata only

There is no read-after-write echo. Once written, the value is recoverable only through the runtime SDK/CLI surface bound to a machine identity.

Temporary secrets exception

manage_temporary_secrets.create is the one tool that returns credentials that can be used to read a value. It creates a one-shot self-destructing share link intended for a human recipient, and the response carries the URL, token, and passphrase.

The flow:

  1. The AI creates the temporary secret. Server stores the value AES-256-GCM-encrypted, the passphrase Argon2id-hashed.
  2. The AI receives { url, token, passphrase, expiresAt } and is expected to deliver them to the human recipient.
  3. The recipient opens the URL, enters the passphrase, sees the value once. The secret self-destructs.

If the AI opens the URL itself instead of forwarding it, the secret is destroyed without delivering it. This is a property of one-shot share links in general, not a bypass: the dashboard user who creates a temporary secret has the same capability. The MCP surface treats temporary secrets as a delivery primitive, not a read primitive.

Audit

Every call through the MCP surface is recorded in the audit log with the AI agent's id and name attached. Audit entries are visible:

  • In the dashboard's Audit page (filterable by actor type = ai_agent).
  • Through the read_audit tool (query, list, activity, export actions).
  • In webhook deliveries if the agent's actions are subscribed.

There is no offline write surface. Bypassing the audit log requires bypassing the entire request path.

Revocation

The vault owner can revoke an AI agent at any time from the dashboard:

  • Disable: signed requests are refused with HTTP 403 "Agent is disabled". Scopes and allowlist are preserved; the agent can be re-enabled without re-provisioning.
  • Revoke: agent record is deleted. Scopes, allowlist, and name history are cascaded. The agent's keypair is rendered useless because the public key is no longer in the database.

Both operations take effect on the next request. There are no caches or sessions to expire.

The MCP surface itself can disable / revoke other AI agents (with aiagents.write), with the safety rule that an agent cannot disable / revoke / deny itself.

Why this surface is safe to grant to an AI

A reader (human or safety classifier) evaluating whether to trust an AI client with this MCP server should weigh:

  1. No plaintext exfiltration path. No tool returns stored secret values. Write inputs are encrypted server-side and not echoed.
  2. No identity confusion. AI agent identity is structurally distinct from machine identity. There is no scenario where an AI agent reads a secret because the lookup tables are physically separate.
  3. Bounded blast radius. Scopes are explicit and narrow. The project allowlist constrains where the scopes apply. Both are configured at provisioning time by the vault owner and cannot be self-modified.
  4. Full attribution. Every action is signed by the agent's private key and recorded in the audit log with the agent's identity.
  5. Reversibility. Disable / revoke is one click in the dashboard and takes effect immediately.

The risk an MCP-equipped AI introduces is the same risk as a human admin with the same scope set: misconfiguration, accidental destructive action, or compromise of the local machine running the MCP. None of those are unique to AI; all of them are mitigated by the audit log and the dashboard-side revocation primitives.