GHSA-JFC7-64V2-MR8C
Vulnerability from github – Published: 2026-06-26 19:11 – Updated: 2026-06-26 19:11Impact
The preAuthEncoding function in @sigstore/core uses Node.js 'ascii' encoding when converting the PAE (Pre-Authentication Encoding) string to bytes. This allows payloadType to be mutated after signing without invalidating the signature, breaking the type-binding guarantee that DSSE is designed to provide.
In packages/core/src/dsse.ts, the PAE function builds a string containing payloadType and then encodes it with Buffer.from(prefix, 'ascii').
In Node.js, 'ascii' encoding for string-to-Buffer is equivalent to 'latin1', which truncates characters above U+00FF to their low byte. This means for any ASCII character, there exist Unicode characters (at U+01xx, U+02xx, etc.) that produce the identical encoded byte:
| Original | Codepoint | Mutant | Codepoint | Encoded byte |
|---|---|---|---|---|
t |
U+0074 | Ŵ |
U+0174 | 0x74 |
e |
U+0065 | ť |
U+0165 | 0x65 |
An attacker can substitute every character in payloadType with a Unicode variant whose low byte matches, producing identical PAE bytes and a passing signature verification.
Additionally, payloadType.length returns the JavaScript string length (UTF-16 code units) rather than the UTF-8 byte length required by the DSSE spec, though this is only a contributing factor for non-ASCII types.
Reproduction
const { preAuthEncoding } = require('@sigstore/core/dist/dsse.js');
const payload = Buffer.from('hello world');
const original = preAuthEncoding('text/plain', payload);
// U+01xx chars whose low bytes match the original ASCII chars
const mutant = preAuthEncoding('\u0174\u0165\u0178\u0174/\u0170\u016c\u0161\u0169\u016e', payload);
console.log('PAE bytes equal:', original.equals(mutant)); // true — should be false
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.2.0"
},
"package": {
"ecosystem": "npm",
"name": "@sigstore/core"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.2.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-48758"
],
"database_specific": {
"cwe_ids": [
"CWE-347"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-26T19:11:19Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Impact\nThe `preAuthEncoding` function in `@sigstore/core` uses Node.js `\u0027ascii\u0027` encoding when converting the PAE (Pre-Authentication Encoding) string to bytes. This allows `payloadType` to be mutated after signing without invalidating the signature, breaking the type-binding guarantee that DSSE is designed to provide.\n\nIn `packages/core/src/dsse.ts`, the PAE function builds a string containing `payloadType` and then encodes it with `Buffer.from(prefix, \u0027ascii\u0027)`.\n\nIn Node.js, `\u0027ascii\u0027` encoding for string-to-Buffer is equivalent to `\u0027latin1\u0027`, which **truncates characters above U+00FF to their low byte**. This means for any ASCII character, there exist Unicode characters (at U+01xx, U+02xx, etc.) that produce the identical encoded byte:\n\n| Original | Codepoint | Mutant | Codepoint | Encoded byte |\n|----------|-----------|--------|-----------|--------------|\n| `t` | U+0074 | `\u0174` | U+0174 | `0x74` |\n| `e` | U+0065 | `\u0165` | U+0165 | `0x65` |\n\nAn attacker can substitute every character in `payloadType` with a Unicode variant whose low byte matches, producing **identical PAE bytes** and a passing signature verification.\n\nAdditionally, `payloadType.length` returns the JavaScript string length (UTF-16 code units) rather than the UTF-8 byte length required by the DSSE spec, though this is only a contributing factor for non-ASCII types.\n\n#### Reproduction\n\n```javascript\nconst { preAuthEncoding } = require(\u0027@sigstore/core/dist/dsse.js\u0027);\nconst payload = Buffer.from(\u0027hello world\u0027);\n\nconst original = preAuthEncoding(\u0027text/plain\u0027, payload);\n// U+01xx chars whose low bytes match the original ASCII chars\nconst mutant = preAuthEncoding(\u0027\\u0174\\u0165\\u0178\\u0174/\\u0170\\u016c\\u0161\\u0169\\u016e\u0027, payload);\n\nconsole.log(\u0027PAE bytes equal:\u0027, original.equals(mutant)); // true \u2014 should be false\n```",
"id": "GHSA-jfc7-64v2-mr8c",
"modified": "2026-06-26T19:11:20Z",
"published": "2026-06-26T19:11:19Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/sigstore/sigstore-js/security/advisories/GHSA-jfc7-64v2-mr8c"
},
{
"type": "PACKAGE",
"url": "https://github.com/sigstore/sigstore-js"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L",
"type": "CVSS_V3"
}
],
"summary": "@sigstore/core has DSSE payloadType type-binding failure"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.