The import_apply function joins attacker-controlled tar member names directly onto kb_dir without sanitizing ../ sequences:
dest = kb_dir / member.name # member.name from tar archive
# ...
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_bytes(tar.extractfile(member).read()) # writes anywhere on disk
The manifest check at line 227 (if member.name not in recorded) is circular — the manifest is also inside the attacker-controlled tarball.
Reproduction
- Craft a
.tar.gz where one member has name ../../evil.txt and manifest.json lists it as a valid file.
- Run
vouch import-apply evil-bundle.tar.gz or call kb.import_apply via JSONL/MCP.
evil.txt is written two directories above .vouch/.
This is the classic CVE-2007-4559 pattern.
Suggested fix
dest = (kb_dir / member.name).resolve()
if not str(dest).startswith(str(kb_dir.resolve())):
raise RuntimeError(f"path traversal in bundle: {member.name}")
The
import_applyfunction joins attacker-controlled tar member names directly ontokb_dirwithout sanitizing../sequences:The manifest check at line 227 (
if member.name not in recorded) is circular — the manifest is also inside the attacker-controlled tarball.Reproduction
.tar.gzwhere one member has name../../evil.txtandmanifest.jsonlists it as a valid file.vouch import-apply evil-bundle.tar.gzor callkb.import_applyvia JSONL/MCP.evil.txtis written two directories above.vouch/.This is the classic CVE-2007-4559 pattern.
Suggested fix