NIST deprecates RSA-2048 from 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
MEDIUM severity

CVE-2026-126367 — @grpc/grpc-js-xds

MEDIUMFix: grpc/grpc-node@3fc3b0e

CVE-2026-126367 is a medium-severity (CVSS 4.8) CWE-625 vulnerability in @grpc/grpc-js-xds. A fix is available for @grpc/grpc-js-xds — see the affected versions and patch details below.

@grpc/grpc-js: safe_regex incorrectly applies partial string matching for patterns with alternation

Published
Updated
Affected
2 pkgs
Patched
2 / 2
Exploits
None indexed
Exploitation data as of Nov 12, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Real-World Exposure

2 pkgs affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

5other npm packages depend on this — each one inherits the vulnerability until it's patched upstream
@grpc/grpc-js-xdsnpm
5Kdownloads / week

Description

Summary

The StringMatcher/PathMatcher for safe_regex builds its RE2 as ^${regex}$ with no surrounding group. Because alternation has the lowest precedence, a pattern "a|b|c" compiles to /^a|b|c$/ -- "starts with a" OR "contains b" OR "ends with c" instead of a full match of the whole value. Configurations with safe_regex matchers that use a regex alternation (a natural way to list methods or service identities) therefore apply partial string matches. This impacts route selection, xDS credentials, and RBAC.

Details

src/matcher.ts: SafeRegexValueMatcher and PathSafeRegexValueMatcher both do

this.targetRegexImpl = new RE2(`^${targetRegex}$`, 'u');

and apply() returns targetRegexImpl.test(value). With a top-level alternation the anchors bind only to the first/last branch.

https://github.com/grpc/grpc-node/blob/e742b11bd8039f3dbea4cd29afdf5d95a8ba6b92/packages/grpc-js-xds/src/matcher.ts#L59

https://github.com/grpc/grpc-node/blob/e742b11bd8039f3dbea4cd29afdf5d95a8ba6b92/packages/grpc-js-xds/src/matcher.ts#L232

src/route.ts (getPredicateForStringMatcher / getPredicateForHeaderMatcher) builds these for every safe_regex StringMatcher/HeaderMatcher, and src/rbac.ts uses them for RBAC url_path permissions/principals, authenticated.principal_name (mTLS SAN/CN), requested_server_name and header rules. So an ALLOW rule written with an alternation grants access to method names, certificate identities or header values that extend an intended one. DENY policies only become broader (fail-safe).

https://github.com/grpc/grpc-node/blob/e742b11bd8039f3dbea4cd29afdf5d95a8ba6b92/packages/grpc-js-xds/src/route.ts#L28

https://github.com/grpc/grpc-node/blob/e742b11bd8039f3dbea4cd29afdf5d95a8ba6b92/packages/grpc-js-xds/src/route.ts#L47

Proof of concept

In a scratch directory:

npm init -y
npm i @grpc/[email protected] @grpc/grpc-js
node poc.cjs

poc.cjs calls the exported RBAC engine directly (the functions grpc-js uses to make the authorization decision); no server or network:

const { parseConfig } = require('@grpc/grpc-js-xds/build/src/rbac.js')
const { SafeRegexValueMatcher } = require('@grpc/grpc-js-xds/build/src/matcher.js')
const { Metadata } = require('@grpc/grpc-js')

console.log(new SafeRegexValueMatcher('admin|editor').toString()) // SafeRegex(/^admin|editor$/u)

const sr = regex => ({ match_pattern: 'safe_regex', safe_regex: { regex } })
const info = urlPath => ({ urlPath, headers: new Metadata(), tls: true, peerCertificate: null,
  destinationIp: '10.0.0.1', destinationPort: 443, sourceIp: '10.0.0.2' })
const g = parseConfig({ action: 'ALLOW', policies: { p: {
  permissions: [{ rule: 'url_path', url_path: { path: sr('/acme.Users/GetUser|/acme.Users/ListUsers') } }],
  principals: [{ identifier: 'any', any: true }] } } })

console.log('GetUser       ', g.apply(info('/acme.Users/GetUser')))       // true  (intended)
console.log('DeleteUser    ', g.apply(info('/acme.Users/DeleteUser')))    // false (control)
console.log('GetUserSecrets', g.apply(info('/acme.Users/GetUserSecrets'))) // true <-- BYPASS

Observed (grpc-js-xds 1.14.1, grpc-js 1.14.6):

matcher "admin|editor" -> SafeRegex(/^admin|editor$/u);
    apply("administrator")=true, apply("not-an-editor")=true
ALLOW url_path /acme.Users/GetUser|/acme.Users/ListUsers:
    /acme.Users/GetUser         -> ALLOWED (intended)
    /acme.Users/DeleteUser      -> denied  (control)
    /acme.Users/GetUserSecrets  -> ALLOWED <-- bypass (policy intends deny)
control, same regex as (?:...|...): /acme.Users/GetUserSecrets -> denied
ALLOW authenticated principal_name spiffe://prod/ns/web/sa/frontend|...ops/sa/admin:
    spiffe://prod/ns/web/sa/frontend-batch -> ALLOWED <-- bypass

Impact

Users of @grpc/grpc-js-xds who are using safe_regex matchers with alternation for route selection, xDS credentials SAN matchers, or RBAC rules may see incorrectly permissive behavior.

Affected Packages

2 total 2 fixed
EcosystemPackageVulnerable rangeFix
📦npm@grpc/grpc-js-xds≥ 1.14.0&&< 1.14.21.14.2npm install @grpc/grpc-js-xds@1.14.2
📦npm@grpc/grpc-js-xdsall versions1.13.2npm install @grpc/grpc-js-xds@1.13.2

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @grpc/grpc-js-xds, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update @grpc/grpc-js-xds to 1.14.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-126367 is resolved across your whole dependency graph.

  3. Workarounds

    Put an independent control in front of the weakness: restrict the affected endpoint or interface to trusted networks, require an additional authentication factor or proxy-level check, and invalidate existing sessions and credentials in case the flaw has already been used.

Frequently Asked Questions

### Summary The StringMatcher/PathMatcher for `safe_regex` builds its RE2 as `^${regex}$` with no surrounding group. Because alternation has the lowest precedence, a pattern `"a|b|c"` compiles to `/^a|b|c$/` -- "starts with a" OR "contains b" OR "ends with c" instead of a full match of the whole value. Configurations with `safe_regex` matchers that use a regex alternation (a natural way to list methods or service identities) therefore apply partial string matches. This impacts route selection, xDS credentials, and RBAC. ### Details `src/matcher.ts`: `SafeRegexValueMatcher` and `PathSafeRege
O3 Security · Impact-Aware SCA

Is CVE-2026-126367 in your dependencies?

Find it across npm, including transitive dependencies.