fix: the daemon dashboard matches its bind host case-insensitively - #421
fix: the daemon dashboard matches its bind host case-insensitively#421Sma1lboy wants to merge 1 commit into
Conversation
A bind host carrying any uppercase letter — routine for mDNS names like `MyMac.local` — made originAllowed 403 every browser request. The browser and `new URL().hostname` lowercase the Origin host, but the allowed bind host was compared with its original case, so `mymac.local` never matched `MyMac.local` and the whole LAN dashboard was locked out. Hostnames are case-insensitive (RFC 4343); the Origin check now lowercases both sides.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
Claude finished @Sma1lboy's task in 2m 45s —— View job Review: PR #421 — daemon dashboard case-insensitive host matchVerdict: Correct, well-tested fix for the daemon web-server path — but it leaves a byte-for-byte duplicate of the same bug live in the PTY sidecar, so the LAN dashboard this PR claims to unblock will still fail to open terminals for the same mixed-case bind hosts.
Blocking
Nit
What's solid
|
#447) (#584) * fix(api): reject malformed integer flags instead of silently coercing parseInt stops at the first non-digit, so --id 5abc parsed as 5 and flipped the status of a real, wrong issue while exiting 0, and --count 1e3 quietly spawned one task. A shared parsePositiveInt helper now requires the whole value to be digits (safe integer, > 0) and replaces all three bare parseInt sites: the spec gate, VerbArgs.int, and the --agents count parser. * fix(api): read-output tail keeps its last line over the byte cap boundedTail's byte-budget loop counted the final line first, so a single line larger than 64KB (a minified dump, a base64 blob with no newline) pushed the window start past the end and returned a completely empty tail — a coordinator agent reading the pane got nothing and drew the wrong conclusion. The loop now never trims away the last line, mirroring buildHistoryPage's never-fewer-than-one floor. * fix(daemon): match the dashboard bind host case-insensitively Browsers (and the WHATWG URL parser) lowercase the Origin host they send, but allowedHost was compared with its original case, so binding to an mDNS name like MyMac.local 403'd every LAN browser request. Hostnames are case-insensitive (RFC 4343); the comparison now is too. * fix(daemon): decode automation precheck output as one UTF-8 stream Node splits pipe data events at arbitrary byte offsets, so decoding each chunk on its own turned a multi-byte character straddling the seam into a replacement char — a gh pr list precheck full of CJK/emoji titles came back mojibake. Concatenate the raw bytes and decode once, and cap the tail by whole code points so the slice can't strand a lone surrogate at the cut.
Direction
Bugs / correctness — found by daily code review of the daemon web-transport surface (
packages/kobe-daemon/src/daemon/web-origin.ts).Problem
The daemon-hosted dashboard gates cross-origin browser requests with
originAllowed(origin, { allowedHost }). When the daemon binds to a non-loopback host,allowedHostis derived from the bind hostname (allowedHostForBindHost) with only a.trim()— the original case is preserved.But the value it is compared against,
originHostname(origin), comes fromnew URL(origin).hostname, which the WHATWG URL spec always lowercases — and browsers likewise lowercase the host in theOriginheader. So the comparison was:Any bind host carrying an uppercase letter therefore never matched. This is routine, not exotic: macOS mDNS names like
MyMac.local/Johns-MBP.localcarry uppercase. The result was every browser request to the LAN dashboard returning403 forbidden— the whole dashboard locked out. DNS hostnames are case-insensitive by RFC 4343, so the comparison was objectively wrong.Fix
Lowercase the allowed bind host at the comparison site so both sides are compared case-insensitively, matching the case-folding the loopback check in the same file already does:
Loopback origins, missing-Origin (non-browser) requests, the no-allowed-host reject path, and non-http(s) scheme rejection are all unchanged.
How verified
packages/kobe/test/daemon/web-origin.test.ts(10 cases): no-Origin allow, loopback allow, no-allowed-host reject, exact-host match, mixed-case bind host match (the regression), different-host reject, non-http(s) scheme reject,isLoopbackOrigin, andallowedHostForBindHostloopback-drop + LAN passthrough../daemon/web-originfrompackages/kobe-daemon/package.jsonso the pure module is unit-testable via the same import convention as sibling daemon tests.bun run lintandbun run typecheckboth green.patchchangeset.Follow-ups (deliberately deferred)
terminalLinesCR-overwrite incli/api/read-output.tskeeping only the last\rsegment (correct for the common≥-length redraw /\x1b[K-clear cases), andsearchQueryKeystrokein the sidebar rejecting astral code points (seq.length !== 1). Each is a candidate for its own PR.Generated by Claude Code