fix(notary): bind credential issuance to Relay provenance#410
Conversation
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
b6b3fdf to
42b6441
Compare
|
Exact-head CI update: The first matrix correctly rejected two stale The focused repair changes only that optional diagnostic fixture to a complete registry-backed Relay consultation, so the tests again reach their intended outcomes:
Amended exact head: Verification:
The full amended exact-head remote matrix is running. The PR remains draft until every applicable check is green. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 42b6441892
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for claim_id in levels.into_iter().flatten() { | ||
| allowed_claim_ids | ||
| .insert(BoundedClaimId::new(claim_id).map_err(|_| EvidenceError::InvalidRequest)?); |
There was a problem hiding this comment.
Validate subject-bound inputs for dependency closures
When a subject-access request selects an allowed root that has registry-backed dependencies, this loop now grants SubjectBound capability to every claim in the dependency closure. Startup still runs validate_subject_bound_registry_inputs only over subject_access.allowed_claims, and credential dependencies are intentionally not listed there because they do not carry the credential profile. That means a registry-backed dependency can map its Relay input from caller-controlled or otherwise non-bound paths such as target.attributes.*, pass config validation, and then execute under a subject token during evaluation/OID4VCI issuance for data not bound to the authenticated subject. Please validate the registry input bindings for the full closure before adding dependencies to the subject-bound capability.
Useful? React with 👍 / 👎.
| validate_relay_activation_shape(&self.evidence.claims)?; | ||
| self.subject_access.validate(&self.auth, &self.evidence)?; | ||
| self.validate_oid4vci_cross_block()?; | ||
| validate_credential_claim_bindings(&self.evidence)?; |
There was a problem hiding this comment.
Reject unsupported delegated-proof dependencies
After the global registry_backed dependency ban was removed, this replacement validation only checks credential claim closures. A delegated relationship can now configure its registry-backed proof_claim with a registry-backed depends_on and still pass startup, but delegated runtime capability only permits Relay consultation for the proof claim itself (require_relay_consultation_capability), so every delegated evaluation using that relationship fails at preflight instead of being rejected as an invalid config. Please either validate delegated proof closures here or explicitly reject proof-claim dependencies.
Useful? React with 👍 / 👎.
| "result": { | ||
| "evaluation_id": evaluation_id, | ||
| "issued_at": issued_at, | ||
| "provenance": provenance, | ||
| }, |
There was a problem hiding this comment.
Bind the signed result fields to the Relay execution
This new execution binding commits the Relay pin and public provenance, but not the claim result fields that are actually disclosed in the SD-JWT (value, satisfied, and related result metadata). If a stored evaluation is partially corrupted or modified after evaluation by changing only results[0].value from false to true, require_issuable_evaluation_provenance still recomputes the same binding and issuance signs the altered value because sd_jwt::issue reads directly from evaluation.results. Include the signed result payload (at least value/satisfied and subject fields used for issuance) in the binding so partial stored-record mutation is caught before signing.
Useful? React with 👍 / 👎.
Summary
Runtime and public-truth portion of #384. The authoring portion is already integrated through #390. Keep #384 open until the final Notary schema closure under #227 confirms the same narrowed contract.
Why
A signed declaration, holder proof, authentication result, or relationship proof is not authoritative registry evidence. Previously, a hand-authored Notary configuration or stored evaluation could still reach direct or OID4VCI signing from a source-free claim even though generated project authoring had removed that capability.
The implementation also preserves usability: source-free and delegated evaluation-only journeys remain supported, invalid credential bindings fail during configuration with actionable remediation, and registry-backed claim dependencies work end to end instead of failing late at issuance.
Developer and operator impact
Security boundary
Each credential-capable evaluation stores one exact compiler pin per claim and a normalized set of successful Relay executions. A versioned canonical SHA-256 binding covers claim/version/profile/contract/purpose, consultation ULID/acquisition time, evaluation/result timing, and exact ClaimProvenance. Issuance reconstructs the active dependency closure and rejects partial modification, ID swaps, missing, duplicate, or extra records before signing.
This unkeyed digest detects partial corruption or mutation. It is not an authenticity mechanism against an operator able to rewrite every stored field and recompute the digest; database authorization and audit controls remain that boundary.
OID4VCI preserves its existing nonce-before-evaluation ordering. Static source-free configuration denial occurs before nonce access; stored-provenance verification occurs after evaluation but before issuer resolution, status, or signing. Broader transaction semantics remain #57.
Validation
da3c7c7dcc8898503aa9439b42e05fe8e3d63c06; Cargo.lock unchangedThe optional OpenAPI breaking comparison was not run locally because
OPENAPI_CONTRACT_BASE_REFwas unset; remote CI is authoritative.The only overlap with draft #407 is removal of the obsolete held hosted-credential-tour link from the shared spreadsheet tutorial. #407 must preserve that deletion when rebased.
Merge only after the full remote matrix is green on exact head
b6b3fdf578fa6a72b96595839d174a6b388a97d2.