Writes under brain/raw/ follow symlinks: processed/, rejected/, .processed markers, tunnel-candidates #158

Open
opened 2026-10-03 14:57:17 +00:00 by mathias · 2 comments
Owner

Found by the PR #156 review, 2026-10-03. Pre-existing; PR #156 confines reads from raw/, not writes.

Reproduced:

  • a symlinked raw/processed: the watcher copies the note's contents outside raw/
  • a symlinked raw/rejected: RejectNote writes the note outside
  • a dangling symlink raw/m.md.processed: the watcher creates the target file outside

Read only: the tunnel-candidates-*.md append (internal/brain/tunnel.go:168) follows a symlink.

The watcher cases need no API call: a Syncthing peer plants the symlink and the next poll writes through it.

Fix: do every write under raw/ through the same os.Root (root.OpenFile/root.Mkdir/root.Create; os.Root refuses to traverse symlinks out of the root), and refuse a symlinked processed//rejected//failed/.

Done when: all four cases have a test that fails before the fix.


Created via git-mcp on behalf of @mathias

Found by the PR #156 review, 2026-10-03. Pre-existing; PR #156 confines *reads* from raw/, not writes. Reproduced: - a symlinked `raw/processed`: the watcher copies the note's contents outside raw/ - a symlinked `raw/rejected`: `RejectNote` writes the note outside - a dangling symlink `raw/m.md.processed`: the watcher creates the target file outside Read only: the `tunnel-candidates-*.md` append (`internal/brain/tunnel.go:168`) follows a symlink. The watcher cases need no API call: a Syncthing peer plants the symlink and the next poll writes through it. **Fix:** do every write under raw/ through the same `os.Root` (`root.OpenFile`/`root.Mkdir`/`root.Create`; os.Root refuses to traverse symlinks out of the root), and refuse a symlinked `processed/`/`rejected/`/`failed/`. **Done when:** all four cases have a test that fails before the fix. --- _Created via git-mcp on behalf of @mathias_
mathias added the risk/mediumsize/mtype/fix labels 2026-10-03 14:57:17 +00:00
Author
Owner

One more write path that follows symlinks, found while doing #157 slice 4: wiki.RebuildIndex (ingestion/internal/wiki/index.go) writes wiki/index.md with os.WriteFile. If wiki/index.md is a symlink (the brain is a Syncthing folder), the pipeline overwrites the symlink's target, which can be outside the brain. Fix it the same way as the other #158 writes: open the file through the brain root with O_NOFOLLOW, or write a temp file and rename it inside the root.


Created via git-mcp on behalf of @mathias

One more write path that follows symlinks, found while doing #157 slice 4: `wiki.RebuildIndex` (`ingestion/internal/wiki/index.go`) writes `wiki/index.md` with `os.WriteFile`. If `wiki/index.md` is a symlink (the brain is a Syncthing folder), the pipeline overwrites the symlink's target, which can be outside the brain. Fix it the same way as the other #158 writes: open the file through the brain root with O_NOFOLLOW, or write a temp file and rename it inside the root. --- _Created via git-mcp on behalf of @mathias_
Author
Owner

Write paths confirmed by execution in the PR #176 review (2026-10-05). Name them explicitly here:

  • pipeline.BackfillRefs: reads a regular concept page, a symlink is swapped in, and os.WriteFile(dest) follows it. The in-brain symlink target was overwritten about 5,000 times in 3 s. This was already the case on main.
  • pipeline.buildAndWrite (MkdirAll + os.WriteFile of every page) and wiki.AppendLog have the same shape.
  • provenance.ApplyFile: read-modify-write by path. Move it to brainfs.ReadRegular + WriteRegular once #170 lands.

A design point for the fix: reviseOrCreate now refuses a static symlinked page (#176), which aborts buildAndWrite partway. Earlier pages are written, the index rebuild and log append are skipped, and the watcher moves the source to failed/. Skip-with-warning per page may be better than aborting.


Created via git-mcp on behalf of @mathias

**Write paths confirmed by execution in the PR #176 review (2026-10-05). Name them explicitly here:** - **`pipeline.BackfillRefs`:** reads a regular concept page, a symlink is swapped in, and `os.WriteFile(dest)` follows it. The in-brain symlink target was overwritten about 5,000 times in 3 s. This was already the case on main. - **`pipeline.buildAndWrite`** (MkdirAll + `os.WriteFile` of every page) and **`wiki.AppendLog`** have the same shape. - **`provenance.ApplyFile`:** read-modify-write by path. Move it to `brainfs.ReadRegular` + `WriteRegular` once #170 lands. **A design point for the fix:** `reviseOrCreate` now refuses a static symlinked page (#176), which aborts `buildAndWrite` partway. Earlier pages are written, the index rebuild and log append are skipped, and the watcher moves the source to `failed/`. Skip-with-warning per page may be better than aborting. --- _Created via git-mcp on behalf of @mathias_
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/hyperguild#158