Preview release.These docs are a work in progress. Pages are still being written, links may break, and structure may shift without notice. Treat everything here as a draft and report issues onGitHub.
This page is for a reviewer or auditor who already reasons in terms of assets, trust boundaries, and adversaries, but does not yet know where the Registry Stack’s boundaries actually sit. It answers one question: why does this design produce the security properties it claims: which threats does the architecture consider, what does it actually mitigate, and where do the residual risks lie? It is deliberately a map of boundaries and limits, not a runbook. Hardening procedures belong to the hardening checklist, the high-level posture summary to the security overview, and privacy obligations to Data minimization and purpose limitation. Here the goal is a defensible threat model: the boundaries the design draws, the threats it places in and out of scope, and the residual risks.
The two-layer architecture and its primary trust boundary
Section titled “The two-layer architecture and its primary trust boundary”The stack is a two-layer design, and the split between the layers is the first trust boundary the model relies on. A portable metadata layer only describes: it carries no production data, no authentication, and no secrets. A runtime services layer enforces: it is where authentication, authorization, and disclosure actually happen.
The consequence for a reviewer is that the metadata layer authorizes nothing, enforces nothing, and does not assert that any record exists or satisfies a claim. Publishing a dataset, a policy, an offering, or a federation relationship does not grant access and is not proof that a record exists; a published ODRL policy only describes intent until a runtime service binds it into an enforced profile it supports. This also closes one threat directly: because the metadata layer is meant to be distributed and inspected, secrets are never embedded in it. The operator injects them at deployment, which keeps secret material out of the portable artifact entirely.
Components and where they sit on the boundaries
Section titled “Components and where they sit on the boundaries”Five components sit on or beside the trust boundaries:
- Registry Manifest: the offline metadata producer. No production data, no auth, no secrets. It lives entirely on the describe side of the primary boundary.
- Registry Relay: a read-only consultation gateway over sources.
- Registry Notary: claim evaluation, credential issuance, and federation. This is the component that evaluates a modelled question, applies disclosure policy, and issues credentials.
- Registry Platform: the shared security primitives that the runtime services build on.
- Registry Lab: demonstration only, running on fixture credentials. Treat it as out of scope for production trust: its demo and template integrations are integration examples, not a production freshness or replay-protection profile, and a team copying them into production must add request freshness, expiry, or nonce checks itself.
Security-critical primitives are concentrated in Registry Platform so that the behavior is identical across services and reviewable in one place. This is a deliberate attack-surface decision: it removes the risk of divergent, per-service security code that would each need auditing separately.
Trust boundaries in detail
Section titled “Trust boundaries in detail”The service edge (authentication). Authentication is the trust boundary at the service edge. Each service runs exactly one authentication mode (either static-credential fingerprint or OIDC) and authenticates every claim- or record-bearing route before responding. The unauthenticated surfaces are the operational, discovery, and protocol-bootstrap routes: liveness and readiness probes, issuer JWKS public verification keys, credential-issuer discovery, the OID4VCI wallet-flow endpoints, the docs, public credential-status reads, and credential type metadata. The federation evaluations route is exempt from the API-key/OIDC middleware but still requires a verified peer-signed JWS, so it is not anonymous.
Notary to its sources. A runtime boundary sits between Registry Notary and the sources
it reads. Notary calls Relay over HTTP as a data source; it does not link Relay code. The
only connectors are HTTP (registry_data_api, dci, and the source adapter sidecar); there
is no file or database connector.
The source adapter sidecar. The sidecar is an internal trust boundary, not a subject-authorization boundary, and this distinction matters for an auditor. Its bearer token authenticates only the Notary-to-sidecar hop; it is not an end-user, tenant, or subject authorization boundary. It is an internal, private deployment surface behind Notary, not a public subject-facing API. Keeping the token off the public network and scoping its credentials, cache, and backoff state is outside the security model (it is an operator responsibility), and shared cache or backoff state across authorization contexts is not something the model reasons about.
Federation between two Notary services. Federation is a deliberate trust boundary between two Notary services, and it is static-peer only: only configured static peers are admitted. It is not a dynamic trust mesh.
Delegated self-attestation. Delegated self-attestation is a distinct trust context from peer federation; the two are distinct trust contexts. It does not delegate trust to a peer Notary; it stays inside the citizen / OIDC trust context.
Assets protected by the design
Section titled “Assets protected by the design”The assets the design sets out to protect are:
- Private signing keys: The private signing key never leaves the issuer; only the public half is published through issuer JWKS. A verifier never needs a credential to verify, which keeps verification off the protected side entirely.
- Person-level source data: The source rows behind a claim are read in place and never cross the boundary; only a computed, disclosure-shaped answer does.
- Audit integrity: Audit is treated as a security control rather than best-effort logging (see Audit as a control).
- Secret material: Bearer tokens, raw credentials, and key material are kept out of both the portable layer and client-facing surfaces.
The design treats these threat classes as security-relevant: authentication bypass, credential disclosure, audit redaction or integrity failure, signing-key handling bugs, source-connector data leakage, and privacy regressions that expose raw subject identifiers.
Threats the design considers and mitigates
Section titled “Threats the design considers and mitigates”- Secret disclosure via stored credentials or timing: Static credentials are stored only as SHA-256 fingerprints (never the raw secret) and compared in constant time.
- Token forgery / acceptance of untrusted tokens (OIDC mode): A token is trusted only after signature verification against the configured issuer JWKS, plus issuer, audience, and algorithm checks. The service still owns its route scopes regardless of the token.
- Privilege escalation / over-reach: Authorization is scope-based, deny-by-default, and scope-before-source: a caller is refused before any source read or claim evaluation if it lacks the required scope, and reach is never widened at request time.
- An existence / matching oracle: By default a matching failure collapses to a single
public reason (
evidence.not_available), with the granular reason kept only in audit, so the matching surface cannot be used to probe whether a record exists. A deployment may disable this collapse only in a controlled environment; disabling it is a residual confidentiality risk. - Information leakage in error and cache surfaces:
problem+jsonerror bodies carry stable codes only. Bearer tokens, private keys, source values, filesystem paths, and internal error chains stay in protected operator logs, never in responses; principal-scoped responses are not public cache entries. - Over-collection and value injection in claim evaluation: The caller supplies a claim id plus the subject-resolution inputs the claim policy admits, but never the evaluated value; bindings read only the fields they need and reject input paths outside a declared allow-list.
- Disclosure leakage: A redacted result carries neither the value nor the predicate
outcome. Selective-disclosure credentials carry SHA-256 digests of unselected fields, so a
holder cannot present an undisclosed field. Holder binding defaults to
did:jwkfor credential profiles; where a profile keeps binding enabled, and on the self-attestation issuance path, which requires it, the holder is bound by a fresh audience-bound proof-of-possession. An operator can explicitly configureholder_binding.mode: nonefor a bearer-style credential profile, andregistry-notary doctorwarns on that choice. Binding applies only to an issued credential; the plain evaluation result is not a credential and is never holder-bound. - Self-attested data crossing a trust gate: Self-attestation and delegated
self-attestation derive subject, requester, and relationship from the authenticated
principal and scoped authorization details; caller-supplied
requester,relationship, andon_behalf_offields are rejected before any source read, and self-asserted target fields do not satisfy trusted-principal gates. - Spoofed trust context and ungoverned reads (Relay side): Relay ignores trust-context
headers unless the principal is scoped to assert that exact value, and a governed read must
receive a PDP permit or fail closed with a stable
pdp.*code rather than falling back to an ungoverned read. - SSRF / uncontrolled egress: Outbound source fetches are meant to be constrained by Registry
Platform’s outbound-HTTP-policy. Where it is applied, it limits the destinations a source
connector may reach. This is recommended posture rather than a hard guarantee: confirm it
is enabled rather than assuming it. One detail an auditor should note: the guard exposes an
allow_insecure_private_networkopt-in that, per source connection, re-enables plain-HTTP private-network and link-local destinations the strict policy would otherwise refuse. Even with that opt-in, cloud-metadata addresses (such as169.254.169.254and its IPv6 equivalent) stay denied. Treat the opt-in as a deployment decision to review, not a default. - Untrusted federation peers, replay, and wrong-purpose requests: Only configured static
peers are admitted, and before any source read the server verifies peer identity, request
signature, freshness, single use, purpose, profile, and audience. The single-use and
freshness checks are backed by a real replay-store primitive that tracks one-time JWT
jtiand nonce values (federation requestjti, OID4VCIc_nonce, and holder-proofjti).
Audit as a control. Every request touching person-level data can be recorded with the
hashed principal, request id, the principal’s granted scopes, and Data-Purpose context
when present, and a deployment can run audit fail-closed, so an unrecordable request does
not return success. Relay records ordinary scopes unchanged but replaces each exact-value
trust scope with a field-bound registry:trust:<field>:hmac-sha256:<digest> handle under
the deployment audit key. The handle supports investigation and same-key correlation
without disclosing the trusted value. Two caveats for the auditor. First, the recorded scope list is not a
per-route proof of which scope check authorized the request, and per-route audit coverage
is something to verify in your deployment. Second, Notary records hashed principal and
correlation identifiers, and Relay’s trust-scope handles have the same operational
dependency, so operator-side investigation depends on retaining the hashing secret and
request context.
Residual risks and what is left to the operator
Section titled “Residual risks and what is left to the operator”The canonical inventory of every current limit is Known limitations and non-guarantees; the residual risks in this section are the subset a threat model must weigh.
These are the risks the design does not close:
- Key custody is not certified by health checks: Readiness, liveness, and conformance checks do not certify production-grade private-key custody. A deployment using software keys, local JWK files, or demo-generated keys can be reachable and internally consistent yet not production-secure. Custody, rotation, and provider approval remain operator responsibilities.
- The operator boundary is where coverage ends: Secret and key provisioning, key custody and rotation, audit retention and storage, tenant isolation, TLS termination and certificates, edge rate limiting, deployment configuration, and incident response are operator responsibilities, not behavior the model defines.
- The sidecar depends on deployment-network controls: The source adapter sidecar relies on deployment-network egress controls; the model does not enforce them.
- Aggregate routes are not a privacy budget: Relay aggregate routes do not constitute a longitudinal privacy budget; v1 does not track cumulative disclosure across repeated or overlapping aggregate queries. Do not describe a Relay aggregate as privacy-budgeted unless a separate deployed control provides it. This is a residual disclosure risk left to the operator.
- Some claim-definition invariants surface only at runtime: A disclosure default outside the allowed set, a rule source naming an undeclared binding, and the unimplemented plugin rule are not enforced at configuration load; they surface only at evaluation or request time. Duplicate claim ids are different and worse: they are not rejected at load and are never reported, because the runtime lookup returns the first match by id, so a later duplicate is silently shadowed. A source binding with no matching policy resolves on identifiers alone, with no purpose, relationship, or input-minimization gating. These are residual misconfiguration risks left to the operator.
- Known product gaps: No revocation flow is specified (though an optional,
off-by-default credential-status surface can be enabled to mark a credential
revoked), there is no/.well-known/jwt-vc-issuerendpoint, and no built-in data-subject erasure workflow in this version. - Admin reload is non-functional standalone: Notary’s admin reload route returns HTTP 501
(
registry.admin.capability.not_supported) in the standalone router and performs no reload. Runtime configuration changes require deploying a signed local bundle and restarting the service.
What is explicitly out of scope
Section titled “What is explicitly out of scope”These are non-goals for this version. None of them should be read into a Registry Stack conformance claim:
- Dynamic federation: Dynamic trust-chain discovery, audit checkpoint exchange, and federated credential issuance are out of scope. Federation is static-peer only: not a dynamic trust mesh. Replay protection defaults to in-memory, with a Redis-backed shared store available.
- Full Evidence Gateway / external-policy interoperability: Governed Relay PDP enforcement
covers only the supported Evidence Gateway PDP profile (
registry-evidence-gateway-pdp/v1). It is not full Evidence Gateway interoperability, not full OID4VCI issuer behavior, not dynamic external policy discovery, and not enforcement of ODRL terms outside the supported profile. Unsupported ODRL terms or invalid policy identity fail closed. - A full credential issuer: The OID4VCI surface is a scoped self-attestation issuance
subset of OID4VCI Draft 13, not a full issuer or general external-wallet interoperability.
Notary advertises
openid4vci.support: not_full_issuer, and delegated-attestation transaction tokens are rejected at the credential endpoint. - Certified standards compliance: Aligning with a standard is not conforming to it: speaking the shape of OIDC, OAuth 2.0, SD-JWT VC, OID4VCI, CCCEV, W3C DID, or the rest does not certify conformance to any of them, and CCCEV-shaped output is not conformant to CCCEV 2.00.
- Feature-gated surfaces of other builds: Several runtime surfaces (the OGC API Features/Records/EDR adapters and SP DCI sync) are feature-gated and mount only when the build is configured with the matching Cargo feature. Do not infer their presence from the route catalog of a different build. Admin routes run on a separate optional listener and are documented in the generated OpenAPI, but standalone mode does not implement runtime config reload; it returns HTTP 501.
Related
Section titled “Related”- RS-SEC-G: the security model
- RS-PR-NOTARY, RS-PR-RELAY: protocol contracts
- RS-ARC-G: the two-layer architecture
- RS-DM-CLAIM: claim definitions and disclosure
- Harden a production deployment: hardening procedures
- Security overview: the trust posture summary
- Data minimization and purpose limitation