CLI: skip exec SecretRef dry-run resolution unless explicitly allowed (#49322)

* CLI: gate exec SecretRef dry-run resolution behind opt-in

* Docs: clarify config dry-run exec opt-in behavior

* CLI: preserve static exec dry-run validation
This commit is contained in:
Josh Avant
2026-03-17 20:20:11 -05:00
committed by GitHub
parent 9a455a8c08
commit 2d3bcbfe08
7 changed files with 495 additions and 14 deletions

View File

@@ -23,6 +23,39 @@ function createTestRuntime() {
};
}
function createExecDryRunBatch(params: { markerPath: string }) {
const response = JSON.stringify({
protocolVersion: 1,
values: {
dryrun_id: "ok",
},
});
const script = [
'const fs = require("node:fs");',
`fs.writeFileSync(${JSON.stringify(params.markerPath)}, "dryrun\\n", "utf8");`,
`process.stdout.write(${JSON.stringify(response)});`,
].join("");
return [
{
path: "secrets.providers.runner",
provider: {
source: "exec",
command: process.execPath,
args: ["-e", script],
allowInsecurePath: true,
},
},
{
path: "channels.discord.token",
ref: {
source: "exec",
provider: "runner",
id: "dryrun_id",
},
},
];
}
describe("config cli integration", () => {
it("supports batch-file dry-run and then writes real config changes", async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-config-cli-int-"));
@@ -183,4 +216,115 @@ describe("config cli integration", () => {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
it("skips exec provider execution during dry-run by default", async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-config-cli-int-exec-skip-"));
const configPath = path.join(tempDir, "openclaw.json");
const batchPath = path.join(tempDir, "batch.json");
const markerPath = path.join(tempDir, "marker.txt");
const envSnapshot = captureEnv(["OPENCLAW_CONFIG_PATH", "OPENCLAW_TEST_FAST"]);
try {
fs.writeFileSync(
configPath,
`${JSON.stringify(
{
gateway: { port: 18789 },
},
null,
2,
)}\n`,
"utf8",
);
fs.writeFileSync(
batchPath,
`${JSON.stringify(createExecDryRunBatch({ markerPath }), null, 2)}\n`,
"utf8",
);
process.env.OPENCLAW_TEST_FAST = "1";
process.env.OPENCLAW_CONFIG_PATH = configPath;
clearConfigCache();
clearRuntimeConfigSnapshot();
const runtime = createTestRuntime();
const before = fs.readFileSync(configPath, "utf8");
await runConfigSet({
cliOptions: {
batchFile: batchPath,
dryRun: true,
},
runtime: runtime.runtime,
});
const after = fs.readFileSync(configPath, "utf8");
expect(after).toBe(before);
expect(fs.existsSync(markerPath)).toBe(false);
expect(
runtime.logs.some((line) =>
line.includes("Dry run note: skipped 1 exec SecretRef resolvability check(s)."),
),
).toBe(true);
} finally {
envSnapshot.restore();
clearConfigCache();
clearRuntimeConfigSnapshot();
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
it("executes exec providers during dry-run when --allow-exec is set", async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-config-cli-int-exec-allow-"));
const configPath = path.join(tempDir, "openclaw.json");
const batchPath = path.join(tempDir, "batch.json");
const markerPath = path.join(tempDir, "marker.txt");
const envSnapshot = captureEnv(["OPENCLAW_CONFIG_PATH", "OPENCLAW_TEST_FAST"]);
try {
fs.writeFileSync(
configPath,
`${JSON.stringify(
{
gateway: { port: 18789 },
},
null,
2,
)}\n`,
"utf8",
);
fs.writeFileSync(
batchPath,
`${JSON.stringify(createExecDryRunBatch({ markerPath }), null, 2)}\n`,
"utf8",
);
process.env.OPENCLAW_TEST_FAST = "1";
process.env.OPENCLAW_CONFIG_PATH = configPath;
clearConfigCache();
clearRuntimeConfigSnapshot();
const runtime = createTestRuntime();
const before = fs.readFileSync(configPath, "utf8");
await runConfigSet({
cliOptions: {
batchFile: batchPath,
dryRun: true,
allowExec: true,
},
runtime: runtime.runtime,
});
const after = fs.readFileSync(configPath, "utf8");
expect(after).toBe(before);
expect(fs.existsSync(markerPath)).toBe(true);
expect(
runtime.logs.some((line) =>
line.includes("Dry run note: skipped 1 exec SecretRef resolvability check(s)."),
),
).toBe(false);
} finally {
envSnapshot.restore();
clearConfigCache();
clearRuntimeConfigSnapshot();
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
});