What It Is
The layer that decides whether an HTTP request is entertained at all, who it came from, and what that identity is actually allowed to do (which principal it may assert, which capabilities it may invoke). It runs before a Business Transaction is even constructed and is entirely independent of policy evaluation and gateway attestation, which run later and answer different questions.Why It Was Built
Without an identity layer, authority/authorization fields on a submitted transaction are purely caller-declared and never cross-checked against who is actually calling, any caller holding any valid API key could claim to be any human or role in the resulting signed trust record. Caller authentication and scoping close that gap at the door, before anything downstream has a chance to trust a claim it shouldn’t.How It Works
Authentication: StaticKeyAuthenticator
packages/api/src/auth/StaticKeyAuthenticator.ts authenticates against a static, pre-hashed
set of API keys (ApiKeyEntry[], from PARMANA_API_KEYS). Keys are never held in plaintext,
only a SHA-256 hash of each is compared, via timingSafeEqual against the hash bytes (never
the raw key), so a leaked config never yields a usable secret and comparison timing never
leaks information about a partial match.
Multiple entries may share the same callerId, this is how key rotation works: add the new
key’s hash, keep the old one active during migration, then remove the old entry to revoke it,
with no downtime and no code change.
ApiKeyEntry (packages/shared/src/config/ApiKeyEntry.ts):
credentialHolderType is operator-declared metadata set only at issuance time, distinct
from Authority.authorityType, which is caller-declared and lives on the Business
Transaction itself describing the business action’s asserted authority. The two must not be
conflated: one describes who was handed the credential, the other describes what a specific
request claims. isHumanCaller.ts is the only consumer of credentialHolderType, and treats
every value other than exactly AuthorityType.USER, including undefined, as non-human,
fail-closed by default.
The middleware itself
createCallerAuthMiddleware() (packages/api/src/middleware/caller-auth.ts) extracts a
bearer token, authenticates it, and on success attaches callerId,
callerAllowedPrincipalIds, callerAllowedCapabilities, callerCredentialHolderType, and
callerStepUpPublicKey onto the Express Request object (a local type augmentation, same
pattern @parmana/envelope-verifier’s own Express typing uses). On failure it returns 401
with a WWW-Authenticate: Bearer header, and either way it records a caller.rejected or
caller.authenticated audit event through recordCallerAuditEvent before proceeding ,
fail-closed: if the audit write itself fails, the request is rejected with
AuditUnavailableError (503) rather than proceeding unaudited. This applies to both outcomes,
not just denials, a perfectly valid credential whose caller.authenticated write fails also
gets 503, not the 200 it would otherwise get.
Scoping: principal and capability
Two independent checks, deliberately opposite in their fail-closed default:isPrincipalAllowed() (packages/api/src/auth/isPrincipalAllowed.ts) decides whether a
caller may assert a given authority.principalId. Default (no allowedPrincipalIds
configured): a key may only assert itself, principalId must equal callerId exactly.
An unconfigured key proves its own identity and nothing more.
isCapabilityAllowed() (packages/api/src/auth/isCapabilityAllowed.ts) decides whether a
caller may invoke a given capability (the transaction’s intent.action) at all. Default (no
allowedCapabilities, or an empty list): every capability is denied. There is no
meaningful “may invoke its own capability” fallback the way “may only assert itself” is for
principals, an unconfigured key is authorized to invoke nothing until explicitly granted.
The literal string "*" in allowedCapabilities is an explicit, auditable wildcard, never an
implicit default.
PARMANA_AUTH_DISABLED
A deployment can set PARMANA_AUTH_DISABLED=true to accept every request with no caller
authentication at all. This is surfaced not just as a startup log line but as a field on
GET /ready’s own response body (packages/api/src/routes/ready.ts:44-52):
How It Enables Things, With a Concrete Example
examples/tutorials/101-fail-closed-caller-audit-writes/run.ts proves the audit-write
fail-closed guarantee directly: it simulates the storage outage SupabaseCallerAuditSink
would surface, and shows both caller.rejected and caller.authenticated outcomes react
identically (503, not their otherwise-normal status) when the audit write itself fails. There
is no retry, buffering, or queueing, a failure fails closed immediately, once, per request.
packages/api/tests/integration/caller-scoping.integration.test.ts,
caller-principal-scoping.integration.test.ts, and
caller-capability-scoping.integration.test.ts exercise the two scoping checks at the real
HTTP boundary, including a documented IDOR-regression suite (blocking one caller from reading
another’s transactions, receipts, trust records, and refusal records by ID).
How to Validate This Yourself
packages/api/src/middleware/caller-auth.ts, the middleware itself.packages/api/src/auth/StaticKeyAuthenticator.ts,hashApiKey.ts, authentication.packages/api/src/auth/isPrincipalAllowed.ts,isCapabilityAllowed.ts,isHumanCaller.ts,isOwnedByCaller.ts, the four scoping/identity predicates.packages/shared/src/config/ApiKeyEntry.ts, the credential shape.packages/api/src/routes/ready.ts, theauthDisabledwarning field.packages/api/tests/integration/caller-*.integration.test.ts, the real HTTP-level proof for every claim above.
Integration Requirements
Provisioning a new credential:
scripts/generate-api-key.ts (see Chapter 7 for the
step-up-keypair variant used by Policy Governance checkers).