Skip to content
Registry StackDocsv0.25.0

Security overview

View as Markdown

Use this page to assess the security posture of Registry Stack before you deploy it or integrate with it. It summarizes the model the software enforces, names the responsibilities that stay with the operator, and links the public evidence a reviewer can verify. The normative source for everything on this page is the security model specification RS-SEC-G; this page is its narrative summary.

For a focused review, continue with the threat model, known limitations, security self-assessment, and release trust evidence.

WhatWhere it stands
Authentication on every route that touches a record or a claimBuilt in
One inbound authentication mode, and it is OIDCBuilt in; there is no API-key or static-credential mode in either maintained runtime
Deny-by-default authorization, checked before any data is readBuilt in
Only the public half of a signing key is ever publishedBuilt in
Person-level access is written to an audit logBuilt in, and fail-closed with no switch to turn it off
Three disclosure modes, with redacted revealing neither value nor answerBuilt in (Evidence Gateway)
Governed contract verified before the process serves anythingBuilt in (Relay re-derives its package and compares bytes at startup)
A signature over the governed Relay packageNot provided; packageRevision is an integrity digest, not an authenticity proof
Selective-disclosure SD-JWT VC serialization of an assertionBuilt in (a second encoding, not a credential lifecycle)
Holder-bound subject binding, proven at presentation by a key-binding JWTDeclared per requirement, verified by the relying party (frozen profile)
Replay prevention for a presented credentialNot provided anywhere; the challenge lifecycle is the relying party’s
Token issuers authenticate clients by one registered method, asymmetric by defaultBuilt in; client-secret compatibility is explicit and limited to standard authority
Hardened HTTP headers and outbound-traffic limitsRecommended, confirm your build applies them
Key custody, retention, isolation, TLS, rate limitingYour deployment’s job
Compliance with any external standard it citesNot claimed

Every request that returns person-level records or claim results moves through the same pipeline: authenticate, authorize, serve, audit.

  • Authenticate: a maintained runtime service runs exactly one inbound authentication mode, and it is OIDC (REQ-SEC-G-002). Each service trusts exactly one issuer, and tokens are accepted only after signature, issuer, audience, token-type, and algorithm checks (REQ-SEC-G-004). Registry Relay narrows this further: the issuer’s discovery URL must be an https URL byte-identical to its canonical /.well-known/openid-configuration form, the accepted token type is exactly at+jwt, and exactly one of EdDSA, ES256, or RS256 is permitted per deployment.
  • Authorize: authorization is scope-based and deny-by-default. The required scope is enforced before any source is read, and a caller’s reach is never widened at request time (REQ-SEC-G-005, REQ-SEC-G-006). Where a Relay operation declares a purpose constraint or an authority row binding, both are read from the verified token, never from a request field.
  • Serve: the service returns the configured surface: a governed read shaped by a compiled disclosure profile, a claim result, a selected value, a signed assertion, or a denial. Relay denials come from a closed set of 26 problem codes. Every Evidence Gateway claim is answered in one of three disclosure modes: value hands back the computed value, predicate returns only a yes/no, and redacted returns neither the value nor the yes/no; the claim’s policy fixes which modes are allowed. Disclosure modes and computed answers covers this in depth.
  • Audit: the request is recorded in a hash-chained audit envelope capturing the caller principal kind, the authority exercised, a request identifier, and the governed revision the answer was produced under (REQ-SEC-G-008). Audit is fail-closed and there is no configuration to make it otherwise: a Relay request whose audit record cannot be written returns 503 audit.unavailable instead of an answer, and Relay re-checks its audit sink for readiness rather than trusting the check it did at startup (REQ-SEC-G-009).

Every route that returns person-level records or an evidence assertion requires authentication. Operational probes, public verification keys, issuer metadata, and docs or OpenAPI when configured may be public because they carry no person-level data. Evidence Gateway exempts only its probes, its OpenAPI document, its issuer JWKS, and its issuer metadata document; both its assertion route and its evidence-definition discovery route require a verified access token (REQ-SEC-G-006). Registry Relay exempts GET /health, GET /ready, and GET /openapi.json, and beyond those it is the contract, not the route, that decides: each compiled operation carries an access profile that is either public or protected, so a Relay deployment can legitimately publish an open operation and must therefore be reviewed at the contract, not at the route table. Metadata is separately gated by a declared visibility of public, operation-bound, or operator-only, and an operator-only artifact answers 404 resource.not_found rather than confirming it exists. Issuers sign with asymmetric keys and publish only the public half, so any verifier can check a signed artifact without holding a credential of its own (REQ-SEC-G-007).

Evidence Gateway can serialize the same signed assertion as an SD-JWT VC, where the signed body carries hashes of each disclosable field rather than the values. That is a second encoding of one response, not a credential lifecycle: there is no issuance session, credential offer, status list, revocation, reissuance, or persisted credential state, and no part of OID4VCI. A requirement in the immutable bundle declares which of two subject binding modes it issues under. The vocabulary is closed at two values and has no default, and no caller input selects a mode.

By default the binding is audience-scoped: the subject identifier is a pseudonym derived over the audience, so the same person requested for a different audience yields a different identifier. A request may carry an optional holderKey, echoed into the cnf claim; Evidence Gateway binds nothing to it and verifies nothing, so cnf is an unverified caller-supplied convenience for whatever presentation layer the operator runs elsewhere.

A requirement may instead declare the holder-bound mode, where the binding derives from the RFC 7638 thumbprint of the holder key rather than from the audience, cnf is required, and the audience check moves from issuance to presentation. Possession is then proven by a key-binding JWT under RFC 9901 section 4.3, which the relying party verifies. The service still issues no key-binding JWT, holds no challenge or presentation state, and runs no presentation endpoint; verification is a relying-party act, offered as the portable verifier library and the evidence verify-presentation operator command. Reviewers should read three consequences together: the mode accepts cross-verifier correlation through holder key reuse, which the service can neither prevent nor detect; comparing a challenge nonce is not consuming it, so nothing here is replay prevention; and the mode’s profile is frozen, a status it carries only because every negative test it names resolves to an executable test. Known limitations states both modes side by side.

Where a deployment has no identity provider, Registry Mint issues the access tokens Evidence Gateway or Registry Relay verifies. Mint authenticates each registered client by the one method its registration selects. Asymmetric client assertions remain the default and the only method for Evidence or delegated authority; a standard authorization client may explicitly use a Mint-generated secret represented in the registry only by its fingerprint. Mint reads token authority from its own server-side client registry rather than from the request. Its token endpoint does not distinguish an unknown client, an authentication-method mismatch, a bad signature or secret, a replayed assertion identifier, and an expired assertion, so it cannot probe the client registry (REQ-SEC-G-016).

Delegated evaluation, where one service asks a trusted peer to evaluate a claim, does not exist in this stack: no service holds such a surface, and REQ-SEC-G-010 is retired.

Two behaviors are recommended rather than required, so confirm them rather than assume them.

  • Registry Platform sourcing: security-critical primitives (authentication, OIDC verification, audit envelopes, HTTP security, outbound HTTP policy, cryptography, and SD-JWT VC helpers) are meant to come from Registry Platform rather than be reimplemented per service, so they behave the same everywhere and can be reviewed in one place. RS-SEC-G recommends this rather than forcing it (REQ-SEC-G-001), so confirm the service you deploy sources them from Registry Platform rather than reimplementing its own.
  • HTTP hardening: security response headers and limits on outbound source-fetch traffic are recommended posture (REQ-SEC-G-012). Check that your build applies them rather than assuming it. The outbound half applies to Evidence Gateway, which fetches from configured authoritative sources. Registry Relay makes no outbound source request at all: its only outbound traffic is OIDC discovery and JWKS retrieval, so for Relay this reduces to response headers and the request and response limits its runtime file sets.

What Registry Relay checks before it serves

Section titled “What Registry Relay checks before it serves”

Relay has no declared assurance level, no posture endpoint, and no findings-and-waivers catalog. A deployment does not state the assurance it claims; it either satisfies the startup checks or the process does not serve. Those checks are the security surface worth reviewing:

  • The package is verified first. Before Relay opens a source, a sink, or a socket, it re-runs the compiler and the artifact generator over the sealed inputs and compares the results byte for byte against what the package contains. A package that does not re-derive does not start.
  • The runtime file cannot widen the package. runtime.yaml (kind: RelayRuntime) is a closed schema that rejects unknown fields, and it binds only local concerns: listen address, package path, source paths, the audit sink, an optional OIDC issuer, cursor settings, limits, quotas, and shutdown. Nothing in it can add an operation, a readable column, or a disclosure.
  • Contract and runtime must agree. A contract with any protected access requires a configured issuer, and a contract that declares any lookup requires quotas. Neither is a warning.
  • Secrets are referenced, never inlined. The runtime file accepts exactly two reference grammars, secret:env/<NAME> and secret:file/<name>, and nothing else. A file secret must be a single flat filename, owned by the running user, with mode exactly 0600 and a link count of one, and no secret may exceed 64 KiB.
  • The source is opened read-only and pinned. Relay opens SQLite with SQLITE_OPEN_READ_ONLY | SQLITE_OPEN_URI | SQLITE_OPEN_NO_MUTEX, refuses a symlink, refuses a -wal or -journal sidecar beside a snapshot, and requires a snapshot to sit on a read-only filesystem or to be non-writable by its own mode bits.
  • Operational logging is a closed enum. RELAY_LOG accepts off, error, warn, info, debug, or trace; any other value falls back to the crate’s own info filter rather than enabling a dependency’s logging.
  • Unix only, and it fails closed off it. On a non-Unix target Relay’s permission check returns false unconditionally and runtime-path validation returns an error, so the process refuses to start rather than running without the ownership and mode checks it relies on.

Two of these are worth stating as limits rather than as controls. The package check proves the package is internally consistent and unmodified, not that a trusted party produced it: there is no signature over a Relay package. And a snapshot’s immutability past startup is an operator duty, because a process comparing two hashes cannot exclude a privileged writer that changes and restores the bytes between them.

The runtime keys are documented in the Relay configuration reference and the day-two procedures in operate Relay. Evidence Gateway states its own operator obligations in its operator contract rather than through any posture block.

The security model ends where the deployment begins. Secret and key provisioning, key custody and rotation schedules, audit retention and storage, tenant isolation, transport termination and certificate management, edge rate limiting, deployment configuration, and incident response are operator responsibilities (RS-SEC-G Section 9). The software keeps secrets out of distributable artifacts (REQ-SEC-G-013) and provides the primitives; the operator provisions, configures, and operates them. Record those responsibilities in the operator handoff checklist before activation.

Public, checkable evidence, from most to least formal:

These pages are review material, not a certification. The hosted lab is a synthetic-data demo, not a production assurance claim.

Suspected vulnerabilities go through the private disclosure process, never public issues or pull requests. See Report a vulnerability.