fix(e2e): bound secret resolver stdin

This commit is contained in:
Vincent Koc
2026-06-07 04:19:16 +02:00
parent 06e8a74473
commit 326c4e0e35
2 changed files with 72 additions and 2 deletions

View File

@@ -45,6 +45,11 @@ const OUTPUT_CAPTURE_LIMIT_BYTES = readPositiveInt(
4 * 1024 * 1024,
"OPENCLAW_SECRET_PROOF_OUTPUT_BYTES",
);
const RESOLVER_STDIN_LIMIT_BYTES = readPositiveInt(
process.env.OPENCLAW_SECRET_PROOF_RESOLVER_STDIN_BYTES,
1024 * 1024,
"OPENCLAW_SECRET_PROOF_RESOLVER_STDIN_BYTES",
);
const RESULTS_PATH =
process.env.OPENCLAW_SECRET_PROOF_RESULTS_PATH?.trim() ||
path.join(os.tmpdir(), `openclaw-secret-provider-e2e-results-${process.pid}.json`);
@@ -596,15 +601,36 @@ if (!storePath) {
console.error("missing PROOF_SECRET_STORE_PATH");
process.exit(4);
}
const stdinLimitBytes = ${RESOLVER_STDIN_LIMIT_BYTES};
function readStdin() {
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
let body = "";
let bytes = 0;
let failed = false;
const fail = (error) => {
if (failed) {
return;
}
failed = true;
reject(error);
};
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
bytes += Buffer.byteLength(chunk, "utf8");
if (bytes > stdinLimitBytes) {
fail(new Error(\`resolver stdin exceeded \${stdinLimitBytes} bytes\`));
process.stdin.destroy();
return;
}
body += chunk;
});
process.stdin.on("end", () => resolve(body));
process.stdin.on("error", fail);
process.stdin.on("end", () => {
if (!failed) {
resolve(body);
}
});
});
}
@@ -1835,6 +1861,7 @@ export {
runCommand,
startGateway,
waitForManagedGatewayStatus,
writeProofPlugin,
};
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {

View File

@@ -258,6 +258,7 @@ describe("secret provider integration proof harness", () => {
["OPENCLAW_SECRET_PROOF_COMMAND_MS", "150ms"],
["OPENCLAW_SECRET_PROOF_READY_MS", "0"],
["OPENCLAW_SECRET_PROOF_OUTPUT_BYTES", "4mb"],
["OPENCLAW_SECRET_PROOF_RESOLVER_STDIN_BYTES", "4mb"],
])("rejects malformed proof env limit %s=%s", async (name, value) => {
const previous = process.env[name];
process.env[name] = value;
@@ -274,6 +275,48 @@ describe("secret provider integration proof harness", () => {
}
});
it("bounds generated resolver stdin before reading the secret store", async () => {
const root = makeTempDir();
const stateDir = path.join(root, "state");
fs.mkdirSync(stateDir, { recursive: true });
const storePath = path.join(stateDir, "proof-secret-store.json");
fs.writeFileSync(
storePath,
`${JSON.stringify({ mode: "ok", calls: 0, values: { "proof/id": "ok" } }, null, 2)}\n`,
"utf8",
);
const previousLimit = process.env.OPENCLAW_SECRET_PROOF_RESOLVER_STDIN_BYTES;
process.env.OPENCLAW_SECRET_PROOF_RESOLVER_STDIN_BYTES = "64";
try {
const proof = await import(
`${pathToFileURL(proofScriptPath).href}?case=resolver-stdin-${Date.now()}`
);
const plugin = proof.writeProofPlugin({ stateDir });
const result = spawnSync(process.execPath, [plugin.resolverPath], {
cwd: plugin.pluginRoot,
encoding: "utf8",
env: {
...process.env,
PROOF_SECRET_STORE_PATH: storePath,
},
input: JSON.stringify({ ids: ["proof/id"], padding: "x".repeat(512) }),
timeout: 5_000,
});
expect(result.error).toBeUndefined();
expect(result.status).not.toBe(0);
expect(`${result.stderr}${result.stdout}`).toContain("resolver stdin exceeded 64 bytes");
expect(JSON.parse(fs.readFileSync(storePath, "utf8")).calls).toBe(0);
} finally {
if (previousLimit === undefined) {
delete process.env.OPENCLAW_SECRET_PROOF_RESOLVER_STDIN_BYTES;
} else {
process.env.OPENCLAW_SECRET_PROOF_RESOLVER_STDIN_BYTES = previousLimit;
}
}
});
it("fails when proof temp cleanup cannot remove the root", async () => {
const proof = await import(`${pathToFileURL(proofScriptPath).href}?case=cleanup-${Date.now()}`);
const rmSync = vi.spyOn(fs, "rmSync").mockImplementation(() => {