> ## Documentation Index
> Fetch the complete documentation index at: https://docs.parmanasystems.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Security

> What Parmana actually guarantees, precisely scoped, and, as important, what it explicitly does not claim.

<Info>This page is a direct mirror of `docs/CLAIMS.md` sections 3 and 5, read that file for the authoritative version.</Info>

## Claims that hold, with their scope

**Non-bypassable envelope verification (Conditional Claim 3.1).** For any system running
`@parmana/envelope-verifier`, execution requests not authorized by Parmana are
cryptographically impossible to accept, **only** for a receiving system that (a) runs the
verifier and (b) gates every execution-triggering code path behind its result. Parmana
enforces nothing at the network level. As of commit `651497a`, the default local server does
both (a) and (b) for the one connector it registers, see [The gateway](/concepts/the-gateway)
for the mechanism and its one real remaining caveat: a second connector isn't reachable
without a bootstrap code change.

**Fleet-wide single-use requires a shared NonceStore (3.2).** Single-use enforcement is
scoped to whichever `NonceStore` instance performs the check. Independent instances each
using their own store can each accept the same authorization once. `MemoryNonceStore`
loses all state on restart. Every envelope's bounded TTL limits, but does not eliminate,
the exposure window from either gap.

**Durable, third-party-verifiable refusal and audit records (RFC-0021, 3.11).** Every
policy rejection produces a signed Refusal Record, independently verifiable at `POST
/refusal/verify` with no API key or database lookup required — just the artifact and
Parmana's public key. Production caller-authentication audit events
are signed the same way, verifiable at `POST /audit/verify`. Two scope caveats: Refusal
Record writing is evidentiary and fails open by design (a rejection is never blocked or
delayed by it; only the durable record can go silently missing, never the correctness of
the rejection itself), and only production (Supabase) audit sinks sign — the in-memory
test sinks don't.

**Hybrid (Ed25519 + ML-DSA-65) signing capability, not yet deployed (3.13).** Trust
Records and Receipts can be dual-signed with both a classical and a post-quantum
algorithm at once, with verification requiring both signatures independently, fail-closed.
This is a built, tested capability — `CRYPTO_MODE=hybrid` is real, validated
configuration — but it is opt-in and not running anywhere today: every deployed
environment signs Ed25519 alone.
**Caveat:** `@parmana/sign`, the public verification SDK, does not yet recognize the
hybrid envelope shape; a third party verifying a hybrid-signed record through it today
checks the legacy Ed25519 signature only — a genuine but partial verification, not the
full hybrid guarantee.

## Claims Parmana intentionally does not make

Directly from CLAIMS.md §5:

* Execution is impossible to bypass under all circumstances.
* Mathematical proof of execution correctness.
* Cryptographic proof of every aspect of runtime behavior.
* Guaranteed regulatory compliance.
* Absolute prevention of all unauthorized execution.
* Tamper-proof operation in every deployment environment.
* "Non-bypassable" or "the single execution authority" as an unscoped, system-wide claim,
  envelope verification is opt-in per receiving endpoint (see 3.1 above).
* Deterministic signature output for ML-DSA-65, those signatures are randomized by design;
  only verification is deterministic.

## Known incidents

**Policy-evaluation signals were not bound to the executed Intent (2026-07-28, RESOLVED).**
The most severe gap found in this project's history: a live, reproducible bypass of the core
"no unauthorized execution" invariant. `PolicyEngine` evaluated whatever signals a caller
declared in the request body; `ExecutionGateway` signed and executed `intent.target`/
`intent.parameters`, a completely disjoint set of fields nothing cross-checked. A caller
could declare signals describing a small, fully-verified action while `intent` executed
something else entirely, and still receive a signed, `APPROVED` Execution Trust Record for
it. Live proof-of-concept: `signals` declared a fully verified, policy-approved **$5,000**
payment to a known vendor, while `intent` — the part that actually executes — targeted an **attacker-controlled account for $999,999,999**. Before the fix: `200`, policy decision
`APPROVED`, execution `COMPLETED`, a real signed trust record and receipt issued for it — the
exact artifacts this project's independently-verifiable-execution claim rests on, attesting
to something that never happened as described. A related, compounding finding from the same
session: `authority.principalId` (who the trust record says approved the action) was
likewise caller-declared with no binding to the identity `callerId` actually proves — any
caller holding any valid API key could claim to be any human or role, including a successful
"impersonate the CEO" proof-of-concept. Found via an external adversarial security exercise,
not this project's own internal audit process.

Fixed by `Policy.boundSignals` + `SignalIntentBinder` (binds specific signal keys to `intent`
dot-paths, checked before `PolicyEngine.evaluate`; see [Policies and the
decision](/concepts/policies-and-the-decision) for the mechanism), `isPrincipalAllowed`
(binds `authority.principalId` to the authenticated `callerId`), and a compounding IDOR fix,
`isOwnedByCaller` (any caller could previously read any other caller's complete
transaction/trust-record/receipt history). 28 new regression tests reproduce the exact live
exploit shapes and assert they're now rejected, plus positive controls proving legitimate
requests are unaffected. Deliberately **not** fixed by this session, flagged rather than
silently left unmentioned: `boundSignals` only binds the specific fields a policy author
declares — it does not independently verify that an *unbound* signal (`vendorVerified`,
`riskScore`, and similar) is actually true; those remain caller-declared attestations. Full
detail, exact file citations, and the complete regression-test list: `docs/VERIFICATION-GAPS.md`
G-24, the authoritative source for this incident.

**The default signing key committed to this repository before 2026-07-05** was publicly
exposed in the public GitHub repository and must be treated as permanently compromised,
all signatures produced by that key are void for authenticity purposes regardless of when
signed. The key pair was rotated on 2026-07-05. Source: CLAIMS.md, "Key Compromise Notice."

**Four ML-DSA-65 (Dilithium3) private signing keys committed to a feature branch during the
post-quantum signature provider work** were confirmed, by raw key-byte comparison, to be
distinct from any key ever trusted by production or local configuration — unlike the
incident above, no signature produced by this key pair was ever accepted as authentic by
this codebase. A fresh keypair was generated regardless, the feature branch's history
purged, and the exposure independently re-verified across every remote hosting this
repository's history. Source: `04-INCIDENTS-LOG.md`, INC-8.

## Caller authentication, principal binding, and ownership scoping — three separate layers

`packages/api` gates every route except `GET /health` behind a caller bearer key, verified
live this pass, see [Authentication](/api-reference/authentication). Three distinct claims,
not one, and it matters which one you're relying on:

* **Route access is not scoped.** Any authenticated caller can call any route — there is no
  per-key allowlist of which endpoints a given credential may reach. Unchanged, still true.
* **Principal assertion is scoped, as of the G-24 fix above.** A caller may only submit a
  transaction whose `authority.principalId` is itself, or an identity explicitly granted via
  `ApiKeyEntry.allowedPrincipalIds` — never "anything," the default with no grant configured
  is "only itself." `isPrincipalAllowed`, `packages/api/src/auth/isPrincipalAllowed.ts`.
* **Data ownership is scoped, as of the same fix.** A caller can no longer read another
  caller's transactions, trust records, or receipts — `metadata.submittedBy` is stamped
  server-side from the authenticated caller (never trusted from the client), and
  `isOwnedByCaller` gates every read route by it; cross-caller access reads as a clean `404`,
  not a `403` that would confirm the target id exists. `packages/api/src/auth/isOwnedByCaller.ts`.

None of this is mutual TLS (there is none by default), and key rotation is still a manual
operator procedure with no self-service endpoint, see [Deploy
patterns](/guides/deploy-patterns#caller-authentication). This authentication layer is
independent of, and does not substitute for, [Execution
Authorization](/concepts/execution-authorization) or [gateway
attestation](/concepts/gateway-attestation).

## Where hardening work is tracked

KMS/HSM key custody, credential brokering, and network-level enforcement are real,
specific, sequenced plans, not vague future promises. See [Roadmap](/roadmap).
