What I noticed
libs/cli/langgraph_cli/templates.py:107 (in _download_repo_with_requests) passes the downloaded template archive directly through ZipFile.extractall(path). Python's extractall does not validate per-entry paths, so an archive containing entries like ../../etc/passwd or absolute-path entries writes outside the destination directory the operator picked.
Reachability assessment
The --template flag is hard-rejected against the hardcoded TEMPLATE_ID_TO_CONFIG dict (no user-supplied URL injection), and there's no env-var or config-file override of TEMPLATES. So the realistic exploit precondition is a supply-chain compromise of one of the langchain-ai/* template repos (deep-agent-template, simple-agent-template, new-langgraph-project, etc.) or a TLS MITM against github.com. Both are unlikely in normal use.
This is therefore defense-in-depth hardening, not an active vulnerability — but it's cheap to add and matches the cross-vendor baseline (npm tar v7+, create-next-app's use of tar with built-in path sanitation, t3-oss/create-t3-app's bundled-templates approach all already gate extractall-equivalents on entry-path validation).
Proposed fix
Add a small _safe_extract(zip_file, path) helper that, before calling extractall, walks the archive's entries and rejects:
- any entry with an absolute path or drive-prefixed path
- any entry whose
os.path.realpath(os.path.join(path, member)) is not a descendant of os.path.realpath(path) (validated via os.path.commonpath)
If a malformed entry is detected, surface a clear error to the user and exit non-zero rather than silently scribbling outside path. No behavior change for well-formed archives (which is every archive the helper currently sees).
A PR with the patch is already up at #7870 (currently auto-closed by the issue-link CI gate). Happy to update it to reference this issue once it's approved, or to update the patch in any other shape the maintainers prefer.
Refs
What I noticed
libs/cli/langgraph_cli/templates.py:107(in_download_repo_with_requests) passes the downloaded template archive directly throughZipFile.extractall(path). Python'sextractalldoes not validate per-entry paths, so an archive containing entries like../../etc/passwdor absolute-path entries writes outside the destination directory the operator picked.Reachability assessment
The
--templateflag is hard-rejected against the hardcodedTEMPLATE_ID_TO_CONFIGdict (no user-supplied URL injection), and there's no env-var or config-file override ofTEMPLATES. So the realistic exploit precondition is a supply-chain compromise of one of the langchain-ai/* template repos (deep-agent-template,simple-agent-template,new-langgraph-project, etc.) or a TLS MITM againstgithub.com. Both are unlikely in normal use.This is therefore defense-in-depth hardening, not an active vulnerability — but it's cheap to add and matches the cross-vendor baseline (npm
tarv7+,create-next-app's use oftarwith built-in path sanitation, t3-oss/create-t3-app's bundled-templates approach all already gateextractall-equivalents on entry-path validation).Proposed fix
Add a small
_safe_extract(zip_file, path)helper that, before callingextractall, walks the archive's entries and rejects:os.path.realpath(os.path.join(path, member))is not a descendant ofos.path.realpath(path)(validated viaos.path.commonpath)If a malformed entry is detected, surface a clear error to the user and exit non-zero rather than silently scribbling outside
path. No behavior change for well-formed archives (which is every archive the helper currently sees).A PR with the patch is already up at #7870 (currently auto-closed by the issue-link CI gate). Happy to update it to reference this issue once it's approved, or to update the patch in any other shape the maintainers prefer.
Refs