Skip to content
24 changes: 23 additions & 1 deletion test/openclaw-config-guard-absent-hash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ function fixture() {
return { root, configDir, configPath, hashPath };
}

function runGuard(action: "lock" | "unlock", configDir: string, failure = "none") {
function runGuard(action: "lock" | "preflight" | "unlock", configDir: string, failure = "none") {
const result = spawnSync(
"python3",
["-c", RUN_AS_CURRENT_USER, GUARD_PATH, action, configDir, failure],
Expand Down Expand Up @@ -172,6 +172,28 @@ describe("openclaw-config-guard lock with an absent .config-hash", () => {
expect(rejectedNames(configDir)).toEqual([]);
});

it("replaces openclaw.json so a retained writable descriptor cannot mutate the sealed config", () => {
const { configDir, configPath, hashPath } = fixture();
const retainedDescriptor = fs.openSync(configPath, "r+");
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
try {
fs.rmSync(hashPath);

const result = runGuard("lock", configDir);

expect(result.status, JSON.stringify(result.lines)).toBe(0);
const attackerBytes = Buffer.from('{"gateway":{"port":19999}}\n');
expect(fs.writeSync(retainedDescriptor, attackerBytes, 0, attackerBytes.length, 0)).toBe(
attackerBytes.length,
);
fs.fsyncSync(retainedDescriptor);
const verification = runGuard("preflight", configDir);
expect(verification.status, JSON.stringify(verification.lines)).toBe(0);
expect(fs.readFileSync(hashPath, "utf-8")).toBe(CONFIG_HASH_RECORD);
} finally {
fs.closeSync(retainedDescriptor);
}
});

it("relocks idempotently after a synthesized-hash lock without rewriting inodes", () => {
const { configDir, configPath, hashPath } = fixture();
fs.rmSync(hashPath);
Expand Down
Loading