Skip to content

Commit 5969ac8

Browse files
committed
test: parallelize plugin package scan
1 parent c5fcfa1 commit 5969ac8

1 file changed

Lines changed: 94 additions & 36 deletions

File tree

src/plugins/npm-install-security-scan.release.test.ts

Lines changed: 94 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { execFileSync } from "node:child_process";
1+
import { execFile } from "node:child_process";
22
import { copyFileSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
33
import { tmpdir } from "node:os";
44
import { dirname, join, relative, resolve, sep } from "node:path";
5+
import { promisify } from "node:util";
56
import { afterEach, describe, expect, it } from "vitest";
67
import { isScannable, scanDirectoryWithSummary } from "../security/skill-scanner.js";
78

@@ -18,6 +19,9 @@ type PublishablePluginPackage = {
1819
packageName: string;
1920
};
2021

22+
const execFileAsync = promisify(execFile);
23+
const PACKAGE_SCAN_CONCURRENCY = 6;
24+
2125
const 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
}
6367

64-
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
}
7380

7481
function isScannerWalkedPackedPath(packedPath: string): boolean {
@@ -141,6 +148,72 @@ function collectPublishablePluginPackages(): PublishablePluginPackage[] {
141148
.toSorted((left, right) => left.packageName.localeCompare(right.packageName));
142149
}
143150

151+
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+
144217
describe("publishable plugin npm package install security scan", () => {
145218
it("keeps npm-published plugin files clear of unexpected critical hits", async () => {
146219
const unexpectedCriticalFindings: string[] = [];
@@ -149,37 +222,22 @@ describe("publishable plugin npm package install security scan", () => {
149222
REQUIRED_REVIEWED_PUBLISHABLE_CRITICAL_FINDINGS,
150223
);
151224

152-
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
}
181239

182-
expect(unexpectedCriticalFindings).toEqual([]);
240+
expect(unexpectedCriticalFindings.toSorted()).toEqual([]);
183241
expect([...reviewedCriticalFindings].toSorted()).toEqual(
184242
[...expectedReviewedCriticalFindings].toSorted(),
185243
);

0 commit comments

Comments
 (0)