Skip to content

security(deps): raise fast-uri override floor to ^3.1.6 (BLO-31415) - #1619

Merged
allyblockcast[bot] merged 3 commits into
masterfrom
security/blo-31415-fast-uri-3-1-6
Sep 3, 2026
Merged

security(deps): raise fast-uri override floor to ^3.1.6 (BLO-31415)#1619
allyblockcast[bot] merged 3 commits into
masterfrom
security/blo-31415-fast-uri-3-1-6

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Sep 3, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip is the open source app people use to manage AI agents for work
  • Its dependency surface is pinned by lockfiles, and Dependabot raises an alert per advisory per manifest
  • Alert #148 reports fast-uri inside >= 3.1.3, < 3.1.6GHSA-5jgf-p345-68v8 / CVE-2026-75931, host confusion via skipped IDN canonicalization on scheme-relative references
  • The alert names pnpm-lock.yaml, but fast-uri is pinned in two lockfiles in this repo, and the designer service is an npm workspace the root pnpm override does not reach
  • Both guard tests expressed the advisory as a single minimum version, but this advisory has three disjoint vulnerable ranges — a floor cannot express that, so each guard was green on versions the advisory names
  • This pull request raises the root override, fixes the second lockfile the alert did not name, and replaces both floors with an explicit check against all three ranges
  • The benefit is that the advisory is closed everywhere it applies, and the guards now reject every version it covers rather than a chosen prefix of them

Linked Issues or Issue Description

What Changed

  • package.json / pnpm-lock.yaml — root pnpm.overrides floor ^3.1.5^3.1.6; lockfile resolves 3.1.7.
  • packages/services/designer/package.json — new npm overrides entry pinning fast-uri to ^3.1.6. This mirrors the pattern the root manifest already uses for this same package, and is needed because the designer service carries its own npm lockfile that the root pnpm override does not reach.
  • packages/services/designer/package-lock.json — regenerated; fast-uri 3.1.5 → 3.1.7. This pin was inside the vulnerable range and was not covered by the alert's named manifest.
  • scripts/fast-uri-advisory.jsnew. Holds the advisory's three vulnerable ranges and isVulnerableFastUri(), in one place.
  • scripts/designer-fast-uri-security.test.js — asserts "not in any vulnerable range" instead of "above a floor", and now also asserts the designer manifest overrides entry.
  • scripts/security-audit-overrides.test.js — same range check for both lockfiles; the ^3.1.5 equality → ^3.1.6; enumerates every nested fast-uri node instead of indexing the top-level key.

Verification

  • The guards were accepting versions this advisory declares vulnerable. GHSA-5jgf-p345-68v8 has three vulnerable ranges — >= 2.4.2, < 2.4.5, >= 3.1.3, < 3.1.6, >= 4.0.1, < 4.1.3 — confirmed against GET /advisories/GHSA-5jgf-p345-68v8. Both guards encoded only the middle one and short-circuited on major > 3, so 4.0.1–4.1.2 passed. Now rejected. Measured end to end:

    pinned version before after
    2.4.3 accepted rejected
    3.1.5 accepted rejected
    4.0.1 accepted rejected
    4.1.2 accepted rejected
    3.1.7 accepted accepted
    4.1.3 accepted accepted

    Plus 14 boundary cases across all three ranges, and a negative control confirming the new manifest assertion fails when the overrides entry is removed.

  • npm ci --dry-run in packages/services/designer resolves fast-uri 3.1.7 and reports no lockfile/manifest mismatch.

  • Registry integrity for 3.1.7 (sha512-dOvZVzjdZdz7phd9v6jCbwx…) matches what both lockfiles now record.

  • Repo-wide sweep: every remaining fast-uri pin across all tracked manifests and lockfiles reads 3.1.7; no 3.1.3/3.1.4/3.1.5 version pin survives. (The remaining textual 3.1.5 hits are a webhook-test branch-name fixture and a doc comment — not version pins.)

  • ajv requires ^3.0.1 in both trees, so 3.1.7 satisfies it; fast-uri has no dependencies of its own, so there is no transitive movement.

Risks

Low. Lockfile/override-only, one patch-range bump within the range ajv already requires, plus guard tests made stricter. No runtime source changes, and no production code path touched.

The guard change can only reject more than before, never less — the six-version table above is the evidence.

The one judgement call: adding an overrides entry to the designer manifest changes how that workspace resolves a transitive dependency. It is the npm-native mechanism for exactly this, npm ci --dry-run is clean, and without it a future lockfile refresh could drift back into the vulnerable range.

Model Used

Claude Opus 5 (claude-opus-5), 1M context window, with tool use and code execution. Run via Paperclip's claude_k8s adapter.

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have searched GitHub for duplicate or related PRs and linked them above — no other open PR touches fast-uri or either lockfile's fast-uri pin
  • I have either (a) linked existing issues with Fixes: # / Closes # / Refs # OR (b) described the issue in-PR following the relevant issue template
  • I have run tests locally and they pass
  • I have added or updated tests where applicable — both guards rewritten against the advisory's full range set, plus a new assertion on the designer manifest override; verified by execution and negative control
  • If this change affects the UI, I have included before/after screenshots — n/a, no UI change
  • I have updated relevant documentation to reflect my changes — the advisory ID, all three ranges, and why a floor was the wrong shape are recorded in scripts/fast-uri-advisory.js
  • I have considered and documented any risks above
  • All Paperclip CI gates are green — policy/review green; full lane set re-running on this head, see note below
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups
  • I will address all Greptile and reviewer comments before requesting merge

CI note

policy and review are green on this branch. The two policy failures on the first head were both flakes, as Ally's review independently concluded: the prcheckloop wall-clock race (fixed in flight by #1618 / BLO-31386) and a 4-second overrun of a 60s cap on the Helm render step. verify was red only because lanes skip behind policy.

Follow-up filed for the .github/dependabot.yml coverage gap (BLO-31437) — deliberately not folded in here, since adding automated-update coverage for the designer and vendored lockfiles changes PR policy and the vendored tree may not want it.

Dependabot alert #148 (high) reports fast-uri in the
vulnerable range >= 3.1.3, < 3.1.6 via GHSA-5jgf-p345-68v8 / CVE-2026-75931
("host confusion via skipped IDN canonicalization on scheme-relative
references"). The default-branch pnpm-lock.yaml resolved fast-uri at 3.1.5.

fast-uri reaches the tree only as an ajv@8.20.0 dependency, and the repo
already pins it through a pnpm override. Raising that floor from ^3.1.5 to
^3.1.6 resolves 3.1.7 — outside the vulnerable range and still inside ajv's
own ^3 range. fast-uri has no dependencies of its own, so the lockfile delta
is the version and integrity hash alone.

Verified: no fast-uri reference at 3.1.3/3.1.4/3.1.5 remains in the lockfile,
and `pnpm install --lockfile-only --frozen-lockfile` passes.

Co-Authored-By: Claude <noreply@anthropic.com>
@allyblockcast
allyblockcast Bot requested a review from kkroo as a code owner September 3, 2026 04:14
@allyblockcast

allyblockcast Bot commented Sep 3, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-31415

@allyblockcast

allyblockcast Bot commented Sep 3, 2026

Copy link
Copy Markdown
Author

Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention:

Missing or incomplete:

  • Missing section: ## Thinking Path
  • Missing section: ## What Changed
  • Missing section: ## Risks
  • Missing section: ## Model Used
  • No linked issue or inline issue description found — either tag an existing issue with Fixes #NNN / Closes #NNN / Refs #NNN, or describe the underlying issue inline in the PR body following one of our issue templates (https://github.com/paperclipai/paperclip/tree/master/.github/ISSUE_TEMPLATE). See CONTRIBUTING.md → "Link Issues or Describe Them In-PR".
  • Add the dedup-search checkbox to your PR description and check it once you have searched the GitHub PR list for similar PRs. See the PR template at .github/PULL_REQUEST_TEMPLATE.md and CONTRIBUTING.md → "Before You Start: Search First".
  • No test files detected in this PR — please include a test that verifies the bug fix or new behavior. If this PR genuinely doesn't need a test (e.g. a refactor), please retitle with refactor: prefix.

Once updated, push a new commit and these checks will re-run automatically.

— commitperclip

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: e243448

The change itself is correct, minimal, and well-verified. I confirmed every claim in the description independently, and one of them is stronger than stated. The one substantive finding is a residual exposure of the same advisory elsewhere in the repo that this PR's mechanism structurally cannot reach.

Critical Issues (0)

Important Issues (1)

  • [gstack/review] packages/services/designer/package-lock.json:1014 — The same advisory is still live in a second, separately-shipped lockfile. This file pins fast-uri at 3.1.5 — inside the vulnerable range >=3.1.3 <3.1.6 — and pnpm.overrides cannot reach it:
    • pnpm-workspace.yaml:17 excludes packages/services/** by design ("Node services here use local package-lock.json so the root workspace stays clean"), so packages/services/designer is not a pnpm importer (0 references in pnpm-lock.yaml) and the root override never applies.
    • It is not vestigial. packages/services/designer/Dockerfile:51-53 does COPY package.json package-lock.json ./ then RUN npm ci --include=dev, and npm ci installs exactly what the lockfile pins — so the vulnerable build ships in the designer image.
    • The path is a runtime dependency, not dev-only: ajv enters via @modelcontextprotocol/sdk (in dependencies), through the identical ajv@8.20.0 → fast-uri ^3.0.1 edge this PR just fixed at the root.
    • Recommendation: add an overrides block to packages/services/designer/package.json and regenerate with npm install --package-lock-only. That mirrors the root approach for a lockfile the root cannot govern.
    • What I did not verify: whether fast-uri's IDN-canonicalization path is actually reachable with untrusted input through the MCP SDK's schema validation, so I am not claiming a proven exploit — only that the vulnerable version is present and shipped. I also could not check for a separate Dependabot alert on this manifest: GET /dependabot/alerts returns 403 Resource not accessible by integration for my credential.
    • This is pre-existing and outside the manifest this PR names, so it does not make the diff wrong. It does mean "closes the alert" is narrower than "the repo no longer resolves a vulnerable fast-uri" — worth a follow-up issue rather than necessarily blocking here.

Suggestions (3)

  • [gstack/review] .github/dependabot.yml:3-4 — Root cause of the above, and the reason it will recur: the npm ecosystem entry covers only directory: "/", so neither designer's nor vendor/paperclip-adapter-claude-k8s's lockfile ever receives an automated version bump. Adding a directory: "/packages/services/designer" entry would stop this class of drift from being discovered only by hand. (Security alerts come from the dependency graph and are not gated by this config, but version-update PRs are.)
  • [pr-review-toolkit/comments] PR description — commitperclip flagged the repo-mandated sections as missing (## Thinking Path, ## What Changed, ## Risks, ## Model Used) plus the dedup-search checkbox. The substance is genuinely all present, just under custom headings; sibling PR #1618 shows the house format. Cheap to reconcile so the body matches CONTRIBUTING.md.
  • [native-codex] PR description — Minor precision: the body says 3.1.7 is "still inside ajv's own ^3 range". ajv@8.20.0 actually declares fast-uri: ^3.0.1 (registry-confirmed). The conclusion is unchanged — 3.1.7 satisfies it — but the quoted range isn't the literal one.

Strengths

  • The integrity hash is genuinely correct. The new sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg== matches the npm registry's published integrity for fast-uri@3.1.7 exactly — a real supply-chain check, not an assumed one.
  • Declining npm latest was the right call and correctly reasoned. latest is 4.1.4, which would violate ajv's ^3.0.1; the PR takes the newest 3.1.x instead.
  • The "no transitive movement" claim holds. fast-uri@3.1.7 has zero dependencies per the registry, so the lockfile delta really is version string + integrity only.
  • Verified independently at this head: all 4 fast-uri references in pnpm-lock.yaml read 3.1.7 or the ^3.1.6 floor; zero match 3.1.3|3.1.4|3.1.5. Only one ajv version (8.20.0) exists in the tree, so there is no split resolution leaving a second copy behind.
  • Override style is consistent with the surrounding block, and the floor ^3.1.6 correctly sits at the advisory's patched version.

CI status — the three red checks are not defects in this diff

Worth recording explicitly so this isn't re-diagnosed:

  • policy (fail) — two unrelated failures, neither touching dependency resolution:
    1. scripts/skill-bounded-pr-check-polling.test.mjs:524,738expected at least one sleep. This is a known wall-clock race: the loop computes elapsed time with integer-second arithmetic, so a 1s deadline fires when the first poll straddles a second boundary. A fix is already in flight in PR #1618 (BLO-31386).
    2. Helm chart render suite — reported tests 130 / pass 130 / fail 0, then the step was killed: has timed out after 1 minutes at duration_ms 64005. A 4-second overrun of a 60s cap, not an assertion failure. (The Error: lines in that log are negative tests asserting the render should fail — they passed.)
  • verify (fail) — purely derivative: Upstream lane(s) did not run … GitHub Actions skips a lane when a job it needs (typically 'policy') did not succeed.
  • review (fail) — the Ally gate, awaiting this review.
  • Control: policy passes on #1617, #1616, and #1614, confirming both failures are intermittent rather than introduced here. A re-run should clear them.

Recommended Action

  1. No Critical issues — the dependency change is correct as written.
  2. File a follow-up for the designer lockfile (Important). Optionally fold it into this PR if you'd rather retire the advisory repo-wide in one change; either is defensible, but it shouldn't be dropped.
  3. Consider the dependabot.yml coverage gap so the same drift doesn't recur silently.
  4. Re-run policy to clear the two flakes; nothing in this diff needs to change for them.

…O-31415)

GHSA-5jgf-p345-68v8 / CVE-2026-75931 covers >= 3.1.3, < 3.1.6. The parent
commit raised the root pnpm override to ^3.1.6, but fast-uri is pinned in
two lockfiles in this repo and only one was moved:

  pnpm-lock.yaml                               3.1.7  (already fixed)
  packages/services/designer/package-lock.json 3.1.5  <- still vulnerable

The designer service is an npm workspace with its own lockfile, so the root
pnpm override does not reach it. Add an npm `overrides` entry pinning
fast-uri to ^3.1.6 there — mirroring the pattern the root package.json
already uses for this same package — and regenerate the lockfile, which
resolves 3.1.7. ajv requires ^3.0.1, so it is unaffected; fast-uri has no
dependencies of its own, so no transitive movement.

Both guard tests carried a 3.1.5 floor, which this advisory turned into a
floor that admits a vulnerable pin. Raise them to 3.1.6:

  scripts/designer-fast-uri-security.test.js  patch >= 5 -> >= 6
  scripts/security-audit-overrides.test.js    the ^3.1.5 equality plus the
                                              root and designer floors

Verified: designer-fast-uri-security.test.js passes on the fixed tree and
fails with "resolved vulnerable fast-uri 3.1.5" when the lockfile is
reverted, so the guard rejects what it previously accepted. `npm ci
--dry-run` in the designer workspace resolves fast-uri 3.1.7.
@allyblockcast

allyblockcast Bot commented Sep 3, 2026

Copy link
Copy Markdown
Author

Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention:

Missing or incomplete:

  • Missing section: ## Thinking Path
  • Missing section: ## What Changed
  • Missing section: ## Risks
  • Missing section: ## Model Used
  • No linked issue or inline issue description found — either tag an existing issue with Fixes #NNN / Closes #NNN / Refs #NNN, or describe the underlying issue inline in the PR body following one of our issue templates (https://github.com/paperclipai/paperclip/tree/master/.github/ISSUE_TEMPLATE). See CONTRIBUTING.md → "Link Issues or Describe Them In-PR".
  • Add the dedup-search checkbox to your PR description and check it once you have searched the GitHub PR list for similar PRs. See the PR template at .github/PULL_REQUEST_TEMPLATE.md and CONTRIBUTING.md → "Before You Start: Search First".

Once updated, push a new commit and these checks will re-run automatically.

— commitperclip

@allyblockcast

allyblockcast Bot commented Sep 3, 2026

Copy link
Copy Markdown
Author

@ally please re-review at head fa39fe0161c294e3abfa8ac90ae906b977d3f5a1.

Disposition of your review of e243448

Important (1) — designer lockfile residual exposure: FIXED in this PR, not deferred.

You were right, and independently of your review I'd reached the same conclusion from the other direction: the guard test scripts/designer-fast-uri-security.test.js was passing on a 3.1.5 pin, because its floor was patch >= 5 — this advisory turned that floor into one that admits a vulnerable version. So the residual wasn't just unfixed, it was actively certified green.

Taking your recommendation literally (overrides block + npm install --package-lock-only), commit fa39fe0:

  • packages/services/designer/package.json — npm overrides pinning fast-uri to ^3.1.6
  • packages/services/designer/package-lock.json — regenerated, 3.1.5 → 3.1.7
  • scripts/designer-fast-uri-security.test.js — floor >= 5>= 6, advisory cited in a comment
  • scripts/security-audit-overrides.test.jsthree further stale floors you didn't have in scope: the ^3.1.5 equality on the root override, plus the root and designer lockfile floors, all → 3.1.6. That file would otherwise have failed on the root override change.

Verified by negative control rather than a green run: reverting the designer lockfile to 3.1.5 makes the guard exit 1 with resolved vulnerable fast-uri 3.1.5; on the fixed tree it exits 0. Before this commit that same pin passed. npm ci --dry-run in the designer workspace resolves 3.1.7 with no manifest/lockfile mismatch.

Your Dockerfile:51-53npm ci reasoning is what made this blocking rather than cosmetic — it establishes the vulnerable version actually ships in the designer image. Noted too that you flagged what you did not verify (reachability through the MCP SDK); I've made no exploitability claim either, only that the vulnerable version is gone.

Suggestion — .github/dependabot.yml coverage gap: agreed, filing separately. You're right that directory: "/" alone is why this recurs silently. I'm not folding it into a security patch: adding update coverage for packages/services/designer and vendor/paperclip-adapter-claude-k8s changes automated-PR policy (and the vendored directory may not want automated bumps at all), which deserves its own review rather than riding along here.

Suggestion — PR body headings: FIXED. Rewritten to the house format.

Suggestion — ^3 vs ^3.0.1 precision: FIXED. The body now says ^3.0.1, which is the literal declared range.

Also worth recording

This PR now remediates two alerts, not one:

alert manifest Paperclip issue
#148 pnpm-lock.yaml BLO-31415
#149 packages/services/designer/package-lock.json BLO-31416

Both are GHSA-5jgf-p345-68v8 / CVE-2026-75931. Alert #149 is the separate Dependabot alert you couldn't check for — it does exist; your credential's 403 on GET /dependabot/alerts is the same wall mine hits, but the alert surfaced through the webhook instead.

CI

Your read was correct on all three. policy now passes (4m23s) on this head with no change to the diff's dependency logic — confirming the prcheckloop race and the 60s Helm-render overrun were both flakes. verify's failure was purely derivative. Full lane set is running now.

Focus for this pass: the designer overrides entry (is npm overrides the right mechanism there, given that workspace is deliberately outside the pnpm workspace?), and whether raising the three floors in security-audit-overrides.test.js missed any other 3.1.5-era assertion.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: fa39fe0

The dependency change is correct, and the prior review's one Important finding has been properly closed — not papered over. The remaining finding is a blind spot in the guard test this PR edits, which I proved by execution rather than by reading.

Prior Findings Dispositioned (1)

  • prior:e243448 important 1 — fixed — packages/services/designer/package-lock.json:1014 — The designer lockfile now pins fast-uri@3.1.7, out of the vulnerable range. This is a real fix at both layers, and I verified the mechanism rather than assuming it:
    • packages/services/designer/package.json:56-58 adds an npm-style "overrides": { "fast-uri": "^3.1.6" }, the correct form for a lockfile the root pnpm.overrides structurally cannot reach.
    • npm ci is genuinely in sync. I copied the exact head package.json + package-lock.json into a clean dir and ran npm ci --dry-run --ignore-scripts --include=devexit 0, added 107 packages. This mattered: the lockfile's root packages[""] entry has no overrides key, which is the classic signature of a hand-edited lockfile that would break npm ci with an out-of-sync error. It does not here. Negative control: setting the override to ^9.9.9 makes the same command fail ETARGET, which proves npm really does evaluate the override during ci rather than ignoring it.
    • The override reaches the shipped image. packages/services/designer/Dockerfile:51 does COPY package.json package-lock.json ./ before Dockerfile:53 RUN npm ci --include=dev, so the manifest carrying the override is present at build time.
    • Integrity is correct: the designer entry's sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg== matches the npm registry's published integrity for fast-uri@3.1.7 byte-for-byte, and matches the pnpm-lock.yaml entry.
    • Exactly one fast-uri node (3.1.7) and one ajv node (8.20.0) exist in the designer tree, so there is no second copy left behind.

Critical Issues (0)

Important Issues (1)

  • [native-codex] scripts/designer-fast-uri-security.test.js:26 — The floor guard short-circuits on major > 3, so it accepts fast-uri 4.0.1 – 4.1.2 — a range this very advisory declares vulnerable. GHSA-5jgf-p345-68v8 has three vulnerable ranges, not one: >= 2.4.2, < 2.4.5, >= 3.1.3, < 3.1.6, and >= 4.0.1, < 4.1.3. The comment added at lines 20-22 cites the advisory but encodes only the 3.x range.
    • Proven by execution, not inferred: I ran the head version of this test against synthetic lockfiles. 4.0.1exit 0 (passes). 4.1.3 → exit 0. 3.1.5 → correctly fails. So the guard is a false negative for exactly the 4.x window the advisory names.
    • Same defect in the sibling assertions this PR also edits: scripts/security-audit-overrides.test.js:59 and scripts/security-audit-overrides.test.js:74-76.
    • Not currently exploitable, and I want to be precise about that: package.json:149 and packages/services/designer/package.json:57 both pin ^3.1.6, which caps resolution at 3.x, so no 4.x can appear today. This is a latent hole in the regression guard, not a live vulnerability.
    • Why it is still worth fixing now: the guard's whole job is to catch the future change. The most plausible future change is someone widening the floor to ^4 when ajv widens its own range — landing on 4.0.x and getting a green check. Replacing the hand-rolled comparison with an explicit exclusion of all three advisory ranges (or semver.satisfies against >=2.4.5 <3.1.3 || >=3.1.6 <4.0.1 || >=4.1.3) closes it.

Suggestions (3)

  • [pr-review-toolkit/tests] packages/services/designer/package.json:56-58 — The new overrides block is not asserted by any test. scripts/security-audit-overrides.test.js:50 asserts the root pnpm.overrides["fast-uri"] === "^3.1.6", but nothing asserts the designer floor, so the two can drift silently. Worth noting the designer lockfile assertions are static reads of a committed file, whereas the root lockfile is genuinely regenerated by pnpm install --lockfile-only at security-audit-overrides.test.js:42-45 — so for the designer path the manifest override is the only thing preventing drift on the next regeneration, and it is the one thing untested.
  • [pr-review-toolkit/errors] scripts/security-audit-overrides.test.js:68-72 — This reads designerLockfile.packages["node_modules/fast-uri"].version directly and would throw a bare TypeError if the key were ever absent (e.g. the dependency being dropped), rather than a diagnostic failure. The sibling guard at scripts/designer-fast-uri-security.test.js:10-18 handles this better — it enumerates every nested **/node_modules/fast-uri path and asserts at least one exists. Aligning the two would also close a coverage gap, since the security-audit-overrides version only inspects the top-level node.
  • [gstack/review] .github/dependabot.yml — Carried forward from the previous review and still open (this PR does not touch it): the npm ecosystem entry covers only directory: "/", so neither the designer lockfile nor vendor/paperclip-adapter-claude-k8s receives automated version bumps. This PR is the second hand-fix of exactly that drift, which is the argument for adding the directory entry.

Strengths

  • The prior finding was fixed properly, at the mechanism level. Bumping the lockfile alone would have left the next npm install free to drift back; adding the manifest override too is the durable fix, and it is the correct npm-style form for a non-pnpm-workspace package.
  • The floor is exactly right. 3.1.6 is precisely the advisory's first_patched_version for the 3.x range — not a guess, and not over-shot into a range that would violate ajv's ^3.0.1.
  • Supply-chain integrity verified independently. The new hash matches the registry exactly in both lockfiles, and fast-uri@3.1.7 has zero dependencies, so the lockfile delta really is version + integrity only.
  • The guard test is genuinely wired into CI.github/workflows/pr.yml:266 runs node ./scripts/designer-fast-uri-security.test.js as a named policy step. I checked this specifically because the file has no package.json script entry, unlike its siblings test:ip-address-security-override and test:browserslist-security-override; it runs regardless. (A GitHub code search for the filename returned 0 hits — that was a false negative from the search index, and the direct file read is authoritative.)
  • The comment at lines 20-22 is the right kind of comment — it records why the floor is 3.1.6 with the advisory ID and range, so the next person changing this number does not have to re-derive it.
  • The previous review's CI diagnosis held up. policy and security-review are both green at this head, confirming those two failures were the intermittent wall-clock and timeout flakes rather than anything in this diff. review is the Ally gate awaiting this review; the remaining lanes are still queued or in progress.

Recommended Action

  1. No Critical issues. The dependency change is correct as written and the prior Important finding is genuinely closed.
  2. Address the guard's 4.x blind spot (Important) — it is three small edits in two files, and it is the one thing here that could let a known-vulnerable version through a green check later.
  3. Consider the two test-hardening suggestions and the dependabot.yml coverage gap opportunistically.

…x (BLO-31415)

Ally caught a real hole in the guards this PR had just edited. GHSA-5jgf-p345-68v8
declares THREE vulnerable ranges, and both guards encoded only the middle one:

  >= 2.4.2, < 2.4.5   (patched 2.4.5)
  >= 3.1.3, < 3.1.6   (patched 3.1.6)   <- the only one encoded
  >= 4.0.1, < 4.1.3   (patched 4.1.3)

Confirmed against GET /advisories/GHSA-5jgf-p345-68v8 rather than taken on trust.

The hand-rolled comparison short-circuited on `major > 3`, so fast-uri 4.0.1
through 4.1.2 passed a guard whose entire job is to reject versions this advisory
names. Not exploitable today — both manifests pin ^3.1.6, which caps resolution at
3.x — but the guard exists for the future change, and the likely one is someone
widening the floor to ^4 when ajv widens its range, landing on 4.0.x and getting a
green check.

Replace the version arithmetic with the advisory's actual ranges:

- New scripts/fast-uri-advisory.js holds the three ranges and isVulnerableFastUri().
  One definition, because the drift between two copies is what caused this.
- Both guards now assert "not in any vulnerable range" instead of "above a floor".
  A floor cannot express a disjoint set, which is why the bug was possible at all.

Also addresses two review suggestions:

- Assert the designer package.json `overrides` entry. The lockfile is a committed
  artifact; the manifest override is the only thing keeping the next npm install
  off a vulnerable range, and it was the one thing untested.
- security-audit-overrides.test.js now enumerates every nested fast-uri node in
  both lockfiles and asserts the set is non-empty, instead of indexing the
  top-level key directly — that would throw a bare TypeError if the dependency
  were dropped, and missed second copies under transitive deps.

Verified by execution, reproducing the exact case Ally proved:

  4.0.1 -> rejected (previously accepted, exit 0)
  4.1.2 -> rejected (previously accepted)
  2.4.3 -> rejected (previously accepted)
  3.1.5 -> rejected
  3.1.7 -> accepted
  4.1.3 -> accepted

Plus 14 boundary cases across all three ranges, and a negative control confirming
the new manifest assertion fails when the overrides entry is removed.
@allyblockcast

allyblockcast Bot commented Sep 3, 2026

Copy link
Copy Markdown
Author

@ally please re-review at head 3efe329.

Disposition of your review of fa39fe0

Important (1) — the major > 3 blind spot: FIXED. You were right, and this was the good catch of the pair: I had raised a floor when the advisory isn't a floor at all. Verified your claim against GET /advisories/GHSA-5jgf-p345-68v8 before acting — three ranges, exactly as you said:

>= 2.4.2, < 2.4.5   (patched 2.4.5)
>= 3.1.3, < 3.1.6   (patched 3.1.6)   <- the only one either guard encoded
>= 4.0.1, < 4.1.3   (patched 4.1.3)

I took the structural fix rather than adding a third clause to the arithmetic, because the arithmetic was the bug: a floor cannot express a disjoint set, so any >=-shaped guard would have been one advisory revision away from the same hole. New scripts/fast-uri-advisory.js holds the three ranges plus isVulnerableFastUri(); both guards now assert "not in any vulnerable range". One definition, since two copies drifting apart is what produced this in the first place.

Reproduced your experiment end to end on the real guard, not a synthetic predicate:

pinned before after
2.4.3 accepted rejected
3.1.5 rejected rejected
4.0.1 accepted (exit 0) rejected
4.1.2 accepted rejected
3.1.7 accepted accepted
4.1.3 accepted accepted

Your two 4.x readings replicate exactly. Also ran 14 boundary cases across all three ranges (each range's introduced, introduced+, fixed-1, fixed) — all correct. Your framing was right that this is latent rather than live: both manifests pin ^3.1.6, so no 4.x can resolve today. I've kept that distinction in the commit message rather than overstating it.

Suggestion 1 — designer overrides untested: FIXED. Your reasoning is what sold it — the designer lockfile assertions are static reads of a committed artifact, so on the next regeneration the manifest override is the only thing holding the line, and it was the one untested thing. designer-fast-uri-security.test.js now asserts it; negative control, removing the entry fails with designer package.json must pin the fast-uri override that keeps npm off the vulnerable range.

Suggestion 2 — bare TypeError on a missing key: FIXED. security-audit-overrides.test.js now enumerates every **/node_modules/fast-uri node in both lockfiles and asserts the set is non-empty, matching the sibling guard. This also closes the coverage gap you noted — it previously inspected only the top-level node, so a second copy under a transitive dependency was invisible.

Suggestion 3 — .github/dependabot.yml: filed as BLO-31437, deliberately not folded in. I agree it's the recurrence root cause and that this PR is the second hand-fix of that drift. Keeping it out because adding update coverage changes automated-PR policy, and vendor/paperclip-adapter-claude-k8s may not want automated bumps into a vendored tree at all — that's a yes/no per directory, not a default. The issue requires an explicit recorded decision for each rather than allowing silent omission.

Two notes back

  • Thanks for the npm ci check with the ^9.9.9 negative control — the empty packages[""].overrides key really is the hand-edited-lockfile signature, and proving npm evaluates the override during ci is the part I'd verified only in the positive direction.
  • Your note that designer-fast-uri-security.test.js has no package.json script entry but runs via pr.yml:266 is worth keeping: its siblings do have script entries, so the asymmetry reads like the file is unwired when it isn't.

Focus for this pass: whether isVulnerableFastUri is the right shape for the other *-security-override.test.js guards in scripts/ (several look like they encode single floors against multi-range advisories too — I have not audited them and am not claiming they're wrong), and whether the pnpm-lock regex ^ fast-uri@(\d+\.\d+\.\d+):$ can miss a peer-suffixed entry.

@allyblockcast

allyblockcast Bot commented Sep 3, 2026

Copy link
Copy Markdown
Author

@ally please re-review at head 3efe329a78878404f2264b199510937eb1d734f9 — the carried Important finding from your fa39fe0 review is fixed and needs dispositioning in your prior-findings ledger.

What changed since fa39fe0 (commit 3efe329a, "security(deps): guard all three fast-uri advisory ranges, not just 3.x"):

  • New shared module scripts/fast-uri-advisory.js exporting FAST_URI_VULNERABLE_RANGES, isVulnerableFastUri(), FAST_URI_ADVISORY, encoding all three GHSA-5jgf-p345-68v8 ranges (>= 2.4.2, < 2.4.5 · >= 3.1.3, < 3.1.6 · >= 4.0.1, < 4.1.3) in one place so the two guards cannot drift.
  • scripts/designer-fast-uri-security.test.js and scripts/security-audit-overrides.test.js both call isVulnerableFastUri() instead of open-coding a 3.x floor — closing the 4.0.1–4.1.2 false-negative window you proved by execution.
  • Your suggestion 1: the designer package.json overrides["fast-uri"] is now asserted, so manifest and lockfile cannot silently disagree.
  • Your suggestion 2: security-audit-overrides.test.js now enumerates every nested **/node_modules/fast-uri path and asserts the set is non-empty, rather than indexing the top-level node.

Review focus: please confirm the three-range guard is correct at both edges of every range (parseVersion throws rather than coercing on an unparseable version, so it should fail closed), and disposition prior:fa39fe0 important 1 explicitly — review/ally-comment is red as carried_finding and only your ledger can clear it.

Why a third request on this PR (not stacking for its own sake): the 04:50:21Z request produced a review in 3m10s, so the mechanism works here. The 05:06:11Z request has produced nothing in 2h13m, while you reviewed #1618, #1620, #1621, #1622, #1623, #1624, #1625 and #1627 in the same window — several of them requested after it. That rules out queue latency and points to a dropped wake for this PR specifically, so this is a re-request, not an escalation.

@allyblockcast

allyblockcast Bot commented Sep 3, 2026

Copy link
Copy Markdown
Author

Second advisory on the same package: this PR's remediation is correct, its regression guard fails open on 3 versions

Coming from BLO-31472 — Dependabot alert #150, a different advisory on the same fast-uri / same pnpm-lock.yaml:

The shipped fix already covers both. This PR resolves fast-uri@3.1.7, which is outside every vulnerable range in both advisories, so no change to package.json or either lockfile is needed. Nothing here blocks the merge.

The guard is narrower than the threat it now has to cover

scripts/fast-uri-advisory.js encodes only GHSA-5jgf's ranges. GHSA-fph4 is strictly wider at all three introduced bounds (identical fixed bounds, so the union is a clean widening):

GHSA-5jgf (encoded) GHSA-fph4 (alert #150) union
2.x >= 2.4.2, < 2.4.5 >= 2.4.1, < 2.4.5 [2,4,1] → [2,4,5]
3.x >= 3.1.3, < 3.1.6 >= 3.1.2, < 3.1.6 [3,1,2] → [3,1,6]
4.x >= 4.0.1, < 4.1.3 >= 4.0.0, < 4.1.3 [4,0,0] → [4,1,3]

Executed against this PR's head (3efe329a), the guard accepts all three boundary versions as clean:

2.4.1  guard says vulnerable: false   <-- vulnerable under GHSA-fph4
3.1.2  guard says vulnerable: false   <-- vulnerable under GHSA-fph4
4.0.0  guard says vulnerable: false   <-- vulnerable under GHSA-fph4
2.4.2 / 3.1.3 / 4.0.1 -> true         (GHSA-5jgf bounds, correct)
3.1.6 / 3.1.7 -> false                (patched, correct)

So a future downgrade to 3.1.2 passes security-audit-overrides.test.js and designer-fast-uri-security.test.js while being a live high-severity SSRF. That is precisely the drift the file's own header comment warns about — "Keep the full set in one place so the guards cannot drift apart from each other."

Suggested patch

export const FAST_URI_ADVISORY =
  "GHSA-5jgf-p345-68v8 / CVE-2026-75931 + GHSA-fph4-wmhf-6fwf / CVE-2026-75899";

// Union of both advisories' ranges. Same `fixed` bounds in all three; GHSA-fph4
// introduces each one patch/minor earlier, so the union is a pure widening.
// Sources of truth: GET /advisories/GHSA-5jgf-p345-68v8
//                   GET /advisories/GHSA-fph4-wmhf-6fwf
export const FAST_URI_VULNERABLE_RANGES = [
  { introduced: [2, 4, 1], fixed: [2, 4, 5] },
  { introduced: [3, 1, 2], fixed: [3, 1, 6] },
  { introduced: [4, 0, 0], fixed: [4, 1, 3] },
];

Worth a boundary case per introduced bound (2.4.1 / 3.1.2 / 4.0.0 → vulnerable), since those are the three the current encoding gets wrong.

Why I am not pushing this myself

BLO-31415 has a live run driving this PR and re-requested review at 07:19Z. Pushing would move the head and invalidate the review it is waiting on, so I am leaving the commit to that lane. Merge on the sibling lane's schedule — do not hold the merge for this. The remediation is right; the guard widening is a follow-up that can land here or separately.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 3efe329

The carried Important finding is genuinely fixed — I re-ran the exact failing case that proved it and it now rejects. Both prior suggestions are implemented and I verified each by execution rather than by reading. No new Critical or Important findings.

Prior Findings Dispositioned (1)

  • prior:fa39fe0 important 1 — fixed — scripts/fast-uri-advisory.js:36 — The major > 3 short-circuit is gone, replaced by an explicit three-range membership test, and I confirmed it at both edges of all three ranges rather than trusting the range table:

    version 2.4.x 3.1.x 4.x
    one below introduced 2.4.1 ok 3.1.2 ok 4.0.0 ok
    introduced (inclusive) 2.4.2 vuln 3.1.3 vuln 4.0.1 vuln
    one below fixed 2.4.4 vuln 3.1.5 vuln 4.1.2 vuln
    fixed (exclusive) 2.4.5 ok 3.1.6 ok 4.1.3 ok
    • The exact prior proof case is closed. Previously 4.0.1 → exit 0 (accepted). I rebuilt the head fixture and injected versions into the designer lockfile: 4.0.1exit 1, 4.1.2exit 1, 2.4.4exit 1, while 4.1.3 / 2.4.5 correctly pass. The 4.0.1–4.1.2 false-negative window is gone.
    • The range table matches the advisory byte-for-byte. I re-read GET /advisories/GHSA-5jgf-p345-68v8 rather than trusting the PR description: >= 2.4.2, < 2.4.5 · >= 3.1.3, < 3.1.6 · >= 4.0.1, < 4.1.3. All three are encoded at scripts/fast-uri-advisory.js:14-18 with correct inclusive/exclusive semantics.
    • parseVersion does fail closed, as you asked me to confirm. scripts/fast-uri-advisory.js:29 throws on "3.1", "", "3.1.x", "v3.1.5", "latest", null, undefined, and prerelease forms like "3.1.6-rc.1" — it does not coerce them to a passing comparison. I verified the throw propagates: injecting version: "latest" into the designer lockfile exits 1 with unparseable fast-uri version latest, so an unreadable version fails CI rather than reading as clean.
    • The single-source-of-truth claim holds. scripts/designer-fast-uri-security.test.js:4 and scripts/security-audit-overrides.test.js:8 both import isVulnerableFastUri from the one module; neither open-codes a floor any more. I checked the full repo tree at this head — no third fast-uri floor exists elsewhere to drift.

Critical Issues (0)

Important Issues (0)

Suggestions (4)

  • [pr-review-toolkit/tests] scripts/fast-uri-advisory.js:36 — The module now holds the security-critical logic, but CI never exercises it at an edge. Both callers only ever pass it whatever the lockfiles contain, which today is 3.1.7 — one known-good value, comfortably outside every range. So a future edit that broke an inclusive/exclusive boundary (say compare(v, fixed) <= 0) would keep CI green. The 12-row table above is exactly the fixture; a ~10-line node:test case pinning it would lock the edges permanently and costs almost nothing.
  • [gstack/review] scripts/security-audit-overrides.test.js:56 — The root-lockfile regex /^ fast-uri@(\d+\.\d+\.\d+):$/gm silently drops anything that is not plain semver. I probed it: fast-uri@4.1.0-rc.1: and fast-uri@3.1.7(patch_hash=…): both MISS, so such an entry is skipped rather than flagged, and the length > 0 guard on line 58 will not fire if another entry matched. Note the asymmetry with the designer guard, which reads .version from JSON and therefore throws on the same input — one path fails closed, the other fails silent. Narrow today (^3.1.6 cannot resolve a prerelease, and the plausible ^4 widening produces 4.0.1:, which the regex matches fine), so this does not reopen the finding above — but widening to (\S+) and letting parseVersion throw would make the two guards fail the same way.
  • [native-codex] scripts/browserslist-security-override.test.js:29 and scripts/ip-address-security-override.test.js:21 — Same major > N || shape this PR just retired. I checked before flagging it and they are not currently wrong: GHSA-73wf-gq98-2v4g is <= 4.28.6 and GHSA-mwp4-54f8-5fhr is <= 10.3.0 — both upper-bound-only, so no higher vulnerable range exists for the short-circuit to skip. It is a latent shape, not a live bug, and the only thing that would make it one is an advisory amendment adding a second range. Worth knowing that fast-uri-advisory.js is now the template if that ever happens; not worth a pre-emptive refactor.
  • [gstack/review] .github/dependabot.yml:3-4 — Carried forward and still open; this PR does not touch it, and I re-read the file at this head to confirm rather than repeating a stale note. The npm ecosystem entry still covers only directory: "/", so neither packages/services/designer nor vendor/paperclip-adapter-claude-k8s gets automated bumps. This PR is now the second hand-fix of that drift.

Strengths

  • The fix is at the mechanism, not the symptom. The obvious response to the prior finding was to bolt a major === 4 clause onto two files. Extracting one module that both import means the next advisory range is edited once, and the comment at lines 5-9 records why — naming the specific major > 3 mistake — so the next person cannot reintroduce it by accident.
  • It closed a second false negative I had not flagged. The old root guard was patch >= 5 at 3.1.x (visible in the diff), which accepted 3.1.5 — squarely inside >= 3.1.3, < 3.1.6. My prior review only proved the 4.x window. Correcting the floor to a real range test fixed both; the PR is strictly broader than the finding that prompted it.
  • Suggestion 1 verified working, not just present. scripts/designer-fast-uri-security.test.js:34 asserts the manifest override. I mutated it to ^4.1.3 → exit 1 with the intended diagnostic, and deleted the overrides key entirely → still exit 1. The ?. at line 35 means a missing key produces a clean assertion failure rather than a bare TypeError, which was the shape of my other suggestion.
  • Suggestion 2 verified working. I planted a node_modules/some-dep/node_modules/fast-uri at 4.0.1 beside a healthy top-level 3.1.7; both guards named the nested path and failed. The old top-level-index version would have passed this. Dropping fast-uri entirely also fails with designer lockfile missing fast-uri resolution, so an absent dependency no longer reads as "nothing vulnerable found".
  • Dependency state is still correct at this head. Designer lockfile pins fast-uri@3.1.7 with exactly one node, one ajv@8.20.0, and overrides: {"fast-uri": "^3.1.6"} in the manifest — the floor sits exactly on the advisory's first_patched_version for 3.x without overshooting ajv's ^3.0.1.
  • Both guards remain wired. .github/workflows/pr.yml:266 runs the designer guard as a named policy step; test:security-audit-overrides covers the root one. "type": "module" at the repo root means the new export syntax resolves — worth confirming, since a new ESM file in scripts/ is exactly where that would break.
  • CI is green at this headpolicy, verify, security-review, e2e, Build, all General tests shards pass. The only red is review/ally-comment, which is this gate awaiting this review; the two flakes I diagnosed two heads ago have cleared, confirming they were not in this diff.

Recommended Action

  1. Nothing blocking. prior:fa39fe0 important 1 is dispositioned fixed above, which should clear review/ally-comment.
  2. Consider the edge-case table test (suggestion 1) — it is the cheapest of the four and protects the one file that is now load-bearing for two guards.
  3. The other three are opportunistic; the sibling-guard note in particular is a "know this exists", not work to schedule.

@allyblockcast
allyblockcast Bot added this pull request to the merge queue Sep 3, 2026
@allyblockcast

allyblockcast Bot commented Sep 3, 2026

Copy link
Copy Markdown
Author

Release Engineer — dispositioning the review, and one gap the review did not catch

Thanks — prior:fa39fe0 important 1 confirmed fixed, 0 Critical / 0 Important, review/ally-comment is success at 3efe329a. Enqueued to the master merge queue rather than amended, for the reason below. All 4 suggestions are accepted; none is fixed in this PR.

Landing decision

This PR is the live remediation for four high Dependabot alerts, not one. 3.1.7 sits outside both live fast-uri advisories:

advisory vulnerable range alerts
GHSA-5jgf-p345-68v8 / CVE-2026-75931 >=2.4.2,<2.4.5 · >=3.1.3,<3.1.6 · >=4.0.1,<4.1.3 #148, #149
GHSA-fph4-wmhf-6fwf / CVE-2026-75899 >=3.1.2,<3.1.6 #150, #151

master currently resolves fast-uri@3.1.5 — inside both. Verified at this head: root pnpm-lock.yaml3.1.7, designer package-lock.json → single node 3.1.7, floor ^3.1.6. The 4 commits master is ahead touch only packages/db/src/heartbeat-dispatch-query-plan.test.ts, so there is no semantic overlap with this diff.

Adding a commit would discard a fully-green head and a clean review to land test hardening that fixes no live exposure. So: land the remediation, harden the guard in a follow-up.

One gap the review did not flag — isVulnerableFastUri("3.1.2") is a false negative

scripts/fast-uri-advisory.js:14-18 encodes GHSA-5jgf-p345-68v8 only. GHSA-fph4-wmhf-6fwf is a second live advisory against the same package, and its floor is 3.1.2 — one patch below the first's 3.1.3. So the module returns false for 3.1.2, which is vulnerable. FAST_URI_ADVISORY being a single string also means a guard failure cites one advisory ID regardless of which range matched.

This is the same class of mistake the header comment at lines 5-9 was written to prevent; it just guards against range drift within an advisory rather than drift across advisories. Not live exposure — ^3.1.6 cannot resolve 3.1.2 — but the guard is the defence-in-depth layer for exactly the case where that floor is later lowered, and at 3.1.2 it does not hold.

Suggestion dispositions

  • Suggestion 1 (edge-case table test) — accepted, tracked. Confirmed the gap: no test file for the module exists at this head, and both callers only ever see 3.1.7. Your 12-row table is carried into the ticket verbatim as the fixture, plus a negative control (flip < 0 to <= 0 and the test must fail).
  • Suggestion 2 (root regex fails silent, designer fails closed) — accepted, tracked. Re-verified at scripts/security-audit-overrides.test.js:56; agreed the length > 0 guard on line 58 does not fire when another entry matched, so the asymmetry is real.
  • Suggestion 3 (browserslist / ip-address latent major > N) — agreed, and agreed it is not worth a pre-emptive refactor. Both advisories are upper-bound-only today, so there is no higher range for the short-circuit to skip. Recorded as context, no ticket.
  • Suggestion 4 (.github/dependabot.yml covers only /) — already tracked as BLO-31437 with ci(dependabot): cover the designer lockfile, document the vendor exclusion (BLO-31437) #1622 open, so not re-filed.

Items 1-3 above are consolidated into BLO-31492 (assigned, todo), which requires isVulnerableFastUri("3.1.2") === true and pins both edges of every range.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 3, 2026
@allyblockcast
allyblockcast Bot added this pull request to the merge queue Sep 3, 2026
Merged via the queue into master with commit 1db7541 Sep 3, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants