fix(finqa_env): prevent path traversal via agent-supplied company/table names#1008
Open
OfficialAbhinavSingh wants to merge 1 commit into
Open
Conversation
…le names The finqa MCP tools (get_descriptions, get_table_info, sql_query) joined the agent-controlled company_name/table_name into a filesystem path guarded only by an existence check (os.path.isdir/isfile), not a containment check. An agent could pass '..' components to enumerate directories and read arbitrary .json files outside the data directory (e.g. withheld answer data or mounted secrets) over the MCP boundary. Add a _resolve_within() helper that resolves the joined path and rejects any result that escapes companies_path, mirroring the intent of the existing pointer guards. Applied to all three tools. Adds a self-contained regression test (no downloaded dataset required).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The FinQA env's MCP tools (
get_descriptions,get_table_info,sql_query) build a filesystem path from the agent-suppliedcompany_name/table_nameand guard it only with an existence check (os.path.isdir/os.path.isfile), not a containment check. An agent can pass..components over the MCP boundary to enumerate directories and read arbitrary.jsonfiles outside the data directory (e.g. withheld answer data for other episodes → reward hacking, or any.jsonmounted into the container). This adds a_resolve_within()helper that rejects any path escapingcompanies_path, applied to all three tools.Type of Change
Alignment Checklist
.claude/docs/PRINCIPLES.mdand this PR aligns with our principles.claude/docs/INVARIANTS.mdand no invariants are violatedruff format --check,ruff check,usort check) and the finqa tests — all passRFC Status
Details
envs/finqa_env/server/tools.py— the sinks:company_name/table_namecome straight from the MCPCallToolActionarguments, and the only validation isos.path.isdir/isfile— an existence check, not containment...traversal therefore escapescompanies_path.Fix: a small
_resolve_within(*parts)helper resolves the joined path withos.path.realpathand returnsNoneunless it stays withincompanies_path(viaos.path.commonpath). Each tool now rejects aNoneresult. Legitimate company/table access is unchanged; the fix runs before any file read. Blast radius is confined to the env's own container/process, but it breaks the intended "companies are a closed set fromget_available_companies()" boundary.Test Plan
New self-contained test class
TestToolsPathTraversalintests/envs/test_finqa_environment.py(syntheticinput_companies/intmp_path+ asecret.jsonoutside it; needs only pandas, not the downloaded dataset, so it runs in CI — unlike the dataset-gatedTestTools).Verified red → green against a synthetic layout:
Before the fix (vulnerable):
After the fix (blocked, legit access intact):
Full file:
67 passed, 16 skipped(skips = dataset-gated integration tests).ruff format --check,ruff check,usort checkall clean on the changed files.Claude Code Review
Self-reviewed the diff for scope and the containment logic (realpath + commonpath,
ValueErrorguard for cross-drive on Windows, applied uniformly to all three sinks). No new dependencies; no public API change; docstring follows the HF doc-builder format.Note
Medium Risk
Security-sensitive fix at the MCP agent boundary for filesystem reads; behavior change is limited to rejecting malicious paths, with dedicated regression tests.
Overview
Closes a path-traversal hole (CWE-22) in FinQA MCP tools where
company_nameandtable_namewere joined underinput_companies/with only existence checks, so..could read JSON outside the data directory.Adds
_resolve_within()inFinQATools:realpath+commonpathagainstcompanies_path, returningNoneon escape (with a Windows cross-drive guard).get_descriptions,get_table_info, andsql_querynow resolve paths through this helper before any directory listing or file read;sql_queryuses a generic “table not found” error when resolution fails.Adds
TestToolsPathTraversalwith a synthetic layout (secret.jsonoutsideinput_companies/) so CI verifies legitimate access and blocks traversal via company or table names—no downloaded dataset required.Reviewed by Cursor Bugbot for commit 4050b9b. Bugbot is set up for automated code reviews on this repo. Configure here.