mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
fix(release): harden trusted plugin scan
This commit is contained in:
@@ -332,7 +332,6 @@ jobs:
|
||||
fi
|
||||
if [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "plugin-prerelease" ]]; then
|
||||
echo "- Plugin prerelease: \`Plugin Prerelease\` with \`target_ref=${TARGET_SHA}\`"
|
||||
echo "- Plugin prerelease Node exclusions: \`${plugin_prerelease_node_exclusions}\`"
|
||||
else
|
||||
echo "- Plugin prerelease: skipped by rerun group"
|
||||
fi
|
||||
|
||||
@@ -72,7 +72,7 @@ const COMMON_REVIEWED_CRITICAL_FINDING_COUNTS = new Map<string, number>([
|
||||
|
||||
const REVIEWED_RELEASE_LAYOUTS = Object.freeze([
|
||||
{
|
||||
id: "beta3",
|
||||
id: "frozen-legacy",
|
||||
findings: new Map<string, number>([
|
||||
["@openclaw/codex:dangerous-exec:src/app-server/sandbox-exec-server/http.ts", 1],
|
||||
["@openclaw/codex:dangerous-exec:src/app-server/sandbox-exec-server/processes.ts", 1],
|
||||
@@ -382,19 +382,12 @@ function expectedRequiredFindingsForPackage(
|
||||
);
|
||||
}
|
||||
|
||||
export async function runPluginNpmSecurityScan(params: {
|
||||
candidateDir: string;
|
||||
toolingDir: string;
|
||||
}): Promise<PluginNpmSecurityScanReport> {
|
||||
const candidateDir = realpathSync(params.candidateDir);
|
||||
const toolingDir = realpathSync(params.toolingDir);
|
||||
const [candidateSha, toolingSha, packages] = await Promise.all([
|
||||
gitOutput(candidateDir, ["rev-parse", "HEAD"]),
|
||||
gitOutput(toolingDir, ["rev-parse", "HEAD"]),
|
||||
listPublishablePluginPackages(candidateDir),
|
||||
]);
|
||||
|
||||
const packageResults = await Promise.all(packages.map(scanPublishablePluginPackage));
|
||||
export function buildPluginNpmSecurityScanReport(params: {
|
||||
candidateSha: string;
|
||||
packageResults: ScanPackageResult[];
|
||||
toolingSha: string;
|
||||
}): PluginNpmSecurityScanReport {
|
||||
const { candidateSha, packageResults, toolingSha } = params;
|
||||
const allReviewedFindings = packageResults.flatMap((result) => result.reviewedCriticalFindings);
|
||||
const layout = resolveReviewedSourceLayout(allReviewedFindings);
|
||||
const errors: string[] = [];
|
||||
@@ -402,11 +395,11 @@ export async function runPluginNpmSecurityScan(params: {
|
||||
if (!layout) {
|
||||
errors.push("Reviewed critical findings do not match exactly one supported release layout.");
|
||||
}
|
||||
if (packages.length === 0) {
|
||||
if (packageResults.length === 0) {
|
||||
errors.push("No publishable npm plugins were found in the candidate checkout.");
|
||||
}
|
||||
|
||||
const publishablePackageNames = new Set(packages.map((plugin) => plugin.packageName));
|
||||
const publishablePackageNames = new Set(packageResults.map((result) => result.packageName));
|
||||
const requiredFindingCounts = new Map<string, number>([
|
||||
...COMMON_REVIEWED_CRITICAL_FINDING_COUNTS,
|
||||
...(layout?.findings ?? []),
|
||||
@@ -462,3 +455,18 @@ export async function runPluginNpmSecurityScan(params: {
|
||||
toolingSha,
|
||||
};
|
||||
}
|
||||
|
||||
export async function runPluginNpmSecurityScan(params: {
|
||||
candidateDir: string;
|
||||
toolingDir: string;
|
||||
}): Promise<PluginNpmSecurityScanReport> {
|
||||
const candidateDir = realpathSync(params.candidateDir);
|
||||
const toolingDir = realpathSync(params.toolingDir);
|
||||
const [candidateSha, toolingSha, packages] = await Promise.all([
|
||||
gitOutput(candidateDir, ["rev-parse", "HEAD"]),
|
||||
gitOutput(toolingDir, ["rev-parse", "HEAD"]),
|
||||
listPublishablePluginPackages(candidateDir),
|
||||
]);
|
||||
const packageResults = await Promise.all(packages.map(scanPublishablePluginPackage));
|
||||
return buildPluginNpmSecurityScanReport({ candidateSha, packageResults, toolingSha });
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
assertCompleteScannerSummary,
|
||||
buildPluginNpmSecurityScanReport,
|
||||
collectNpmPackedFiles,
|
||||
resolveReviewedSourceLayout,
|
||||
runPluginNpmSecurityScan,
|
||||
@@ -13,12 +14,12 @@ import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
|
||||
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
||||
|
||||
describe("scripts/lib/plugin-npm-security-scan.mts", () => {
|
||||
it("accepts only the complete current and beta3 source layouts", () => {
|
||||
it("accepts only the complete current and frozen-legacy source layouts", () => {
|
||||
const current = [
|
||||
"@openclaw/codex:dangerous-exec:src/app-server/sandbox-exec-server/sandbox-child.ts",
|
||||
"@openclaw/codex:dangerous-exec:src/app-server/transport-process-containment.ts",
|
||||
];
|
||||
const beta3 = [
|
||||
const frozenLegacy = [
|
||||
"@openclaw/codex:dangerous-exec:src/app-server/sandbox-exec-server/http.ts",
|
||||
"@openclaw/codex:dangerous-exec:src/app-server/sandbox-exec-server/processes.ts",
|
||||
"@openclaw/codex:dangerous-exec:src/node-cli-sessions.ts",
|
||||
@@ -26,9 +27,9 @@ describe("scripts/lib/plugin-npm-security-scan.mts", () => {
|
||||
];
|
||||
|
||||
expect(resolveReviewedSourceLayout(current)?.id).toBe("current");
|
||||
expect(resolveReviewedSourceLayout(beta3)?.id).toBe("beta3");
|
||||
expect(resolveReviewedSourceLayout(beta3.slice(0, -1))).toBeUndefined();
|
||||
expect(resolveReviewedSourceLayout([...current, beta3[0]!])).toBeUndefined();
|
||||
expect(resolveReviewedSourceLayout(frozenLegacy)?.id).toBe("frozen-legacy");
|
||||
expect(resolveReviewedSourceLayout(frozenLegacy.slice(0, -1))).toBeUndefined();
|
||||
expect(resolveReviewedSourceLayout([...current, frozenLegacy[0]!])).toBeUndefined();
|
||||
expect(resolveReviewedSourceLayout([...current, current[0]!])).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -97,5 +98,19 @@ describe("scripts/lib/plugin-npm-security-scan.mts", () => {
|
||||
});
|
||||
expect(report.summary.packageCount).toBe(report.packages.length);
|
||||
expect(report.summary.packageCount).toBeGreaterThan(0);
|
||||
|
||||
const packageResults = structuredClone(report.packages);
|
||||
packageResults[0]!.unexpectedCriticalFindings.push(
|
||||
`${packageResults[0]!.packageName}:dangerous-exec:src/candidate-owned-scanner.ts:1:exec`,
|
||||
);
|
||||
const rejectedReport = buildPluginNpmSecurityScanReport({
|
||||
candidateSha: report.candidateSha,
|
||||
packageResults,
|
||||
toolingSha: report.toolingSha,
|
||||
});
|
||||
expect(rejectedReport.status).toBe("fail");
|
||||
expect(rejectedReport.errors).toContainEqual(
|
||||
expect.stringContaining("unexpected critical findings"),
|
||||
);
|
||||
}, 120_000);
|
||||
});
|
||||
|
||||
@@ -326,6 +326,9 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
|
||||
expect(runSecurityScan?.run).toContain(
|
||||
"node --import tsx scripts/plugin-npm-security-scan.mts",
|
||||
);
|
||||
expect(runSecurityScan?.run).not.toContain(
|
||||
".release-candidate/scripts/plugin-npm-security-scan.mts",
|
||||
);
|
||||
expect(runSecurityScan?.run).toContain("--candidate-root .release-candidate");
|
||||
expect(runSecurityScan?.run).toContain('--candidate-sha "$CANDIDATE_SHA"');
|
||||
expect(uploadReport).toMatchObject({
|
||||
@@ -341,6 +344,7 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
|
||||
expect(pluginSource).not.toContain("npm-install-security-scan.release.test.ts");
|
||||
expect(pluginSource).not.toContain("node_test_exclude_patterns_json");
|
||||
expect(releaseSource).not.toContain("plugin_prerelease_node_exclude_patterns_json");
|
||||
expect(releaseSource).not.toContain("Plugin prerelease Node exclusions");
|
||||
expect(pluginDispatch?.run).not.toContain("node_test_exclude_patterns_json");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user