CVE-2026-40190: langsmith: prototype pollution enables auth bypass, RCE

GHSA-fw9q-39r9-c252 MEDIUM
Published April 10, 2026
CISO Take

The LangSmith JavaScript/TypeScript SDK (npm ≤ 0.5.17) contains an incomplete prototype pollution fix in its vendored lodash `set()` function: the guard blocks `__proto__` mutation but leaves the `constructor.prototype` traversal path wide open, allowing any attacker who controls key names in data processed by the `createAnonymizer()` API to poison `Object.prototype` across the entire Node.js runtime. With 2,384 downstream npm dependents and a package risk score of 77/100, the blast radius spans a significant slice of JavaScript/TypeScript LLM tooling built on the LangChain ecosystem. No public exploit or CISA KEV entry exists today and the attack requires attacker-controlled input keys (AC:H), but in environments pairing LangSmith with Pug, EJS, or Handlebars template rendering, prototype pollution is a well-documented path to remote code execution — making the real-world severity higher than the CVSS 5.6 suggests. Upgrade to langsmith 0.5.18 immediately; if patching is blocked, add input validation at the anonymizer boundary to reject any keys containing the strings `constructor` or `prototype` before data reaches `createAnonymizer()`.

Sources: GitHub Advisory NVD ATLAS OpenSSF

Risk Assessment

Rated Medium (CVSS 5.6) but the effective risk is higher in typical AI observability deployments. The AC:H rating requires attacker-controlled key names in anonymized data, which is a realistic condition in multi-tenant LangSmith tracing pipelines, API gateways that forward user-supplied JSON, or any LangChain application that logs external inputs. The prototype pollution root cause (CWE-1321) is well understood and PoC code ships in the advisory itself, lowering the exploitation bar to moderate. The 43 prior CVEs in the same package and an OpenSSF scorecard of 6.2/10 signal systemic supply chain hygiene concerns that elevate institutional risk beyond this single issue. For organizations using Node.js-based LLM pipelines, the authentication bypass impact alone justifies critical-tier internal prioritization despite the official Medium rating.

Attack Kill Chain

Malicious Input Crafting
Adversary constructs a JSON payload with keys following the `constructor.prototype.X` path pattern, ensuring string values match the target application's anonymizer regex (e.g., contain PII-like patterns).
AML.T0043.003
Anonymizer Trigger
The crafted payload is submitted to any endpoint that invokes `createAnonymizer()` on attacker-influenced data; the anonymizer's path extractor produces a dotted path traversing `constructor.prototype`.
AML.T0049
Prototype Pollution
The incomplete `baseAssignValue()` guard allows `set()` to write attacker-controlled values onto `Object.prototype`, poisoning all objects in the Node.js process for the remainder of its lifetime.
AML.T0010.001
Impact Realization
Polluted prototype properties propagate to all objects, enabling authentication bypass via truthy `isAdmin` checks, denial of service by overwriting `toString`/`valueOf`, or RCE through template engine sinks consuming polluted properties.
AML.T0106

Affected Systems

Package Ecosystem Vulnerable Range Patched
langsmith npm <= 0.5.17 0.5.18
132.4K OpenSSF 6.2 2.4K dependents Pushed 6d ago 16% patched ~285d to patch Full package profile →

Do you use langsmith? You're affected.

Severity & Risk

CVSS 3.1
5.6 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Moderate

Attack Surface

AV AC PR UI S C I A
AV Network
AC High
PR None
UI None
S Unchanged
C Low
I Low
A Low

Recommended Action

  1. PATCH: Upgrade langsmith npm package to 0.5.18 or later across all Node.js services and CI/CD pipelines. Run `npm audit` or `pnpm audit` to identify transitive dependents.
  2. WORKAROUND (if patching is blocked): Add input sanitization before calling `createAnonymizer()` — reject or strip any object keys that equal `constructor`, `prototype`, or `__proto__` before the data reaches the SDK.
  3. DETECTION: Audit Node.js application logs for unexpected properties appearing on plain objects (e.g., log `Object.getOwnPropertyNames(Object.prototype)` at startup and periodically). Integrate `--frozen-intrinsics` Node.js flag where feasible to harden the prototype chain.
  4. SCANNING: No Nuclei template is currently available; write a custom integration test that passes `{ 'constructor.prototype.canary': 'pwned' }` through the anonymizer and checks `({}).canary` post-call.
  5. DEPENDENCY HYGIENE: The 43 CVEs in the same package and OpenSSF score of 6.2 warrant a broader supply chain review of all langchain-ai npm packages in use.

Classification

Compliance Impact

This CVE is relevant to:

EU AI Act
Art. 15 - Accuracy, robustness and cybersecurity for high-risk AI systems
ISO 42001
A.6.2.5 - AI system supply chain management
NIST AI RMF
GOVERN-6.1 - AI risk in supply chain, third party, and organizational policies
OWASP LLM Top 10
LLM05 - Supply Chain Vulnerabilities LLM07 - Insecure Plugin Design

Frequently Asked Questions

What is CVE-2026-40190?

The LangSmith JavaScript/TypeScript SDK (npm ≤ 0.5.17) contains an incomplete prototype pollution fix in its vendored lodash `set()` function: the guard blocks `__proto__` mutation but leaves the `constructor.prototype` traversal path wide open, allowing any attacker who controls key names in data processed by the `createAnonymizer()` API to poison `Object.prototype` across the entire Node.js runtime. With 2,384 downstream npm dependents and a package risk score of 77/100, the blast radius spans a significant slice of JavaScript/TypeScript LLM tooling built on the LangChain ecosystem. No public exploit or CISA KEV entry exists today and the attack requires attacker-controlled input keys (AC:H), but in environments pairing LangSmith with Pug, EJS, or Handlebars template rendering, prototype pollution is a well-documented path to remote code execution — making the real-world severity higher than the CVSS 5.6 suggests. Upgrade to langsmith 0.5.18 immediately; if patching is blocked, add input validation at the anonymizer boundary to reject any keys containing the strings `constructor` or `prototype` before data reaches `createAnonymizer()`.

Is CVE-2026-40190 actively exploited?

No confirmed active exploitation of CVE-2026-40190 has been reported, but organizations should still patch proactively.

How to fix CVE-2026-40190?

1. PATCH: Upgrade langsmith npm package to 0.5.18 or later across all Node.js services and CI/CD pipelines. Run `npm audit` or `pnpm audit` to identify transitive dependents. 2. WORKAROUND (if patching is blocked): Add input sanitization before calling `createAnonymizer()` — reject or strip any object keys that equal `constructor`, `prototype`, or `__proto__` before the data reaches the SDK. 3. DETECTION: Audit Node.js application logs for unexpected properties appearing on plain objects (e.g., log `Object.getOwnPropertyNames(Object.prototype)` at startup and periodically). Integrate `--frozen-intrinsics` Node.js flag where feasible to harden the prototype chain. 4. SCANNING: No Nuclei template is currently available; write a custom integration test that passes `{ 'constructor.prototype.canary': 'pwned' }` through the anonymizer and checks `({}).canary` post-call. 5. DEPENDENCY HYGIENE: The 43 CVEs in the same package and OpenSSF score of 6.2 warrant a broader supply chain review of all langchain-ai npm packages in use.

What systems are affected by CVE-2026-40190?

This vulnerability affects the following AI/ML architecture patterns: LangChain JS/TS agent frameworks, LLM observability and tracing pipelines, AI data anonymization and PII redaction pipelines, Multi-tenant AI API gateways, LangGraph workflow orchestration.

What is the CVSS score for CVE-2026-40190?

CVE-2026-40190 has a CVSS v3.1 base score of 5.6 (MEDIUM).

Technical Details

NVD Description

# GHSA-fw9q-39r9-c252: Prototype Pollution via Incomplete Lodash `set()` Guard in `langsmith-sdk` **Severity:** Medium (CVSS ~5.6) **Status:** Fixed in 0.5.18 --- ## Summary The LangSmith JavaScript/TypeScript SDK (`langsmith`) contains an incomplete prototype pollution fix in its internally vendored lodash `set()` utility. The `baseAssignValue()` function only guards against the `__proto__` key, but fails to prevent traversal via `constructor.prototype`. This allows an attacker who controls keys in data processed by the `createAnonymizer()` API to pollute `Object.prototype`, affecting all objects in the Node.js process. --- ## Affected Products | Product | Affected Versions | Component | |---------|-------------------|-----------| | `langsmith` (npm) | <= 0.5.17 | `js/src/utils/lodash/baseAssignValue.ts`, `js/src/anonymizer/index.ts` | | langchain-ai/langsmith-sdk | GitHub main branch (as of 2026-03-24) | JS/TypeScript SDK | **Not affected:** The Python SDK (`langsmith` on PyPI) does not use lodash or an equivalent pattern. --- ## Root Cause The SDK vendors an internal copy of lodash's `set()` function at `js/src/utils/lodash/`. The `baseAssignValue()` function at `baseAssignValue.ts:11` implements a guard for prototype pollution: ```typescript function baseAssignValue(object: Record<string, any>, key: string, value: any) { if (key === "__proto__") { Object.defineProperty(object, key, { configurable: true, enumerable: true, value: value, writable: true, }); } else { object[key] = value; // ← No guard for "constructor" or "prototype" keys } } ``` This blocks `__proto__` pollution but does **not** block the `constructor.prototype` traversal path. When `set()` is called with a path like `"constructor.prototype.polluted"`: 1. `castPath()` splits it into `["constructor", "prototype", "polluted"]` 2. `baseSet()` iterates: `obj.constructor` → `Object` → `Object.prototype` 3. `assignValue(Object.prototype, "polluted", value)` calls `baseAssignValue()` 4. Key is `"polluted"` (not `"__proto__"`), so the guard is bypassed 5. `Object.prototype.polluted = value` — all objects are polluted --- ## Attack Vector via Anonymizer The `createAnonymizer()` API (importable as `langsmith/anonymizer`) processes data by: 1. **Extracting string nodes** — `extractStringNodes()` walks an object recursively and builds dotted paths from keys 2. **Applying regex replacements** — If a string value matches a configured pattern, the node is marked for update (`anonymizer/index.ts:95`) 3. **Writing back with `set()`** — `set(mutateValue, node.path, node.value)` writes the replaced value back (`anonymizer/index.ts:123`) An attacker who controls keys in data being anonymized can construct a nested object where the path resolves to `constructor.prototype.X`: ```javascript { wrapper: { "constructor.prototype.isAdmin": "contains-secret-pattern" } } ``` `extractStringNodes()` produces path `"wrapper.constructor.prototype.isAdmin"`. When the replacement triggers and `set()` writes back, it traverses up to `Object.prototype`. Although `createAnonymizer()` uses `deepClone()` at `anonymizer/index.ts:62` (`JSON.parse(JSON.stringify(data))`), the prototype chain traversal escapes the clone boundary because `clone.wrapper.constructor` resolves to the global `Object` constructor, not a cloned copy. --- ## Proof of Concept ```javascript import { createAnonymizer } from "langsmith/anonymizer"; const anonymizer = createAnonymizer([ { pattern: "secret", replace: "[REDACTED]" } ]); console.log("BEFORE:", ({}).isAdmin); // undefined const maliciousInput = { wrapper: { "constructor.prototype.isAdmin": "this-is-secret-data" } }; anonymizer(maliciousInput); console.log("AFTER:", ({}).isAdmin); // "this-is-[REDACTED]-data" console.log("Array:", [].isAdmin); // "this-is-[REDACTED]-data" function checkAccess(user) { if (user.isAdmin) return "ACCESS GRANTED"; return "ACCESS DENIED"; } console.log(checkAccess({ name: "bob" })); // "ACCESS GRANTED" ← BYPASSED ``` --- ## Impact Prototype pollution in a Node.js process can enable: 1. **Authentication bypass** — `if (user.isAdmin)` checks succeed on all objects 2. **Remote Code Execution** — Exploitable in template engines (Pug, EJS, Handlebars, Nunjucks) via polluted prototype properties that reach `eval()`/`Function()` sinks 3. **Denial of Service** — Overwriting `toString`, `valueOf`, or `hasOwnProperty` on all objects 4. **Data exfiltration** — Polluting serialization methods to inject attacker-controlled values --- ## Remediation In `baseAssignValue.ts`, extend the guard to cover `constructor` and `prototype` keys: ```typescript function baseAssignValue(object, key, value) { if (key === "__proto__" || key === "constructor" || key === "prototype") { Object.defineProperty(object, key, { configurable: true, enumerable: true, value, writable: true, }); } else { object[key] = value; } } ``` As defense in depth, `extractStringNodes()` in `anonymizer/index.ts` should also sanitize or reject path segments matching `constructor` or `prototype` before passing them to `set()`. --- ## Timeline | Date | Event | |------|-------| | 2026-03-24 | Initial report submitted | | 2026-04-09 | Vendor confirmed; fixed in 0.5.18 | --- ## Credits Reported by: OneThing4101

Exploitation Scenario

An adversary targeting a SaaS platform that uses LangChain.js with LangSmith tracing to log and redact user conversation data submits an API request where the conversation payload includes field names crafted as `constructor.prototype.isAdmin`. The LangSmith anonymizer's `extractStringNodes()` walks the object, constructs the dotted path `wrapper.constructor.prototype.isAdmin`, finds the string value matches the PII regex (e.g., it contains a pattern like an email), marks it for replacement, and calls `set(clone, 'wrapper.constructor.prototype.isAdmin', '[REDACTED]')`. The `baseAssignValue()` guard only checks for `__proto__`, so the traversal reaches `Object.prototype.isAdmin = '[REDACTED]'`. From this point forward, every access control check of the form `if (req.user.isAdmin)` in the same Node.js process evaluates to truthy for all users, granting universal admin access. In a LangGraph-based platform also rendering HTML via a template engine, the adversary can escalate to RCE by polluting properties consumed in template rendering contexts.

CVSS Vector

CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L

Timeline

Published
April 10, 2026
Last Modified
April 10, 2026
First Seen
April 11, 2026

Related Vulnerabilities