Skip to content

[security] CLI: templates._download_repo_with_requests extracts ZIP archives without per-entry path validation (CWE-22 / Zip Slip) #7871

@JAE0Y2N

Description

@JAE0Y2N

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions