Files
my-pi/pi-permission-system/test/access-intent/bash/command-enumeration.test.ts
T
云服务部-叶林立andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 2b21163a18 fix(pi-permission-system): bash 命令枚举下降进入控制流与赋值载荷
for/while/if/case/{ }/函数体此前只按整段文本匹配,
"for d in ...; do n=$(find ...); done" 中的 find 绕过了 find* deny(真实会话复现);
x=$(grep ...)、export X=$(find ...)、! grep ... 同样绕过。

控制流语句仍整段判定,并逐条枚举条件与体内命令;do/else/case 分支容器只下降不单独成单元;
其余语句兜底额外枚举其中的 $(…)。结果只会更严,hard guard 共享同一枚举。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-22 00:14:49 -07:00

180 lines
5.5 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
resetWarmBashParser,
warmBashParser,
} from "#src/access-intent/bash/parser";
import { parseBashCommandsSync } from "#src/access-intent/bash/sync-commands";
describe("collectCommands — control-flow descent", () => {
beforeEach(async () => {
resetWarmBashParser();
await warmBashParser();
});
afterEach(() => {
resetWarmBashParser();
});
it("emits a for-loop's body commands in addition to the whole statement", () => {
expect(
parseBashCommandsSync('for d in a b; do find "$d" -name x; done'),
).toEqual([
{ text: 'for d in a b; do find "$d" -name x; done' },
{ text: 'find "$d" -name x' },
]);
});
it("emits a command substitution in the for value list as a nested context", () => {
expect(
parseBashCommandsSync('for f in $(find . -name x); do echo "$f"; done'),
).toEqual([
{ text: 'for f in $(find . -name x); do echo "$f"; done' },
{ text: "find . -name x", context: "command_substitution" },
{ text: 'echo "$f"' },
]);
});
it("emits the condition and body commands of a redirected while loop", () => {
expect(
parseBashCommandsSync('while read l; do grep x "$l"; done < list'),
).toEqual([
{ text: 'while read l; do grep x "$l"; done' },
{ text: "read l" },
{ text: 'grep x "$l"' },
]);
});
it("emits an if's condition, branch, and else commands", () => {
expect(
parseBashCommandsSync("if grep -q x f; then echo y; else rm z; fi"),
).toEqual([
{ text: "if grep -q x f; then echo y; else rm z; fi" },
{ text: "grep -q x f" },
{ text: "echo y" },
{ text: "rm z" },
]);
});
it("emits a case item's commands", () => {
expect(parseBashCommandsSync("case $x in a) find . ;; esac")).toEqual([
{ text: "case $x in a) find . ;; esac" },
{ text: "find ." },
]);
});
it("emits a command substitution in the case subject as a nested context", () => {
expect(
parseBashCommandsSync("case $(rm x) in a) find . ;; esac"),
).toEqual([
{ text: "case $(rm x) in a) find . ;; esac" },
{ text: "rm x", context: "command_substitution" },
{ text: "find ." },
]);
});
it("descends a brace group", () => {
expect(parseBashCommandsSync("{ grep x f; }")).toEqual([
{ text: "{ grep x f; }" },
{ text: "grep x f" },
]);
});
it("descends a function definition's body", () => {
expect(parseBashCommandsSync("f() { grep x f; }")).toEqual([
{ text: "f() { grep x f; }" },
{ text: "{ grep x f; }" },
{ text: "grep x f" },
]);
});
it("descends nested loops", () => {
expect(
parseBashCommandsSync("for a in 1; do for b in 2; do find .; done; done"),
).toEqual([
{ text: "for a in 1; do for b in 2; do find .; done; done" },
{ text: "for b in 2; do find .; done" },
{ text: "find ." },
]);
});
it("descends a c-style for loop", () => {
expect(
parseBashCommandsSync("for ((i=0;i<3;i++)); do echo i; done"),
).toEqual([
{ text: "for ((i=0;i<3;i++)); do echo i; done" },
{ text: "echo i" },
]);
});
it("emits no container units for a clean loop body", () => {
expect(parseBashCommandsSync('for d in a b; do echo "$d"; done')).toEqual([
{ text: 'for d in a b; do echo "$d"; done' },
{ text: 'echo "$d"' },
]);
});
it("does not emit a [[ ]] test as a separate command unit", () => {
expect(parseBashCommandsSync("if [[ -f x ]]; then echo y; fi")).toEqual([
{ text: "if [[ -f x ]]; then echo y; fi" },
{ text: "echo y" },
]);
});
it("catches a substitution hosted inside a [[ ]] test", () => {
expect(
parseBashCommandsSync('if [[ "$(rm x)" = y ]]; then echo y; fi'),
).toEqual([
{ text: 'if [[ "$(rm x)" = y ]]; then echo y; fi' },
{ text: "rm x", context: "command_substitution" },
{ text: "echo y" },
]);
});
});
describe("collectCommands — assignment, declaration, and negation", () => {
beforeEach(async () => {
resetWarmBashParser();
await warmBashParser();
});
afterEach(() => {
resetWarmBashParser();
});
it("emits a substitution in a plain assignment value as a nested context", () => {
expect(parseBashCommandsSync("x=$(grep foo f)")).toEqual([
{ text: "x=$(grep foo f)" },
{ text: "grep foo f", context: "command_substitution" },
]);
});
it("emits a substitution in an export declaration value", () => {
expect(parseBashCommandsSync("export X=$(find .)")).toEqual([
{ text: "export X=$(find .)" },
{ text: "find .", context: "command_substitution" },
]);
});
it("emits a substitution in a local declaration inside a function body", () => {
expect(parseBashCommandsSync("f() { local y=$(rg z); }")).toEqual([
{ text: "f() { local y=$(rg z); }" },
{ text: "{ local y=$(rg z); }" },
{ text: "rg z", context: "command_substitution" },
]);
});
it("emits a negated command's inner command as a normal unit", () => {
expect(parseBashCommandsSync("! grep -q x f")).toEqual([
{ text: "! grep -q x f" },
{ text: "grep -q x f" },
]);
});
it("emits a negated condition's inner command inside an if", () => {
expect(parseBashCommandsSync("if ! grep -q x f; then :; fi")).toEqual([
{ text: "if ! grep -q x f; then :; fi" },
{ text: "! grep -q x f" },
{ text: "grep -q x f" },
{ text: ":" },
]);
});
});