From 2f3ed86944c7568a3f9a77354faa265213aae6c9 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Sun, 11 Oct 2026 17:51:09 -0400 Subject: [PATCH 1/3] Give CI's token read-only access to the repository CodeQL actions/missing-workflow-permissions (code-scanning alert 1, on ci.yml line 11): ci.yml declared no `permissions`, so its GITHUB_TOKEN got the repository default, which is "write". The last CI run on main (run 38112853444, at b49217a) logs 17 scopes at write, among them Contents, PullRequests, Statuses, Actions, Issues, Packages and Pages, and only Metadata, Models and VulnerabilityAlerts at read. The job needs none of that. Checkout reads the repository, and setup-node uses the token only to look up the Node release manifest. Nothing in `npm ci` or `npm run verify` reads a token: `git grep` finds no GITHUB_TOKEN, GH_TOKEN, github.token or api.github.com in the tracked tree. The npm cache is unaffected. setup-node saves it with the runner's ACTIONS_RUNTIME_TOKEN, which `permissions` does not govern. data-explorer-vscode has run this shape since its 03f61fd, on the same checkout@v7 and setup-node@v7. Its last push to main (run 38113832364) logs the token as Contents: read and Metadata: read, then "Cache mode: write", a Node download from actions/node-versions, and "Cache saved with the key". release.yml already declares `contents: write` for its Release, which is why only ci.yml was flagged; this matches that shape. The job keeps its name, build-and-test, the ruleset's required check. The file parses with js-yaml to the same name, on and jobs as before, plus permissions {contents: read}. --- .github/workflows/ci.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9aca506..b608b82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,17 @@ on: pull_request: branches: [main] +# Reading is all the token does here: checkout, and setup-node's lookup of the +# Node release manifest. This workflow writes nothing back — no comment, no +# commit, no release — and setup-node saves its npm cache with the runner's +# own token, which `permissions` does not govern. Spelled out rather than left +# to the repository default, which lives in the settings rather than in this +# file and can grant write to everything without anyone touching a workflow +# file. Mirrors release.yml, which asks for `contents: write` because it +# genuinely creates a GitHub Release. +permissions: + contents: read + jobs: build-and-test: runs-on: ubuntu-latest From e29326142a185e15f11f59ba37338adc6a3d68fa Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Sun, 11 Oct 2026 17:55:39 -0400 Subject: [PATCH 2/3] Lock source-map-js at 1.2.2, which caps an indexed map's section offsets Dependabot alert 1, GHSA-68fv-2mgg-jv7q (CVE-2026-93749, high): source-map-js through 1.2.1 takes an indexed source map's per-section offset.line as given, so a tiny map with a huge offset blocks the event loop while its mappings are copied out. 1.2.2 refuses an offset that is not a non-negative integer, and a line offset past 1e7, nested sections included. Here it is dev-only and transitive: vitest's vite -> postcss and @vitest/coverage-v8's magicast, each asking ^1.2.1. It never reaches a consumer, since the packed tarball is dist/ only (check:pack). The entry's version, resolved and integrity are edited by hand, because registry.npmjs.org is unreachable from where this was made and npm there resolves through an internal mirror. The three values are what npm writes: - The integrity is the sha512 of the 1.2.2 tarball's bytes, computed locally, and equals the registry's dist.integrity. The registry's signature over "source-map-js@1.2.2:" verifies against npm's public key as sigstore/root-signing publishes it, and fails with one character of the integrity changed. The same hash of 1.2.1's tarball, from the same mirror, is the integrity this lockfile already held. - 1.2.2 has no dependencies and no install script, and keeps license BSD-3-Clause and engines node >=0.10.0, so nothing else in the entry moves. - `npm update source-map-js --package-lock-only` on the old lockfile writes this file byte for byte, once its one changed resolved URL, the mirror's, is put back on registry.npmjs.org. - `npm install --package-lock-only` leaves this file byte for byte as it is. `npm ci` from it installs 1.2.2 for both dependents (npm ls). `npm audit` reports the high on the old lockfile and nothing on this one. All 101 resolved URLs are on registry.npmjs.org, and check:leak passes. `npm run verify` passes under Node 24.14.1 with 205 test files and 6,604 tests passed and 26 skipped, the counts CI logged at b49217a on Node 22. `npm run build` leaves dist/ unchanged. --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c631a6a..e0f7aa2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1320,9 +1320,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { From c5a937040ec3687255c19adda086b2788c1f45e3 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Sun, 11 Oct 2026 18:17:34 -0400 Subject: [PATCH 3/3] ci.yml's comment: setup-node reads with the token only on a tool-cache miss The comment above `permissions` said the token is for checkout and for setup-node's lookup of the Node release manifest, and so does 2f3ed86's message. setup-node looks the manifest up only when the runner's tool cache lacks the Node asked for. In run 38112853444 (push to main, at b49217a) it logged "Found in cache @ /opt/hostedtoolcache/node/22.23.3/x64" and fetched nothing. data-explorer-vscode's run 38113832364 missed the cache and logged "Acquiring 20.20.2" from actions/node-versions. So the lookup is the most setup-node does with the token, not what it does on every run. The comment also said the repository default can grant write to everything. Under that default, run 38112853444 logs 17 scopes at write and 3 at read (Metadata, Models and VulnerabilityAlerts), so "almost every scope" is what the setting grants. Only the comment changes. The file parses (Ruby YAML) to the same value as before, and no line is longer than 78 columns. CodeQL 2.27.2, the version that raised the alert, run with its actions-code-scanning suite: on b49217a's workflows it reports actions/missing-workflow-permissions at ci.yml 11:5-19:29, where GitHub's alert is, and on this commit's it reports nothing. --- .github/workflows/ci.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b608b82..b00e3d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,14 +6,15 @@ on: pull_request: branches: [main] -# Reading is all the token does here: checkout, and setup-node's lookup of the -# Node release manifest. This workflow writes nothing back — no comment, no -# commit, no release — and setup-node saves its npm cache with the runner's -# own token, which `permissions` does not govern. Spelled out rather than left -# to the repository default, which lives in the settings rather than in this -# file and can grant write to everything without anyone touching a workflow -# file. Mirrors release.yml, which asks for `contents: write` because it -# genuinely creates a GitHub Release. +# Reading is all the token does here: checkout, and, only when the runner's +# tool cache lacks the Node asked for, setup-node's lookup of the Node release +# manifest. This workflow writes nothing back — no comment, no commit, no +# release — and setup-node saves its npm cache with the runner's own token, +# which `permissions` does not govern. Spelled out rather than left to the +# repository default, which lives in the settings rather than in this file +# and can grant write to almost every scope without anyone touching a +# workflow file. Mirrors release.yml, which asks for `contents: write` +# because it genuinely creates a GitHub Release. permissions: contents: read