From 716196bba512ece05c09c1ed935bb16ff01b6006 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 31 Aug 2026 20:49:05 +0000 Subject: [PATCH] =?UTF-8?q?fix(ci):=20OSV=20suppression=20config=20actuall?= =?UTF-8?q?y=20loads=20=E2=80=94=20explicit=20global=20--config=20+=20expi?= =?UTF-8?q?ring,=20reasoned=20ignores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ignore file was inert from v1.65.0.0: OSV-Scanner only auto-discovers configs named osv-scanner.toml (no leading dot) and applies them per-directory, so the root config never covered lib/diagram-render/bun.lock either way. The workflow now passes --config=.osv-scanner.toml globally. Every IgnoredVulns entry carries a reason with an upgrade trigger and an ignoreUntil expiry (~90 days) so suppressions must be re-justified. A wiring test pins flag ↔ filename ↔ entry hygiene so the file can never silently go inert again. Co-Authored-By: Claude Fable 5 --- .github/workflows/osv-scanner.yml | 1 + .osv-scanner.toml | 41 +++++++++++++++++++++++++++ test/osv-config-wiring.test.ts | 47 +++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 test/osv-config-wiring.test.ts diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml index a5f4131ff..067d9a58d 100644 --- a/.github/workflows/osv-scanner.yml +++ b/.github/workflows/osv-scanner.yml @@ -21,6 +21,7 @@ jobs: uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@f4cfcc01edc9c8b756a9b873b7a623ca674da51e # v2.3.8 with: scan-args: |- + --config=.osv-scanner.toml --include-git-root --recursive ./ diff --git a/.osv-scanner.toml b/.osv-scanner.toml index 56ca32123..dc8a6c6eb 100644 --- a/.osv-scanner.toml +++ b/.osv-scanner.toml @@ -2,6 +2,19 @@ # Direct/transitive dependency versions are pinned to their fixed releases via # the `overrides` block in package.json; this file only records advisories we # have assessed as not-reachable or not-fixable without disproportionate risk. +# +# LOADING CONTRACT: OSV-Scanner only auto-discovers configs named +# osv-scanner.toml (no leading dot) and applies them per-directory — a root +# config never covers lib/diagram-render/bun.lock. The workflow therefore +# passes an explicit global `--config=.osv-scanner.toml`; the wiring test +# (test/osv-config-wiring.test.ts) pins that flag to this filename so the +# suppression file can never silently go inert again (it was inert from +# v1.65.0.0 to v1.78.0.0). +# +# Every entry carries `ignoreUntil` — suppressions expire and must be +# re-justified; the re-scan on expiry either finds a fix landed upstream or +# forces a fresh decision. Tracking issues are filed at ship time and named in +# each reason. [[IgnoredVulns]] id = "GHSA-frvp-7c67-39w9" @@ -13,3 +26,31 @@ id = "GHSA-frvp-7c67-39w9" # vulnerability we do not expose. Re-evaluate if the MCP SDK becomes a direct, # server-hosting dependency. reason = "Unreachable transitive (unused @modelcontextprotocol/sdk); fix requires a risky major override on a pinned peer dep." +ignoreUntil = 2026-11-30T00:00:00Z + +[[IgnoredVulns]] +id = "GHSA-5p2g-fcmc-qvqq" +# image-size 1.2.1 via html-to-docx@1.8.0 (pins ^1.0.0). No fixed release +# exists (FIXED VERSION = --). Exposure: image-size only parses images the +# user themselves embeds into their own generated .docx — no untrusted input +# path. Upgrade trigger: an image-size release with a fix, or html-to-docx +# moving off it. Tracking issue filed at ship (v1.78.0.0 wave). +reason = "No fixed version exists; local-only input path (user's own docx images). Re-evaluate on expiry." +ignoreUntil = 2026-11-30T00:00:00Z + +[[IgnoredVulns]] +id = "GHSA-w3rx-r6r6-pgpr" +# Same package/node as GHSA-5p2g-fcmc-qvqq above; same rationale. +reason = "No fixed version exists; local-only input path (user's own docx images). Re-evaluate on expiry." +ignoreUntil = 2026-11-30T00:00:00Z + +[[IgnoredVulns]] +id = "GHSA-p7fg-763f-g4gf" +# @anthropic-ai/sdk 0.81.0 nested under @anthropic-ai/claude-agent-sdk@0.2.117, +# which is deliberately exact-pinned (eval-harness stability; the v1.77 wave +# pinned the whole harness after repeated CLI-drift breakage) and declares +# ^0.81.0 (0.x caret = 0.81.x only), so the 0.91.1 fix cannot be reached +# without violating the harness pin. 4.8 MEDIUM. Upgrade trigger: the next +# deliberate claude-agent-sdk bump. Tracking issue filed at ship (v1.78.0.0). +reason = "Fix requires breaking the deliberate eval-harness agent-sdk pin; MEDIUM severity accepted until the next harness bump." +ignoreUntil = 2026-11-30T00:00:00Z diff --git a/test/osv-config-wiring.test.ts b/test/osv-config-wiring.test.ts new file mode 100644 index 000000000..47a1d3d88 --- /dev/null +++ b/test/osv-config-wiring.test.ts @@ -0,0 +1,47 @@ +/** + * OSV scanner config wiring (#2679-wave / v1.78.0.0). + * + * The suppression file was inert from v1.65.0.0 to v1.78.0.0: OSV-Scanner + * only auto-discovers configs named `osv-scanner.toml` (no leading dot) and + * applies them PER-DIRECTORY — a repo-root config never covers + * lib/diagram-render/bun.lock. The workflow must therefore pass an explicit + * global `--config` naming the file that actually exists. These tests pin + * that three-way agreement (workflow flag ↔ file on disk ↔ entry hygiene) so + * the filename and the flag can never drift apart silently again. + */ +import { describe, expect, test } from "bun:test"; +import * as fs from "node:fs"; +import * as path from "node:path"; + +const ROOT = path.resolve(import.meta.dir, ".."); +const WORKFLOW = path.join(ROOT, ".github", "workflows", "osv-scanner.yml"); +const CONFIG = path.join(ROOT, ".osv-scanner.toml"); + +describe("osv-scanner config wiring", () => { + test("workflow passes an explicit --config (auto-discovery never covers nested lockfiles)", () => { + const wf = fs.readFileSync(WORKFLOW, "utf-8"); + const m = wf.match(/--config=(\S+)/); + expect(m).not.toBeNull(); + // The flag must name a file that exists at repo root. + expect(fs.existsSync(path.join(ROOT, m![1]))).toBe(true); + }); + + test("every IgnoredVulns entry has id, reason, and an ignoreUntil expiry", () => { + const cfg = fs.readFileSync(CONFIG, "utf-8"); + const entries = cfg.split("[[IgnoredVulns]]").slice(1); + expect(entries.length).toBeGreaterThan(0); + for (const entry of entries) { + expect(entry).toMatch(/^id = "GHSA-/m); + expect(entry).toMatch(/^reason = ".{20,}/m); + // Suppressions must expire — a permanent exception is a silent hole. + expect(entry).toMatch(/^ignoreUntil = \d{4}-\d{2}-\d{2}/m); + } + }); + + test("ignoreUntil dates are TOML datetimes the scanner can parse (not strings)", () => { + const cfg = fs.readFileSync(CONFIG, "utf-8"); + // TOML datetime is unquoted; a quoted date silently parses as a string + // and (depending on scanner version) may be ignored. + expect(cfg).not.toMatch(/ignoreUntil = "/); + }); +});