---
id: 43b943b9-4e27-4b4a-a7d2-550f9a1e99d5
title: Hostile .forge symlinks escape the repo and publish host files
state: closed
labels:
- kind:security
assignees: []
milestone: null
created: 2026-10-27T18:39:53.651563186Z
updated: 2026-10-27T19:53:10.436268596Z
---
## Problem

The `.forge` symlink refusal is incomplete. `read_text_capped` checks only the final file, but build readers follow a symlinked `.forge` root or section root, and artifact copy plus PR patch/CI log reads have no symlink check at all. A hostile repo can make `hu13 build` copy host files into the public site (including private inbox files, SSH keys, or CI secrets).

Verified vectors:

1. `.forge` -> `/tmp/secret-forge` (external tree with `issues/...` and `wiki/secret.md`): build emits `issues/leak.html` and `wiki/secret.md.html` containing the external text.
2. `.forge/wiki` -> `/tmp/secret-wiki`: build emits the external markdown page.
3. `.forge/releases/out/v1` -> `/tmp/secret-dir`: `collect_artifacts`/`copy_tree` follow the root symlink and copy `/tmp/secret-dir/secret.txt` to `dist/releases/v1/secret.txt` (downloadable).
4. `.forge/prs/<uuid>/patch.diff` -> `/tmp/secret.txt`: build renders the external file as the PR patch.
5. `.forge/ci/runs/<ts>/steps/00.log` -> `/tmp/secret.log`: build renders the external file as a CI log.

`src/forge.rs:640` and `src/forge.rs:842` use raw `std::fs::read`; `src/releases.rs:127`/`181`/`476` start at the configured root; `src/wiki.rs` and `src/releases.rs` only reject entry-level symlinks.

## Impact

Arbitrary local-file disclosure into `dist/` when building an untrusted checkout. The same files can be served by `serve`/Pages. The patch and CI log reads are also unbounded, so a large `.forge` file can OOM the build even without a symlink.

## Fix

- Reject a symlinked `.forge`, `.forge/wiki`, `.forge/releases`, `.forge/releases/out`, and `.forge/releases/out/<version>` root (or resolve and require it under the repo).
- Check symlinks for `patch.diff` and every CI step log before reading; route them through a capped reader.
- Add regression tests using symlinked roots and symlinked patch/log fixtures.
