brain_tunnel: path traversal lets an MCP token holder rewrite any file the pod can write #169
Closed
opened 2026-10-04 20:24:01 +00:00 by mathias
·
6 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
No labels
risk/high
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#169
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 2026-10-04 while doing #157 slice 5. Verified by execution (
TestWriteTunnelRefusesATargetThatTraversesOutOfTheBrain, red on main57d0fb0).The bug
brain_tunnel(internal/mcp/handlers.go) passes the caller'ssourceandtargettobrain.WriteTunnel. That function checks onlywingOf(wiki/<wing>/...with at least 4 segments), then callsfilepath.Join(brainDir, path).Joincleans.., sowiki/b/x/../../../../<anything>.mdresolves outside the brain.appendSeeAlsothen reads that file and rewrites it with an appended## See alsolink.Effect
Anyone with the brain MCP token (agents; the public
/mcpendpoint is bearer-authenticated) can append text to any existing file the pod user can write:raw/,sessions/,classification.yaml, other wingsThe file must exist. There is no read-back, since the response is only
{"status":"ok"}. The rewrite also turns symlinks into followed writes.Fix
PR to follow:
wiki/.../<slug>.mdpathWriteRegular(Lstat + O_NOFOLLOW + os.SameFile on the handle)Still to check after the fix
brain_tunnelcalls with..happened. Audit logs are in Loki (the audit middleware logs the tool name and args?).filepath.Join(brainDir, <caller path>)found no other unvalidated caller path.IndexDocgetsbrain_tunnel's raw paths, and #168 confines that read.Created via git-mcp on behalf of @mathias
Log check, 2026-10-04. Loki query
{namespace="supervisor"} |= "mcp tool call"over 30 days returns 83 lines in total, all from the recent audit logging:There are zero
brain_tunnelcalls, and no logged line containsbrain_tunnelat all. So nothing in the logged window exploited this. Calls before tool-call logging began can't be checked, because before that the server logged nothing per tool call. Fix PR: #170, which is in adversarial review.Created via git-mcp on behalf of @mathias
Correction: the line "a grep found no other unvalidated caller path" was wrong. The PR #170 review checked it by execution and found two more handlers with the same class of bug:
classification.yaml,raw/andsessions/), and a final symlink was written through.session_idwas not validated, so a caller could create or append a.jsonlfile outside the sessions dir.Both are fixed in PR #170 (round 2, head below), which this issue now covers.
brain_edit,brain_promote/brain_reject,brain_ingestand BuildWingIndex already validate their paths; the reviewer ran the same probes against them and they refused.The audit-log check on the earlier comment covers these too. In the logged window: session_log 1 call, which was a normal session id from this session's own tooling, and brain_update 0 calls.
Created via git-mcp on behalf of @mathias
Precision on the session_log call: the log line (2026-10-03 16:30, principal=local-cli) doesn't record the
session_id, so I can't say from the log alone what it was. The only sessions file modified that day issessions/bb979e40-bbaf-56fa-8d31-f0bf8f3a740c.jsonl, a plain UUID name, which matches that call. Nothing suggests a traversal, but that is an inference, not proof. Loggingsession_id(sanitised) in the tool-call line would make it checkable.Created via git-mcp on behalf of @mathias
Gatekeeper verdict on PR #170, round 2 (
87fa28e): BLOCKED. Full review is on the PR (comments 15038 for round 1, 15040 for round 2).The brain_tunnel, brain_update and session_log fixes hold under execution through the MCP handlers. Two guards in
session.Appendhave no test that fails when they are removed: the ban on/in session ids, and theO_NONBLOCKthat is its only FIFO guard. Both need a test that goes red.Residuals to record explicitly:
Gatekeeper verdict on PR #170, round 3 (
6325c2b): PASS. The full review is on the PR. This isrisk/high, so merging is Mathias's call. Nothing was merged./in the session id breaks the new escape test, and droppingO_NONBLOCKbreaks the new FIFO test.WriteRegularand its test were only moved, with byte-identical bodies, and nothing from #168 was dropped.77e32derecorded 52 escapes.CI=1 task checkexited 0, and-race -count=10passed. The PR also passes on top of the current main,b6ef116.Closed by #170 (
febab3e), verified live, 2026-10-05. The pod runs the merge image; the graph backfill indexed 1593 notes with no errors...target is refused: "not a clean wiki note path".TestUpdateNoteRefusesPathsOutsideKnowledgeAndWikiNotes, the race test, and the reviewer's end-to-end handler probes in rounds 2–3.Follow-ups: #173 (static intermediate in-brain dir redirect on writes) and #174 (DetectTunnels FIFO hang, PR coming next).
Created via git-mcp on behalf of @mathias