# Trestle is vulnerable to arbitrary file write via path traversal in author generate commands (Incomplete fix of CVE-2026-46345)

Incomplete fix left catalog/profile/ssp-generate joining raw --output to trestle_root, letting an attacker-chosen absolute or ../ path write markdown outside the workspace.

- Payout: Research
- Severity: high
- Program: compliance-trestle
- Found by: Yanchon918s
- Disclosed: 2026-09-24
- CVE: CVE-2026-57171
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-r4vp-3vw6-r2x5
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-r4vp-3vw6-r2x5
- Tags: area:oss domain:oss impact:dos impact:integrity kind:research platform:ghsa sev:high tech:python technique:incomplete-fix technique:source-review vuln:path-traversal

**Target:** Python CLI (trestle author catalog/profile/ssp-generate -o/--output)
**Root cause:** The CVE-2026-46345 fix added PathSecurityValidator.validate_local_path only to jinja.py; the sibling generate commands still build markdown_path = trestle_root / args.output and gate it solely with is_directory_name_allowed(), which only inspects parts[0] and permits absolute paths and non-leading '..' segments.
**Steps:**
1. pip install -e . (v4.0.3 source), mkdir /tmp/poc_ws && cd /tmp/poc_ws && trestle init
2. Create catalogs/mycat/catalog.json with one group grp1 and control ac-1
3. Run `trestle author catalog-generate -n mycat -o /tmp/TRESTLE_ESCAPE_ABS`
4. Observe /tmp/TRESTLE_ESCAPE_ABS/grp1/ac-1.md written outside the workspace
5. Repeat with `-o 'subdir/../../../../../../tmp/TRESTLE_ESCAPE_DOTDOT'` to bypass is_directory_name_allowed
6. Differential: `trestle author jinja -i template.j2 -o '/tmp/TRESTLE_JINJA_BLOCKED'` is blocked with 'Security violation: Path traversal blocked'
**How found:** Noticed the jinja fix commit touched only jinja.py, then grepped the sibling author commands for the same trestle_root / args.output join and found no validate_local_path call, only is_directory_name_allowed; analyzed that guard's parts[0]-only logic to derive absolute-path and non-leading-.. bypasses, then runtime-confirmed with a differential against the patched jinja command.
**Exploitation:** Attacker who influences the -o/--output value (e.g. CI pipeline deriving output dir from repo-controlled data) causes generated markdown to be written to an arbitrary location as the process owner; with --force-overwrite, clear_folder() runs shutil.rmtree on the attacker-chosen existing directory first, giving recursive deletion. Escalation to code execution by overwriting a script the CI later invokes.
**Escalation:** Overwrite files consumed by the CI pipeline (scripts) for indirect code execution; --force-overwrite adds recursive directory deletion.
**Insight:** When a fix adds a path-containment validator, grep every other sink using the same `root / user_input` join pattern — and check whether the pre-existing guard only inspects parts[0], since absolute paths and `subdir/../../..` slip past it while naive `../x` is blocked.

## Proof of concept (copied exactly from the original report)

**catalog-generate absolute path escape PoC**

```
trestle author catalog-generate -n mycat -o /tmp/TRESTLE_ESCAPE_ABS
ls /tmp/TRESTLE_ESCAPE_ABS/grp1/ac-1.md
```

**Non-leading .. traversal bypass PoC**

```
trestle author catalog-generate -n mycat -o 'subdir/../../../../../../tmp/TRESTLE_ESCAPE_DOTDOT'
ls /tmp/TRESTLE_ESCAPE_DOTDOT/grp1/ac-1.md   # -> exists, outside the workspace
```

**Differential jinja blocked on same install**

```
echo 'hello {{ 1+1 }}' > template.j2
trestle author jinja -i template.j2 -o '/tmp/TRESTLE_JINJA_BLOCKED'
```

