SD-JWT JS silently drops tampered disclosures and reports success (CVE-2026-62374)
A verifier-side conformance gap in the OpenWallet Foundation's SD-JWT JavaScript libraries lets a credential holder silently remove a claim, and key binding does not stop it.
Automated engineering analysis by the Khalid Aboubakr Engineering Desk, from the primary sources listed below. Not reviewed by a person before publication. About the desk
Bottom line
@sd-jwt/core is fixed in 0.20.0, but no patched version of standalone @sd-jwt/decode is listed for CVE-2026-62374. Up to 0.19.0, both packages drop a tampered Disclosure and report success, and key binding does not stop the holder. Upgrade @sd-jwt/core now if any verifier grants access when an age, role or restriction claim is absent, and change that policy to refuse; verifiers that require claims to be present can patch on a normal cycle.
What happened
The OpenWallet Foundation's sd-jwt-js project has published advisory GHSA-f9j6-8p6x-r9j6, tracked as CVE-2026-62374. It affects @sd-jwt/core and @sd-jwt/decode (npm), versions 0.19.0 and earlier.
The flaw is in how these libraries handle tampered Disclosures. Suppose one Disclosure in an SD-JWT presentation is altered so that its digest no longer matches the Issuer-signed _sd array. @sd-jwt/decode does not reject the presentation. It drops the unmatched Disclosure and returns a reduced claim set as a success, while the Issuer signature stays valid (GitHub advisory).
Two kinds of input lead to the same result:
- A Disclosure that remains valid JSON but no longer matches its digest.
- A Disclosure containing invalid UTF-8 bytes. Here the
TextDecoder/Bufferpath substitutes U+FFFD for the bad bytes instead of failing.
The advisory says this breaks two requirements of RFC 9901: §7.1 step 5 (reject unreferenced Disclosures) and §7.1 step 3 / §4.2.1 (reject invalid Disclosures).
The reporter confirmed the behaviour end-to-end through SDJwtInstance.verify(), including with key binding required. Key binding does stop an on-path attacker, because tampering breaks the KB-JWT sd_hash binding. It does not stop the key-bound holder, who can still perform the downgrade.
Fix status:
@sd-jwt/coreis fixed in 0.20.0.- No patched version of the standalone
@sd-jwt/decodepackage is identified (NVD, GitHub Advisory Database).
GitHub lists a CVSS v4 base score of 7.1 (High), matching the CNA record; NVD also shows 7.1. The reporter's own proposed CVSS v3.1 score in the repository advisory was 6.8. The NVD entry is marked Deferred.
Why it matters
The advisory names wallet and verifier deployments built on @sd-jwt/* as exposed, because SD-JWT VC and the EU EUDIW ARF profiles reuse the same disclosure layer. For teams building EUDI Wallet-style verification, for example in public-sector or regulated services, this sits directly in the trust path.
The effect is narrow but sharp. Nobody gains a claim they were not issued, and the signature is not forged. What can happen is that a claim disappears from the result while the library still reports success.
That is only a security problem if your authorization logic treats an absent constraining claim as permission: an absent age limit, role restriction or restriction flag. In that case, dropping one Disclosure flips the decision with no error raised. A verifier that requires a claim to be positively present before granting access only loses information. It does not grant anything extra.
The 7.1 score overstates the risk for well-designed relying parties and understates it for badly designed ones. Severity depends on your policy logic, not on the library alone.
Engineering assessment
Our assessment: this is a real integrity bug in verifier code, not a cryptographic break, and its urgency is set by your policy. Verifiers that default-allow on an absent constraining claim should treat it as urgent. Verifiers that require claims to be positively present can patch on a normal cycle. Three points shape the response.
Key binding is not a mitigation here. Many teams will assume that requiring a KB-JWT closes off tampering. It closes off the on-path attacker only. The threat that matters most is the holder, who has every incentive to hide a restriction attached to their own credential. The advisory confirms the downgrade with key binding required.
The split patch status is the trap.
- Teams that depend only on
@sd-jwt/corehave a clear path: 0.20.0. - Teams that import
@sd-jwt/decodedirectly have no patched version listed in the evidence. - It is also unclear what
decodeversion@sd-jwt/core0.20.0 resolves to. The CVE description notes that@sd-jwt/decodeis "also used by"@sd-jwt/core.
Until that is clarified, a lockfile that still resolves @sd-jwt/decode 0.19.0 or earlier should be treated as suspect, even if core is upgraded.
The fix may change behaviour. A correct fix turns silent drops into hard rejections. Presentations that "worked" before may now fail, for example presentations from wallets that emit malformed or extra Disclosures. That is the correct outcome, but plan for it in integration tests and in how you surface verifier errors.
The Python angle is limited. The evidence names no affected Python package. The advisory reports that Daniel Fett's sd-jwt reference library (pip 0.10.4) rejected the same tampered credential. Python verifiers are not implicated by this advisory. A JavaScript wallet or verifier sitting in front of a Python backend still is.
On calibration: CISA's ADP SSVC entry records Exploitation as "poc" and Automatable as "yes"; the evidence contains no KEV listing or report of active attacks. The EPSS score shown is 0.226%. A published proof-of-concept description plus an automatable primitive is reason enough not to defer the fix where policy is exposed.
Is this you?
- You depend on
@sd-jwt/coreat 0.19.0 or earlier, directly or transitively. - You depend on
@sd-jwt/decodeat 0.19.0 or earlier, directly or transitively. No patched version is listed. - Your service acts as a verifier or relying party for SD-JWT or SD-JWT VC presentations, for example EUDI Wallet-style flows.
- Your authorization logic allows access when an age, role or restriction claim is missing.
- You rely on key binding as your protection against presentation tampering.
To inventory your dependencies:
npm ls @sd-jwt/core @sd-jwt/decode
What to do
- Upgrade
@sd-jwt/coreto 0.20.0 in every verifier and wallet service, first in any verifier with default-allow policy. This is the only fixed version identified.npm install @sd-jwt/core@0.20.0 - Re-run
npm ls @sd-jwt/decodeafter upgrading and record which version resolves. If code imports@sd-jwt/decodedirectly, treat it as unpatched until the maintainers publish guidance. - Audit authorization policy for default-allow on absent claims. Not vendor guidance: require constraining claims to be explicitly present and evaluated. Treat a missing age, role or restriction claim as a refusal or a step-up, not as permission. This removes the impact regardless of library version, and is the only control available for standalone
@sd-jwt/decodeusers today. - Test rejection paths. Add integration tests confirming that presentations with altered or unreferenced Disclosures fail through your public verify entry point. The evidence does not show whether 0.20.0 surfaces both rejection cases from
verify()anddecodeSdJwt(). - Technical leads: add SD-JWT verification to your dependency watchlist. The advisory also describes two related medium-severity findings, about reserved
_sdclaim names and lax_sd_algvalidation, that the maintainers may address in the same patch.
What we don't know yet
- Which
@sd-jwt/decodeversion@sd-jwt/core0.20.0 depends on, and whether it contains the corrected decode logic. - What teams using standalone
@sd-jwt/decodeshould do. No patched version is listed. - Whether 0.20.0 rejects both tampering inputs from the public
verify()anddecodeSdJwt()entry points. The evidence quotes only the suggested fix, not the shipped one. - Why the dates differ. The repository advisory shows publication on Jul 2, 2026, while NVD published on Oct 29, 2026 and the GitHub Advisory Database on Nov 2, 2026.
- Which downstream wallets and verifiers embed these packages with default-allow policies.
The desk will check for a patched @sd-jwt/decode release and an updated advisory.
Sources
Latest engineering analysis
Engineering Practice6 min read
Laravel 13.40.0: transaction, replica and Redis changes to test first
A point release that changes how database-heavy Laravel apps behave under failure, plus a security-flavoured Concurrency fix that ships with no advisory.
7 min read
Stanza 1.14.0 reopens pickle RCE that weights_only=True can't block
Upgrading Stanza past 1.12.2 fixed one pickle code-execution path, but 1.14.0 added a second one in the lemmatizer that weights_only=True does not see; 1.15.0 fixes it.
7 min read
Amazon Redshift JDBC Driver CVE-2026-119084: unsafe class loading fixed in 2.3.0
AWS has fixed an unsafe-reflection flaw in the Amazon Redshift JDBC Driver in 2.3.0, but exposure depends on who controls your connection strings and what sits on your classpath.