Skip to content
Registry StackDocsDevelopment (unreleased)

Evidence Gateway security model

View as Markdown

Evidence Gateway’s security model is contract-driven. The invariant matrix in products/evidence/contracts/security-invariant-matrix.yaml is the source of truth for what the runtime guarantees, products/evidence/contracts/security-test-traceability.yaml binds every invariant to the named tests that prove it, and continuous integration regenerates and byte-diffs both contracts through the evidence-contracts job, which runs products/evidence/scripts/check-contracts.sh. A change that narrows a guarantee or drops its test fails that job rather than merging unnoticed.

The matrix pins 40 numbered invariants, V1-I01 through V1-I40, each with a rule, the threat it closes, its enforcement, and one negative_test id. This section groups them by theme and cites each id in backticks; the matrix itself is the complete, normative text.

Evidence Gateway evaluates only predefined, versioned requirement revisions (V1-I01). The request schema is closed, so callers cannot supply thresholds, expressions, scripts, paths, headers, source fields, relationship types, adapter parameters, or response projections (V1-I02). Because a bundle enabling several requirements at once can reconstruct a protected value across them, the complete enabled bundle is reviewed as one disclosure surface (V1-I03).

Principals and attributes derive only from configured, validated authentication sources, and missing data denies rather than falling back to another claim (V1-I04). One authorization decision binds requester, optional actor, requirement revision, purpose, every role, profile, and origin tuple, authority, and audience together, so partial matches across grants cannot be unioned into access (V1-I05). Selector profiles and values are provider-lookup inputs only, never proof of authority (V1-I06), and a caller-supplied consent, approval, or grant reference cannot create authority on its own (V1-I07). Callers cannot choose selector fields, operators, weights, thresholds, normalization, or query plans; named profiles close the exact field set (V1-I08).

Source calls are fixed by trusted configuration and executed only by the core, closing off SSRF, credential forwarding, and script-directed networking (V1-I09). Provider lookup exposes only match, no_match, or ambiguous; Evidence Gateway never surfaces or chooses candidates (V1-I10). Rhai scripts return one closed lookup result and declared typed concept values only (V1-I11), and the core rejects undeclared concepts, extra fields, and any value that violates its type, codelist, cardinality, precision, or size before evidence construction (V1-I12). Public evidence is assembled only by the core after that validation, so a script cannot inject envelope fields, subject identifiers, or unsupported claims (V1-I25).

Missing facts, undefined decisions, script failures, audit failures, and evaluation failures all fail closed rather than releasing partial or unaudited evidence (V1-I13). Raw source responses are never persisted or logged (V1-I14), and selector, source, and disclosed values never appear in logs or native audit (V1-I15). No-match, ambiguous, missing-fact, and inconsistent-derivation outcomes collapse to one public problem shape by default, so a caller cannot use response shape, message, or timing as an existence oracle for registry membership (V1-I16). Subject bindings are scoped to their audience, purpose, role, and selector bundle, so the same subject is not linkable across purposes or relying parties (V1-I17).

Configuration is immutable for the life of the serving process: there is no runtime override, reload, merge, or fallback path (V1-I18). One process serves one operator-controlled trust domain, with one service trust domain, issuer governance boundary, bundle lifecycle, signer, and audit boundary (V1-I19). Rate controls are defense in depth rather than a substitute for safe bundle design; combination validation runs independently of configured rate limits (V1-I20).

A signed flattened JWS over the exact evidence payload is mandatory, available to every authorized grant, and the default response; unsigned output exists only through its own exact media type when both the bundle and the matched grant permit it (V1-I21). Missing or failed signing never falls back to unsigned evidence (V1-I22). Private signing material is owned by the core alone and stays absent from bundle values, Rhai, logs, audit, and errors (V1-I23). A signature authenticates the provider and payload integrity; it does not assert legal-signature status or source truth (V1-I24).

Discovery, nonce handling, and response-format authorization

Section titled “Discovery, nonce handling, and response-format authorization”

Evidence Gateway definition discovery is authenticated and requester-scoped; it never creates authority or exposes deployment internals such as selectors, source plans, or credentials (V1-I26). Every request carries one canonical 32-byte random nonce that is echoed into the evidence payload but never stored, uniqueness-checked, or exposed to authorization, rate limits, Rhai, source requests, logs, metrics, traces, or native audit (V1-I27). The unsigned response format is authorized only when the immutable bundle enables it and the one complete matched grant also permits it; API selection, runtime configuration, or other grants create no permission on their own (V1-I28). The final immutable response bytes exist before the disclosure-release audit is durably accepted, and those are the exact bytes released afterward (V1-I29). Every native audit event records the closed response-protection mode, and a signing key identity is present exactly for signed release (V1-I30).

Strict signed verification compares the response against independently retained expectations, the expected nonce, the expected unordered set of unique role-bound subject bindings, and the expected concept identifiers, forms, and cardinalities, and returns one generic policy-mismatch error rather than revealing which comparison failed (V1-I31). An access token carrying a proof-of-possession confirmation claim is denied rather than accepted as an ordinary bearer token, because Evidence Gateway validates no sender proof and accepting one would discard the constraint the token was issued under (V1-I32).

The local assurance profile alone may omit requirement fixtures, while production and evidence-grade retain complete fixture validation (V1-I34). Assertions, definition responses, and audit events carry the governed assurance profile, and verification compares it with an independent expectation (V1-I35). Credential-free source access is limited to local authoring against an exact numeric loopback HTTP origin and sends no authentication header (V1-I36). Configured authority claims use distinct token members and cannot reuse registered JWT claims, except that the principal may use sub (V1-I37). Every authority selector set must fill each selector-bound path template it can activate (V1-I38). A search-then-fetch acquisition is one fixed search and, only after a unique schema-valid match, one fixed fetch; only validated prior facts cross the stage boundary and no response or script can create another call (V1-I40).

Operational telemetry is off by default, served only on a separate operator-private listener, and every series label is drawn from a closed set (route template, method, status category, problem code) rather than from request content (V1-I33).

The matrix also pins 23 cross-cutting controls, keyed by name rather than a V1-I id, each with its own negative test. They cover bundle and secret trust (config_trust, secret_parsing), durable audit ordering (audit_order), listener exposure and outbound transport (transport_identity, outbound_tls_and_proxy, transport_pinning), secret file identity (secret_file_identity), the closed runtime file (runtime_ownership_split), request- and script-boundary details (request_preparation, subject_role_order, source_response_shape, script_resource_exhaustion, reserved_header_aliases, jwks_route_parity, unsigned_envelope_distinct, exact_decimal, entity_reference_projection), signing and audit key governance (signing_key_governance, audit_key_epoch), and the SD-JWT VC serialization (sd_jwt_vc_projection_integrity, sd_jwt_vc_format_authorization, sd_jwt_vc_holder_key_closed, sd_jwt_vc_claims_closed).

Each invariant’s negative_test id in the matrix is a key into products/evidence/contracts/security-test-traceability.yaml, which lists the file and function name of every test that proves it. A negative test may be split across several functions, but the matrix states one binding review rule: a test may be split, never weakened or deleted.

The mapping is not narrative. crates/registry-evidence/tests/security_contract_traceability.rs reads both contracts and checks three things: every matrix id has a traceability entry and every traceability entry maps to a matrix id, so neither can drift from the other; each referenced test file exists and contains a matching fn <name>( signature; and that signature sits under a #[test] or #[tokio::test] attribute rather than a plain function that happens to share a name. A traceability entry that names a deleted or renamed test fails this check before it fails anything else.

Terminal window
products/evidence/scripts/check-contracts.sh

Three concrete examples:

  • V1-I04 (principals and attributes derive only from configured authentication sources) traces to sec-missing-principal-no-fallback, proven by missing_principal_never_falls_back_to_client_id_or_azp in crates/registry-evidence/src/runtime_tests.rs.
  • V1-I23 (private signing material stays core-owned and absent from bundle values, Rhai, logs, audit, and errors) traces to sec-private-key-canary-unreachable, proven by yaml_names_and_secret_references_are_strict in crates/registry-evidence/src/config.rs and jwks_contains_public_material_only in crates/registry-evidence/src/signing.rs.
  • V1-I29 (final immutable response bytes exist before the disclosure-release audit is durably accepted) traces to sec-release-bytes-pre-audited, proven by four tests in crates/registry-evidence/src/runtime_tests.rs: disclosure_audit_failure_prevents_signed_response_release, disclosure_audit_failure_prevents_unsigned_response_release, signing_failure_returns_a_problem_and_never_an_unsigned_body, and sd_jwt_signing_failure_no_fallback_format.

The invariant matrix describes what the evidence binary enforces in code. products/evidence/OPERATOR-CONTRACT.md states duties that remain with the operator because no service-side control can hold them for a deployment it does not otherwise touch.

Owner-only key files. Each secret file below the configured secretProviders.file.root must be a regular, non-symlink file owned by the service identity with mode 0600; the file provider rejects anything else. Audit and subject-binding secret files must contain independently generated raw key bytes and be at least 32 bytes. The runtime derives separated audit-chain and identifier subkeys from each master, but Evidence Gateway also requires the two secret references and resolved master bytes to be distinct.

Immutable bundle and runtime file. The operator mounts one reviewed governed bundle and one closed runtime.yaml read-only at startup; the bundle directory, the runtime file, and every captured artifact must be non-writable to the service process, with directories and files carrying no write bits. There is no runtime upload, editor, approval API, hot reload, merge, mutation, governed-field override, or fallback bundle or runtime file: a new revision is a new deployment, not a live change.

Secret handling. Source credentials and local-development private signing material reach Evidence Gateway only through the secret-reference mechanism and must not appear in YAML values, Rhai, command arguments, environment dumps, logs, audit, errors, snapshots, or generated contracts. Production and evidence-grade deployment uses a workload-local Vault or OpenBao Transit proxy over a Unix socket, leaving the service private key non-exportable. The operator configures one active public ES256 P-256 JWK, whose derived RFC 7638 thumbprint is its kid, and retains published public keys for at least the maximum assertion validity plus allowed clock skew. A revoked key can be neither active nor published.

Suspected Evidence Gateway vulnerabilities, including credential disclosure, authentication bypass, audit redaction failure, source connector data leakage, and signing-key handling bugs, go through the private disclosure process in SECURITY.md, never a public issue or pull request. See Report a vulnerability for the complete in-scope list and reporting steps.