mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 12:26:38 -06:00
fix(release): keep prerelease scanner supplemental
This commit is contained in:
@@ -101,10 +101,9 @@ function writePluginArtifact(params: {
|
||||
writeFileSync(
|
||||
join(artifactDir, "plugin-npm-security-artifact.json"),
|
||||
`${JSON.stringify({
|
||||
artifactKind: "publication-equivalent-plugin-tarball",
|
||||
artifactKind: "supplemental-inert-package-input",
|
||||
candidateSha: CANDIDATE_SHA,
|
||||
extensionId: params.extensionId,
|
||||
packOwner: "scripts/plugin-npm-publish.sh",
|
||||
packageDir: `extensions/${params.extensionId}`,
|
||||
packageName: params.packageName,
|
||||
packageVersion,
|
||||
@@ -117,7 +116,7 @@ function writePluginArtifact(params: {
|
||||
);
|
||||
return {
|
||||
artifact: {
|
||||
artifactKind: "publication-equivalent-plugin-tarball" as const,
|
||||
artifactKind: "supplemental-inert-package-input" as const,
|
||||
artifactDir,
|
||||
candidateSha: CANDIDATE_SHA,
|
||||
compressedBytes: readFileSync(tarballPath).byteLength,
|
||||
@@ -126,7 +125,6 @@ function writePluginArtifact(params: {
|
||||
0,
|
||||
),
|
||||
extensionId: params.extensionId,
|
||||
packOwner: "scripts/plugin-npm-publish.sh" as const,
|
||||
packageDir: `extensions/${params.extensionId}`,
|
||||
packageName: params.packageName,
|
||||
packageVersion,
|
||||
@@ -192,37 +190,37 @@ describe("scripts/lib/plugin-npm-security-scan.mts", () => {
|
||||
expect(resolveReviewedSourceLayout([...current, current[0]!])).toBeUndefined();
|
||||
});
|
||||
|
||||
it("scans malicious output generated by the canonical publication pack boundary", async () => {
|
||||
const candidateRoot = tempDirs.make("openclaw-plugin-security-built-pack-");
|
||||
const artifactRoot = tempDirs.make("openclaw-plugin-security-built-artifacts-");
|
||||
const packageDir = join(candidateRoot, "extensions", "generated");
|
||||
const trustedPackScript = join(
|
||||
tempDirs.make("openclaw-plugin-security-trusted-pack-"),
|
||||
"pack.sh",
|
||||
);
|
||||
const generatedPath = join(packageDir, "dist", "generated.js");
|
||||
const lifecycleMarkers = ["prepare", "prepack", "postpack"].map((name) =>
|
||||
join(candidateRoot, `${name}-ran`),
|
||||
it("scans checked-in malicious code without running candidate hooks or helpers", async () => {
|
||||
const candidateRoot = tempDirs.make("openclaw-plugin-security-inert-pack-");
|
||||
const artifactRoot = tempDirs.make("openclaw-plugin-security-inert-artifacts-");
|
||||
const packageDir = join(candidateRoot, "extensions", "inert");
|
||||
const executionMarkers = ["asset", "prepare", "prepack", "postpack", "replacement"].map(
|
||||
(name) => join(candidateRoot, `${name}-ran`),
|
||||
);
|
||||
initGitRepo(candidateRoot);
|
||||
mkdirSync(packageDir, { recursive: true });
|
||||
const markerCommand = (marker: string) =>
|
||||
`node -e "require('node:fs').writeFileSync(${JSON.stringify(marker)}, 'ran')"`;
|
||||
const generatedCode = 'const { execSync } = require("node:child_process");\nexecSync("id");\n';
|
||||
const assetBuild = "node build-assets.mjs";
|
||||
const maliciousCode = 'const { execSync } = require("node:child_process");\nexecSync("id");\n';
|
||||
writeFileSync(
|
||||
join(packageDir, "package.json"),
|
||||
`${JSON.stringify({
|
||||
files: ["dist", "openclaw.plugin.json", "package.json"],
|
||||
name: "@openclaw/test-generated-pack",
|
||||
files: [
|
||||
"build-assets.mjs",
|
||||
"index.js",
|
||||
"openclaw.plugin.json",
|
||||
"package.json",
|
||||
"plugin-npm-security-scan.mjs",
|
||||
],
|
||||
name: "@openclaw/test-inert-pack",
|
||||
openclaw: {
|
||||
assetScripts: { build: assetBuild },
|
||||
assetScripts: { build: markerCommand(executionMarkers[0]!) },
|
||||
release: { publishToNpm: true },
|
||||
},
|
||||
scripts: {
|
||||
postpack: markerCommand(lifecycleMarkers[2]!),
|
||||
prepack: markerCommand(lifecycleMarkers[1]!),
|
||||
prepare: markerCommand(lifecycleMarkers[0]!),
|
||||
postpack: markerCommand(executionMarkers[3]!),
|
||||
prepack: markerCommand(executionMarkers[2]!),
|
||||
prepare: markerCommand(executionMarkers[1]!),
|
||||
},
|
||||
version: "2026.8.1-beta.1",
|
||||
})}\n`,
|
||||
@@ -230,32 +228,18 @@ describe("scripts/lib/plugin-npm-security-scan.mts", () => {
|
||||
);
|
||||
writeFileSync(
|
||||
join(packageDir, "openclaw.plugin.json"),
|
||||
`${JSON.stringify({ id: "generated" })}\n`,
|
||||
`${JSON.stringify({ id: "inert" })}\n`,
|
||||
"utf8",
|
||||
);
|
||||
writeFileSync(join(packageDir, "index.js"), maliciousCode, "utf8");
|
||||
writeFileSync(
|
||||
join(packageDir, "build-assets.mjs"),
|
||||
[
|
||||
'import { mkdirSync, writeFileSync } from "node:fs";',
|
||||
'mkdirSync("dist", { recursive: true });',
|
||||
`writeFileSync("dist/generated.js", ${JSON.stringify(generatedCode)});`,
|
||||
"",
|
||||
].join("\n"),
|
||||
`require("node:fs").writeFileSync(${JSON.stringify(executionMarkers[0])}, "ran");\n`,
|
||||
"utf8",
|
||||
);
|
||||
writeFileSync(
|
||||
trustedPackScript,
|
||||
[
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
'repo_root="$2"',
|
||||
'package_dir="$4"',
|
||||
'cd "$repo_root/$package_dir"',
|
||||
'asset_build="$(node -p "require(\'./package.json\').openclaw.assetScripts.build")"',
|
||||
'bash -c "$asset_build"',
|
||||
'npm pack --json --ignore-scripts --pack-destination "$OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR"',
|
||||
"",
|
||||
].join("\n"),
|
||||
join(packageDir, "plugin-npm-security-scan.mjs"),
|
||||
`require("node:fs").writeFileSync(${JSON.stringify(executionMarkers[4])}, "ran");\n`,
|
||||
"utf8",
|
||||
);
|
||||
execFileSync("git", ["-C", candidateRoot, "add", "."]);
|
||||
@@ -263,7 +247,7 @@ describe("scripts/lib/plugin-npm-security-scan.mts", () => {
|
||||
const candidateSha = execFileSync("git", ["-C", candidateRoot, "rev-parse", "HEAD"], {
|
||||
encoding: "utf8",
|
||||
}).trim();
|
||||
const outputDir = join(artifactRoot, `plugin-npm-security-package-${candidateSha}-generated`);
|
||||
const outputDir = join(artifactRoot, `plugin-npm-security-package-${candidateSha}-inert`);
|
||||
const toolingSha = execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf8" }).trim();
|
||||
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
@@ -279,41 +263,36 @@ describe("scripts/lib/plugin-npm-security-scan.mts", () => {
|
||||
"--candidate-sha",
|
||||
candidateSha,
|
||||
"--extension-id",
|
||||
"generated",
|
||||
"inert",
|
||||
"--output-dir",
|
||||
outputDir,
|
||||
"--package-dir",
|
||||
"extensions/generated",
|
||||
"extensions/inert",
|
||||
"--package-name",
|
||||
"@openclaw/test-generated-pack",
|
||||
"@openclaw/test-inert-pack",
|
||||
"--tooling-sha",
|
||||
toolingSha,
|
||||
],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: "test",
|
||||
OPENCLAW_PLUGIN_SECURITY_TEST_PUBLISH_SCRIPT: trustedPackScript,
|
||||
},
|
||||
env: { ...process.env, NODE_ENV: "test" },
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(0);
|
||||
expect(readFileSync(generatedPath, "utf8")).toBe(generatedCode);
|
||||
for (const marker of lifecycleMarkers) {
|
||||
for (const marker of executionMarkers) {
|
||||
expect(existsSync(marker)).toBe(false);
|
||||
}
|
||||
const metadata = JSON.parse(
|
||||
readFileSync(join(outputDir, "plugin-npm-security-artifact.json"), "utf8"),
|
||||
) as { artifactKind?: unknown; tarballName?: unknown };
|
||||
expect(metadata.artifactKind).toBe("publication-equivalent-plugin-tarball");
|
||||
expect(metadata.artifactKind).toBe("supplemental-inert-package-input");
|
||||
expect(typeof metadata.tarballName).toBe("string");
|
||||
const expectedPackage = {
|
||||
extensionId: "generated",
|
||||
packageDir: "extensions/generated",
|
||||
packageName: "@openclaw/test-generated-pack",
|
||||
extensionId: "inert",
|
||||
packageDir: "extensions/inert",
|
||||
packageName: "@openclaw/test-inert-pack",
|
||||
packageVersion: "2026.8.1-beta.1",
|
||||
};
|
||||
const loaded = loadPluginNpmSecurityArtifacts({
|
||||
@@ -327,7 +306,7 @@ describe("scripts/lib/plugin-npm-security-scan.mts", () => {
|
||||
expect(scanned.scanErrors).toEqual([]);
|
||||
expect(scanned.packageResults[0]?.unexpectedCriticalFindings).toContainEqual({
|
||||
line: 2,
|
||||
path: "dist/generated.js",
|
||||
path: "index.js",
|
||||
ruleId: "dangerous-exec",
|
||||
});
|
||||
});
|
||||
@@ -600,6 +579,7 @@ describe("scripts/lib/plugin-npm-security-scan.mts", () => {
|
||||
expect(report.errors).toContain(
|
||||
"Plugin npm security scan exceeded the total finding-count limit.",
|
||||
);
|
||||
expect(report.scanScope).toBe("supplemental-inert-package-input");
|
||||
expect(JSON.stringify(report)).toBe(
|
||||
JSON.stringify(
|
||||
buildPluginNpmSecurityScanReport({
|
||||
|
||||
@@ -300,7 +300,7 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
|
||||
(step: WorkflowStep) => step.name === "Install trusted scanner dependencies",
|
||||
);
|
||||
const runSecurityScan = securityScan.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Scan publication-equivalent plugin artifacts",
|
||||
(step: WorkflowStep) => step.name === "Scan supplemental inert plugin inputs",
|
||||
);
|
||||
const uploadReport = securityScan.steps.find(
|
||||
(step: WorkflowStep) => step.name === "Upload plugin npm security scan report",
|
||||
@@ -372,12 +372,21 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
|
||||
expect(JSON.stringify(securityPackage)).not.toContain("id-token");
|
||||
expect(JSON.stringify(securityPackage)).not.toContain("packages: write");
|
||||
expect(JSON.stringify(securityPackage)).not.toContain("${{ secrets.");
|
||||
expect(securityPrepareSource).toContain('"scripts", "plugin-npm-publish.sh"');
|
||||
expect(securityPrepareSource).toContain('"--pack"');
|
||||
expect(securityPrepareSource).toContain("shell: false");
|
||||
expect(JSON.stringify(securityPackage)).not.toContain("plugin-npm-publish.sh");
|
||||
expect(JSON.stringify(securityPackage)).not.toContain("plugin-npm-runtime-build");
|
||||
expect(JSON.stringify(securityPackage)).not.toContain("generate-npm-package-lock");
|
||||
expect(securityPrepareSource).not.toContain("plugin-npm-publish.sh");
|
||||
expect(securityPrepareSource).not.toContain("plugin-npm-runtime-build");
|
||||
expect(securityPrepareSource).not.toContain("generate-npm-package-lock");
|
||||
expect(securityPrepareSource).not.toContain("...process.env");
|
||||
expect(securityPrepareSource).toContain('"--ignore-scripts"');
|
||||
expect(securityPrepareSource).toContain('"--workspaces=false"');
|
||||
expect(securityPrepareSource).toContain(
|
||||
'NPM_CONFIG_USERCONFIG: join(tmpdir(), "openclaw-plugin-security-empty-npmrc")',
|
||||
);
|
||||
expect(securityPrepareSource).not.toContain("GITHUB_OUTPUT: process.env.GITHUB_OUTPUT");
|
||||
expect(securityPrepareSource).toContain("resolveCandidatePluginPackageDir");
|
||||
expect(pluginNpmReleaseSource).toContain("bash .release-tooling/scripts/plugin-npm-publish.sh");
|
||||
expect(pluginNpmReleaseSource).toContain('--pack "${PACKAGE_DIR}"');
|
||||
expect(pluginNpmReleaseSource).toContain("plugin-publication-artifact.mjs verify");
|
||||
expect(nodeShard.needs).toEqual(["resolve-candidate", "preflight"]);
|
||||
expect(runNodeShard?.run).toContain('spawnSync("pnpm", ["test", "--", ...configs]');
|
||||
expect(pluginSource).not.toContain("npm-install-security-scan.release.test.ts");
|
||||
|
||||
Reference in New Issue
Block a user