MAQPNADocs

Security model of the stack

Trust boundaries across MAQPNA, llamay and kosa8, where identity is checked, how the three audit chains are bound into the MAQPNA ledger, and how to verify them end to end.

This page describes what each product in the Kosa8 Labs stack trusts, where identity is checked, and how llamay's and kosa8's audit chains are anchored in the MAQPNA audit ledger. It ends with a procedure to verify all three chains yourself and a hardening checklist. For MAQPNA's own threat model, read the security model and threat model and trust zones.

Trust boundaries#

flowchart LR
    subgraph Z1["Untrusted: the agent"]
        A["Agent code<br/>in kosa8 microVM"]
    end
    subgraph Z2["MAQPNA control plane"]
        IB["Identity broker"]
        GW["Gateway"]
        L[("Audit ledger")]
    end
    subgraph Z3["Perimeter services"]
        LL["llamay"]
        KD["kosa8d (host)"]
    end
    A -->|"session token only"| GW
    IB -->|"token after sandbox check"| A
    IB -->|"Get, egress, inventory"| KD
    GW -->|"gateway's llamay key"| LL
    GW -->|"socket or TLS + token"| KD
    GW --> L
Boundary What crosses it What protects it
Agent → gateway Tool, model and egress calls A signed, short-lived session token on every call. The gateway checks scope, policy, budgets, DLP and the kill switch before it forwards anything.
Agent → network Nothing but the gateway kosa8 egress policy allow:<gateway IPv4>, enforced on the host and in the guest. No DNS, no other address.
Agent → llamay Nothing directly Agents only reach llamay through /llm, which proxies chat completions, completions, embeddings and the model list. llamay's context routes are not reachable from an agent. In a cluster, a NetworkPolicy admits only the gateway pods.
Gateway → llamay Model calls and context operations The gateway's own llamay API key, re-read from the key file on every call. The agent's token is never sent to llamay.
Gateway and broker → kosa8d Sandbox checks, decision mirror, audit reads The kosa8d unix socket (mode 0600, current user only), or a TCP daemon with a bearer token. MAQPNA refuses kosa8.url with plain HTTP to a non-loopback host.
Host → agent sandbox The session token Written to the container's stdin, read once into /tmp/maqpna/token (mode 0600). Never in argv or the container environment.

Where identity is checked#

  1. Before the token exists. With maqpna dev up --kosa8, the identity broker runs with -sandbox-check kosa8. Before it mints a session token it asks kosa8d itself, not the caller:

    • the sandbox exists and its labels name the same namespace, agent, user and session (or, for a fork or restore, the parent session);
    • its egress, as kosa8 enforces it now, is exactly the gateway;
    • its isolation is at least microvm and its attestation at least inventory;
    • every measurement (kernel, rootfs, image) is in -sandbox-check-allowed, when that file is set.

    The result becomes the token's maqpna_substrate claim. 2. On every call. The gateway verifies the session token, its scopes and revocations. Policy rules can match the verified substrate. This rule denies every tool of MCP server shell to a session that did not pass a sandbox check:

    rules:
      - name: shell-needs-a-verified-sandbox
        servers: ["shell"]
        substrate:
          attestation: ["none"]
        action: deny
    

    A session no check ran for has isolation and attestation none. Use substrate.checks: [kosa8] to match only sessions the kosa8 check verified. See Identity checks and the ToolPolicy reference. 3. At llamay. llamay checks the gateway's bearer key. llamay sees one caller, the gateway, so the gateway enforces per-session isolation: it strips every spelling of llamay_session an agent sends (including case and escape variants) and binds the session's own context. 4. At kosa8d. kosa8d trusts whoever can open its socket, or holds its TCP token.

How the audit chains are bound together#

Three tamper-evident logs describe one agent session:

Chain Who writes it What it records How MAQPNA binds it
MAQPNA audit ledger Gateway Every decision, approval, revocation and result The record of authority; hash-chained, with signed checkpoints when a checkpoint key is set.
llamay request audit llamay (-audit FILE) One record per HTTP request: actor, path, status, model, context, token counts. Never prompts or completions. SHA-256 chain. The gateway reads the file, verifies the chain and writes llamay/audit-anchor with ext.llamayAuditSeq and ext.llamayAuditHead when the head moves.
kosa8 audit kosa8d (~/.kosa8/audit.jsonl) Sandbox events: boot, fork, egress denials, wipes. HMAC-SHA256 chain. The gateway reads kosa8d's verdict and head and writes kosa8/audit-anchor with ext.kosa8AuditSeq, ext.kosa8AuditHead and ext.kosa8AuditVerified.

The binding works in both directions:

  • Per call. Each llamay model call in the ledger carries ext.llamayContext. The same id is the context field of llamay's audit record. A forked session's first call also carries ext.llamayContextParent.
  • Per decision. With mirrorDecisions, each MCP decision is posted to kosa8 as an mcp.decision record with refs.mcp.event set to maqpna-ledger-<seq>, the tool's name, rule and argument digest (SHA-256), never the arguments or result.
  • Per head. Anchor records are ordinary ledger records. They are covered by the ledger's hash chain and signed checkpoints and appear in evidence packs. An auditor can show that llamay's chain had head H at seq N when the ledger said so.
flowchart LR
    LA["llamay audit<br/>seq N, hash H"] -->|"verified by gateway"| AN1["ledger: llamay/audit-anchor<br/>llamayAuditSeq=N"]
    KA["kosa8 audit<br/>head seq, hash"] -->|"kosa8d verdict"| AN2["ledger: kosa8/audit-anchor"]
    AN1 --> CK["signed checkpoint"]
    AN2 --> CK
    MC["ledger: model call<br/>ext.llamayContext"] -.->|"same context id"| LA
    DC["ledger: MCP decision seq S"] -.->|"maqpna-ledger-S"| KA

What the gateway does when a chain breaks#

Event Ledger record Effect
llamay audit file missing, broken link, altered record, or rewritten llamay/audit-unverified (once per distinct failure) With failClosed: true, every llamay endpoint leaves model routing. Calls fail over to a non-llamay endpoint or get 503 llamay_audit_unverified.
llamay restarted without -audit-continue (new chain from genesis) llamay/audit-anchor with ext.llamayAuditRestartedFrom (seq:hash of the replaced head) Anchored at once. A restart and a rewritten file look the same, so the record names the head that was replaced.
kosa8d reports a broken chain kosa8/audit-unverified Recorded. kosa8 has no fail-closed switch in MAQPNA.
kosa8 head goes back (lower seq, or same seq with another hash) kosa8/audit-unverified Recorded. kosa8d verifies with its own key, so a log it replaced still verifies; only the anchored head shows it.

The gateway re-verifies the whole llamay file every 30 checks and pins the verified head. llamay's hash is unkeyed, so without the pin a chain recomputed from genesis around an altered record would verify.

Verify the chain end to end#

These steps use a local MAQPNA from maqpna dev up --kosa8 --llamay MODEL, run from the directory that holds .maqpna.

  1. Load the gateway URL and admin token into your shell:

    eval "$(maqpna dev env)"
    
  2. Verify the MAQPNA ledger. The gateway recomputes its hash chain and exits with code 3 if it is broken:

    maqpna audit verify --gateway "$MAQPNA_GATEWAY_URL"
    
  3. Verify llamay's request audit offline and note the head:

    maqpna llamay verify-audit .maqpna/llamay-audit.jsonl -o json
    

    The output has verified, records, headSeq and headHash. Exit code 3 means the chain is broken. Give rotated files oldest first, with --continuation when the first file continues an earlier one.

  4. Find the last llamay anchor in the ledger and check it against llamay's chain:

    grep '"llamay/audit-anchor"' .maqpna/audit.jsonl | tail -n 1
    

    Take ext.llamayAuditSeq (N) and ext.llamayAuditHead (H). The record with seq N in .maqpna/llamay-audit.jsonl must have hash H:

    jq -c 'select(.seq == N) | .hash' .maqpna/llamay-audit.jsonl
    
  5. Verify kosa8's chain with kosa8's own tool, then compare its head with the last kosa8/audit-anchor record:

    kosa8 audit verify
    grep '"kosa8/audit-anchor"' .maqpna/audit.jsonl | tail -n 1
    
  6. Match individual calls. For a model call, look up its ext.llamayContext in the ledger and find the llamay audit records with the same context. For a tool call at ledger seq S, find the kosa8 mcp.decision record whose refs.mcp.event is maqpna-ledger-S.

In a cluster, fetch the ledger with its signed checkpoints and verify it offline instead of step 2:

maqpna audit fetch ledger --gateway "$GW" --out ledger.jsonl
maqpna audit verify ledger.jsonl --jwks ledger.jsonl.jwks.json --checkpoints ledger.jsonl.checkpoints.jws

For a broken ledger, follow the ledger divergence runbook.

Threats and which layer stops them#

Threat MAQPNA llamay kosa8
Agent calls a tool it should not Policy denies or holds for approval — —
Agent exfiltrates data to the internet Egress through the gateway only; DLP on arguments — Egress allow:<gateway> on host and guest; denials audited
Agent reads another session's model state Strips llamay_session, binds per session Contexts scoped to the caller's key —
Prompts leave the perimeter Residency and sovereignty checks on model endpoints Runs on your hardware; in-cluster pod has no egress unless it pulls a model —
Agent token stolen from the sandbox environment Short-lived token; kill switch — Token delivered over stdin into a 0600 file, never in env or argv
Agent spoofs where it runs Broker checks the sandbox with kosa8d before mint (maqpna_substrate) — Reports labels, egress and inventory digests
Model-request log edited or truncated Anchors head; failClosed stops routing to llamay SHA-256 chain —
Sandbox log replaced Anchors head; records a head that goes back — HMAC chain, kosa8 audit verify
Revoked session keeps model state Drops its contexts at once (llamay/context-revoked); refuses restore — dev restore removes a restored sandbox the gateway refuses
Forked clone runs as the parent Re-keys the clone or removes it — Not stopped: clones resume at once (see the note above)

Not covered in this release: kosa8 has no measured boot, so attestation is inventory (digests the host reports). kosa8d's TCP listener has no TLS of its own.

Hardening checklist#

  • Keep failClosed: true on every llamay audit anchor (the default in dev up and the chart).
  • In a cluster, set llamay.audit.anchor.enabled: true with a ReadWriteMany audit volume.
  • Run llamay with -audit-strict and -audit-sync (the chart does), or use the sovereign build.
  • Keep the llamay API key in a file or Secret mounted only into the gateway. Never put it on argv.
  • Price llamay models in pricingFile so budgets apply to them.
  • Set spec.model.jurisdiction on agents that use llamay, so sovereignty checks can use it.
  • Air-gapped: use llamay.models.bundle.image, not models.pull.
  • Set llamay.quarantineSnapshotDir if you want a quarantined session's model state kept as evidence.
  • Pin agent images by digest in --kosa8-image.
  • When you run maqpna-identity yourself, set -sandbox-check-allowed with the kernel, rootfs and image digests you accept, and update it before you upgrade kosa8.
  • Deny risky tools to sessions without a verified substrate (substrate.attestation: ["none"], action deny).
  • Make agents read MAQPNA_TOKEN_FILE on every call. Do not cache the token.
  • Firewall the gateway port on untrusted networks: dev up --kosa8 listens on the LAN address.
  • Remote kosa8d only behind TLS (https:// in kosa8.url) with a tokenFile.
  • Set KOSA8_OFFLINE=1 on hosts that must not report usage to kosa8.
  • Alert on maqpna_gateway_llamay_audit_verified == 0 and maqpna_gateway_kosa8_audit_verified == 0. See Metrics.

Next steps#