Tag: npm

  • GHSA-j656-3hf2-fvjc: DBHub: MySQL and MariaDB comment interpretation bypasses read-only SQL validation

    • Severity: MEDIUM
    • Ecosystems: npm
    • Packages: @bytebase/dbhub

    The read-only mode can be bypassed on the MySQL and MariaDB connectors. In read-only mode a caller can still execute arbitrary write and DDL statements (for example DROP TABLE), which defeats the protection that read-only mode is meant to provide when a database is exposed to an AI agent.

    Root cause

    The problem is a mismatch between how DBHub’s SQL parser handles single-line comments and how MySQL and MariaDB actually parse them.

    In src/utils/sql-parser.ts, scanSingleLineComment treats “–” as the start of a comment no matter what character follows it. MySQL and MariaDB only begin a “–” comment when the two dashes are followed by whitespace, a control character, or the end of the line. Otherwise the dashes are read as two minus signs and the rest of the line is ordinary SQL.

    The read-only check in src/tools/execute-sql.ts splits the input and validates each statement with isReadOnlySQL, but the connector then executes the original, unmodified string. The MySQL and MariaDB connectors open their connection pool with multipleStatements: true (src/connectors/mysql/index.ts:54 and src/connectors/mariadb/index.ts:53) and run the whole string in a single call.

    Because of this, the following payload:

    SELECT 1–1;DROP TABLE victim

    is seen by DBHub as a single SELECT statement, since everything after “–” is treated as a comment and the read-only check passes. MySQL and MariaDB instead parse it as “SELECT 1 – -1” followed by “DROP TABLE victim” and execute both statements.

    Pr…

    Source: GitHub Security Advisories · 2026-11-02

  • GHSA-f9j6-8p6x-r9j6: SD JWT JS: Tampered disclosures silently dropped instead of rejected

    • Severity: HIGH
    • CVE: CVE-2026-62374
    • Ecosystems: npm, npm
    • Packages: @sd-jwt/decode, @sd-jwt/core

    ## Summary

    When one Disclosure in an SD-JWT presentation is tampered so its digest no longer matches the Issuer-signed `_sd` array, `@sd-jwt/decode` 0.19.0 does **not** reject the presentation. It silently drops the unmatched Disclosure and returns a **reduced claim set**, reporting success. The Issuer signature is untouched and valid.

    This is a downgrade/omission primitive. A malicious or curious **holder** can present a tampered Disclosure and have the relying party receive *fewer* claims than the credential carries, with no error surfaced. Where the relying party does **not** enforce key binding, an **in-transit (on-path) attacker** can achieve the same by mangling one Disclosure’s bytes; where key binding **is** enforced, on-path tampering instead breaks the holder’s KB-JWT `sd_hash` binding (so it is rejected), and the downgrade is then exercisable by the key-bound holder — confirmed end-to-end with KB required.

    Two distinct tampering inputs converge on the same silent-drop sink:

    1. **Genuinely non-decodable Disclosure** (broken base64url -> bytes that are not valid UTF-8, e.g. an injected `0xF2` lead byte). RFC 9901 §7.1 step 3 / §4.2.1 require rejection. `@sd-jwt/decode` instead **lossy-decodes** it: the `TextDecoder`/`Buffer` path substitutes U+FFFD (REPLACEMENT CHARACTER) for the invalid byte and recovers a syntactically valid disclosure (the `salt` gains a U+FFFD, claim `family_name` = `”Smith”`). The recovered Disclosure then hashes to a digest **absent** from…

    Source: GitHub Security Advisories · 2026-11-02

  • GHSA-4528-x5fx-h6hg: joi: quadratic-time split in `string().email({ multiple: true })` allows denial of service

    • Severity: HIGH
    • CVE: CVE-2026-118772
    • Ecosystems: npm, npm, npm
    • Packages: joi, @hapi/joi, joi

    ### Summary

    `Joi.string().email({ multiple: true })` takes quadratic time to split a value that contains a long run of whitespace. One request with 100 KB of spaces blocks the Node.js event loop for several seconds, which causes a denial of service.

    ### Details

    When the `multiple` option is set, the `email` rule splits the value into addresses with a regular expression that removes whitespace on each side of the separator:

    “`js
    const regex = new RegExp(`\\s*[${options.separator ? escapeRegex(options.separator) : ‘,’}]\\s*`);
    // …
    const emails = options.multiple ? value.split(regex) : [value];
    “`

    `String.prototype.split` tries a match at each position of the value. At each position inside a whitespace run, the leading `\s*` reads the rest of the run, does not find a separator, and backtracks through all of it. A run of `n` whitespace characters without a separator therefore costs `n²` steps. This applies to the default separator and to all custom `separator` values.

    The fix lets the leading whitespace match start only at the beginning of a whitespace run, so the split is linear:

    “`js
    const regex = new RegExp(`(?:(?

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-8494-f6mr-cjpv: @hapi/content: quadratic-time parsing of Content-Disposition parameters allows denial of service

    • Severity: HIGH
    • CVE: CVE-2026-119202
    • Ecosystems: npm, npm
    • Packages: @hapi/content, content

    ### Summary

    `Content.disposition()` takes quadratic time when a `Content-Disposition` parameter contains a long token without `=`. One unauthenticated multipart request can block the Node.js event loop for several minutes.

    ### Details

    `Content.disposition()` reads the parameters with a global regular expression in `String.prototype.replace()`:

    “`js
    /([^\=\*\s]+)(\*)?\s*\=\s*(?:([^;’”\s]+\'[\w-]*\'[^;\s]+)|(?:\”([^”]*)\”)|([^;\s]*))/g
    “`

    The parameter name part `([^\=\*\s]+)` has no anchor. When a run of name characters has no `=` after it, the match attempt reads the full run and fails. The global scan then starts again at the next character and reads the remaining run again. The total time is proportional to the square of the run length.

    `@hapi/pez` calls `Content.disposition()` on the header of every part in a `multipart/form-data` body. In hapi, this occurs on each route with `payload.multipart` enabled, before the route handler and before any validation. The only size limit is the route `payload.maxBytes`, which is 1 MB by default.

    The fix adds an alternative that matches the full run of name characters when no `=` follows it. The scan then continues after the run, and the callback ignores this match. A match attempt that starts later in the same run always fails in the same way, so the parse result does not change. The parse time is now linear.

    “`js
    /([^\=\*\s]+)(\*)?\s*\=\s*(?:([^;’”\s]+\'[\w-]*\'[^;\s]+)|(?:\”([^”]*)\”)|([^;\s]*))|[^\=\*\s]+/g
    “`

    Every rel…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-g3hv-75wg-m249: minimatch has potential denial of service via globparts amplification

    • Severity: MEDIUM
    • CVE: CVE-2026-118764
    • Ecosystems: npm
    • Packages: minimatch

    minimatch 10.2.6 contains an attacker-controlled pattern amplification in firstPhasePreProcess() when optimizationLevel >= 2.

    researcher: Reid Downing (b1nv0id)
    files:

    – src/index.ts: 690-770
    – src/index.ts: 780-796

    issue:
    input-controlled growth of globParts during first-phase preprocessing, followed by quadratic second-phase processing.

    the **/../ transformation clones a globparts entry and appends the clone to the same globparts array:
    “`
    parts.splice(gs, 1)
    const other = parts.slice(0)
    other[gs] = ‘**’
    globParts.push(other)
    “`
    this occurs inside a do…while (didSomething) fixpoint loop, allowing generated entries to be processed again.

    the expanded globparts array is subsequently processed by secondPhasePreProcess(), which performs pairwise comparisons:
    “`
    for (let i = 0; i < globParts.length – 1; i++) {
    for (let j = i + 1; j < globParts.length; j++) {
    this.partsMatch(…)
    }
    }
    “`
    an attacker who can supply a glob pattern can therefore cause additional pattern entries to be generated during preprocessing, followed by quadratic pairwise processing, resulting in excessive cpu and memory consumption.

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-3g3m-2fjr-6pw4: Glob: AbortSignal cancellation bypass allows post-timeout filesystem traversal

    • Severity: MEDIUM
    • CVE: CVE-2026-118773
    • Ecosystems: npm
    • Packages: glob

    ### Summary

    In glob 13.0.6, an aborted async walk rejects or emits an error but continues recursively reading directories in the background.

    The root cause is that abort branches in src/walker.ts call cb() without returning. As a result, callers that rely on signal or AbortSignal.timeout() to bound glob traversal cost can still incur filesystem I/O and CPU work after the request has already been rejected.

    ### Details

    This was reproduced on glob v13.0.6 (commit e80cb38ae60d6cbff9e75f39032a994858994d35).

    The README documents that the signal option cancels the glob walk. However, in src/walker.ts, the following methods call cb() when this.signal?.aborted is true but do not return:

    – GlobUtil.walkCB()
    – GlobUtil.walkCB2()
    – GlobUtil.walkCBSync()
    – GlobUtil.walkCB2Sync()

    Example vulnerable pattern:

    if (this.signal?.aborted) cb()

    Because execution continues after cb(), the walker still reaches processor.processPatterns(), readdirCB(), and recursive walkCB2() calls.

    The issue was directly demonstrated against v13.0.6. A source-history review shows the same missing-return pattern is present in tagged releases from v9.0.0 through v13.0.6.

    The issue affects both glob() and globStream().

    Recommended fix:
    Change each abort branch to return immediately:

    if (this.signal?.aborted) return cb()

    ### Proof of concept

    Environment:
    – Linux x86_64
    – Node.js v22.23.3
    – npm 10.9.9
    – glob 13.0.6

    Reproduction steps:

    1. Install the affected package:
    npm init -y
    npm install glob@13.0.…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-4642-5mcj-2rqw: Forge: ASN.1 OID first arc truncation in asn1.oidToDer

    • Severity: LOW
    • CVE: CVE-2026-118775
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary

    `asn1.oidToDer()` did not range-check the first two arcs of an OID string, so an OID such as `0.42.840.113549.1.1.1` or `4294977127.18.840.113549.1.1.1` was encoded as the DER of a different OID (here rsaEncryption, `1.2.840.113549.1.1.1`). This is an incomplete fix of CVE-2025-66030. Fixed in node-forge 1.4.1.

    ### Details

    X.690 section 8.19 encodes the first two arcs of an OID as one subidentifier, `40 * arc1 + arc2`, where the first arc is 0, 1 or 2 and the second arc is at most 39 when the first is 0 or 1. `asn1.oidToDer()` in `lib/asn1.js` computed that value with no range check and wrote it with `putByte()`, which keeps only the low 16 bits. Out-of-range first arcs therefore produced the encoding of a different OID: `0.42.840.113549.1.1.1` gives `40 * 0 + 42 = 42` and `4294977127.18.840.113549.1.1.1` wraps to 42, both the first byte of rsaEncryption. The fix for CVE-2025-66030 (GHSA-65ch-62r8-g69g, node-forge 1.3.2) added a 32-bit check for the later arcs only.

    A related encoding bug made valid OIDs with a first subidentifier above 127 (`2.48` and higher, e.g. `2.999`) encode and decode incorrectly, because the first subidentifier was always written and read as a single byte.

    The fix rejects out-of-range first arcs and encodes and decodes the first subidentifier in base 128 like the others (the latter from public PR #1148).

    ### Patches
    Fixed in node-forge 1.4.1. `asn1.oidToDer()` throws for OIDs with out-of-range first arcs, and the first subidentifier…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-6wvx-3r32-xmcg: Forge: Prototype Pollution in forge.http cookie handling via a malicious Set-Cookie

    • Severity: MEDIUM
    • CVE: CVE-2026-118777
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary
    The `forge.http` client stored cookies from `Set-Cookie` response headers in a plain object keyed by cookie name and path. A malicious server could use the cookie name `__proto__` to add properties to `Object.prototype`, use inherited names such as `constructor` to add properties to `Object`, and send an empty cookie value to delete `Object.prototype` properties. Fixed in node-forge 1.4.1.

    ### Details
    `client.setCookie()` in `lib/http.js` stored a cookie with `client.cookies[cookie.name][cookie.path] = cookie`, creating `client.cookies[cookie.name]` only when `!(cookie.name in client.cookies)`. Both values come from the server’s `Set-Cookie` header via `response.getCookies()`. Because `in` also sees inherited properties:

    – a cookie named `__proto__` skipped the initialization, `client.cookies[‘__proto__’]` returned `Object.prototype`, and the cookie path was written as a new `Object.prototype` property, visible on every object;
    – a cookie named `constructor` wrote onto the global `Object` function the same way;
    – a cookie with an empty value calls `client.removeCookie(name, path)`, which used the same `in` checks and then `delete client.cookies[name][path]`, so `Set-Cookie: __proto__=; path=toString` deleted `Object.prototype.toString`. This path skipped the cookie’s secure-flag and domain checks, so it also worked for `http://` clients.

    `forge.http` is not loaded by `require(‘node-forge’)`. It is included in the `forge.all` browser bundle and is used with a so…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-525v-9gp9-w22h: Forge: TLS client crash when the server sends no certificate

    • Severity: MEDIUM
    • CVE: CVE-2026-118778
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary
    The forge TLS client’s check for a missing server certificate read the wrong property and never ran. A malicious TLS server, or an on-path attacker who removes the server’s Certificate message, can make `connection.process()` throw a `TypeError`, which crashes applications that use the forge TLS client. Fixed in node-forge 1.4.1.

    ### Details
    After ServerHelloDone, `tls.handleServerHelloDone()` in `lib/tls.js` is meant to reject a server that sent no certificate with a fatal `insufficient_security` alert, after consulting the application’s `verify` callback. The check tested `c.serverCertificate`, which is never set (the certificate is stored as `c.session.serverCertificate`), so it never ran. The client state machine allows ServerHelloDone directly after ServerHello, so the handshake continued to `tls.createClientKeyExchange()`, which reads `c.session.serverCertificate.publicKey` and throws a `TypeError` on the null certificate. The exception propagates out of `connection.process()` without calling the `verify` or `error` callbacks.

    There is no authentication bypass: without a server certificate the client cannot build its RSA key exchange, so the handshake cannot complete.

    The fix reads `c.session.serverCertificate` and always fails the handshake in that case, still calling `verify` so the application can customize the alert. Failing even when `verify` returns `true` is needed because the key exchange requires the certificate.

    #### Patches
    Fixed in node-forge…

    Source: GitHub Security Advisories · 2026-10-30

  • GHSA-7wch-p9px-956x: Forge: Regular expression denial of service in PEM decoding (pem.decode)

    • Severity: HIGH
    • CVE: CVE-2026-118779
    • Ecosystems: npm
    • Packages: node-forge

    ### Summary
    `pem.decode()`, which every node-forge API that parses PEM input uses, split messages with a regular expression that takes polynomial time on input without a matching END line, and parsed encapsulated headers with regular expressions that take quadratic time on long header lines. A remote attacker can block the Node.js event loop for seconds to minutes with a crafted input of a few tens of kilobytes. Fixed in node-forge 1.4.1.

    ### Details
    `pem.decode()` in `lib/pem.js` found PEM messages with this regular expression:

    /\s*—–BEGIN ([A-Z0-9- ]+)—–\r?\n?([\x21-\x7e\s]+?(?:\r?\n\r?\n))?([:A-Za-z0-9+\/=\s]+?)—–END \1—–/g

    The optional headers group and the body group are adjacent, lazy, and share characters (whitespace and the base64 alphabet), and the match must end with an END line whose label equals the BEGIN label (`\1`). When no such END line follows, the engine tries every split of the text between the two groups, for every label split and at every BEGIN line in the input. The work grows polynomially with the input length (roughly cubic for repeated BEGIN/END lines with mismatched labels, quadratic for a BEGIN line followed by many newlines).

    The encapsulated header parsing in the same function had two smaller quadratic cases: trimming each header line with `/\s+$/`, and an unanchored header regex `([\x21-\x7e]+):…` that retries at every position of a long line without a colon. These are reachable when a message does match.

    All PEM entry points…

    Source: GitHub Security Advisories · 2026-10-30