#107 Hostile .forge symlinks escape the repo and publish host files
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:
.forge->/tmp/secret-forge(external tree withissues/...andwiki/secret.md): build emitsissues/leak.htmlandwiki/secret.md.htmlcontaining the external text..forge/wiki->/tmp/secret-wiki: build emits the external markdown page..forge/releases/out/v1->/tmp/secret-dir:collect_artifacts/copy_treefollow the root symlink and copy/tmp/secret-dir/secret.txttodist/releases/v1/secret.txt(downloadable)..forge/prs/<uuid>/patch.diff->/tmp/secret.txt: build renders the external file as the PR patch..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.diffand every CI step log before reading; route them through a capped reader. - Add regression tests using symlinked roots and symlinked patch/log fixtures.
Uunknown ·
Fixed in PR #61 (
3df93c3a-bd5e-43f4-bb73-0f3dd886eeb1, commitee6e71b, mergede834a46). CI run2026-10-27T19-50-04.907798288Zpassed; fullcargo test,node --test tests/pages_inbox.test.mjs, fmt, and clippy green..forge,.forge/wiki,.forge/releases, and.forge/releases/out/<ver>are resolved withpaths::repo_subdirand must stay inside the checkout;sorted_entriesskips symlinks; patch/CI-log reads useread_text_capped/read_bytes_cappedwith the 256 KiB cap. Verified external wiki/release artifacts/patch/CI logs and a symlinked.forgeroot never reachdist/; tests intests/review5_hardening.rs.