Skip to content

Commit c3aeb9a

Browse files
committed
Lock secret-before-listing ordering for shell secret paths
Pure ls of a secret name still asks, and chains flag the content-reading half; bounded listings stay exempt and unbounded listings still ask. The secret-first ordering already holds at all three sites (classify auto-allow, gate segment guard, auto-shell policy) — these tests pin it.
1 parent 15968a1 commit c3aeb9a

1 file changed

Lines changed: 49 additions & 0 deletions

File tree

src/permission/classify-security.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,3 +1080,52 @@ describe("CL-6988 — nested / escaped interpreter peels do not auto-allow", ()
10801080
expect(rule?.effect === "ask" || rule?.effect === "deny").toBe(true);
10811081
});
10821082
});
1083+
1084+
describe("CL-5420 — secret checks run before pure-listing exemptions", () => {
1085+
test("a pure listing of a secret name still asks", () => {
1086+
const rule = autoShellRuleForCall(shellCall("ls .env"));
1087+
expect(rule?.name).toBe("sensitive-path");
1088+
expect(rule?.effect).toBe("ask");
1089+
});
1090+
1091+
test("a chain with a safe listing half flags the content-reading half", () => {
1092+
const rule = autoShellRuleForCall(shellCall("ls /tmp && cat .env"));
1093+
expect(rule?.name).toBe("sensitive-path");
1094+
expect(rule?.effect).toBe("ask");
1095+
});
1096+
1097+
test("a bounded listing with no secret reference stays exempt", () => {
1098+
expect(autoShellRuleForCall(shellCall("ls /tmp"))).toBeUndefined();
1099+
});
1100+
1101+
test("a flag-glued secret path asks", () => {
1102+
const rule = autoShellRuleForCall(
1103+
shellCall("bun --env-file=.env run publish.ts"),
1104+
);
1105+
expect(rule?.name).toBe("sensitive-path");
1106+
expect(rule?.effect).toBe("ask");
1107+
});
1108+
1109+
test("unbounded listing still asks", () => {
1110+
expect(autoShellRuleForCall(shellCall("ls -R"))?.name).toBe(
1111+
"unbounded-listing",
1112+
);
1113+
});
1114+
1115+
test("the gate asks on a pure listing of a secret name", async () => {
1116+
let asked = 0;
1117+
const gate = createPermissionGate({
1118+
approvals: [],
1119+
requestApproval: async () => {
1120+
asked++;
1121+
return { allow: false };
1122+
},
1123+
interactive: true,
1124+
skipPermissions: false,
1125+
reactorGated: false,
1126+
});
1127+
const verdict = await gate.evaluate(shellCall("ls .env"));
1128+
expect(verdict.allowed).toBe(false);
1129+
expect(asked).toBe(1);
1130+
});
1131+
});

0 commit comments

Comments
 (0)