






















@@ -1,9 +1,9 @@
1-import { execFile } from "node:child_process";
2-import { copyFileSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
1+import { execFile, spawnSync } from "node:child_process";
2+import fs, { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
33import { tmpdir } from "node:os";
44import { dirname, join, relative, resolve, sep } from "node:path";
55import { promisify } from "node:util";
6-import { afterEach, describe, expect, it } from "vitest";
6+import { afterEach, describe, expect, it, vi } from "vitest";
77import { isScannable, scanDirectoryWithSummary } from "../security/skill-scanner.js";
8899type NpmPackFile = {
@@ -130,11 +130,73 @@ function stageScannerRelevantPackedFiles(
130130return stageDir;
131131}
132132133-function collectPublishablePluginPackages(): PublishablePluginPackage[] {
134-return readdirSync("extensions", { withFileTypes: true })
133+function listPublishablePluginPackageDirs(): string[] {
134+const externalDirs = listExternalPluginPackageDirs();
135+if (externalDirs) {
136+return externalDirs;
137+}
138+return fs
139+.readdirSync("extensions", { withFileTypes: true })
135140.filter((entry) => entry.isDirectory())
136-.flatMap((entry) => {
137-const packageDir = join("extensions", entry.name);
141+.map((entry) => join("extensions", entry.name))
142+.toSorted();
143+}
144+145+function listExternalPluginPackageDirs(): string[] | null {
146+const packageFiles = listGitExtensionPackageFiles() ?? listFindExtensionPackageFiles();
147+if (!packageFiles) {
148+return null;
149+}
150+return packageFiles
151+.flatMap((file) => {
152+const match = /^extensions\/([^/]+)\/package\.json$/u.exec(file);
153+return match?.[1] ? [join("extensions", match[1])] : [];
154+})
155+.toSorted();
156+}
157+158+function listGitExtensionPackageFiles(): string[] | null {
159+const result = spawnSync("git", ["ls-files", "--", "extensions/*/package.json"], {
160+cwd: process.cwd(),
161+encoding: "utf8",
162+maxBuffer: 1024 * 1024,
163+stdio: ["ignore", "pipe", "ignore"],
164+});
165+if (result.status !== 0) {
166+return null;
167+}
168+return result.stdout
169+.split("\n")
170+.map((line) => line.trim())
171+.filter((line) => line.length > 0)
172+.toSorted();
173+}
174+175+function listFindExtensionPackageFiles(): string[] | null {
176+const result = spawnSync(
177+"find",
178+[resolve("extensions"), "-maxdepth", "2", "-type", "f", "-name", "package.json"],
179+{
180+cwd: process.cwd(),
181+encoding: "utf8",
182+maxBuffer: 1024 * 1024,
183+stdio: ["ignore", "pipe", "ignore"],
184+},
185+);
186+if (result.status !== 0) {
187+return null;
188+}
189+return result.stdout
190+.split("\n")
191+.map((line) => line.trim())
192+.filter((line) => line.length > 0)
193+.map((file) => relative(process.cwd(), file).split(sep).join("/"))
194+.toSorted();
195+}
196+197+function collectPublishablePluginPackages(): PublishablePluginPackage[] {
198+return listPublishablePluginPackageDirs()
199+.flatMap((packageDir) => {
138200const packageJsonPath = join(packageDir, "package.json");
139201let packageJson: {
140202name?: unknown;
@@ -231,6 +293,21 @@ async function scanPublishablePluginPackage(plugin: PublishablePluginPackage): P
231293}
232294233295describe("publishable plugin npm package install security scan", () => {
296+it("lists publishable plugin packages without scanning extension directories in-process", () => {
297+const readDir = vi.spyOn(fs, "readdirSync");
298+try {
299+const packages = collectPublishablePluginPackages();
300+301+expect(packages.length).toBeGreaterThan(0);
302+expect(
303+packages.every((plugin) => plugin.packageDir.split(sep).join("/").startsWith("extensions/")),
304+).toBe(true);
305+expect(readDir).not.toHaveBeenCalled();
306+} finally {
307+readDir.mockRestore();
308+}
309+});
310+234311it("keeps npm-published plugin files clear of unexpected critical hits", async () => {
235312const unexpectedCriticalFindings: string[] = [];
236313const reviewedCriticalFindings = new Set<string>();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。