Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 4aa35bab6d4147ad9685a440d830b6e6
Branch: dev/3-journal-link-swap
Base: 8626664
At: 2026-11-01T18:22:06Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
A journal folder swapped for a link after the host started keeps writing outside the home, and says nothing.
core/src/host/journal.js asks what the folder is exactly once: open() (:242-268) runs dirKind(dirname(path)) and throws JournalError('the journal folder is not a plain directory') for anything but a real directory, and the file itself is checked with lstat (:252-259). That whole function is invoked once at construction — const opened = open().catch(stop) (:269). write() (:271-305) then appends (appendFile(path, text, {mode: 0o600}), :300) or replaces (temp + rename, :291-292) without ever asking again what the folder or the file is now.
The same repository already states the right rule one file over: core/src/host/memory.js:528 asks writeFolder(root) before every append, with the comment "Checked when the write runs" (:538). The state journal is the only writer that asks only at start-up.
Reproduction (a probe that imports the real journal.js, no stubs; on main at 8626664d):
- A a link in place before the host starts:
warnings = 1, outside files = 0— refused, correct, that is A symlinked journal folder lets the host chmod and write into a folder outside the home #2056/A symlinked journal folder lets the host chmod and write into a folder outside the home #2065. - B the folder is real at start-up, then
home/journalis replaced with a link to a folder outside the home:warnings = 0, and the outside folder gainsstate.jsonlholding{"seq":2,…,"key":["n"],"prev":1,"next":2,"actor":"host"}— a new journal file written outside the home, silently. - C
home/journal/state.jsonlitself is replaced with a link to a file outside the home whose content isPRIVATE LINE\n:warnings = 0, and that file becomesPRIVATE LINE\n{"seq":2,…}— someone else's file is appended with a snapshot. - D control, nothing swapped:
0warnings, both lines stay in the home — unchanged.
Expected behaviour: the same answer #2056/#2065 give at start-up, asked again when the write runs. A journal that has stopped being this host's own file or folder must record nothing and produce one warning (ds-bot: state journal … is not recording: … — the path record().catch(stop) already provides), instead of writing into a folder or file the team never named. A missing folder is still this writer's to create, as before.
Acceptance (red first, core/test/state-journal.test.js, reusing the existing tempHome() / openJournal / posix fixtures):
- Real folder at start-up →
record()once →rmSyncthe folder andsymlinkSync(outside, join(home,'journal'), 'dir')→record()again:warnings.length === 1, warning matches/not recording/, andreaddirSync(outside)is empty. (Today: 0 warnings andstate.jsonlappears outside.) state.jsonlreplaced with a link whose target is pre-filledPRIVATE LINE\n→record()again: that file is byte-for-byte unchanged. (Today: one snapshot line is appended to it.)- Control run, nothing swapped: 0 warnings, both lines inside the home.
- The four existing A symlinked journal folder lets the host chmod and write into a folder outside the home #2056/A symlinked journal folder lets the host chmod and write into a folder outside the home #2065 cases stay green, unchanged.
Scope: core/src/host/journal.js, core/test/state-journal.test.js, the patches/ds-bot/files/src/host/journal.js mirror, one sentence in the Fail-closed section of docs/subsystems/core-host.md, and a changelog fragment.
Known coverage gap this issue also closes: deleting the journal write guard and the saveCursors guard currently leaves all 19 #2068 cases green (FlyBot Test, 2026-11-01), so case 1 and 2 above are the first tests that fail without them.
Dedupe: journal folder link write / state journal link swap / journal symlink searched — only #2055, #2056, #2065, #2057, #2067, #2068, #2071, all closed, all the start-up swap. state journal link swap returns nothing. No open issue and no backlog item covers the mid-process swap.
Tier: T2 (security surface: a write escaping the home). Effort: XS.
Activity
HugoXK commentedon Nov 1, 2026
Claim
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 4aa35bab6d4147ad9685a440d830b6e6
Branch: dev/3-journal-link-swap
Assignee (running the work): FlyBot Dev-3
Base at posting: 8626664d0d1df1c1 (live main after #2067, #2071, #2072)
Posted so this issue carries a claim comment and not only the block in its body: the helper that created it writes the block into the body but posts no comment, and a claim comment is the record other readers scan.
Branch and PR follow within 24h; red-first readings and the six numbers come in the PR body.
docs(host): say what the journal does with a swapped link
docs(host): describe the journal's swapped-link guard as it behaves
HugoXK commentedon Nov 2, 2026
Release
Written-by: FlyBot Lead
Released: 4aa35bab6d4147ad9685a440d830b6e6
Reason: stood down
At: 2026-11-02T00:24:32Z