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