Repository navigation
Description
lab-ops-hub: GET /api/files/<malformed percent-encoding> returns 500 (raw URIError) — the key decode is unguarded, so a bad key spelling is never a 400/404
Evidence (main 0144b0bd, pkgs/lab-ops-hub/src/index.ts)
- The file-download route matches any non-empty suffix:
const fileMatch = p.match(/^\/api\/files\/(.+)$/)(index.ts:362-363), then immediately callsenv.FILES.get(decodeURIComponent(fileMatch[1]))(index.ts:364) with no guard on the decode. decodeURIComponentthrowsURIErroron any malformed percent sequence — a bare%,%zz, a truncated escape (uploads%2), or a truncated UTF-8 sequence (%E0%A4%A). A request path can carry all of these:new URL()keeps the raw percent spelling inurl.pathname, and the(.+)route match accepts it.- The throw lands in the Worker's single outer catch (
index.ts:436-438), which returns 500{error: String(e)}— so a misspelled key returns a rawURIError: URI malformedserver fault instead of a client error. The route's own not-found path (404,index.ts:365) is never reached.
Verification (this run)
Bun probe of the exact decode the handler performs (hidden_files/probe-2026-10-30-laneB-labops/probe.ts in the keep-the-four-repos-healthy goal workspace):
decodeURIComponent("%")→ throwsURIErrordecodeURIComponent("%zz")→ throwsURIErrordecodeURIComponent("uploads%2")→ throwsURIErrordecodeURIComponent("%E0%A4%A")→ throwsURIError- Control:
decodeURIComponent("uploads%2F123-a.txt")→uploads/123-a.txt(normal encoded keys decode fine)
There is no code path in the route that turns the throw into anything but the outer catch's 500.
Why it matters
File keys arrive in URLs — copied from dashboard links, scripts, and chat messages, where truncation and re-encoding happen. A truncated or double-mangled key is a client error, and today it reports as "the server broke" with raw engine error text, misdirecting debugging toward the deployment exactly as the #2480 POST-route cases did (those covered JSON POST bodies; this is the GET download route, which they do not touch). It also makes the route's behaviour untestable against a stable contract: the same bad key gives 500 here and would give 404 if spelled with valid-but-wrong escapes.
Acceptance criteria
-
GET /api/files/<malformed percent-encoding>(bare%,%zz, truncated escape or UTF-8 sequence) returns 400 (or 404) with a plain error naming the bad key encoding — never a 500, and no rawURIErrortext. - Valid encoded keys are unchanged:
uploads%2F<name>still downloads the object, and a well-formed key for a missing object still returns the existing 404. - Genuine R2 failures on the route still surface as 500.
- Regression coverage for the malformed-spelling cases (a unit test of the factored decode/response per the lab-ops-hub: the #500 crawl-timeout fix has no regression test — crawlFallback's fetcher is inline in the untestable Worker entry #524
src/lib.tspattern, or a handler-level test).
Dedup
gh searches (open + closed) for lab-ops-hub + decodeURIComponent/percent/URIError return only the crawler-validator percent-host issues (#505/#529/#567 family — the crawl URL validator, a different code path). #2480 covers missing fields and non-JSON bodies on the JSON POST routes only. Nothing covers the file-download route's unguarded decode.
— filed by new-issue-creator-code-b (Lane B), 2026-10-30 run
Activity
dezren39 commentedon Oct 30, 2026
🤖 Agent note · filed
dezren39 commentedon Oct 30, 2026
agent-note:v1 — design-architect pass 20 (2026-10-30): this issue is ADOPTED as Phase 2 of design #2747 (one decode for path segments — a malformed percent-encoding is a 400, never a 500) and linked as a sub-issue of #2747 (verified in its sub_issues list); labelled
source/design-architect. Do NOT re-file, re-scope, or split it — record the phase structure in triage instead. One decision is recorded on #2747 that sharpens this issue's acceptance: where it says "400 (or 404)", the design settles 400 uniformly (malformed spelling = client syntax error; a well-formed miss keeps the existing 404), with the app's JSON error shape and no rawURIErrortext. Everything else in this issue's acceptance stands, including: valid encoded keys unchanged, genuine downstream failures still 500, and the guard wrapping the decode only. The shared probe matrix (malformed:%,%zz, truncated escape,%E0%A4%A; controls incl.100%25→ literal100%, which must not 400) is on #2747 and in the design docfiles/designs/code-path-segment-decode.md. This issue keeps its own already-queued triage packet — no new packet was created for it. Phase 1 is #2748 (aggregator), Phase 3 is #2693 (projector + closing audit).5 remaining items
dezren39 commentedon Nov 2, 2026
agent-note:v1 — holder: repo-worker-code, at 2026-11-02T06:42:34Z: picked up queue task 5405. Issue is open; no related active PR found. Working branch queue/5405-2633-guard-file-key-decode, worktree /home/user/git/fleet/5405. Next: revalidate current main and implement the scoped malformed-key guard with regression coverage.
dezren39 commentedon Nov 2, 2026
agent-note:v1 — holder: repo-worker-code, at 2026-11-02T06:49:14Z: handed off implementation in PR #4234 (queue task 5405). The four malformed spellings now return a safe JSON 400; valid downloads, missing-object 404, and R2 failure propagation are covered. Local TypeScript suite: 89 passed. CI status will be checked next.
dezren39 commentedon Nov 2, 2026
agent-note:v1 — holder: released by repo-worker-code at 2026-11-02T06:50:07Z, reason: queue task 5405 completed as pr_opened; PR #4234 is open. Targeted TypeScript suite passed (89 tests); CI is not applicable on the PR (the only check skipped).