Skip to content

A journal folder swapped for a link after the host started keeps writing outside the home, with no warning #2074

Closed

Description

@HugoXK

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):

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):

  1. Real folder at start-up → record() once → rmSync the folder and symlinkSync(outside, join(home,'journal'), 'dir') → record() again: warnings.length === 1, warning matches /not recording/, and readdirSync(outside) is empty. (Today: 0 warnings and state.jsonl appears outside.)
  2. state.jsonl replaced with a link whose target is pre-filled PRIVATE LINE\n → record() again: that file is byte-for-byte unchanged. (Today: one snapshot line is appended to it.)
  3. Control run, nothing swapped: 0 warnings, both lines inside the home.
  4. 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

  1. HugoXK commented on Nov 1, 2026

    @HugoXK
    ContributorAuthor

    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.

  2. HugoXK commented on Nov 2, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 4aa35bab6d4147ad9685a440d830b6e6
    Reason: stood down
    At: 2026-11-02T00:24:32Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions