fix(security): issuer binding + fail-closed verification (SOC-32, SOC-35)#5
Open
chongkan wants to merge 2 commits into
Open
fix(security): issuer binding + fail-closed verification (SOC-32, SOC-35)#5chongkan wants to merge 2 commits into
chongkan wants to merge 2 commits into
Conversation
added 2 commits
July 19, 2026 11:22
…OC-32, SOC-35) Verification was fail-open and unbound to the issuer: an unsigned or unresolvable credential could report valid:true (SOC-35), and a credential could claim a trusted issuer while being signed by an attacker's unrelated key (SOC-32). Both closed, fail-closed by default. SOC-32 — issuer binding (src/verifier.ts checkProofs): - Requires at least one valid proof bound to credential.issuer: the proof's verificationMethod DID equals the issuer, or the new optional VerifierConfig.verifyIssuerBinding(issuer, verificationMethod) hook authorizes it (assertionMethod delegation; fails closed if the hook throws). Every proof must still verify; multi-party co-signers from other DIDs remain supported. checkProofs now returns allValid && issuerBoundValid. SOC-35 — fail-closed (src/verifier.ts verify): - Missing proof / missing resolver / unresolved key / bad signature now push errors, not warnings. New VerificationResult.signatureVerified and VerifyOptions.requireSignature (default true; opt-out only for explicit structural-only checks). BREAKING: valid:true now requires a verified signature bound to the issuer. Consumers using delegated signing keys (verificationMethod DID != issuer DID) must supply verifyIssuerBinding, or the default direct-binding check rejects them. Downstream cr-vc-sdk (SOC-16/19) and attestto-app (SOC-10) must wire this. Also: bump to 0.3.0, add @vitest/coverage-v8 dev dep (+ lockfile) so 'pnpm test:coverage' works (kept package.json and pnpm-lock consistent). Tests: 155 pass (+7 security, incl. a genuine forgery — valid attacker signature with mismatched issuer -> rejected). typecheck clean, build green.
The workflow ran 'npm ci', but this repo has only pnpm-lock.yaml (no package-lock.json), so install always failed. Switch to pnpm/action-setup + pnpm install --frozen-lockfile. Lint marked non-blocking (eslint not installed yet — tracked SOC-60); Stub guard + Test + Build gate PRs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Security release for
@attestto/vc-sdk(v0.2.0 → 0.3.0). Verification was fail-open and not bound to the issuer, so an unsigned/unresolvable credential could reportvalid: true(SOC-35), and a credential could claim a trustedissuerwhile signed by an attacker's unrelated key (SOC-32). Both are now closed, fail-closed by default. Closes SOC-32, SOC-35.SOC-32 — issuer binding
checkProofsnow returnsallValid && issuerBoundValid: every proof must verify and at least one valid proof must be bound tocredential.issuer— either the proof'sverificationMethodDID equals the issuer, or the optionalVerifierConfig.verifyIssuerBinding(issuer, verificationMethod)hook authorizes it (DID-documentassertionMethoddelegation; fails closed if the hook throws). Multi-party co-signers from other DIDs remain supported (they must also verify). Blocks the "claim a trusted issuer, sign with your own key" forgery.SOC-35 — fail-closed verification
Missing proof, missing resolver, unresolved key, or bad signature now push errors (not warnings), so
validcan never betruefor an unverifiable credential. AddsVerificationResult.signatureVerifiedandVerifyOptions.requireSignature(defaulttrue).Breaking change (consumers)
valid: truenow requires a verified signature bound to the issuer:requireSignature: false.verificationMethod's DID differs fromissuer(e.g. issuerdid:web:xsigning with a separateassertionMethodkey), you MUST provideverifyIssuerBindingor the default direct-binding check rejects it.cr-vc-sdk(SOC-16/19) andattestto-app(SOC-10) must wire this hook if they use delegated keys.Verification
tsc --noEmitclean,tsupbuild green. CHANGELOG updated (Keep a Changelog / SemVer).Note
Also bumps to 0.3.0 and adds
@vitest/coverage-v8dev dep (+ lockfile, kept consistent) sopnpm test:coverageworks — the security change itself issrc/verifier.ts+src/types.ts+ the newtests/verifier-security.spec.ts.