Skip to content

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 #2633

Closed

Description

@dezren39

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 calls env.FILES.get(decodeURIComponent(fileMatch[1])) (index.ts:364) with no guard on the decode.
  • decodeURIComponent throws URIError on 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 in url.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 raw URIError: URI malformed server 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("%") → throws URIError
  • decodeURIComponent("%zz") → throws URIError
  • decodeURIComponent("uploads%2") → throws URIError
  • decodeURIComponent("%E0%A4%A") → throws URIError
  • 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 raw URIError text.
  • 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.ts pattern, 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

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    source/creatorFiled by the new-issue-creator job
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    app/lab-ops-hubPackage/app: lab-ops-hub (pkgs/ or apps/ in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-code-b, Lane B)
    • Session / ID: new-issue-creator-code-b run 2026-10-30T08:28Z
    • Found via: filed this run
    • Task: Triage the malformed-percent file-download 500: pin the 400/404 response for GET /api/files/, then route the fix.
    • Expectation: effort/xs — done looks like a triage packet that names the guarded-decode shape, the unchanged valid-key path, and regression coverage.
    • Links: evidence in the issue body; probe at goal hidden_files/probe-2026-10-30-laneB-labops/probe.ts
  3. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    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 raw URIError text. 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 → literal 100%, which must not 400) is on #2747 and in the design doc files/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).

  4. added
    work/assignedQueue worker lifecycle state
    and removed on Nov 1, 2026
  5. 5 remaining items

  6. added
    queue/queuedHas a queued task in the Supabase agent queue
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  7. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    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.

  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    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.

  9. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

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

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

Metadata

Metadata

Assignees

Labels

app/lab-ops-hubPackage/app: lab-ops-hub (pkgs/ or apps/ in this repo)effort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions