An identity provider syncing user accounts into your app is a trusted, automated channel. CVE-2026-48170, published June 22, turns that channel into a way to corrupt every object in a running Node.js process. The flaw sits in scim-patch, a small npm library that applies the account updates your identity provider sends, and it is fixed in version 0.9.1. If a service of yours uses it to handle user provisioning, this is today's work.
The library pulls roughly 108,000 downloads a week, so it is not obscure. It rates 9.1 on the CVSS scale, and the damage it allows is not data theft. It is state corruption: a single crafted update can set a property on JavaScript's shared object prototype, and from there the consequences depend on what the rest of your code trusts.
What one bad update actually does
SCIM is the standard protocol an identity provider such as Okta or Microsoft Entra uses to create and update accounts inside the apps your company runs. When someone's title changes or they join a group, the provider sends a SCIM PATCH request, and a library on your side applies that change to the user record. scim-patch is one of the common libraries that does the applying.
The bug is a prototype pollution flaw, tracked as CWE-1321. In JavaScript, almost every plain object inherits from one shared parent, Object.prototype. The advisory describes a PATCH whose value object carries a key shaped like __proto__.someProp. Instead of writing that property onto the one user being updated, the library walks the dotted path and writes it onto the shared prototype. After that single request, every plain object in the process appears to carry the property, until the process restarts.
What that buys an attacker depends on your own code, and the advisory is careful to frame these as the realistic outcomes seen in similar bugs rather than a guaranteed exploit. If any authentication or middleware check reads a flag such as actor.isAdmin off a plain object and assumes the key is absent unless set, the poisoned prototype can make that flag look true for everyone. Code that branches on obj.name or obj.type can be steered down the wrong path, and the advisory points to a real case where polluting the property a database driver uses to name prepared statements caused a denial of service. It is the same shape as a small library quietly disabling a check and making an assumption meaningless. The one certain fact is the one that matters: a single request reaches every object in the process.
The endpoint you trusted is the attack surface
The reason this deserves your attention is where the input comes from. CVSS scores the privilege required as low, because the request only needs whatever your SCIM endpoint already accepts, which for most setups is a provisioned identity-provider client. That sounds reassuring and is not. Provisioning clients use long-lived service credentials, the malicious body looks exactly like an ordinary attribute update, and the whole purpose of a SCIM endpoint is to accept JSON from a system outside your direct control. A feature you enabled for single-sign-on convenience is reachable by anyone who can drive that provisioning channel, and the request will not stand out in your logs. Trusting identity data that arrives from outside is exactly how a forged identity once walked past a signature check as admin.
There is also a timing trap for whoever investigates. The poison persists across requests, so the symptom, a sudden admin or an odd crash, can surface minutes or hours after the request that planted it. Anyone reading the logs will be staring at the wrong request. This is the kind of misplaced-trust failure we keep seeing, where one overlooked default was enough to take over a whole cluster.
Checking the key name is not enough
It is tempting to fix this by blocking the bare word __proto__. That is the brittle path, and it is the same mistake that keeps reopening bugs elsewhere. The dangerous key here was nested inside a dotted path, not sent as a top-level field, which is exactly how a naive string check gets bypassed. We watched the same shape play out when an encoded variant reopened an auth bypass that a string filter had supposedly closed. The durable fix is structural: reject the dangerous keys, __proto__, constructor, and prototype, before walking the path, or apply patches onto objects that have no prototype at all. That is the approach the maintainer took in 0.9.1.
Patch to 0.9.1, then hunt the poisoned keys
Update scim-patch to 0.9.1 now. That closes the hole. If you cannot upgrade immediately, screen incoming SCIM PATCH bodies and reject any whose value object contains __proto__, constructor, or prototype in a key, including dotted forms, at your gateway or in a small middleware before the library runs.
For detection, the request to look for is a SCIM PATCH to your user endpoint, such as /Users/:id, carrying one of those keys in its patch operations. Log and alert on it at the web application firewall or the app layer. Then, because the effect is delayed, correlate any unexplained privilege change or branching-logic crash against recent provisioning traffic rather than the request that tripped the symptom. Restarting the Node.js process clears a poisoned prototype, which is worth knowing for incident response, but it is not a fix on its own.
One last thing worth saying plainly: a SCIM endpoint is an untrusted-input boundary that most teams file under internal plumbing. It is authenticated, it is automated, and it is fed by a system you do not own. Treat the data crossing it like any other request from the outside, validate it as such, and this whole class of bug gets a lot smaller.