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#
-
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
microvmand its attestation at leastinventory; - every measurement (kernel, rootfs, image) is in
-sandbox-check-allowed, when that file is set.
The result becomes the token's
maqpna_substrateclaim. 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 servershellto a session that did not pass a sandbox check:rules: - name: shell-needs-a-verified-sandbox servers: ["shell"] substrate: attestation: ["none"] action: denyA session no check ran for has isolation and attestation
none. Usesubstrate.checks: [kosa8]to match only sessions the kosa8 check verified. See Identity checks and theToolPolicyreference. 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 ofllamay_sessionan 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 thecontextfield of llamay's audit record. A forked session's first call also carriesext.llamayContextParent. - Per decision. With
mirrorDecisions, each MCP decision is posted to kosa8 as anmcp.decisionrecord withrefs.mcp.eventset tomaqpna-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
Hat seqNwhen 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.
-
Load the gateway URL and admin token into your shell:
eval "$(maqpna dev env)" -
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" -
Verify llamay's request audit offline and note the head:
maqpna llamay verify-audit .maqpna/llamay-audit.jsonl -o jsonThe output has
verified,records,headSeqandheadHash. Exit code 3 means the chain is broken. Give rotated files oldest first, with--continuationwhen the first file continues an earlier one. -
Find the last llamay anchor in the ledger and check it against llamay's chain:
grep '"llamay/audit-anchor"' .maqpna/audit.jsonl | tail -n 1Take
ext.llamayAuditSeq(N) andext.llamayAuditHead(H). The record withseqN in.maqpna/llamay-audit.jsonlmust havehashH:jq -c 'select(.seq == N) | .hash' .maqpna/llamay-audit.jsonl -
Verify kosa8's chain with kosa8's own tool, then compare its head with the last
kosa8/audit-anchorrecord:kosa8 audit verify grep '"kosa8/audit-anchor"' .maqpna/audit.jsonl | tail -n 1 -
Match individual calls. For a model call, look up its
ext.llamayContextin the ledger and find the llamay audit records with the samecontext. For a tool call at ledger seq S, find the kosa8mcp.decisionrecord whoserefs.mcp.eventismaqpna-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: trueon every llamay audit anchor (the default indev upand the chart). - In a cluster, set
llamay.audit.anchor.enabled: truewith aReadWriteManyaudit volume. - Run llamay with
-audit-strictand-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
pricingFileso budgets apply to them. - Set
spec.model.jurisdictionon agents that use llamay, so sovereignty checks can use it. - Air-gapped: use
llamay.models.bundle.image, notmodels.pull. - Set
llamay.quarantineSnapshotDirif you want a quarantined session's model state kept as evidence. - Pin agent images by digest in
--kosa8-image. - When you run
maqpna-identityyourself, set-sandbox-check-allowedwith 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"], actiondeny). - Make agents read
MAQPNA_TOKEN_FILEon every call. Do not cache the token. - Firewall the gateway port on untrusted networks:
dev up --kosa8listens on the LAN address. - Remote kosa8d only behind TLS (
https://inkosa8.url) with atokenFile. - Set
KOSA8_OFFLINE=1on hosts that must not report usage to kosa8. - Alert on
maqpna_gateway_llamay_audit_verified == 0andmaqpna_gateway_kosa8_audit_verified == 0. See Metrics.