brain/raw/ readers follow symlinks (pending, promote); raw/ is writable via Syncthing #153
Closed
opened 2026-10-03 14:32:32 +00:00 by mathias
·
8 comments
No Branch/Tag Specified
main
fix/pipeline-writes-158
fix/edit-path-io-192
fix/create-regular-188
fix/guard-special-179
fix/linkcheck-keeps-write-178
fix/write-dir-redirect-173
fix/tunnel-detect-174
fix/pipeline-root-157
w3/154-mcp-auth-wiring
fix/tunnel-traversal
test/in-brain-targets-171
fix/brain-scan-root-157
fix/graph-wiki-root-157
fix/vectorstore-root-157
fix/notes-root-157
fix/search-root-157
fix/brainwide-root-157
fix/raw-symlinks-153
feat/symlink-tripwire-157
fix/rest-auth-149
fix/123-brain-edit-envelope-check
feat/106-page-aware-extraction
fix/105-source-drift-on-main
fix/104-surface-merge-conflicts-on-main
agent/b1-compile-on-ingest
agent/104-surface-merge-conflicts
agent/105-source-drift
agent/107-loud-unconfigured-alerting
feat/brain-read-edit
fix/capture-i1-refusal-names-target
renovate/configure
feat/consolidate-single-harness
icebox/cmd-routing
feat/brain-pending-promote
fix/close-session-classification-refresh
fix/capture-summarywriter
v0.24.0
v0.23.1
v0.23.0
v0.22.0
v0.21.1
v0.21.0
v0.20.1
v0.20.0
v0.19.1
v0.19.0
v0.18.4
v0.18.3
v0.18.2
v0.18.1
v0.18.0
v0.17.0
v0.16.1
v0.16.0
v0.15.0
v0.14.0
v0.13.2
v0.13.1
v0.13.0
v0.12.1
v0.12.0
v0.11.2
icebox/cmd-routing-2026-07-01
v0.11.1
v0.11.0
v0.10.0
v0.9.0
v0.8.0
v0.7.0
v0.6.0
v0.5.0
v0.4.1
v0.4.0
v0.3.0
v0.2.0
v0.1.0
Labels
Clear labels
agent/ready
blocked/client
blocked/supplier
blocked/upstream
risk/high
risk/low
risk/medium
size/l
size/m
size/s
size/xs
status:dispatch
status:dispatched
status:review
status:reviewing
type/chore
type/decision
type/docs
type/feat
type/fix
type/ops
type/research
type/test
Meets the agent-ready contract; authorizes unattended execution
Waiting on the client
Waiting on a supplier
Waiting on an upstream project or vendor
Irreversible, security-relevant, or control plane
Reversible, local, test-covered
Touches shared state, deploys or data
Too big - split it
Under two days
Under half a day
Under an hour
Maintenance, no behaviour change
Human decision required; never agent/ready
Documentation only
New capability
Corrects broken behaviour
Infrastructure, deploy, runtime
Investigate; output is a finding, not code
Test coverage only
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mathias/hyperguild#153
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found by the PR #152 gatekeeper review, 2026-10-03. Pre-existing; PR #152 confines
/ingest-pathandbrain_ingestbut not the other readers ofraw/.1. Other raw/ readers follow symlinks
GET /pendingand MCPbrain_pendingreturned the target contents ofraw/x.md -> <file outside the brain>in their excerpt. Verified by counting a marker; the bytes were not printed.brain_promote(internal/api/promote.go:108) would copy such a target intowiki/, after which brain-sync pushes it to Gitea.Fix: treat a symlink anywhere under
raw/as not-a-note. Lstat inListPending/PromoteNote, or reuseResolveIngestPath. Better still, open throughos.OpenRoot(brain/raw)so reads can't leave the root. That also closes item 3.2. raw/ is writable from outside the API
/var/lib/supervisor/brainis a Syncthingsendreceivefolder shared with flamingo.raw/is not in.stignore, and Syncthing syncs symlinks. uid 1000 on koala and the brain-* CronJobs that mount the folder can also write there. No API route lets a remote caller plant a file of its choosing in raw/, so for now the exposure is limited to whoever controls flamingo or koala.Decide: exclude
raw/symlinks from Syncthing (.stignoreor the folder's symlink setting), or accept it, given that item 1's fix makes symlinks inert.3. TOCTOU in ingest
ResolveIngestPathchecks the path, thenextract.Textreopens it by name. Exploiting that needs write access to raw/ (item 2) plus winning a race, so it's low. The fix is the same as item 1: open inside the root and extract from the handle.4. Undocumented assumption
If
raw/itself were a symlink to/, confinement would allow everything. On koala today it's a real directory. Assert that at startup (refuse to serve ingest ifraw/is a symlink), or document it.Created via git-mcp on behalf of @mathias
Decision (Mathias, 2026-10-03), item 2: accept the Syncthing write path to
brain/raw/. No.stignorechange and no symlink-sync change.Reasoning: PR #156 makes symlinks inert for every raw/ reader. pending, promote, reject,
/ingest-path,brain_ingestand the watcher all read through anos.Rooton raw/ and refuse non-regular files and a symlinked raw/. So a symlink arriving over Syncthing can't lead a read outside raw/. A regular file arriving over Syncthing is legitimate inbox content, which is what the folder is for.This acceptance depends on #156 merging. If #156 doesn't land, item 2 is open again.
Created via git-mcp on behalf of @mathias
Gatekeeper review of PR #156 (head
8a91feb): BLOCK. Full evidence is on the PR thread (#156).Summary: under execution the raw/ read control held. Symlinks at every depth, symlinked dirs, a symlinked raw/,
.., and a 385k-iteration symlink-swap race all produced sentinel count 0. Blocked because:(1) the item-3 ingest call sites have no assertion: reverting them to
extract.Textstays green;(2) the watcher symlink test passes vacuously (no positive control);
(3) the item-2 recommendation says symlinks are inert "in every reader", but the whole brain is Syncthing-shared and a
wiki/symlink is followed bybrain_search(verified). That needs its own issue before item 2 is decided;(4) a FIFO swapped in between Lstat and Open hangs TextIn, ListPending and the watcher (local write needed);
(5) pre-existing write escapes through symlinked
raw/processed,raw/rejectedand*.processedmarkers.Correction: the premise of the item-2 decision above was false. I told Mathias that once raw/ readers are fixed, the Syncthing write path can't leak outside files. The PR #156 review showed that's only true for
raw/. The whole/var/lib/supervisor/brainfolder is Syncthing-shared (.stignorelists only sessions, training-data, .git and log.md), so a peer can plantwiki/<wing>/<hall>/leak.md -> <outside file>.search.Query(brain_search) returned the target's contents in the excerpt. Reproduced, sentinel count 1.vectorstore/sync.go,search.hydrate,mcp/tools_context.go readExcerpt, graphsync and frontmatterlint follow such symlinks too.The "accept" decision is withdrawn pending a re-ask. The wiki/ exposure is tracked separately (issue to follow).
Created via git-mcp on behalf of @mathias
Gatekeeper round 2 on PR #156 (head
87c80e4): BLOCK. Details are on the PR.Round-1 findings 1, 2, 3, 6 and 7 are fixed and verified by running them. The FIFO hang is gone under a race of millions of iterations. Still blocking: the FIFO fix at its three call sites (readRawNote, TextIn, copyFromRoot) has no test. Reverting all three to a plain
Openleavesgo test ./...green, and that revert brings the hang back. Non-blocking: theextract.Text(call-site lint can be bypassed with an aliased import or a function value (rule 9).Gatekeeper round 3 on PR #156 (head
8094200): BLOCK. Details are on the PR.The round-2 findings are fixed. New: the parser lint forbids
Open/OpenFilebut notReadFile. Changing one line inreadRawNotetoos.ReadFile(filepath.Join(r.Name(), base))leavesgo test ./...green and brings the TOCTOU escape back (19,016 sentinel leaks into ListPending under the swap race). Changing it tor.ReadFile(base)brings the FIFO hang back.Gatekeeper round 4 on PR #156 (head
d5b2be0): BLOCK, one item left. Details are on the PR.The race tests caught 10/10 of my flips, still caught them under CPU starvation, and showed no flakiness at -count=20 or under -race. Remaining gap: the temp copy for pdf/docmark formats has no test. Replacing
Text(tmp.Name())with a read by name keeps the suite green, and a symlink-swap race onr.docxthen leaked 33,988 times to a stub docmark server (0 at head).Gatekeeper round 5 on PR #156 (head
8e88dde): PASS. Evidence is on the PR.Every flip in the table goes red (the race tests caught 10/10 or 5/5 of the flips). No flakiness under -count=20, -race, or CPU starvation. CI run 8900 is green. Not merged by the gatekeeper: merging deploys, and the approval to merge reached me only through another agent. Follow-ups #157 and #158 remain open.
Shipped and verified live. PR #156 squash-merged as
687afa0after 5 adversarial review rounds (Mathias pre-approved merging on PASS). CD rolled outingestion:687afa0;/pendingwith the token returns 200 and there are no startup errors.By item:
/ingest-path,brain_ingestand the watcher read through anos.Rooton raw/ and refuse non-regular files. The watcher was a reader the issue didn't list. Behavioural race tests fail on any leaked byte or hang: symlink swap, FIFO swap, and docx through a fake docmark.Write escapes under raw/ are #158. Closing.
Created via git-mcp on behalf of @mathias