Skip to content

Commit 6209964

Browse files
chore(osv): exempt sprintf-js GHSA-hp3w-g68c-fv3c until 2026-11-05 (no fixed release exists) (#21952)
Part of #21945 Clause-②: no ## What this does This PR adds one `[[IgnoredVulns]]` entry to `osv-scanner.toml`, for **GHSA-hp3w-g68c-fv3c** (`sprintf-js` 1.1.3, DoS through unbounded precision specifiers, CVSS 6.9). It is the only one of `main`'s four OSV advisories with no fixed release. The ledger header's convention 3 says an exemption lands in its own `osv-exemption`-labelled PR, so this PR carries that entry and nothing else. The other three advisories are fixed in #21951 (lockfile re-lock plus a `katex` override). **Which half this leaves:** the three fixable advisories, which are #21951's. This PR alone does not turn the OSV step green, and neither does #21951 alone. The card stays open when this PR merges, and the PM finishes it after both have landed. ```toml [[IgnoredVulns]] id = "GHSA-hp3w-g68c-fv3c" ignoreUntil = 2026-11-05 reason = "GHSA-hp3w-g68c-fv3c — no fixed sprintf-js exists (1.1.3, the latest release, is the last affected version), and it arrives only transitively through tedious 18.6.2 (driver-sql's optional mssql peer) and fengari 0.1.5 (under ioredis-mock, a service-cluster-redis devDependency), whose latest releases (tedious 20.3.3, fengari 0.1.5) still require ^1.1.3; remove when sprintf-js publishes a fix or both parents drop it." ``` The entry follows the header's conventions: - **Convention 1:** `ignoreUntil` is a bare TOML date, 30 days out, which is the default and well under the 90-day ceiling. - **Convention 2:** `reason` is the advisory URL, an em dash, then one sentence on why the advisory cannot be fixed right now. - **One id only:** the scanner applies an exemption to the advisory's aliases too, so the CVE alias (CVE-2026-97058) gets no entry of its own. ## Exemption decision ### There is no fix to take | Fact | Reading | |---|---| | Advisory range (OSV offline npm DB, downloaded 2026-10-06) | `introduced: 0` up to `last_affected: 1.1.3`, so there is no `fixed` event | | `npm view sprintf-js version` | `1.1.3` (published 2023-09-11, the latest) | | `tedious` (latest 20.3.3, `next` 20.3.4) | every major checked (18.6.2, 19.0.0, 19.2.1, 20.0.0, 20.3.3, 20.3.4) declares `sprintf-js ^1.1.3` | | `fengari` (latest 0.1.5) | declares `sprintf-js ^1.1.3` | | `pnpm why -r sprintf-js` | one copy, 1.1.3; the only parents are `fengari@0.1.5` and `tedious@18.6.2` | No override can help, because there is no version to override to. Moving either parent to a newer release changes nothing either. ### Where each parent is used in this workspace - **`tedious` 18.6.2** is the MSSQL driver. `@objectstack/driver-sql` declares it as an **optional peer** (`packages/drivers/driver-sql/package.json:35`, with `optional: true` at `:44`). The workspace installs a project's own peers (`.npmrc` `auto-install-peers=true`), and `knex@3.3.0` picks it up as its optional mssql peer, both under `driver-sql` and under `driver-sqlite-wasm`. - In this repository it is reached only by driver-sql tests that build a `client: 'mssql'` driver without a server: `sql-driver-date-bucket.test.ts:163`, `sql-driver-text-case-conformance.test.ts:366`, `sql-driver-20446-empty-flip.test.ts:168` and `sql-driver-20987-json-membership-move.test.ts:163`. - The CLI's bundler keeps it external (`packages/cli/src/utils/config.ts:95`). - A downstream install that uses mssql brings its own `tedious`, and so its own `sprintf-js`. This entry does not reach that install, and nothing here can. - **`fengari` 0.1.5** is a Lua VM in JavaScript. It sits under `ioredis-mock` 8.13.1, directly and through `fengari-interop`, and `ioredis-mock` is a **devDependency** of `@objectstack/service-cluster-redis` (`package.json:33`). - It runs only in that package's tests, where `ioredis-mock` executes the package's two Lua scripts. Both are constants: `RELEASE_SCRIPT` at `src/lock.ts:18` and `RENEW_SCRIPT` at `src/lock.ts:30`, sent by the `eval` calls at `src/lock.ts:163` and `:186`. - In production, `ioredis` sends `EVAL` to a real Redis, and `fengari` is never loaded. The published package depends on `ioredis` only. ### Does either path pass an untrusted format string to `sprintf`? The advisory's precondition is an attacker who controls the **format string**, so that a precision specifier outside what `toFixed` / `toExponential` / `toPrecision` accept throws a `RangeError`. - **tedious: no.** All 12 `sprintf` calls in `tedious@18.6.2/lib` take a string-literal format, and data only ever enters as an argument: - `value-parser.js:409` and `:485`; - `metadata-parser.js:104` and `:355`; - `prelogin-payload.js:204`; - `login7-payload.js:397` (four calls); - `packet.js:131`, `:144` and `:155`. - **fengari: only from Lua source, and only partly.** The Lua script supplies the format. - `string.format` (`fengari/src/lstrlib.js:334`) passes the script's own format to `sprintf` at `:361`, `:373` and `:391`. - It does so only after `scanformat` (`:301`) caps width and precision at two digits and raises `invalid format (width or precision too long)` (`:314`). So the advisory's over-100 precision never reaches `sprintf`. Measured: `string.format("%.100f", 1.5)` is a Lua error, and `"%.99f"` formats normally. - The lower edge, `"%.0g"` (`toPrecision(0)`), does still reach `sprintf`. It fails the Lua call (measured: non-zero status). - The other three `sprintf` calls in that file (`:172`, `:188` and `:285`) use constant formats. - Exposure therefore needs an attacker who can write the Lua source that `ioredis-mock` runs. In this workspace the only Lua source is the two constant scripts above, in tests. **Net:** this repository passes no untrusted format string to `sprintf` on any path. ### Routes considered and not taken - **Drop `tedious` from `driver-sql`'s optional peers.** This removes a published peer declaration, which is a contract change for MSSQL users. The four tests above build an mssql client, and their own comments say knex resolves `tedious` from this workspace, so they would need a different stand-in client (not measured here). It would also not clear `fengari`. That is outside this card. - **Replace `ioredis-mock`.** That would mean a test-double migration for `service-cluster-redis`, whose version pair is pinned in `src/ioredis-pair.pin.test.ts`. It would not clear `tedious`. Also outside this card. Both parents would have to go for the advisory to leave the lockfile, so neither route is a fix on its own. ### Renewal trigger `ignoreUntil = 2026-11-05`. After that date the scanner stops filtering the advisory and the step goes red on its own. **Remove the entry when `sprintf-js` publishes a fix, or when both `tedious` and `fengari` drop it.** Removing it can ride along with that fix, while a renewal is a new decision and needs its own `osv-exemption` PR. ## OSV reading at this head (`e6a3636f`) Measured locally with OSV-Scanner **v2.3.8**, the version `validate-deps.yml` pins. `api.osv.dev` answers 403 from this container, so the scan ran in offline mode against the OSV npm database the scanner downloaded on 2026-10-06. Exit 1, with `sprintf-js` filtered and three rows left, which #21951 fixes: ```text GHSA-hp3w-g68c-fv3c and 1 alias have been filtered out because: GHSA-hp3w-g68c-fv3c — no fixed sprintf-js exists … Filtered 1 vulnerability from output | https://osv.dev/GHSA-238p-pmpm-9mq7 | 2.1 | npm | katex | 0.16.47 | 0.18.2 | pnpm-lock.yaml | | https://osv.dev/GHSA-jqcg-44mw-7w3h | 9.1 | npm | proxy-addr | 2.0.7 | 2.0.8 | pnpm-lock.yaml | | https://osv.dev/GHSA-68fv-2mgg-jv7q | 8.7 | npm | source-map-js | 1.2.1 | 1.2.2 | pnpm-lock.yaml | ``` This ledger scanned with #21951's lockfile: exit 0, `No issues found`. ## Changeset `skip-changeset`. `osv-scanner.toml` is repo-root configuration and ships in no package's `files[]`. ## Local verification, at `e6a3636f` The gates come from `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at this head: 13 commands. The `--ran` reconciliation, with exit codes recorded before any pipe, is **13 derived, 13 run, 0 NOT-MEASURED, 0 UNRUN**: - `node scripts/check-changeset-fixed.mjs`: 0. - `node scripts/check-closing-keyword-parity.mjs` (+ `--self-test`): 0 / 0. - `node scripts/check-comment-mask-corpus.mjs`: 0. - `node scripts/check-osv-exemptions.mjs` (+ `--self-test`): 0 / 0. The verdict line reads `✓ 1 OSV exemption(s) in osv-scanner.toml: all carry an unexpired ignoreUntil within 90 days and a reason with an advisory link.` - `pnpm check:driver-memory-census` · `check:gitlink-declared` · `check:nul-bytes` · `check:override-consistency` · `check:refd-timer-probe` · `check:vendor-export-contract-resolve` · `check:watch-hint-literal`: 0 each. ## Acceptance notes - The ledger's header still says "This ledger currently holds ZERO exemptions." Once this entry lands, that sentence is no longer true. This PR leaves the header byte-identical so that its diff is the entry alone. If the reviewer wants it, the one-line count edit can go into this same PR. - The `osv-exemption` label did not exist in this repository before this PR: `GET /labels/osv-exemption` answered 404. No exemption has landed since the convention was written. The additive label write on this PR creates it. --- _Generated by [Claude Code](https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 01e0f71 commit 6209964

1 file changed

Lines changed: 8 additions & 2 deletions

File tree

‎osv-scanner.toml‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
# yet, where the alternative is pinning a required check red indefinitely, and
99
# a permanently red required check is worth exactly as much as no scan at all.
1010
#
11-
# This ledger currently holds ZERO exemptions. That is the intended steady
12-
# state, not a coincidence — read the whole header before you change it.
11+
# This ledger's intended steady state is ZERO exemptions, not a coincidence:
12+
# every entry below is a dated exception with its own renewal date. Read the
13+
# whole header before you change it.
1314
#
1415
# ---------------------------------------------------------------------------
1516
# Three conventions govern every entry (decided on #4965)
@@ -83,3 +84,8 @@
8384
#
8485
# Verify locally: node scripts/check-osv-exemptions.mjs
8586
# Prove the check: node scripts/check-osv-exemptions.mjs --self-test
87+
88+
[[IgnoredVulns]]
89+
id = "GHSA-hp3w-g68c-fv3c"
90+
ignoreUntil = 2026-11-05
91+
reason = "https://github.com/advisories/GHSA-hp3w-g68c-fv3c — no fixed sprintf-js exists (1.1.3, the latest release, is the last affected version), and it arrives only transitively through tedious 18.6.2 (driver-sql's optional mssql peer) and fengari 0.1.5 (under ioredis-mock, a service-cluster-redis devDependency), whose latest releases (tedious 20.3.3, fengari 0.1.5) still require ^1.1.3; remove when sprintf-js publishes a fix or both parents drop it."

0 commit comments

Comments
 (0)