refactor(install): prepare immutable standalone script assembly (#162556)

Prepare trusted install-smoke sealing to inline a fixed shared policy member from the immutable candidate archive. Preserve existing standalone sources and payload identity/integrity checks; never execute a candidate generator.

Seven boundary tests passed on Linux Testbox. Changed checks and both import-cycle checks passed; independent review found no actionable issues.
This commit is contained in:
Peter Steinberger 2026-10-01 01:50:54 -07:00 • committed by GitHub
parent 3eaefa52af
commit de7997edc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 77 additions and 11 deletions

View file

@ -4,6 +4,7 @@ import { createHash } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { assembleStandaloneInstaller } from "./lib/standalone-installers.mjs";
const MANIFEST_NAME = "install-smoke-candidate-payload.json";
const SCHEMA = "openclaw.install-smoke-candidate-payload/v1";
@ -239,16 +240,18 @@ export async function sealInstallSmokeCandidatePayload(
// Re-read installers from the immutable source archive after candidate execution. Candidate
// build hooks never get a writable handle to the sealed scripts consumed by privileged jobs.
const installScript = runPythonTarReader([
"repo-file",
options.archivePath,
"scripts/install.sh",
]);
const cliInstallScript = runPythonTarReader([
"repo-file",
options.archivePath,
"scripts/install-cli.sh",
]);
const readInstaller = (name: string) =>
assembleStandaloneInstaller(
runPythonTarReader(["repo-file", options.archivePath, `scripts/${name}`]).toString("utf8"),
() =>
runPythonTarReader([
"repo-file",
options.archivePath,
"scripts/install-policy.sh",
]).toString("utf8"),
);
const installScript = readInstaller("install.sh");
const cliInstallScript = readInstaller("install-cli.sh");
await fs.copyFile(sourceTarballPath, path.join(options.outputDir, "candidate.tgz"));
await writeExclusive(
path.join(options.outputDir, "candidate-pack.json"),

View file

@ -0,0 +1,13 @@
const POLICY_INCLUDE = 'source "${BASH_SOURCE[0]%${BASH_SOURCE[0]##*/}}./install-policy.sh"';
// The source can come from a sealed archive. Never evaluate candidate shell code
// or load its generator while assembling a privileged install-smoke payload.
export function assembleStandaloneInstaller(source, readPolicy) {
if (!source.includes(POLICY_INCLUDE)) {
return source;
}
if (source.indexOf(POLICY_INCLUDE) !== source.lastIndexOf(POLICY_INCLUDE)) {
throw new Error("Installer must include shared policy exactly once");
}
return source.replace(POLICY_INCLUDE, () => readPolicy().trimEnd());
}

View file

@ -1,6 +1,14 @@
import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import { mkdirSync, readFileSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
import {
existsSync,
mkdirSync,
readFileSync,
statSync,
symlinkSync,
unlinkSync,
writeFileSync,
} from "node:fs";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
@ -96,6 +104,29 @@ function verifyOptions(payloadDir: string, manifestSha256: string, sourceArchive
}
describe("install smoke candidate payload", () => {
it("inlines archived policy without executing candidate code or reading mutable sources", async () => {
const fixture = createFixture();
const scripts = path.join(fixture.root, "candidate-root/scripts");
const marker = path.join(fixture.root, "executed");
writeFileSync(
path.join(scripts, "install.sh"),
'#!/bin/bash\nsource "${BASH_SOURCE[0]%${BASH_SOURCE[0]##*/}}./install-policy.sh"\necho install\n',
);
writeFileSync(path.join(scripts, "install-policy.sh"), `touch '${marker}'\n`);
createTarball(fixture.archivePath, fixture.root, ["candidate-root"]);
writeFileSync(path.join(scripts, "install-policy.sh"), "mutable source must not be used\n");
await sealInstallSmokeCandidatePayload({
...IDENTITY,
archivePath: fixture.archivePath,
outputDir: fixture.payloadDir,
packageDir: fixture.packageDir,
});
expect(existsSync(marker)).toBe(false);
expect(readFileSync(path.join(fixture.payloadDir, "install.sh"), "utf8")).toBe(
`#!/bin/bash\ntouch '${marker}'\necho install\n`,
);
});
it("seals source installers and package bytes into a fully bound payload", async () => {
const fixture = await sealFixture();
const verified = await verifyInstallSmokeCandidatePayload(
@ -138,6 +169,25 @@ describe("install smoke candidate payload", () => {
);
});
it("rejects a policy include backed by an archive symlink", async () => {
const fixture = createFixture();
const scripts = path.join(fixture.root, "candidate-root/scripts");
writeFileSync(
path.join(scripts, "install.sh"),
'#!/bin/bash\nsource "${BASH_SOURCE[0]%${BASH_SOURCE[0]##*/}}./install-policy.sh"\n',
);
symlinkSync("install-target.sh", path.join(scripts, "install-policy.sh"));
createTarball(fixture.archivePath, fixture.root, ["candidate-root"]);
await expect(
sealInstallSmokeCandidatePayload({
...IDENTITY,
archivePath: fixture.archivePath,
outputDir: fixture.payloadDir,
packageDir: fixture.packageDir,
}),
).rejects.toThrow("scripts/install-policy.sh must be a regular file");
});
it("rejects tampering with the final payload file after sealing", async () => {
const fixture = await sealFixture();
writeFileSync(path.join(fixture.payloadDir, "install-cli.sh"), "tampered\n");