Repository navigation
Give CI's token read-only access, and lock source-map-js at 1.2.2 - #72
Merged
Merged
Conversation
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}.
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:<integrity>" 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.
…e 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.
This was referenced Oct 11, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two hygiene alerts. 2f3ed86 answers the first and e293261 the second. c5a9370 then corrects two statements in the comment that 2f3ed86 added. None of the three touches
src/, sodist/is unchanged:npm run buildon the branch leaves it byte for byte as it was.1. CodeQL
actions/missing-workflow-permissions: ci.yml (code-scanning alert 1)Exposure.
ci.ymldeclared nopermissions, so itsGITHUB_TOKENgot the repository default, and that default iswrite(default_workflow_permissions: "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. Only Metadata, Models and VulnerabilityAlerts are at read. On a push to main, or a pull request from a branch in this repository (only fork pull requests get a read-only token), any step could have written to the repository with that token, including a compromised action or a dependency's install script undernpm ci. Checkout keeps the token available to later steps (persist-credentials: true). It writes the token to a credentials file in the runner's temp directory, andincludeIfentries in.git/configpoint at that file. A later step can read the file directly or let git use it.Change. A top-level
permissions: contents: read, with a comment saying why (2f3ed86). The block's shape and placement follow mathworks/data-explorer-vscode@03f61fd. The comment is reworded: vscode's says checking out is the only thing the token is for, which leaves out setup-node; this one accounts for setup-node and its cache too. c5a9370 corrects two statements in that comment:The job keeps its name,
build-and-test, which is the required status check of theProtect mainruleset.release.ymlis untouched: it already declarescontents: writefor its Release, which is why onlyci.ymlwas flagged.Why
contents: readis enough:actions/checkoutonly reads the repository.actions/setup-nodeuses the token for one thing: looking up the Node release manifest on actions/node-versions. That is a read, and it happens only on a tool-cache miss. In run 38112853444, setup-node loggedFound in cache @ /opt/hostedtoolcache/node/22.23.3/x64and looked nothing up.ACTIONS_RUNTIME_TOKEN, whichpermissionsdoes not govern. data-explorer-vscode has run with exactly this block since 03f61fd, on the samecheckout@v7andsetup-node@v7. Its last push to main (run 38113832364) logs the token asContents: readandMetadata: read. The same run then logsCache mode: write,Acquiring 20.20.2 - x64 from https://github.com/actions/node-versions/…, andCache saved with the key: …. So both a tool-cache miss and a cache save have already run under this block.npm ciornpm run verifyreads a token.git grepon the tracked tree finds none ofGITHUB_TOKEN,GH_TOKEN,github.token,api.github.com,ACTIONS_RUNTIMEoroctokit.Checked:
name,onandjobsas before, pluspermissions: {contents: read}(js-yaml at 2f3ed86, Ruby YAML at c5a9370). No line is longer than 78 columns.actions-code-scanningsuite (17 rules) over the two workflow files. At b49217a it reportsactions/missing-workflow-permissionsatci.yml11:5-19:29, exactly where GitHub's alert is. At this branch's head it reports nothing. The alert should therefore close on the first scan of main after the merge.2. Dependabot: source-map-js 1.2.1 → 1.2.2 (alert 1, GHSA-68fv-2mgg-jv7q / CVE-2026-93749, high)
Exposure. source-map-js through 1.2.1 accepts any
offset.linein an indexed source map's sections. A tiny map with a huge offset blocks the event loop while its mappings are copied out. Here it is dev-only and transitive. It comes in through vitest (vite → postcss) and through@vitest/coverage-v8(magicast), and both of those require^1.2.1. 1.2.2 refuses a section offset that is not a non-negative integer, and a line offset past 1e7 (nested sections included).What reaches a consumer. A consumer's own
node_modulesgets onlydist/(check:pack). A git-dependency install does fetch source-map-js, though, and that is how data-explorer-vscode takes core (github:mathworks/data-explorer-core#v1.36.3).prepare(andbuild; npm prepares a git dependency that declares any ofbuild,prepare,prepack,preinstall,installorpostinstall). So npm clones core into a temporary directory and runsnpm install --force --include=dev …there, using the clone's own lockfile, before it packs the clone.prepareis onlytsc, and typescript has no dependencies. Nothing in that clone parses a source map.npm pack git+file://<this repo>#<ref>and an empty npm cache. With v1.36.3 as the ref, the clone's install fetchedsource-map-js-1.2.1.tgz. With this branch as the ref, it fetchedsource-map-js-1.2.2.tgz. In both cases the packed tarball holds onlydist/,package.json,README.mdandLICENSE. (This machine's npm setsignore-scripts=true, soprepareitself was skipped. The dev install in the clone runs either way.)e293261's message says source-map-js "never reaches a consumer", because the packed tarball is
dist/only. That sentence overstates the case. The paragraph above is the accurate account. Likewise, c5a9370's message calls CodeQL 2.27.2 "the version that raised the alert"; 2.27.1 raised it, and 2.27.2 is the version of the latest scan of main.Change. Three lines of
package-lock.json: thenode_modules/source-map-jsentry'sversion,resolvedandintegrity.Why the edit is hand-made. registry.npmjs.org cannot be reached from the machine this was prepared on (the proxy answers 502). npm there resolves through an internal mirror, and any
resolvedURL npm writes points at that mirror, whichcheck:leakforbids in a committed lockfile. So the entry was edited by hand, andresolvedstays onhttps://registry.npmjs.org/. The integrity hash is computed from the tarball's bytes, so it holds whichever registry serves them.How it was proven:
sha512-KGj/8Y43…NXNP3Vw==. It equals the registry'sdist.integrity.source-map-js@1.2.2:<integrity>with ECDSA. That signature verifies against npm's public key as published in sigstore/root-signing (targets/registry.npmjs.org/keys.json), fetched from GitHub rather than from the mirror. Changing one character of the integrity makes the verification fail.package.jsonhas no dependencies and no install script. It keepslicense: BSD-3-Clauseandengines.node >=0.10.0.npm update source-map-js --package-lock-onlyon the old lockfile, then put its one changedresolvedURL (the mirror's) back on registry.npmjs.org. The result is this file, byte for byte.npm install --package-lock-onlyleaves the edited file byte for byte unchanged.npm cifrom it, with the default registry and the lockfile left untouched, installs 1.2.2.node -p "require('source-map-js/package.json').version"prints1.2.2, andnpm lsshows 1.2.2 for both dependents. Under Node 22.23.3 and npm 10.9.9, the versions CI used,npm ciadded 77 packages and reportedfound 0 vulnerabilities. CI'snpm ciat b49217a reported1 high severity vulnerability.npm audit --package-lock-onlyreports the high (GHSA-68fv-2mgg-jv7q) on the old lockfile andfound 0 vulnerabilitieson this one.resolvedURLs are onhttps://registry.npmjs.org/, andnpm run check:leakpasses.Verification
npm run verifypasses at the branch head, c5a9370, in two local runs:npm ci;Both runs give the same results:
check:pack: 559 files, 684.5 kB;check:leakpasses, and so doescheck:browser(153 modules).git statusis clean afternpm ciand after each verify run.One local run differs from CI. This machine's npm config sets
ignore-scripts=true, so the localnpm ciran no lifecycle scripts. With this lockfile, that skips two things:prepare, which is the sametscbuild that verify runs next;