fix: allow quoted pipes in read-only bash parsing (#1194)
This commit is contained in:
@@ -430,6 +430,74 @@ test("plan mode - denies non-read-only Bash", () => {
|
||||
expect(result.matchedRule).toBe("plan mode");
|
||||
});
|
||||
|
||||
test("plan mode - allows Bash heredoc write to plan file", () => {
|
||||
permissionMode.setMode("plan");
|
||||
|
||||
const permissions: PermissionRules = {
|
||||
allow: [],
|
||||
deny: [],
|
||||
ask: [],
|
||||
};
|
||||
|
||||
const planPath = join(homedir(), ".letta", "plans", "unit-test-plan.md");
|
||||
const command = `cat > ${planPath} <<'EOF'\n# Plan\n- step 1\nEOF`;
|
||||
|
||||
const result = checkPermission(
|
||||
"Bash",
|
||||
{ command },
|
||||
permissions,
|
||||
"/Users/test/project",
|
||||
);
|
||||
|
||||
expect(result.decision).toBe("allow");
|
||||
expect(result.matchedRule).toBe("plan mode");
|
||||
});
|
||||
|
||||
test("plan mode - denies Bash heredoc write outside plans dir", () => {
|
||||
permissionMode.setMode("plan");
|
||||
|
||||
const permissions: PermissionRules = {
|
||||
allow: [],
|
||||
deny: [],
|
||||
ask: [],
|
||||
};
|
||||
|
||||
const command = "cat > /tmp/not-a-plan.md <<'EOF'\n# Plan\nEOF";
|
||||
|
||||
const result = checkPermission(
|
||||
"Bash",
|
||||
{ command },
|
||||
permissions,
|
||||
"/Users/test/project",
|
||||
);
|
||||
|
||||
expect(result.decision).toBe("deny");
|
||||
expect(result.matchedRule).toBe("plan mode");
|
||||
});
|
||||
|
||||
test("plan mode - denies Bash heredoc write when extra commands follow", () => {
|
||||
permissionMode.setMode("plan");
|
||||
|
||||
const permissions: PermissionRules = {
|
||||
allow: [],
|
||||
deny: [],
|
||||
ask: [],
|
||||
};
|
||||
|
||||
const planPath = join(homedir(), ".letta", "plans", "unit-test-plan.md");
|
||||
const command = `cat > ${planPath} <<'EOF'\n# Plan\nEOF\necho 'extra command'`;
|
||||
|
||||
const result = checkPermission(
|
||||
"Bash",
|
||||
{ command },
|
||||
permissions,
|
||||
"/Users/test/project",
|
||||
);
|
||||
|
||||
expect(result.decision).toBe("deny");
|
||||
expect(result.matchedRule).toBe("plan mode");
|
||||
});
|
||||
|
||||
test("plan mode - allows read-only Bash commands", () => {
|
||||
permissionMode.setMode("plan");
|
||||
|
||||
@@ -503,6 +571,18 @@ test("plan mode - allows read-only Bash commands", () => {
|
||||
);
|
||||
expect(chainedResult.decision).toBe("allow");
|
||||
|
||||
// quoted pipes in regex patterns should be treated as literals and allowed
|
||||
const quotedPipeResult = checkPermission(
|
||||
"Bash",
|
||||
{
|
||||
command:
|
||||
'rg -n "memfs|memory filesystem|memory_filesystem|skills/|SKILL.md|git-backed|sync" letta tests -S',
|
||||
},
|
||||
permissions,
|
||||
"/Users/test/project",
|
||||
);
|
||||
expect(quotedPipeResult.decision).toBe("allow");
|
||||
|
||||
// cd && dangerous command should still be denied
|
||||
const cdDangerousResult = checkPermission(
|
||||
"Bash",
|
||||
|
||||
@@ -182,6 +182,15 @@ describe("isReadOnlyShellCommand", () => {
|
||||
expect(isReadOnlyShellCommand("ls -la | grep txt | wc -l")).toBe(true);
|
||||
});
|
||||
|
||||
test("allows pipe characters inside quoted args", () => {
|
||||
expect(
|
||||
isReadOnlyShellCommand(
|
||||
'rg -n "memfs|memory filesystem|memory_filesystem|skills/|SKILL.md|git-backed|sync" letta tests -S',
|
||||
),
|
||||
).toBe(true);
|
||||
expect(isReadOnlyShellCommand("grep 'foo|bar|baz' file.txt")).toBe(true);
|
||||
});
|
||||
|
||||
test("blocks pipes with unsafe commands", () => {
|
||||
expect(isReadOnlyShellCommand("cat file | rm")).toBe(false);
|
||||
expect(isReadOnlyShellCommand("echo test | bash")).toBe(false);
|
||||
@@ -203,6 +212,13 @@ describe("isReadOnlyShellCommand", () => {
|
||||
test("blocks command substitution", () => {
|
||||
expect(isReadOnlyShellCommand("echo $(rm file)")).toBe(false);
|
||||
expect(isReadOnlyShellCommand("echo `rm file`")).toBe(false);
|
||||
expect(isReadOnlyShellCommand('echo "$(rm file)"')).toBe(false);
|
||||
expect(isReadOnlyShellCommand('echo "`rm file`"')).toBe(false);
|
||||
});
|
||||
|
||||
test("allows literal redirects inside quotes", () => {
|
||||
expect(isReadOnlyShellCommand('echo "a > b"')).toBe(true);
|
||||
expect(isReadOnlyShellCommand("echo 'a >> b'")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user