Skip to content

pm-stack(aggregator): a projector registry link with an unsafe-integer ref 500s /search and the project page — projector gate admits it, projectorLinks never validates, renderSearch + roadmap rows call refUrl unguarded #6211

Closed

Description

@dezren39

Summary

A projector registry link whose issue number is an unsafe integer breaks two aggregator pages at render. The chain has three mismatched gates:

  1. The projector admits it. POST /links validation (pm-stack/projector/src/server.ts:246) is a shape regex, /^[\w.-]+\/[\w.-]+#\d+$/. Its \d+ admits numbers parseRef rejects — exactly the anti-pattern model.ts:463-466 documents for the shared rule (pm-stack(aggregator): validateNewComment/validateNewAction accept ref keys parseRef rejects (GH_REF number unbounded) #4952). developing-today/code#99999999999999999999 (20 digits) passes with a 201.
  2. The aggregator trusts it. projectorLinks (pm-stack/aggregator/src/sources/live.ts:484-488) maps row.ref verbatim — no isValidRef filter — and enrichWithRegistryLinks (live.ts:496-507) merges those links into board items (live.ts:1164) and roadmap rows (live.ts:1198). Legacy/seeded rows in pmp_links bypass the POST gate entirely and take the same path.
  3. Two render sites call refUrl unguarded on those links. parseRef (model.ts:454-460) throws ref number is not a safe integer, so:
    • /search: renderSearch (render.ts:510) calls refUrl(ref) for every result ref not in the items map — a registry ref is unresolved by construction, so any search matching that board item or roadmap row 500s.
    • Project pages: renderProgramProject's roadmap rows (render.ts:6741) call refUrl(l.ref) for every link on the row, so one bad registry link on one roadmap row takes down that project's whole page.

Evidence (verified this run, laptop main 04f7869)

Probe ~/git/fleet/probe-search-ref/probe.ts (bun, real aggregator sources from the laptop checkout):

projector POST /links gate regex admits BAD: true
isValidRef(BAD): false | isValidRef(GOOD): true
search(board BAD): THREW: ref number is not a safe integer: developing-today/code#99999999999999999999
search(board GOOD control): OK len=19929 hasGoodLink=true
search(roadmap BAD): THREW: ref number is not a safe integer: developing-today/code#99999999999999999999
project(roadmap BAD): THREW: ref number is not a safe integer: developing-today/code#99999999999999999999
project(roadmap GOOD control): OK len=29872 hasGoodLink=true

BAD is developing-today/code#99999999999999999999 carried as a registry Link — the exact shape enrichWithRegistryLinks produces from a pmp_links row. GOOD is the same cases with #123.

Why it matters

Registry links are the cross-repo linking mechanism the board and roadmap are built around ("the registry is where a code task's link to an opencode PR lives", live.ts:492-495), and they are writable through an authenticated API whose only ref check disagrees with the consumer. One stored link — a typo'd or pasted oversized number is enough — permanently 500s /search for matching queries and the affected project page until someone deletes the row from the database by hand. Siblings already filed cover other producers of the same throw (lined-up task titles, #6210 board strip at render.ts:1506 — the same registry links also reach that strip; objectives #6174; reconciled overview #6205); this is the registry-link producer plus the two render sites none of them cover. The session-record path (render.ts:1912) already shows the house pattern for untrusted refs: try/catch and omit only the bad link.

Acceptance criteria

  • A registry link whose ref fails isValidRef never reaches a renderer: filter (with a logged/provenance-visible drop) in projectorLinks or enrichWithRegistryLinks, and/or align the projector's POST /links gate with parseRef semantics (safe-integer number) so the row cannot be stored in the first place. Both layers preferred — stored legacy rows must not stay lethal.
  • /search and project-page roadmap rendering survive a board item / roadmap row carrying such a link: the item renders, the bad link is omitted or shown unlinked, sibling good links still render as links.
  • Regression tests alongside the existing aggregator suites (search-view / program rendering) pinning: unsafe-integer registry ref on a board item, on a roadmap row, and a non-GitHub-shaped stored ref if the filter is the chosen layer.
  • Existing behavior unchanged for valid refs (controls above).

Related: #6210 (same links reach the board strip via titleRefs; registry links are a second producer), #6174, #6205, #3766 (project-config validator, same gate-disagreement shape, different gate), #2485 (session taskRefs, fixed with the try/catch pattern at render.ts:1912).

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
    on Nov 5, 2026
  2. developing-today commented on Nov 5, 2026

    @developing-today
    Contributor
  3. added
    source/creatorFiled by the new-issue-creator job
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    on Nov 5, 2026
  4. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  5. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 5, 2026
  6. 18 remaining items

  7. developing-today commented on Nov 5, 2026

    @developing-today
    Contributor

    🤖 Agent note · state: picked up

  8. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · adopted as a design phase

    • Agent: design architect (repo-design-architect-code), pass 55
    • Task: this issue is ADOPTED as Phase 2 of design A ref that cannot link is plain text — one ref contract for the aggregator (design) #6242, "A ref that cannot link is plain text — one ref contract for the aggregator" (linked as its sub-issue) — not a parallel filing. Its existing queue state is unchanged; no new packet was filed for it.
    • Expectation: triage records the phase structure instead of re-filing, re-scoping or splitting this slice away from the design. Its own acceptance stands, with its 'both layers preferred' resolved as the design's rules 1+2+4 in order: projectorLinks filters registry rows at consumption (legacy pmp_links rows disarmed), the projector POST /links gate aligns with isValidRef semantics (no local regex), and /search + roadmap rendering goes through the safe-link helper.
    • Design doc: files/designs/code-aggregator-ref-contract.md (keep-the-four-repos-healthy goal workspace).
  9. added a commit that references this issue on Nov 5, 2026
  10. developing-today commented on Nov 5, 2026

    @developing-today
    Contributor

    🤖 Agent note · state: done

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

Metadata

Metadata

Assignees

Labels

app/aggregatorPackage/app: aggregator (pm-stack in this repo)app/pm-stackpm-stack (Pad/Grist) in developing-today/codekind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobstatus/triagedTriaged and routed (see agent note)status:donetask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions