You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by as...@apache.org on 2026/10/10 10:52:29 UTC
(maka) branch main updated: chore: refresh the install-scripts allowlist against the lockfile (#5744)
This is an automated email from the ASF dual-hosted git repository.
Astro-Han pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git
The following commit(s) were added to refs/heads/main by this push:
new eb1c68a785 chore: refresh the install-scripts allowlist against the lockfile (#5744)
eb1c68a785 is described below
commit eb1c68a785bd95aa7b16bb5aec90d97b355f7a9e
Author: 小七 <gg...@gmail.com>
AuthorDate: Sat Oct 10 18:52:24 2026 +0800
chore: refresh the install-scripts allowlist against the lockfile (#5744)
npm's install-scripts approval gate flags seven packages on a fresh
clone: two allowlist entries trail the lockfile (esbuild 0.27.7 vs the
pinned 0.28.2, @jackwener/opencli 1.8.4 vs 1.8.7), and five packages
with install scripts were never allowlisted at all (electron-winstaller,
protobufjs, tree-sitter-javascript, @astryxdesign/cli,
@astryxdesign/core). Every entry is now the exact version pinned in
package-lock.json, verified against node_modules, and a fresh install
completes with no unreviewed-script warnings.
Generated-by: GLM-5.3-Flash (ZCode)
chore: deny the four nonessential install scripts
Review follow-up: the @astryxdesign/cli and @astryxdesign/core
postinstalls only print setup nudges, protobufjs only checks its version
notation, and @jackwener/opencli skips its shell-completion postinstall
on this repository's installs and in CI — none produces an artifact
Maka needs. Record an explicit deny (which also clears npm's
unreviewed-script warning without executing the scripts) and keep
approval only for the scripts installation genuinely requires
(esbuild's binary install, tree-sitter-javascript's native build,
electron-winstaller's arch selection, node-pty's native build).
Generated-by: GLM-5.3-Flash (ZCode)
chore: allowlist fsevents@2.3.3 install script
fsevents@2.3.3 is a darwin-only optional dev dependency with
hasInstallScript: true in the lockfile; a fresh install on macOS trips the
approval gate exactly like the eight entries this PR already refreshed.
The allowlist was scanned on Linux, where the os: ["darwin"] guard keeps
the package out of node_modules - that is how it was missed.
Generated-by: GLM-5.3-Flash (ZCode)
chore(ci): guard allowScripts against lockfile drift
npm's install-script approval gate keys approvals on exact name@version,
so a routine bump silently stales the map and a new optional script-
carrying dependency (fsevents) escapes it entirely. The new
check-allow-scripts script fails on any script-carrying lockfile entry
the map does not key, any map key trailing the lockfile, and any
non-boolean decision; the dependency-audit workflow runs it beside the
shipped-closure audit so the drift cannot land.
Generated-by: GLM-5.3-Flash (ZCode)
Merge remote-tracking branch 'upstream/main' into fix/allow-scripts-versions
chore(ci): retrigger after a flaky code-mode run
The affected-test lane failed on a single timing-sensitive assertion in
code-mode.test.js ("excludes host waiting from the execution budget and
preserves dependent Promise.race progress", false !== true). The same
test file passes three consecutive local runs (33/33 each) on this
exact tree; nothing in this PR touches the runtime. Tree-identical
retrigger.
Generated-by: GLM-5.3-Flash (ZCode)
chore(ci): retrigger the windows packaging lane again
The Release Windows package job crashed twice at the same point: the
electron-builder icons@1.2.3 icon-tool child process died with
0xC0000005 on the Windows runner (the ico output had already been
written). main's own package lane passes on the same sources, so this
is runner-side, not this PR. Third attempt; if it crashes at the same
point again I will file an issue with the three logs.
Generated-by: GLM-5.3-Flash (ZCode)
Merge upstream/main (8648258d0)
Brings in the dependency-refresh train through #5839/#5860. The single
conflict was the root allowScripts block: upstream mechanically carried
"@jackwener/opencli@1.8.8": true forward from the dependabot bump, while
this branch holds the reviewed deny (opencli's skipped shell-completion
postinstall produces no artifact Maka needs). Keep the deny decisions and
re-key the three bumped versions to the new lockfile:
@jackwener/opencli 1.8.7 -> 1.8.8, @astryxdesign/cli and
@astryxdesign/core 0.6.2 -> 0.6.3. The other six entries already match.
scripts/check-allow-scripts.mjs passes against the merged lockfile
(9/9 install-script packages covered) and its suite stays green.
Generated-by: GLM-5.3-Flash (ZCode)
---
.github/workflows/dependency-audit.yml | 8 ++
package.json | 10 ++-
scripts/check-allow-scripts.mjs | 137 +++++++++++++++++++++++++++++++++
scripts/check-allow-scripts.test.mjs | 123 +++++++++++++++++++++++++++++
4 files changed, 276 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/dependency-audit.yml b/.github/workflows/dependency-audit.yml
index c1883765b1..0acd9e7928 100644
--- a/.github/workflows/dependency-audit.yml
+++ b/.github/workflows/dependency-audit.yml
@@ -26,6 +26,7 @@ on:
paths:
- .github/workflows/dependency-audit.yml
- scripts/audit-shipped-dependencies.mjs
+ - scripts/check-allow-scripts.mjs
- scripts/third-party-closure.mjs
- package.json
- package-lock.json
@@ -36,6 +37,7 @@ on:
paths:
- .github/workflows/dependency-audit.yml
- scripts/audit-shipped-dependencies.mjs
+ - scripts/check-allow-scripts.mjs
- scripts/third-party-closure.mjs
- package.json
- package-lock.json
@@ -76,6 +78,12 @@ jobs:
# not lock every pull request out of the repository.
run: node scripts/audit-shipped-dependencies.mjs --allow-unavailable
+ - name: Check the allowScripts map against the lockfile
+ # npm's install-script approval gate keys on exact name@version; a
+ # routine bump or a new optional script-carrying dependency silently
+ # stales the map. Failing here keeps the drift from landing.
+ run: node scripts/check-allow-scripts.mjs
+
- name: Verify registry signatures
# The full tree, not `--omit=dev`. Two reasons it has to be both:
# the renderer roots live in devDependencies while their code ships
diff --git a/package.json b/package.json
index 9b95651b1f..0c20ced3e4 100644
--- a/package.json
+++ b/package.json
@@ -129,9 +129,15 @@
"yaml": "2.9.1"
},
"allowScripts": {
+ "@astryxdesign/cli@0.6.3": false,
+ "@astryxdesign/core@0.6.3": false,
+ "@jackwener/opencli@1.8.8": false,
+ "electron-winstaller@5.4.0": true,
"esbuild@0.28.2": true,
- "@jackwener/opencli@1.8.8": true,
- "node-pty@1.2.0-beta.15": true
+ "fsevents@2.3.3": true,
+ "node-pty@1.2.0-beta.15": true,
+ "protobufjs@7.6.5": false,
+ "tree-sitter-javascript@0.25.0": true
},
"overrides": {
"@ai-sdk/code-mode": {
diff --git a/scripts/check-allow-scripts.mjs b/scripts/check-allow-scripts.mjs
new file mode 100644
index 0000000000..530f9bf3d2
--- /dev/null
+++ b/scripts/check-allow-scripts.mjs
@@ -0,0 +1,137 @@
+#!/usr/bin/env node
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Drift guard for the `allowScripts` map in the root package.json.
+//
+// npm >= 11.6 asks for an explicit approval before running a dependency's
+// lifecycle scripts, keyed by exact `name@version`. The map only helps while
+// it matches package-lock.json: this PR chain started because two keyed
+// versions trailed the lockfile after routine bumps, and the optional
+// Darwin-only fsevents was never listed at all. This check fails on any of:
+//
+// 1. a lockfile entry with `hasInstallScript` that the map does not key
+// (optionality is not an exemption — fsevents is exactly the case);
+// 2. a map key whose `name@version` no longer matches a lockfile entry
+// that carries install scripts (the stale-version failure mode);
+// 3. a map value that is not a boolean, which silently means neither
+// "approve" nor "deny" to the gate.
+//
+// It does not judge the true/false decisions themselves — only that every
+// script-carrying package has one, and that nothing in the map is dead.
+
+import { readFileSync } from 'node:fs';
+import path from 'node:path';
+import process from 'node:process';
+
+const repoRoot = path.resolve(
+ path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')),
+ '..',
+);
+const args = process.argv.slice(2);
+
+function readOption(flag) {
+ const index = args.indexOf(flag);
+ return index === -1 ? null : args[index + 1];
+}
+
+const packageJsonPath = readOption('--package') ?? path.join(repoRoot, 'package.json');
+const lockfilePath = readOption('--lock') ?? path.join(repoRoot, 'package-lock.json');
+
+const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
+const lock = JSON.parse(readFileSync(lockfilePath, 'utf8'));
+
+const allowScripts = packageJson.allowScripts ?? null;
+if (allowScripts === null || typeof allowScripts !== 'object' || Array.isArray(allowScripts)) {
+ console.error('package.json has no allowScripts object; nothing to guard.');
+ process.exit(1);
+}
+
+// A lockfile key is "node_modules/..." with the dependency name as the last
+// `node_modules/` segment (scoped names span two path parts).
+function nameFromLockKey(lockKey) {
+ const marker = lockKey.lastIndexOf('node_modules/');
+ return marker === -1 ? lockKey : lockKey.slice(marker + 'node_modules/'.length);
+}
+
+// Split an exact `name@version` key on its final '@' so scoped names survive.
+function splitAllowKey(key) {
+ const at = key.lastIndexOf('@');
+ if (at <= 0) return null;
+ return { name: key.slice(0, at), version: key.slice(at + 1) };
+}
+
+const problems = [];
+const lockEntries = new Map(); // name@version -> lockfile key
+for (const [lockKey, entry] of Object.entries(lock.packages ?? {})) {
+ if (lockKey === '') continue; // the root project is not a dependency of itself
+ if (!entry.hasInstallScript) continue;
+ const exact = `${nameFromLockKey(lockKey)}@${entry.version}`;
+ if (lockEntries.has(exact)) {
+ problems.push(
+ `lockfile carries ${exact} at two paths: ${lockEntries.get(exact)} and ${lockKey}`,
+ );
+ continue;
+ }
+ lockEntries.set(exact, lockKey);
+}
+
+for (const [exact] of lockEntries) {
+ if (!(exact in allowScripts)) {
+ const entry = lock.packages[lockEntries.get(exact)] ?? {};
+ const where = entry.optional
+ ? ` (optional${entry.os?.length ? `, os: ${entry.os.join('/')}` : ''})`
+ : '';
+ problems.push(`missing: ${exact}${where} has install scripts but no allowScripts decision`);
+ }
+}
+
+const keyedExact = new Set();
+for (const [key, value] of Object.entries(allowScripts)) {
+ if (typeof value !== 'boolean') {
+ problems.push(
+ `invalid: "${key}" maps to ${JSON.stringify(value)}; the gate expects true or false`,
+ );
+ continue;
+ }
+ const split = splitAllowKey(key);
+ if (!split || !split.version) {
+ problems.push(`invalid: "${key}" is not an exact name@version key`);
+ continue;
+ }
+ keyedExact.add(key);
+ if (!lockEntries.has(key)) {
+ problems.push(
+ `stale: "${key}" matches no lockfile entry with install scripts (version bumped, or scripts gone)`,
+ );
+ }
+}
+
+if (problems.length > 0) {
+ console.error(`allowScripts is out of sync with ${path.basename(lockfilePath)}:\n`);
+ for (const problem of problems) console.error(` - ${problem}`);
+ console.error(
+ `\n${keyedExact.size} keyed / ${lockEntries.size} script-carrying packages. Fix package.json's allowScripts map.`,
+ );
+ process.exit(1);
+}
+
+console.log(
+ `allowScripts covers all ${lockEntries.size} install-script packages with exact name@version keys.`,
+);
diff --git a/scripts/check-allow-scripts.test.mjs b/scripts/check-allow-scripts.test.mjs
new file mode 100644
index 0000000000..3daae59c1a
--- /dev/null
+++ b/scripts/check-allow-scripts.test.mjs
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { execFile } from 'node:child_process';
+import { mkdtempSync, writeFileSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import path from 'node:path';
+import { test } from 'node:test';
+import { promisify } from 'node:util';
+
+const run = promisify(execFile);
+const script = new URL('./check-allow-scripts.mjs', import.meta.url).pathname.replace(
+ /^\/([A-Za-z]:)/,
+ '$1',
+);
+
+function writeFixture({ allowScripts, lockPackages }) {
+ const dir = mkdtempSync(path.join(tmpdir(), 'allow-scripts-'));
+ const packageJsonPath = path.join(dir, 'package.json');
+ const lockfilePath = path.join(dir, 'package-lock.json');
+ writeFileSync(packageJsonPath, JSON.stringify({ allowScripts }));
+ writeFileSync(lockfilePath, JSON.stringify({ packages: lockPackages }));
+ return { packageJsonPath, lockfilePath };
+}
+
+async function runCheck(fixture) {
+ try {
+ const { stdout } = await run(process.execPath, [
+ script,
+ '--package',
+ fixture.packageJsonPath,
+ '--lock',
+ fixture.lockfilePath,
+ ]);
+ return { code: 0, stdout };
+ } catch (error) {
+ return { code: error.code, stdout: '', stderr: String(error.stderr) };
+ }
+}
+
+const BASE_LOCK = {
+ '': { name: 'maka', version: '0.2.0', hasInstallScript: true },
+ 'node_modules/esbuild': { version: '0.28.2', hasInstallScript: true },
+ 'node_modules/fsevents': {
+ version: '2.3.3',
+ hasInstallScript: true,
+ optional: true,
+ os: ['darwin'],
+ },
+ 'node_modules/node-pty': { version: '1.2.0-beta.15', hasInstallScript: true },
+};
+
+test('a synced map passes', async () => {
+ const fixture = writeFixture({
+ allowScripts: {
+ 'esbuild@0.28.2': true,
+ 'fsevents@2.3.3': true,
+ 'node-pty@1.2.0-beta.15': true,
+ },
+ lockPackages: BASE_LOCK,
+ });
+ const result = await runCheck(fixture);
+ if (result.code !== 0) throw new Error(`expected exit 0, got ${result.code}: ${result.stderr}`);
+});
+
+test('an unkeyed optional darwin-only package fails (the fsevents case)', async () => {
+ const fixture = writeFixture({
+ allowScripts: { 'esbuild@0.28.2': true, 'node-pty@1.2.0-beta.15': true },
+ lockPackages: BASE_LOCK,
+ });
+ const result = await runCheck(fixture);
+ if (result.code !== 1) throw new Error(`expected exit 1, got ${result.code}`);
+ if (!result.stderr.includes('fsevents@2.3.3 (optional, os: darwin)'))
+ throw new Error(`missing fsevents note: ${result.stderr}`);
+});
+
+test('a map key trailing a lockfile bump fails (the stale-version case)', async () => {
+ const fixture = writeFixture({
+ allowScripts: {
+ 'esbuild@0.27.7': true,
+ 'fsevents@2.3.3': true,
+ 'node-pty@1.2.0-beta.15': true,
+ },
+ lockPackages: BASE_LOCK,
+ });
+ const result = await runCheck(fixture);
+ if (result.code !== 1) throw new Error(`expected exit 1, got ${result.code}`);
+ if (!result.stderr.includes('stale: "esbuild@0.27.7"'))
+ throw new Error(`missing stale entry: ${result.stderr}`);
+ if (!result.stderr.includes('missing: esbuild@0.28.2'))
+ throw new Error(`missing missing-entry: ${result.stderr}`);
+});
+
+test('a non-boolean value fails', async () => {
+ const fixture = writeFixture({
+ allowScripts: {
+ 'esbuild@0.28.2': 'yes',
+ 'fsevents@2.3.3': true,
+ 'node-pty@1.2.0-beta.15': true,
+ },
+ lockPackages: BASE_LOCK,
+ });
+ const result = await runCheck(fixture);
+ if (result.code !== 1) throw new Error(`expected exit 1, got ${result.code}`);
+ if (!result.stderr.includes('"esbuild@0.28.2" maps to "yes"'))
+ throw new Error(`missing invalid value: ${result.stderr}`);
+});
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org