Three more from the refactor review: a duplicated host check, a dead waiter, and a rule with an unwritten exception - #1585
Merged
Conversation
…es access
`isLoopbackHost` existed twice, and the two disagreed. `src/loopback-host.ts`
matches the whole 127.0.0.0/8 range and explains why a `startsWith('127.')`
would be wrong — `127.evil.com` is the rebound host the guard exists to reject.
`dashboard/lib/profiles.ts` compared against `127.0.0.1` alone, so every other
address in the range read as remote to the browser.
Nothing was exploitable: the token gate and the Host check use the strict one,
and the browser's copy only labels the connection and remembers the local
origin. What it was is two answers to one question — the shape this refactor set
out to remove — and a loose implementation sitting where someone would find it
first.
`loopback-host.ts` imports nothing, so it is already the leaf shape `client.ts`
re-exports for the browser, the same way `preference-defaults.ts` shares the
values both sides act on. The dashboard reads it from there now and its copy is
gone.
The part worth more than the deduplication: the strict one had no test at all,
while the loose one had three. So the module that decides whether a request skips
the token now has its own — the range, the rebound name, bind-all and routable
addresses, and the Host header's port and IPv6 brackets.
Verified: both typechecks, 1452 node tests, 773 dashboard tests.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGg6YdQthErYV63HRVPzrw
`waitForInterrupt` had no caller. D4b made the daemon foreground-only, and `runDaemon` blocks until it is signalled — `cli.ts` says so two hundred lines above: "Blocks until the server is signalled (SIGINT/SIGTERM)". The CLI's own promise-on-SIGINT was what the detached mode needed to keep the process alive after handing the dashboard off, and nothing has needed it since. It is the only uncalled non-exported function in 147 modules, which is a fair report on the rest of that sweep. Verified: both typechecks, 1452 node tests, 773 dashboard tests. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PGg6YdQthErYV63HRVPzrw
MEMORY.md says anything deleted must already be on the remote, with no exception. `deleteProjectAgent` breaks that on purpose: it force-removes the worktree and discards uncommitted work, because the session is being thrown away. Its own doc is explicit — "the one destructive-of-history action, which is why the surfaces that call it confirm first". Two ways to settle that, and the entry is the one that is wrong. Making delete push first would mean a user who asked to throw a session away gets its work published instead, which is the opposite of what they pressed. So the rule now says what it always meant: it binds what the framework does on its own initiative — the sweeps, teardown, retention — and a confirmed delete is the exception, because losing the work is the point of it. Docs only; nothing under test or in CI reads MEMORY.md. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PGg6YdQthErYV63HRVPzrw
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Findings 8–10 from the second review pass over #1536, one commit each. Follows #1571 and #1572.
e029e82— one answer to "is this host local", and a test on the copy that decides accessisLoopbackHostexisted twice and the two disagreed:src/loopback-host.tsdashboard/lib/profiles.ts127.0.0.1127.0.0.2127.1.2.3127.evil.comNothing was exploitable — the token gate and the Host check use the strict one; the browser's copy only labels the connection and remembers the local origin. What it was is two answers to one question, with the loose implementation sitting where someone looking for it would find it first.
loopback-host.tsimports nothing, so it is already the leaf shapeclient.tsre-exports for the browser — the same arrangementpreference-defaults.tsuses to keep both sides on one set of values. The dashboard reads it from there now and its copy is gone.The part worth more than the deduplication: the strict implementation had no test at all, while the loose one had three. The module that decides whether a request skips the token now has its own — the full 127.0.0.0/8 range, the rebound
127.evil.comname, bind-all and routable addresses, and the Host header's port and IPv6 brackets.8a5d596— the CLI's own interrupt waiter went out with the background daemonwaitForInterruptincli.tshad no caller. D4b made the daemon foreground-only andrunDaemonblocks until signalled —cli.tssays exactly that two hundred lines above. The promise-on-SIGINT was what detached mode needed to stay alive after handing the dashboard off.It is the only uncalled non-exported function in 147 modules, which is a fair report on the rest of that sweep.
e8354db— the removal rule admits the one deletion that is meant to lose workMEMORY.mdsays anything deleted must already be on the remote, with no exception.deleteProjectAgentbreaks that deliberately: it force-removes the worktree and discards uncommitted work, because the session is being thrown away — its doc calls it "the one destructive-of-history action, which is why the surfaces that call it confirm first."The entry is the half that is wrong. Making delete push first would publish the work of a user who pressed a button meaning "throw this away". So the rule now binds what the framework does on its own initiative — the sweeps, teardown, retention — and names the confirmed delete as the exception.
What the same pass checked and found sound
E4 (seven jobs, one
setInterval, no sweep kept a timer), E5 (removeProjectWorktreereally is the one implementation behind every retention surface;stale-branchrefuses anything with an open PR or no PR history), E1 (agent.ts: "Nothing stops a session for spending"), no unused dependencies, and — apart from the one above — no logic duplicated across the packages A7 merged.Verification
After
pnpm clean: both typechecks, a full build, 1452 node tests (+10: the new loopback suite), 773 dashboard tests (81 files) (−1: the loose copy's test moved to the module that owns the behaviour).Generated by Claude Code