


























@@ -1,7 +1,8 @@
1-import { execFileSync } from "node:child_process";
1+import { execFile } from "node:child_process";
22import { copyFileSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
33import { tmpdir } from "node:os";
44import { dirname, join, relative, resolve, sep } from "node:path";
5+import { promisify } from "node:util";
56import { afterEach, describe, expect, it } from "vitest";
67import { isScannable, scanDirectoryWithSummary } from "../security/skill-scanner.js";
78@@ -18,6 +19,9 @@ type PublishablePluginPackage = {
1819packageName: string;
1920};
202122+const execFileAsync = promisify(execFile);
23+const PACKAGE_SCAN_CONCURRENCY = 6;
24+2125const REQUIRED_REVIEWED_PUBLISHABLE_CRITICAL_FINDINGS = new Set([
2226"@openclaw/acpx:dangerous-exec:src/codex-auth-bridge.ts",
2327"@openclaw/acpx:dangerous-exec:src/runtime-internals/mcp-proxy.mjs",
@@ -61,14 +65,17 @@ function parseNpmPackFiles(raw: string, packageName: string): string[] {
6165.toSorted();
6266}
636764-function collectNpmPackedFiles(packageDir: string, packageName: string): string[] {
65-const raw = execFileSync("npm", ["pack", "--dry-run", "--json", "--ignore-scripts"], {
66-cwd: packageDir,
67-encoding: "utf8",
68-maxBuffer: 128 * 1024 * 1024,
69-stdio: ["ignore", "pipe", "pipe"],
70-});
71-return parseNpmPackFiles(raw, packageName);
68+async function collectNpmPackedFiles(packageDir: string, packageName: string): Promise<string[]> {
69+const { stdout } = await execFileAsync(
70+"npm",
71+["pack", "--dry-run", "--json", "--ignore-scripts"],
72+{
73+cwd: packageDir,
74+encoding: "utf8",
75+maxBuffer: 128 * 1024 * 1024,
76+},
77+);
78+return parseNpmPackFiles(stdout, packageName);
7279}
73807481function isScannerWalkedPackedPath(packedPath: string): boolean {
@@ -141,6 +148,72 @@ function collectPublishablePluginPackages(): PublishablePluginPackage[] {
141148.toSorted((left, right) => left.packageName.localeCompare(right.packageName));
142149}
143150151+async function mapWithConcurrency<T, U>(
152+items: readonly T[],
153+concurrency: number,
154+fn: (item: T) => Promise<U>,
155+): Promise<U[]> {
156+const results = new Array<U>(items.length);
157+let nextIndex = 0;
158+const workerCount = Math.min(concurrency, items.length);
159+await Promise.all(
160+Array.from({ length: workerCount }, async () => {
161+while (nextIndex < items.length) {
162+const index = nextIndex;
163+nextIndex += 1;
164+results[index] = await fn(items[index]!);
165+}
166+}),
167+);
168+return results;
169+}
170+171+async function scanPublishablePluginPackage(plugin: PublishablePluginPackage): Promise<{
172+reviewedCriticalFindings: string[];
173+expectedReviewedCriticalFindings: string[];
174+unexpectedCriticalFindings: string[];
175+}> {
176+const reviewedCriticalFindings: string[] = [];
177+const expectedReviewedCriticalFindings: string[] = [];
178+const unexpectedCriticalFindings: string[] = [];
179+const packedFiles = await collectNpmPackedFiles(plugin.packageDir, plugin.packageName);
180+for (const packedFile of packedFiles) {
181+const key = `${plugin.packageName}:dangerous-exec:${normalizePackedFindingPath(packedFile)}`;
182+if (OPTIONAL_REVIEWED_PUBLISHABLE_DIST_CRITICAL_FINDINGS.has(key)) {
183+expectedReviewedCriticalFindings.push(key);
184+}
185+}
186+const stageDir = stageScannerRelevantPackedFiles(plugin.packageDir, packedFiles);
187+const summary = await scanDirectoryWithSummary(stageDir, {
188+excludeTestFiles: true,
189+maxFiles: 10_000,
190+});
191+192+for (const finding of summary.findings) {
193+if (finding.severity !== "critical") {
194+continue;
195+}
196+const packedPath = normalizePackedFindingPath(
197+relative(stageDir, finding.file).split(sep).join("/"),
198+);
199+const key = `${plugin.packageName}:${finding.ruleId}:${packedPath}`;
200+if (
201+REQUIRED_REVIEWED_PUBLISHABLE_CRITICAL_FINDINGS.has(key) ||
202+OPTIONAL_REVIEWED_PUBLISHABLE_DIST_CRITICAL_FINDINGS.has(key)
203+) {
204+reviewedCriticalFindings.push(key);
205+continue;
206+}
207+unexpectedCriticalFindings.push([key, `${finding.line}`, finding.evidence].join(":"));
208+}
209+210+return {
211+ reviewedCriticalFindings,
212+ expectedReviewedCriticalFindings,
213+ unexpectedCriticalFindings,
214+};
215+}
216+144217describe("publishable plugin npm package install security scan", () => {
145218it("keeps npm-published plugin files clear of unexpected critical hits", async () => {
146219const unexpectedCriticalFindings: string[] = [];
@@ -149,37 +222,22 @@ describe("publishable plugin npm package install security scan", () => {
149222REQUIRED_REVIEWED_PUBLISHABLE_CRITICAL_FINDINGS,
150223);
151224152-for (const plugin of collectPublishablePluginPackages()) {
153-const packedFiles = collectNpmPackedFiles(plugin.packageDir, plugin.packageName);
154-for (const packedFile of packedFiles) {
155-const key = `${plugin.packageName}:dangerous-exec:${normalizePackedFindingPath(packedFile)}`;
156-if (OPTIONAL_REVIEWED_PUBLISHABLE_DIST_CRITICAL_FINDINGS.has(key)) {
157-expectedReviewedCriticalFindings.add(key);
158-}
225+const packageResults = await mapWithConcurrency(
226+collectPublishablePluginPackages(),
227+PACKAGE_SCAN_CONCURRENCY,
228+scanPublishablePluginPackage,
229+);
230+for (const result of packageResults) {
231+for (const key of result.expectedReviewedCriticalFindings) {
232+expectedReviewedCriticalFindings.add(key);
159233}
160-const stageDir = stageScannerRelevantPackedFiles(plugin.packageDir, packedFiles);
161-const summary = await scanDirectoryWithSummary(stageDir, {
162-excludeTestFiles: true,
163-maxFiles: 10_000,
164-});
165-166-for (const finding of summary.findings) {
167-if (finding.severity !== "critical") {
168-continue;
169-}
170-const packedPath = normalizePackedFindingPath(
171-relative(stageDir, finding.file).split(sep).join("/"),
172-);
173-const key = `${plugin.packageName}:${finding.ruleId}:${packedPath}`;
174-if (expectedReviewedCriticalFindings.has(key)) {
175-reviewedCriticalFindings.add(key);
176-continue;
177-}
178-unexpectedCriticalFindings.push([key, `${finding.line}`, finding.evidence].join(":"));
234+for (const key of result.reviewedCriticalFindings) {
235+reviewedCriticalFindings.add(key);
179236}
237+unexpectedCriticalFindings.push(...result.unexpectedCriticalFindings);
180238}
181239182-expect(unexpectedCriticalFindings).toEqual([]);
240+expect(unexpectedCriticalFindings.toSorted()).toEqual([]);
183241expect([...reviewedCriticalFindings].toSorted()).toEqual(
184242[...expectedReviewedCriticalFindings].toSorted(),
185243);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。