Skip to content

fix(deps): take the fix for the fifteen OSV advisories blocking every PR - #17029

Merged
os-trump merged 3 commits into
mainfrom
claude/issue-16999-osv-advisory-bumps
Sep 9, 2026
Merged

fix(deps): take the fix for the fifteen OSV advisories blocking every PR#17029
os-trump merged 3 commits into
mainfrom
claude/issue-16999-osv-advisory-bumps

Conversation

@os-trump

@os-trump os-trump commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16999

Clause-②: no — re-derived from the delivered diff, not inherited. The diff moves dependency versions and two pnpm-workspace.yaml override targets. It adds no exported symbol, no key on any published payload, and changes no accept/reject behaviour of ours; no file under packages/spec/src/ is touched. Two published manifests change a declared dependency RANGE (@objectstack/plugin-emailnodemailer, @objectstack/plugin-hono-serverhono), which is what the changeset grades patch — a dependency range is not our published surface. Nothing here forced a change to our own API, so there is nothing to stop and report on that axis.

What was failing, and why it was nobody's PR

Validate Package DependenciesAudit dependencies for known vulnerabilities (OSV-Scanner) exited 1 on 15 advisories across 7 packages (2 Critical, 2 High, 11 Medium). The card established that no lockfile moved; this PR re-confirmed it independently before touching anything: origin/main's pnpm-lock.yaml at the time this branch was cut was byte-identical (sha256 704c58a3e3212f67…) to the tree of the PR whose CI run produced the red. Same scanner input, opposite verdict, ~22h apart — the advisory database moved, not the repository.

Every one of the fifteen names a published fix version, so this is the take-the-fix path osv-scanner.toml's own header describes. No advisory is suppressed, allow-listed or ignored. osv-scanner.toml keeps its zero exemptions and is untouched, as is .github/workflows/validate-deps.yml.

Scanner verdicts — measured, on real CI runs

No OSV scanner is reachable from the dev container (api.osv.dev and the google/osv-scanner release asset are both refused by the egress proxy — CONNECT 403, reported rather than routed around, and there is no osv-scanner package on npm). So every verdict below is a real validate-deps run, dispatched via workflow_dispatch on the same workflow the gate uses.

tree run scanner step job
origin/main @ 8d4690b8f (current, post-#16996) 34301431270 failureTotal 7 packages affected by 15 known vulnerabilities (2 Critical, 2 High, 11 Medium, 0 Low, 0 Unknown), Exit code: 1 failure
this branch @ 2b856a472 (the head this PR ships) 34302074445 success success

The origin/main run is the "before" in its own right — not a stand-in. It scanned the current tip and reported the identical fifteen rows, so this PR clears all 15 and leaves none behind. Nothing was un-clearable: every advisory had a fix version and every fix version was takeable.

What moved

package before after fix version how
next 16.3.1 16.3.3 16.3.3 exact pin in the private apps/docs
sharp 0.35.3 0.35.4 0.35.4 override target ^0.35.0^0.35.4
nodemailer 9.0.5 9.1.1 9.1.0 / 9.1.1 @objectstack/plugin-email declares ^9.1.1
hono 4.12.34 and 4.13.2 4.13.7 (one copy) 4.13.5 override target ^4.12.34^4.13.5, declarations in lockstep
vitest 4.1.10 4.1.11 4.1.11 declared range ^4.1.10^4.1.11 workspace-wide
@vitest/mocker 4.1.10 4.1.11 4.1.11 transitive, follows vitest

next took 16.3.3, the version the advisories name, not the newer 16.3.4 — the fix line, minimally. nodemailer took ^9.1.1 because the four advisories split across two fix lines (9.1.0 and 9.1.1) and one floor has to cover both; the 10.x major is deliberately not taken. hono resolves to 4.13.7 because the override target is a floating ^4.13.5, which is the shape pnpm-workspace.yaml's header mandates for an OSV floor.

The hono duplicate — resolved by moving both, not one

hono was flagged at two resolved versions and the card is right that clearing one is not clearing the gate. Both are gone, and the override lift is what did it:

  • 4.13.2 was our own three declarations, floated up by the old ^4.12.34 target.
  • 4.12.34 was pulled by @modelcontextprotocol/sdk. It sat exactly on the old target's floor, so it already satisfied ^4.12.34 and lockfile inertia never re-resolved it — which is precisely why a duplicate existed at all.

Lifting the target to ^4.13.5 excludes 4.12.34, forces both edges to re-resolve, and the tree deduplicates onto a single hono@4.13.7. Measured after: one hono tuple in the lockfile, down from two. @hono/node-server peers hono@^4.12.34, which 4.13.x satisfies, so both of its resolved versions (2.0.12, 2.1.1) re-peered onto the single copy and neither had to move.

A bump that moved only our three declarations would have left the @modelcontextprotocol/sdk copy flagged and the gate red — the failure mode the card named.

Scope — what did NOT move

The lockfile's registry tuples were diffed base-to-head: 1372 → 1373, 62 removed and 63 added, and every one of them belongs to a flagged package or is forced by one:

  • the sharp platform binaries (@img/sharp-* 0.35.3 → 0.35.4, @img/sharp-libvips-* 1.3.2 → 1.3.3) and node-abi, which sharp's prebuild chain pins;
  • the next platform binaries (@next/swc-*, @next/env 16.3.1 → 16.3.3) and its own transitives isbot, seroval, seroval-plugins;
  • the eight @vitest/* packages, moving with vitest;
  • hono two → one, nodemailer, @inquirer/*, ip-address, type-fest, and second patch-level copies of picomatch and postcss re-resolved inside the updated subtree.

No unrelated package was refreshed.

The ordering constraint — its stated mechanism does not exist, and the branch took origin/main anyway

This branch was dispatched with an ordering constraint: pnpm-lock.yaml was said to be claimed by open PR #16996 under No other open PR may claim the same single-writer path, with instructions to wait rather than push through if that gate refused. It cannot refuse on this path. scripts/check-single-claim-paths.mjs declares exactly one single-claim path:

export const SINGLE_CLAIM_PATHS = [ { path: '.objectui-sha', … } ];

and its own header rules the lock file out by name: "Anything that is merely hot (a lock file, a root manifest, a shared registry) does NOT belong here" — because keying repo-wide on shared paths measured 68 false accusations per 300 PRs, the lock file being the top collision at 33. So no refusal was possible, none occurred, and nothing was pushed through. Recorded because the premise, not the instruction, was wrong.

The substance of the constraint was honoured regardless: #16996 landed as 8d4690b8f while this work was in progress, and this branch merged that tip (3b79bc400) and re-derived the lockfile on top of it. Nothing raced.

Verification

A dependency bump's risk is breakage, so this is a full run, not a narrowed one.

Full workspace build

pnpm build          →  Tasks:    73 successful, 73 total        (VERDICT command-exit 0)

Full test suite — not a narrowed run

pnpm test           →  Tasks:   127 successful, 131 total       (VERDICT command-exit 1)
                       2718 test files · 52937 passed · 1 failed · 304 skipped · 16 todo
                       across the 64 packages that emit a vitest summary

⚠️ One test fails, and it fails identically on origin/main. It is not a regression from this PR, and the control is measured rather than asserted.

@objectstack/verify  ❯ src/harness.host-resolution.test.ts (7 tests | 1 failed)
  × CONTROL — the `declared-unresolvable` remedy is unchanged: declared, not installed (#4719)
AssertionError: expected '✖ FATAL: tenancy posture \'isolated\'…'
              to contain 'It IS declared in /tmp/os-verify-org-…'

The control, run in a separate clean worktree at origin/main = 8d4690b8f with the same build scope as this branch:

tree build before the test result
this branch pnpm build (73/73) Tests 1 failed | 6 passed (7)
origin/main 8d4690b8f pnpm build (73/73) Tests 1 failed | 6 passed (7)same assertion, verbatim
origin/main 8d4690b8f pnpm --filter '@objectstack/verify^...' build only Tests 7 passed (7)

The first control I ran was the third row, and it was invalid — the narrower build leaves packages/plugins/organizations/dist absent, which is itself the discriminator. Recorded because it briefly read as "this PR broke it".

The mechanism: turbo.json's test task declares dependsOn: ["^build"], and @objectstack/organizations is in neither the dependencies nor the devDependencies of @objectstack/verify — it is the dynamically resolved subject of the test. So a full workspace build gives the fixture a resolvable copy the test's "declared, not installed" premise assumes cannot exist, and boot walks past package resolution into the next fatal. Filed as its own card — #17025, unassigned, carrying the repro; ⛔ not fixed here, and out of this card's scope.

The docs / next / sharp surface, which pnpm build excludes by design

Run with the command production runs, read from apps/docs/vercel.json:

pnpm turbo run build --filter=@objectstack/docs
  →  ✓ Generating static pages using 2 workers (1221/1221) in 29.9s
     Tasks:    2 successful, 2 total                            (VERDICT command-exit 0)

next 16.3.3 and sharp 0.35.4 build and render all 1221 pages. (The log carries Failed to load dynamic font … self-signed certificate in certificate chain for two CJK strings — the sandbox's egress proxy refusing a font fetch, not a build error; the build exits 0 and the pages render.)

The build also leaves the tree cleangit status --porcelain is empty afterwards, so gen:schema regenerated authorable-surface.base.json byte-identically, as it must when no spec source moved.

Gate union — derived from a fresh tree, reconciled with --ran

node scripts/pm/dispatch-gates.mjs --ran RAN_FILE --repo objectstack-ai/objectstack
  →  Run reconciliation — 86 derived, 86 run, 0 NOT-MEASURED, 0 UNRUN.
     ✓ 86 derived famil(ies) accounted for — 86 run, 0 NOT-MEASURED.

Every one of the 86 exited 0. No exit 3 PREREQUISITE NOT MET anywhere — the full build ran first, so the build-dependent families (check:dts-closure, check:lean-entry-closure, check:type-source-resolution, check:dual-build-cjs-loads, check:driver-conformance, check:vendor-export-contract-resolve) measured rather than skipped.

One honesty note the tool itself raised and this PR closed rather than declared: the reconciliation warned STALE TREE — 1 file(s) it derives from CHANGED, naming scripts/check-adr-0087-registration.mjs. That family was re-run from origin/main's current copy of the script (fetched to a temp probe beside the original, run, then removed — git status clean afterwards): --self-test 338 assertions ✓, --base origin/mainthis PR adds no declared-breaking changeset. Both exit 0.

The gates most load-bearing for this diff specifically:

pnpm check:override-consistency
  ✓ 9 published-manifest declaration(s) covered by pnpm-workspace.yaml overrides all resolve to their override targets.
  [report] Consumer census: 5 of 33 override(s) …      ← same 5 as before this PR
  [report] Self-expiring selectors: 3 of 33 …          ← same 3 as before this PR

node scripts/check-osv-exemptions.mjs
  ✓ osv-scanner.toml holds zero OSV exemptions (the intended steady state).

Both censuses are byte-for-byte the lists the pre-PR CI run printed, so neither lift introduced a self-expiring selector or an idle override.

The vitest bump cannot move suite counts — read from the published diff, not assumed

The card's warning is the right one to take seriously, so the 4.1.10 → 4.1.11 diff was unpacked from the registry and read rather than reasoned about. Outside version strings and the chunk-hash renames they cause, the entire change in both flagged packages is this:

  • vitest adds one function, maybeCollectChromiumGarbage, called on browser-session teardown. It returns immediately unless provider.name === 'playwright' and config.browser.name === 'chromium' and the platform is linux and config.browser.isolate is set.
  • @vitest/mocker — this is the advisory itself (GHSA-82fw-gwwq-j7x9). Its vitest:interceptor:register dev-server WebSocket event accepted a redirect path that could escape the Vite root; 4.1.11 gates it through isFileLoadingAllowed and adds a registerWebSocketEvents opt-out.

Both live entirely in browser mode. This repo configures none: @vitest/browser is declared in no manifest (grep exit 1, captured before any pipe), and the only browser string in any vitest config is a comment in examples/app-showcase/vitest.config.ts pointing at the separate Playwright e2e/ suite. Nothing in the changed code is reachable from pnpm test, so collection and reporting are byte-identical by construction — and the full run below confirms it empirically rather than resting on that.

Where each bumped package is actually exercised

package its real surface here what covers it
nodemailer a hard dependency of @objectstack/plugin-email (transports/smtp.ts) sys-email-payload.wire.test.ts drives real nodemailer against an in-process fake SMTP server and byte-compares the DATA payload the server received — a MIME-encoding change between 9.0.5 and 9.1.1 could not pass it silently
hono the HTTP server layer under @objectstack/plugin-hono-server and @objectstack/hono the route layer, not just units: wildcard fall-through, basePath mounting, owned-404, dispatcher-result passthrough, the middleware and fallback seams, mounted-route introspection, adapter drain — plus the whole @objectstack/http-conformance package, whose conformance suite includes an integration test
sharp pulled only by next as its image dependency; this repo imports it nowhere (grep exit 1) the docs build below
next the private apps/docs site, excluded from pnpm build by design (turbo run build --filter=!@objectstack/docs) the docs build below, run with the command production runs
vitest / @vitest/coverage-v8 / @vitest/mocker the test runner itself the full suite is its own coverage; see the diff reading above for why the patch cannot reach it

Changeset grade, and why

patch, naming @objectstack/plugin-email and @objectstack/plugin-hono-server.

Those two are the only published packages whose manifests change something a downstream install reads: a declared dependencies range. Everything else in the diff releases nothing —

  • apps/docs is private: true, so its next pin ships nowhere;
  • pnpm-workspace.yaml overrides apply only inside this workspace and are never published (that is the whole reason the two declared ranges move in lockstep with the hono override);
  • the vitest / @vitest/coverage-v8 moves are devDependencies, which no consumer installs.

patch rather than minor because the rule in the Check Changeset step's prose grades on the ACT: a minor is owed for a purely additive widening of a published package's public surface, and a dependency range is not that surface. No exported symbol, accepted key or accepted value moves. skip-changeset would have been wrong in the other direction — two published manifests really do change, so this PR is not "releases nothing".

Two peer ranges deliberately left alone

Both are the same shape, and pnpm-workspace.yaml's own header states the rule: "a peer states what host hono we work against, and a host that pins an old hono owns that copy; narrowing it fixes nothing here and only breaks compatibility."

  • @objectstack/hono keeps peerDependencies.hono: ^4.12.8.
  • @objectstack/metadata-core keeps its optional peerDependencies.vitest: ^4.0.0.

Neither produces a resolution of its own, so neither can put a flagged version in the lockfile.

Acceptance notes — observed, not filed, not fixed here

  • pnpm-workspace.yaml's @hono/node-server note says the resolved version is "unchanged at 2.0.12". The tree now resolves two copies, 2.0.12 and 2.1.1, because @objectstack/plugin-hono-server declares ^2.1.1 while the override target is ^2.0.10. check:override-consistency is green (the ranges intersect) and neither copy is flagged, so this is a stale comment rather than a defect. Both re-peered onto the single hono@4.13.7 in this PR.
  • pnpm install reports ioredis-mock 8.13.1 → unmet peer ioredis@^5: found 6.0.0. Verified pre-existing and untouched: origin/main and this branch resolve ioredis@6.0.0 and ioredis-mock@8.13.1 identically. pnpm only prints it on a run that re-resolves, which is why the sweep surfaced it and a plain install does not.
  • next 16.3.4 and hono 4.13.6/4.13.7 exist above the fix lines. next was deliberately held at the named fix 16.3.3; hono floats to 4.13.7 because an OSV override target must be a floating range inside the major, per this file's mandated selector shape.

Generated by Claude Code

`Validate Package Dependencies` (OSV-Scanner against pnpm-lock.yaml) exits 1 on
fifteen advisories across seven packages, so every PR is red and main's next
scheduled run goes red on its own. All fifteen name a published fix version, so
this is the take-the-fix path osv-scanner.toml's header describes, not the
exemption path; that ledger keeps its zero entries and is untouched, as is
.github/workflows/validate-deps.yml.

  GHSA-2xp9-vwfh-vxw4  9.5  next             16.3.1  -> 16.3.3
  GHSA-p293-qw3h-jr36  9.0  next             16.3.1  -> 16.3.3
  GHSA-rgj7-g3m4-5g8c  8.9  sharp            0.35.3  -> 0.35.4
  GHSA-2x7j-588g-ccc2  7.5  nodemailer       9.0.5   -> 9.1.1
  GHSA-cc9r-2j5m-2m83  6.5  nodemailer       9.0.5   -> 9.1.1
  GHSA-wmmp-3585-3rmp  6.5  nodemailer       9.0.5   -> 9.1.1
  GHSA-8m3c-c648-2xjj  5.9  nodemailer       9.0.5   -> 9.1.1
  GHSA-gqvv-2mrq-wpjv  6.5  hono             4.12.34 + 4.13.2 -> 4.13.7
  GHSA-crvj-82cr-hjcx  5.9  hono             4.12.34 + 4.13.2 -> 4.13.7
  GHSA-g6gw-c38x-mqfc  5.3  hono             4.12.34 + 4.13.2 -> 4.13.7
  GHSA-82fw-gwwq-j7x9  5.9  vitest           4.1.10  -> 4.1.11
  GHSA-82fw-gwwq-j7x9  5.9  @vitest/mocker   4.1.10  -> 4.1.11

hono was resolved at TWO versions and both were flagged. The transitive copy
@modelcontextprotocol/sdk pulled sat exactly on the old `^4.12.34` override
floor, so nothing ever re-resolved it, while our own three declarations floated
up to 4.13.2. Lifting the target to `^4.13.5` excludes the floor, forces both
edges to re-resolve, and the tree deduplicates onto one hono@4.13.7 — moving
only our declarations would have left the transitive copy flagged.

sharp and hono are TARGET-ONLY override lifts: both selectors already sit at the
compatibility boundary this file's header mandates (`<0.36.0` for the 0.x line,
`<5.0.0` for hono), so neither moves.

next is an exact pin in the private docs app. nodemailer and hono move their
declared ranges in lockstep with the override, per the downstream-install rule
the overrides header states — the @objectstack/hono PEER range stays the
permissive `^4.12.8`, and metadata-core's optional `vitest` peer stays `^4.0.0`,
both for the reason that header gives: a peer states what host we work against.

@vitest/coverage-v8 moves in lockstep with vitest because its peer on vitest is
EXACT (`"vitest": "4.1.10"`), so a lone vitest bump would have broken the peer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
Takes the tip that already carries the plugin-audit landing, per the
ordering constraint on this branch. Re-derives pnpm-lock.yaml from the
merged manifests in the next commit.
… merge

The vitest move was originally taken with `pnpm update`, which re-resolves the
whole updated SUBTREE and not just the package named: it dragged second,
patch-level copies of postcss (8.5.28 beside 8.5.26) and picomatch (4.0.7
beside 4.0.5) into the tree, plus @inquirer/*, ip-address, type-fest and
seroval — none of them flagged, none of them forced by a flagged bump.

Regenerating from origin/main's lockfile with a plain `pnpm install`, so the
only inputs are the declared ranges and the two override targets this PR
changes, gives a strictly tighter result:

  registry tuples  1372 -> 1370   (was 1372 -> 1373 under `pnpm update`)
  removed 50, added 48, and EVERY added tuple is in a flagged family

Two duplicates collapse and nothing else moves:
  hono      4.12.34 + 4.13.2  ->  4.13.7        (the OSV duplicate)
  tinyexec  0.3.2 + 1.2.4 + 1.3.0  ->  0.3.2 + 1.3.0
    vitest 4.1.10 held 1.2.4; 4.1.11 declares the same `^1.0.2` and dedupes
    onto the 1.3.0 copy that was already in the tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
@github-actions github-actions Bot added the size/m label Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 2 changed file(s) yielded no anchor (packages/plugins/plugin-email/package.json, packages/plugins/plugin-hono-server/package.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 2 changed package(s)).

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/plugins/plugin-email/package.json, packages/plugins/plugin-hono-server/package.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 11 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d127f9babce9d483ac4c053194360968e1c7ba0apackageMentionDocs.

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tooling labels Sep 9, 2026
@os-trump
os-trump marked this pull request as ready for review September 9, 2026 03:35
@os-trump
os-trump enabled auto-merge September 9, 2026 03:35
@os-trump
os-trump added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit ca31ff6 Sep 9, 2026
41 checks passed
@os-trump
os-trump deleted the claude/issue-16999-osv-advisory-bumps branch September 9, 2026 04:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/m tooling

Projects

None yet

2 participants