From 086a9d7ce4c8859bb6aa8b7cf1711ffedcc7bda7 Mon Sep 17 00:00:00 2001 From: bradAGI <46579244+bradAGI@users.noreply.github.com> Date: Mon, 24 Aug 2026 14:17:33 -0400 Subject: [PATCH] feat(langchain): add LC-009, LC-023 path safety rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LangChain had no path-safety rule; Claude SDK (CSDK-004/012), OpenAI (OAI-006), ADK (ADK-004), and MCP (MCP-005) all ship one. Ships the python/TypeScript pair this pack uses throughout. LC-009 (python): the args_schema does not close the gap — a Pydantic field typed str or Path validates cleanly while still carrying ../../etc/passwd, because the schema constrains the argument's type and not the region of the filesystem it points at. LangChain agents are also a common indirect injection target: a retrieved document or a page fetched mid-run can carry the path the model then passes in. LC-023 (typescript): mirrors CSDK-012, including its coarse-signal caveat — it flags any filesystem write rather than only unnormalized paths, because TS path-normalization analysis is not yet wired. --- langchain/path_safety.yaml | 79 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 langchain/path_safety.yaml diff --git a/langchain/path_safety.yaml b/langchain/path_safety.yaml new file mode 100644 index 0000000..a8feeaf --- /dev/null +++ b/langchain/path_safety.yaml @@ -0,0 +1,79 @@ +policy: + id: langchain_path_safety + name: LangChain filesystem path safety + category: langchain + description: > + Rules that catch a model-supplied filesystem path flowing into an I/O call + inside a LangChain tool without containment. A traversal payload like + ../../etc/passwd reaches real filesystem state if the tool does not resolve + the path and check it against an allowed root. + +rules: + - id: LC-009 + title: LangChain tool uses a path parameter in I/O without validation + severity: high + confidence: 0.7 + language: python + applies_to: + - langchain_tool + scope: tool + # call_uses_unnormalized_path_param is per-param: a tool with two path + # params and one .resolve() correctly fires on the unresolved one. A + # body-wide check would suppress the finding whenever any normalization + # appears, masking real exposure on the other param. + match: + call_uses_unnormalized_path_param: + callees: + - open + - Path + callee_prefixes: + - shutil. + - os. + explanation: > + This tool takes a path-like parameter and hands it to a file or directory + operation without resolving it or checking it against an allowed root, so + a model-supplied ../../etc/passwd reaches the real filesystem. The tool's + args_schema does not close this: a Pydantic field typed str or Path + validates fine while still carrying a traversal payload, because the + schema constrains the argument's type and not the region of the filesystem + it points at. LangChain agents are also a common target for indirect + injection — a retrieved document or a page fetched mid-run can carry the + path the model then passes here — so the argument should be treated as + hostile even when the user is not. Detection is heuristic; confirm the + parameter is genuinely model-supplied before applying the fix. + fix: > + Resolve the path with Path(...).resolve() and assert the result sits under + an allowed root before touching it, rejecting anything that escapes. + Encode the check as a validator on the args_schema field so containment + holds for every tool that accepts the path rather than being + re-implemented per call site. + + - id: LC-023 + title: TypeScript LangChain tool writes to the filesystem + severity: low + confidence: 0.5 + language: typescript + applies_to: + - langchain_tool + scope: tool + match: + has_write_call: true + explanation: > + This LangChain.js tool writes to the filesystem. If the path or the + contents derive from the tool's arguments, the model chooses both, and a + prompt injection can steer the write at any file the Node process can + reach. The injection route is the ordinary one in this framework rather + than an exotic case: a retrieval chain feeds fetched documents into the + same context the model plans from, so text in a page or a knowledge-base + record can supply the filename the model then passes here. The Zod schema + does not help — it constrains the argument's type, not the region of the + filesystem it points at, so a traversal payload validates cleanly. + (Coarse signal — it flags any filesystem write, not only unnormalized + paths, because TypeScript path-normalization analysis is not yet wired. + Confirm the path is genuinely model-supplied before acting.) + fix: > + Confine writes to a dedicated working directory: resolve the final path, + verify it stays under that root before writing, and reject absolute paths + and any input containing "..". Where the tool only ever writes generated + names, derive the filename from an id rather than accepting a path from + the model at all.