Skip to content

Commit af469c8

Browse files
krissdingclaudesteipete
authored
fix(cli): guard secrets plan JSON.parse against malformed input (#109721)
* fix(cli): guard secrets plan JSON.parse against malformed input readPlanFile reads a user-specified file and parses it with JSON.parse without a try-catch. A malformed or corrupted plan file produces a raw SyntaxError that is not user-actionable. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test: tighten malformed secrets plan proof Co-authored-by: 丁宇婷0668001435 <[email protected]> --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
1 parent ce4a16b commit af469c8

2 files changed

Lines changed: 22 additions & 3 deletions

File tree

src/cli/secrets-cli.test.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,15 @@ function createSecretsApplyResult(options?: {
125125
};
126126
}
127127

128-
async function withPlanFile(run: (planPath: string) => Promise<void>) {
128+
async function withPlanFile(
129+
run: (planPath: string) => Promise<void>,
130+
contents = `${JSON.stringify(createManualSecretsPlan())}\n`,
131+
) {
129132
const planPath = path.join(
130133
os.tmpdir(),
131134
`openclaw-secrets-cli-test-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
132135
);
133-
await fs.writeFile(planPath, `${JSON.stringify(createManualSecretsPlan())}\n`, "utf8");
136+
await fs.writeFile(planPath, contents, "utf8");
134137
try {
135138
await run(planPath);
136139
} finally {
@@ -385,6 +388,17 @@ describe("secrets CLI", () => {
385388
});
386389
});
387390

391+
it("shows a user-friendly error when the secrets plan file is malformed JSON", async () => {
392+
await withPlanFile(async (planPath) => {
393+
await expect(
394+
createProgram().parseAsync(["secrets", "apply", "--from", planPath], { from: "user" }),
395+
).rejects.toThrow("__exit__:1");
396+
397+
expect(runtimeErrors.at(-1)).toContain(`Malformed JSON in secrets plan file: ${planPath}`);
398+
expect(runSecretsApply).not.toHaveBeenCalled();
399+
}, "{invalid json");
400+
});
401+
388402
it("does not print skipped-exec note when apply dry-run skippedExecRefs is zero", async () => {
389403
await withPlanFile(async (planPath) => {
390404
runSecretsApply.mockResolvedValue(createSecretsApplyResult({ resolvabilityComplete: false }));

src/cli/secrets-cli.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ async function readPlanFile(pathname: string): Promise<SecretsApplyPlan> {
5353
import("../secrets/plan.js"),
5454
]);
5555
const raw = readFileSync(pathname, "utf8");
56-
const parsed = JSON.parse(raw) as unknown;
56+
let parsed: unknown;
57+
try {
58+
parsed = JSON.parse(raw);
59+
} catch (err) {
60+
throw new Error(`Malformed JSON in secrets plan file: ${pathname}`, { cause: err });
61+
}
5762
if (!isSecretsApplyPlan(parsed)) {
5863
throw new Error(
5964
`Invalid secrets plan file: ${pathname}. Generate a fresh plan with ${formatCliCommand("openclaw secrets configure --plan-out <path>")}.`,

0 commit comments

Comments
 (0)