Skip to content
Registry StackDocsDocumentation preview

Threat model

View as Markdown

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”

Four formal components sit on or beside the trust boundaries. A separate adopter demo sits outside the production trust boundary:

  • 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.
  • Solmara Lab: a separate adopter demo, running on synthetic data and demo configuration. Treat Solmara Lab 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.

The Registry Stack trust boundaries in one map. The portable metadata layer
(Registry Manifest) only describes: no production data, no authentication, no
secrets; it authorizes nothing, and publishing grants no access. Across the
primary trust boundary, the runtime services layer enforces authentication,
authorization, and disclosure. A caller crosses the service edge, where each
service runs exactly one authentication mode, static-credential fingerprint or
OIDC, and every claim- or record-bearing route is authenticated before
responding. Registry Relay owns registry source access, credentials, product-neutral
HTTP and script adaptation, snapshots, and minimized consultation outputs. Registry
Notary consumes only compiler-pinned Relay consultations for registry-backed claims,
or configured self-attestation inputs for Notary-only claims. Federation with a peer
Notary is static-peer only, not a dynamic trust
mesh; it requires a verified peer-signed JWS, and peer identity, signature,
freshness, single use, purpose, profile, and audience are verified before any
Relay consultation or claim evaluation. Delegated self-attestation is a distinct trust context from peer
federation: it delegates no trust to a peer Notary and stays inside the citizen
or OIDC trust context.

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 Relay. A runtime boundary sits between Registry Notary and Registry Relay for every registry-backed claim. The compiler pins the consultation profile, contract hash, purpose, and typed input mapping. Notary authorizes the claim before calling Relay, and Relay applies its own workload authorization before source work. Notary has no direct registry source connector. One Notary authority pairs with one Relay authority, and Notary owns its PostgreSQL transaction, replay, evaluation, audit, and credential-status state.

Relay to registry sources. Registry Relay owns the source boundary. Its compiled integration fixes the origin, credential interface, method and path authority, input placement, output projection, and cumulative limits. Product and version metadata do not select the HTTP, script, or snapshot capability.

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.

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, Relay source-adaptation 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 and deny-by-default. Relay refuses unauthorized callers before source work. Notary refuses unauthorized callers before a Relay consultation or claim evaluation. 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+json error 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 and only the typed request inputs the claim policy admits, never the evaluated value. Relay returns only the compiler-declared typed outputs, and Notary rejects undeclared inputs.
  • 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:jwk for credential profiles; where a profile keeps binding enabled, and on the OID4VCI issuance path, which requires EdDSA did:jwk, the holder is bound by a fresh audience-bound proof-of-possession. An operator can explicitly configure holder_binding.mode: none for a bearer-style credential profile, and registry-notary doctor warns 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, and on_behalf_of fields are rejected before any Relay consultation or claim evaluation, and self-asserted target fields do not satisfy trusted-principal gates.
  • Source-free evaluation promoted to credential evidence: Credential-capable profiles and OID4VCI configurations accept only non-delegated registry-backed claims. Source-free, delegated, legacy, missing, stale, or modified evaluation provenance fails before signer access.
  • Stolen issuer-initiated offer: A transaction code is required by default and is transferred separately from the offer. Codes are single-use, wrong PIN attempts are bounded, and invalid redemptions are rate limited. An explicit no-PIN compatibility profile has a maximum 300-second lifetime, but the unredeemed offer remains bearer credential material that a thief can use during that window.
  • 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: Registry Stack project authoring accepts exact HTTPS source origins. Relay’s compiled plan fixes the destination, method, path authority, safe headers, and cumulative call budgets before the runtime resolves a credential or sends a request. Rhai can control ordinary request flow only inside that host-enforced authority.
  • Untrusted federation peers, replay, and wrong-purpose requests: Only configured static peers are admitted, and before any Relay consultation or claim evaluation 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 jti and nonce values (federation request jti, transaction-bound OID4VCI proof nonce, and holder-proof jti).

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.
  • 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.
  • Project approval remains a semantic control: Schemas and validators reject malformed claims, duplicate identifiers, incomplete Relay pins, and unsupported evidence modes. They cannot decide whether an institution selected the correct source fields, lawful purpose, claim rule, or disclosure default. Those decisions require project review and fixture evidence.
  • 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-issuer endpoint, and no built-in data-subject erasure workflow in this version. When a status-bearing credential is used, its top-level status is not selectively disclosable and client verification fails closed unless the signed status is valid at the exact trusted HTTPS origin.
  • 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.

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. Production replay protection uses the typed Notary-owned PostgreSQL state schema; explicit in-memory state is local and single-instance only.
  • 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 registry-backed, issuer-initiated pre-authorized subset, not a full issuer or general external-wallet interoperability. Notary advertises openid4vci.support: not_full_issuer. It does not claim wallet-facing authorization code, a public nonce endpoint, response next nonce, source-free issuance, ES256 holder proof, EUDI, HAIP, PAR, DPoP, or wallet attestation.
  • 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.