CVE-2026-40190
Description
LangSmith Client SDKs provide SDK's for interacting with the LangSmith platform. Prior to 0.5.18, 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. This vulnerability is fixed in 0.5.18.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
LangSmith JS/TS SDK prior to 0.5.18 has an incomplete prototype pollution fix in its vendored lodash set(), allowing attacker-controlled keys to pollute Object.prototype via constructor.prototype.
Vulnerability
Overview
The LangSmith JavaScript/TypeScript SDK (langsmith) prior to version 0.5.18 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 [1][2][3].
Exploitation
Mechanism
The vulnerability is exploited through the createAnonymizer() API, which processes user-controlled data. The vendored set() function splits a path like "constructor.prototype.polluted" into ["constructor", "prototype", "polluted"]. During iteration, baseAssignValue() is called with the key "polluted", which is not "__proto__", so the guard is bypassed. This allows writing a value to Object.prototype.polluted, polluting all objects in the process [3].
Impact
An attacker who can control keys in data processed by the anonymizer can pollute Object.prototype. This can lead to unexpected behavior across the entire Node.js application, potentially enabling property injection, denial of service, or other security issues depending on how the polluted properties are used by the application or other dependencies [2][3].
Mitigation
The vulnerability is fixed in version 0.5.18 of the langsmith npm package. The fix, implemented in commit 31d3c3aec02892f4312baae112f817d6b2f0ebe3, avoids path-based traversal entirely by directly referencing parent objects and keys when writing back anonymized values [4]. Users should upgrade to version 0.5.18 or later. The Python SDK is not affected [3].
- fix(js): Fix prototype pollution bug in anonymizers by jacoblee93 · Pull Request #2690 · langchain-ai/langsmith-sdk
- NVD - CVE-2026-40190
- Prototype Pollution in langsmith-sdk via Incomplete `__proto__` Guard in Internal lodash `set()`
- fix(js): Fix prototype pollution bug in anonymizers (#2690) · langchain-ai/langsmith-sdk@31d3c3a
AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
langsmithnpm | < 0.5.18 | 0.5.18 |
Affected products
1Patches
131d3c3aec028fix(js): Fix prototype pollution bug in anonymizers (#2690)
17 files changed · +136 −547
js/src/anonymizer/index.ts+63 −18 modified@@ -1,34 +1,64 @@ -import set from "../utils/lodash/set.js"; - export interface StringNode { value: string; path: string; } +interface StringNodeInternal extends StringNode { + // Direct reference to the parent object and key, so we can write back + // without path parsing/traversal (avoids prototype pollution entirely). + parent: Record<string, unknown>; + key: string; + // Unique identity for matching after maskNodes processing. + _id: number; +} + function extractStringNodes(data: unknown, options: { maxDepth?: number }) { const parsedOptions = { ...options, maxDepth: options.maxDepth ?? 10 }; - const queue: [value: unknown, depth: number, path: string][] = [ - [data, 0, ""], - ]; + const queue: [ + value: unknown, + depth: number, + path: string, + parent: Record<string, unknown> | null, + key: string + ][] = [[data, 0, "", null, ""]]; - const result: StringNode[] = []; + let nextId = 0; + const result: StringNodeInternal[] = []; while (queue.length > 0) { const task = queue.shift(); if (task == null) continue; - const [value, depth, path] = task; - if (typeof value === "object" && value != null) { - if (depth >= parsedOptions.maxDepth) continue; - for (const [key, nestedValue] of Object.entries(value)) { - queue.push([nestedValue, depth + 1, path ? `${path}.${key}` : key]); - } + const [value, depth, path, parent, key] = task; + if (typeof value === "string") { + result.push({ + value, + path, + parent: parent as Record<string, unknown>, + key, + _id: nextId++, + }); } else if (Array.isArray(value)) { if (depth >= parsedOptions.maxDepth) continue; for (let i = 0; i < value.length; i++) { - queue.push([value[i], depth + 1, `${path}[${i}]`]); + queue.push([ + value[i], + depth + 1, + `${path}[${i}]`, + value as unknown as Record<string, unknown>, + String(i), + ]); + } + } else if (typeof value === "object" && value != null) { + if (depth >= parsedOptions.maxDepth) continue; + for (const [k, nestedValue] of Object.entries(value)) { + queue.push([ + nestedValue, + depth + 1, + path ? `${path}.${k}` : k, + value as Record<string, unknown>, + k, + ]); } - } else if (typeof value === "string") { - result.push({ value, path }); } } @@ -93,7 +123,7 @@ export function createAnonymizer( }, item.value); if (newValue !== item.value) { - memo.push({ value: newValue, path: item.path }); + memo.push({ ...item, value: newValue }); } return memo; @@ -107,20 +137,35 @@ export function createAnonymizer( nodes.reduce<StringNode[]>((memo, item) => { const newValue = replacer(item.value, item.path); if (newValue !== item.value) { - memo.push({ value: newValue, path: item.path }); + memo.push({ ...item, value: newValue }); } return memo; }, []), } : replacer; + // Build a lookup from _id to internal node for direct write-back. + const nodesById = new Map<number, StringNodeInternal>(); + for (const node of nodes) { + nodesById.set(node._id, node); + } + const toUpdate = processor.maskNodes(nodes); for (const node of toUpdate) { if (node.path === "") { mutateValue = node.value as unknown as T; } else { - set(mutateValue as unknown as object, node.path, node.value); + // Match by _id if available (built-in replacers propagate it from + // the input nodes), otherwise fall back to path matching. + const asInternal = node as Partial<StringNodeInternal>; + const internal = + asInternal._id !== undefined + ? nodesById.get(asInternal._id) + : nodes.find((n) => n.path === node.path); + if (internal) { + internal.parent[internal.key] = node.value; + } } }
js/src/tests/anonymizer.test.ts+73 −0 modified@@ -9,6 +9,79 @@ const EMAIL_REGEX = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}/g; const UUID_REGEX = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g; +describe("prototype pollution prevention", () => { + test("blocks constructor.prototype path", () => { + const anonymizer = createAnonymizer([ + { pattern: "secret", replace: "[REDACTED]" }, + ]); + + // Verify clean state + expect(({} as Record<string, unknown>).isAdmin).toBeUndefined(); + + // Malicious input with prototype pollution attempt + const maliciousInput = { + wrapper: { + "constructor.prototype.isAdmin": "this-is-secret-data", + }, + }; + + anonymizer(maliciousInput); + + // Should NOT pollute Object.prototype + expect(({} as Record<string, unknown>).isAdmin).toBeUndefined(); + }); + + test("blocks __proto__ path", () => { + const anonymizer = createAnonymizer([ + { pattern: "secret", replace: "[REDACTED]" }, + ]); + + expect(({} as Record<string, unknown>).polluted).toBeUndefined(); + + const maliciousInput = { + "__proto__.polluted": "secret-data", + }; + + anonymizer(maliciousInput); + + expect(({} as Record<string, unknown>).polluted).toBeUndefined(); + }); + + test("blocks prototype path", () => { + const anonymizer = createAnonymizer([ + { pattern: "secret", replace: "[REDACTED]" }, + ]); + + expect(({} as Record<string, unknown>).polluted).toBeUndefined(); + + const maliciousInput = { + prototype: { + polluted: "secret-data", + }, + }; + + anonymizer(maliciousInput); + + expect(({} as Record<string, unknown>).polluted).toBeUndefined(); + }); + + test("handles dotted keys distinctly from nested keys", () => { + const anonymizer = createAnonymizer([ + { pattern: "secret", replace: "[REDACTED]" }, + ]); + + const input = { + "a.b": "secret-1", + a: { b: "secret-2" }, + }; + + const output = anonymizer(input); + + expect(output["a.b"]).toBe("[REDACTED]-1"); + expect(output.a.b).toBe("[REDACTED]-2"); + }); +}); + describe("replacer", () => { const replacer = (text: string) => text.replace(EMAIL_REGEX, "[email address]").replace(UUID_REGEX, "[uuid]");
js/src/utils/lodash/assignValue.ts+0 −27 removed@@ -1,27 +0,0 @@ -import baseAssignValue from "./baseAssignValue.js"; -import eq from "./eq.js"; - -/** Used to check objects for own properties. */ -const hasOwnProperty = Object.prototype.hasOwnProperty; - -/** - * Assigns `value` to `key` of `object` if the existing value is not equivalent. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ -function assignValue(object: Record<string, any>, key: string, value: any) { - const objValue = object[key]; - - if (!(hasOwnProperty.call(object, key) && eq(objValue, value))) { - if (value !== 0 || 1 / value === 1 / objValue) { - baseAssignValue(object, key, value); - } - } else if (value === undefined && !(key in object)) { - baseAssignValue(object, key, value); - } -} - -export default assignValue;
js/src/utils/lodash/baseAssignValue.ts+0 −23 removed@@ -1,23 +0,0 @@ -/** - * The base implementation of `assignValue` and `assignMergeValue` without - * value checks. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ -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; - } -} - -export default baseAssignValue;
js/src/utils/lodash/baseSet.ts+0 −52 removed@@ -1,52 +0,0 @@ -// @ts-nocheck - -import assignValue from "./assignValue.js"; -import castPath from "./castPath.js"; -import isIndex from "./isIndex.js"; -import isObject from "./isObject.js"; -import toKey from "./toKey.js"; - -/** - * The base implementation of `set`. - * - * @private - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @param {Function} [customizer] The function to customize path creation. - * @returns {Object} Returns `object`. - */ -function baseSet(object, path, value, customizer) { - if (!isObject(object)) { - return object; - } - path = castPath(path, object); - - const length = path.length; - const lastIndex = length - 1; - - let index = -1; - let nested = object; - - while (nested != null && ++index < length) { - const key = toKey(path[index]); - let newValue = value; - - if (index !== lastIndex) { - const objValue = nested[key]; - newValue = customizer ? customizer(objValue, key, nested) : undefined; - if (newValue === undefined) { - newValue = isObject(objValue) - ? objValue - : isIndex(path[index + 1]) - ? [] - : {}; - } - } - assignValue(nested, key, newValue); - nested = nested[key]; - } - return object; -} - -export default baseSet;
js/src/utils/lodash/castPath.ts+0 −19 removed@@ -1,19 +0,0 @@ -import isKey from "./isKey.js"; -import stringToPath from "./stringToPath.js"; - -/** - * Casts `value` to a path array if it's not one. - * - * @private - * @param {*} value The value to inspect. - * @param {Object} [object] The object to query keys on. - * @returns {Array} Returns the cast property path array. - */ -function castPath(value: any, object: Record<string, any>) { - if (Array.isArray(value)) { - return value; - } - return isKey(value, object) ? [value] : stringToPath(value); -} - -export default castPath;
js/src/utils/lodash/eq.ts+0 −35 removed@@ -1,35 +0,0 @@ -/** - * Performs a - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * comparison between two values to determine if they are equivalent. - * - * @since 4.0.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * const object = { 'a': 1 } - * const other = { 'a': 1 } - * - * eq(object, object) - * // => true - * - * eq(object, other) - * // => false - * - * eq('a', 'a') - * // => true - * - * eq('a', Object('a')) - * // => false - * - * eq(NaN, NaN) - * // => true - */ -function eq(value: any, other: any) { - return value === other || (value !== value && other !== other); -} - -export default eq;
js/src/utils/lodash/getTag.ts+0 −19 removed@@ -1,19 +0,0 @@ -// @ts-nocheck - -const toString = Object.prototype.toString; - -/** - * Gets the `toStringTag` of `value`. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the `toStringTag`. - */ -function getTag(value) { - if (value == null) { - return value === undefined ? "[object Undefined]" : "[object Null]"; - } - return toString.call(value); -} - -export default getTag;
js/src/utils/lodash/isIndex.ts+0 −30 removed@@ -1,30 +0,0 @@ -// @ts-nocheck - -/** Used as references for various `Number` constants. */ -const MAX_SAFE_INTEGER = 9007199254740991; - -/** Used to detect unsigned integer values. */ -const reIsUint = /^(?:0|[1-9]\d*)$/; - -/** - * Checks if `value` is a valid array-like index. - * - * @private - * @param {*} value The value to check. - * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. - * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. - */ -function isIndex(value, length) { - const type = typeof value; - length = length == null ? MAX_SAFE_INTEGER : length; - - return ( - !!length && - (type === "number" || (type !== "symbol" && reIsUint.test(value))) && - value > -1 && - value % 1 === 0 && - value < length - ); -} - -export default isIndex;
js/src/utils/lodash/isKey.ts+0 −36 removed@@ -1,36 +0,0 @@ -// @ts-nocheck -import isSymbol from "./isSymbol.js"; - -/** Used to match property names within property paths. */ -const reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/; -const reIsPlainProp = /^\w*$/; - -/** - * Checks if `value` is a property name and not a property path. - * - * @private - * @param {*} value The value to check. - * @param {Object} [object] The object to query keys on. - * @returns {boolean} Returns `true` if `value` is a property name, else `false`. - */ -function isKey(value, object) { - if (Array.isArray(value)) { - return false; - } - const type = typeof value; - if ( - type === "number" || - type === "boolean" || - value == null || - isSymbol(value) - ) { - return true; - } - return ( - reIsPlainProp.test(value) || - !reIsDeepProp.test(value) || - (object != null && value in Object(object)) - ); -} - -export default isKey;
js/src/utils/lodash/isObject.ts+0 −31 removed@@ -1,31 +0,0 @@ -// @ts-nocheck - -/** - * Checks if `value` is the - * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) - * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * isObject({}) - * // => true - * - * isObject([1, 2, 3]) - * // => true - * - * isObject(Function) - * // => true - * - * isObject(null) - * // => false - */ -function isObject(value) { - const type = typeof value; - return value != null && (type === "object" || type === "function"); -} - -export default isObject;
js/src/utils/lodash/isSymbol.ts+0 −28 removed@@ -1,28 +0,0 @@ -// @ts-nocheck - -import getTag from "./getTag.js"; - -/** - * Checks if `value` is classified as a `Symbol` primitive or object. - * - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. - * @example - * - * isSymbol(Symbol.iterator) - * // => true - * - * isSymbol('abc') - * // => false - */ -function isSymbol(value) { - const type = typeof value; - return ( - type === "symbol" || - (type === "object" && value != null && getTag(value) === "[object Symbol]") - ); -} - -export default isSymbol;
js/src/utils/lodash/LICENSE+0 −49 removed@@ -1,49 +0,0 @@ -The MIT License - -Copyright JS Foundation and other contributors <https://js.foundation/> - -Based on Underscore.js, copyright Jeremy Ashkenas, -DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/> - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/lodash/lodash - -The following license applies to all parts of this software except as -documented below: - -==== - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -Copyright and related rights for sample code are waived via CC0. Sample -code is defined as all source code displayed within the prose of the -documentation. - -CC0: http://creativecommons.org/publicdomain/zero/1.0/ - -==== - -Files located in the node_modules and vendor directories are externally -maintained libraries used by this software which have their own -licenses; we recommend you read them, as their terms may differ from the -terms above. \ No newline at end of file
js/src/utils/lodash/memoizeCapped.ts+0 −69 removed@@ -1,69 +0,0 @@ -// @ts-nocheck - -/** - * Creates a function that memoizes the result of `func`. If `resolver` is - * provided, it determines the cache key for storing the result based on the - * arguments provided to the memoized function. By default, the first argument - * provided to the memoized function is used as the map cache key. The `func` - * is invoked with the `this` binding of the memoized function. - * - * **Note:** The cache is exposed as the `cache` property on the memoized - * function. Its creation may be customized by replacing the `memoize.Cache` - * constructor with one whose instances implement the - * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) - * method interface of `clear`, `delete`, `get`, `has`, and `set`. - * - * @since 0.1.0 - * @category Function - * @param {Function} func The function to have its output memoized. - * @param {Function} [resolver] The function to resolve the cache key. - * @returns {Function} Returns the new memoized function. - * @example - * - * const object = { 'a': 1, 'b': 2 } - * const other = { 'c': 3, 'd': 4 } - * - * const values = memoize(values) - * values(object) - * // => [1, 2] - * - * values(other) - * // => [3, 4] - * - * object.a = 2 - * values(object) - * // => [1, 2] - * - * // Modify the result cache. - * values.cache.set(object, ['a', 'b']) - * values(object) - * // => ['a', 'b'] - * - * // Replace `memoize.Cache`. - * memoize.Cache = WeakMap - */ -function memoize(func, resolver) { - if ( - typeof func !== "function" || - (resolver != null && typeof resolver !== "function") - ) { - throw new TypeError("Expected a function"); - } - const memoized = function (...args) { - const key = resolver ? resolver.apply(this, args) : args[0]; - const cache = memoized.cache; - - if (cache.has(key)) { - return cache.get(key); - } - const result = func.apply(this, args); - memoized.cache = cache.set(key, result) || cache; - return result; - }; - memoized.cache = new (memoize.Cache || Map)(); - return memoized; -} - -memoize.Cache = Map; - -export default memoize;
js/src/utils/lodash/set.ts+0 −39 removed@@ -1,39 +0,0 @@ -// @ts-nocheck - -import baseSet from "./baseSet.js"; - -/** - * Sets the value at `path` of `object`. If a portion of `path` doesn't exist, - * it's created. Arrays are created for missing index properties while objects - * are created for all other missing properties. Use `setWith` to customize - * `path` creation. - * - * **Note:** This method mutates `object`. - * - * Inlined to just use set functionality and patch vulnerabilities - * on existing isolated "lodash.set" package. - * - * @since 3.7.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @returns {Object} Returns `object`. - * @see has, hasIn, get, unset - * @example - * - * const object = { 'a': [{ 'b': { 'c': 3 } }] } - * - * set(object, 'a[0].b.c', 4) - * console.log(object.a[0].b.c) - * // => 4 - * - * set(object, ['x', '0', 'y', 'z'], 5) - * console.log(object.x[0].y.z) - * // => 5 - */ -function set(object, path, value) { - return object == null ? object : baseSet(object, path, value); -} - -export default set;
js/src/utils/lodash/stringToPath.ts+0 −49 removed@@ -1,49 +0,0 @@ -// @ts-nocheck - -import memoizeCapped from "./memoizeCapped.js"; - -const charCodeOfDot = ".".charCodeAt(0); -const reEscapeChar = /\\(\\)?/g; -const rePropName = RegExp( - // Match anything that isn't a dot or bracket. - "[^.[\\]]+" + - "|" + - // Or match property names within brackets. - "\\[(?:" + - // Match a non-string expression. - "([^\"'][^[]*)" + - "|" + - // Or match strings (supports escaping characters). - "([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2" + - ")\\]" + - "|" + - // Or match "" as the space between consecutive dots or empty brackets. - "(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))", - "g" -); - -/** - * Converts `string` to a property path array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the property path array. - */ -const stringToPath = memoizeCapped((string: string) => { - const result = []; - if (string.charCodeAt(0) === charCodeOfDot) { - result.push(""); - } - string.replace(rePropName, (match, expression, quote, subString) => { - let key = match; - if (quote) { - key = subString.replace(reEscapeChar, "$1"); - } else if (expression) { - key = expression.trim(); - } - result.push(key); - }); - return result; -}); - -export default stringToPath;
js/src/utils/lodash/toKey.ts+0 −23 removed@@ -1,23 +0,0 @@ -// @ts-nocheck - -import isSymbol from "./isSymbol.js"; - -/** Used as references for various `Number` constants. */ -const INFINITY = 1 / 0; - -/** - * Converts `value` to a string key if it's not a string or symbol. - * - * @private - * @param {*} value The value to inspect. - * @returns {string|symbol} Returns the key. - */ -function toKey(value) { - if (typeof value === "string" || isSymbol(value)) { - return value; - } - const result = `${value}`; - return result === "0" && 1 / value === -INFINITY ? "-0" : result; -} - -export default toKey;
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-fw9q-39r9-c252ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-40190ghsaADVISORY
- github.com/langchain-ai/langsmith-sdk/commit/31d3c3aec02892f4312baae112f817d6b2f0ebe3ghsaWEB
- github.com/langchain-ai/langsmith-sdk/pull/2690ghsaWEB
- github.com/langchain-ai/langsmith-sdk/security/advisories/GHSA-fw9q-39r9-c252nvdWEB
News mentions
0No linked articles in our index yet.