mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
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>
This commit is contained in:
co-authored by
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
dae1a01f23
commit
2b21163a18
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
EXECUTION_HOST_TYPES,
|
||||
forEachNestedExecution,
|
||||
NESTED_EXECUTION_CONTEXTS,
|
||||
} from "#src/access-intent/bash/nested-execution";
|
||||
import type { TSNode } from "#src/access-intent/bash/parser";
|
||||
import {
|
||||
@@ -72,6 +73,62 @@ const COMMAND_ENUM_DESCEND = new Set([
|
||||
*/
|
||||
const COMMAND_ENUM_SKIP = new Set(["comment", "heredoc_end"]);
|
||||
|
||||
/**
|
||||
* Compound statement node types whose bodies really run commands:
|
||||
* `for`/`while`/`until` loops (tree-sitter-bash folds `until` into
|
||||
* `while_statement`), `if`/`case` statements, `{ … }` brace groups
|
||||
* (`compound_statement`), function definitions, and `!`-negated commands.
|
||||
*
|
||||
* These were once emitted whole without descending (#306 deferred that), which
|
||||
* let a gated command hide inside a loop body — a `for …; do find …; done`
|
||||
* matched only patterns starting with `for`. Each is now emitted whole AND
|
||||
* enumerated child-by-child; a `negated_command` emits `! grep …` whole and
|
||||
* then `grep …` so the `!` prefix cannot defeat a command pattern either.
|
||||
*/
|
||||
const CONTROL_FLOW_TYPES = new Set([
|
||||
"for_statement",
|
||||
"c_style_for_statement",
|
||||
"while_statement",
|
||||
"if_statement",
|
||||
"case_statement",
|
||||
"compound_statement",
|
||||
"function_definition",
|
||||
"negated_command",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Control-flow nodes that are pure body containers rather than statements in
|
||||
* their own right: `do …; done`, `elif`/`else` clauses, and `case` items.
|
||||
* They are descended exactly like {@link CONTROL_FLOW_TYPES} members but are
|
||||
* not emitted as whole units — nobody writes a command pattern against
|
||||
* `do …; done` or `a) find . ;;`, so emitting them only adds noise to prompts
|
||||
* and approval suggestions.
|
||||
*/
|
||||
const CONTROL_FLOW_CONTAINERS = new Set([
|
||||
"do_group",
|
||||
"elif_clause",
|
||||
"else_clause",
|
||||
"case_item",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Named children of a control-flow node that are statements in their own
|
||||
* right and get full command enumeration (whole-emit rules, wrapper
|
||||
* classification, substitution descent). Any other named child — words,
|
||||
* `variable_name`, a for-loop's value list, a case subject or pattern, a
|
||||
* `test_command` `[[ … ]]`, a comment — contributes only the nested
|
||||
* `$(…)`/`<(…)` executions it hosts.
|
||||
*/
|
||||
const CONTROL_FLOW_STATEMENT_TYPES = new Set([
|
||||
"command",
|
||||
"list",
|
||||
"pipeline",
|
||||
"redirected_statement",
|
||||
"subshell",
|
||||
...CONTROL_FLOW_TYPES,
|
||||
...CONTROL_FLOW_CONTAINERS,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Enumerate the command units of a bash program, in source order.
|
||||
*
|
||||
@@ -82,8 +139,19 @@ const COMMAND_ENUM_SKIP = new Set(["comment", "heredoc_end"]);
|
||||
* subshells (`( … )`) — emitting each inner command as its own unit *in
|
||||
* addition to* the enclosing command, since those inner commands really execute
|
||||
* (#306).
|
||||
* Control-flow bodies and `{ … }` brace groups are emitted whole without
|
||||
* descending (deferred).
|
||||
* Control-flow statements (loops, `if`/`elif`/`else`, `case`, `{ … }` groups,
|
||||
* function definitions, `!` negation) are likewise emitted whole and then
|
||||
* descended child-by-child: statement children (including nested control flow
|
||||
* and conditions like `if grep -q …; then`) are enumerated as commands, while
|
||||
* non-statement children (the for-loop value list, case subject/patterns,
|
||||
* `[[ … ]]` tests, names) contribute only the nested executions they host.
|
||||
* Pure body containers (`do …; done`, `elif`/`else` clauses, `case` items)
|
||||
* are descended without a whole-unit emit of their own.
|
||||
* Body commands keep the enclosing execution context — a loop inside `$(…)`
|
||||
* runs there, a top-level loop's body runs in the current shell.
|
||||
* Remaining named statements (`variable_assignment`, `declaration_command`,
|
||||
* `test_command`, …) emit whole and still report the nested executions they
|
||||
* host, so `x=$(grep foo f)` and `export X=$(find .)` gate the inner command.
|
||||
*
|
||||
* The enclosing command/subshell is always still emitted whole, so adding the
|
||||
* nested units can only ever produce a more-restrictive decision, never weaker.
|
||||
@@ -135,9 +203,57 @@ function collectCommandsInto(
|
||||
return;
|
||||
}
|
||||
|
||||
// Any other named statement (compound_statement `{ … }`, if/while/for/case,
|
||||
// function_definition): emit whole, do not descend — deferred (#306).
|
||||
if (CONTROL_FLOW_TYPES.has(node.type)) {
|
||||
out.push(makeUnit(node.text, context)); // never-weaker whole emit
|
||||
descendControlFlowChildren(node, context, out);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CONTROL_FLOW_CONTAINERS.has(node.type)) {
|
||||
// Pure body container — enumerate its contents, emit no unit of its own.
|
||||
descendControlFlowChildren(node, context, out);
|
||||
return;
|
||||
}
|
||||
|
||||
// Any other named statement (variable_assignment, declaration_command,
|
||||
// test_command, …) keeps the whole-emit fallback, but a substitution in its
|
||||
// payload still really runs — `x=$(grep foo f)` or `export X=$(find .)` —
|
||||
// so hosted executions are enumerated in addition to the whole text.
|
||||
out.push(makeUnit(node.text, context));
|
||||
collectHostedCommands(node, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate inside a control-flow statement, keeping the enclosing context.
|
||||
*
|
||||
* Statement children (`command`, `list`, `pipeline`, `redirected_statement`,
|
||||
* `subshell`, or nested control flow such as a `do_group`)
|
||||
* get full enumeration. Other named children are scanned only for nested
|
||||
* executions: a `command_substitution` child directly (the `$(…)` in
|
||||
* `for f in $(…)` or `case $(…) in`) is a context node in its own right — the
|
||||
* strict-subtree {@link forEachNestedExecution} walk would not see it — so it
|
||||
* is descended with its own context, while words, `variable_name`, test and
|
||||
* expression subtrees go through the hosted-execution search.
|
||||
*/
|
||||
function descendControlFlowChildren(
|
||||
node: TSNode,
|
||||
context: BashCommandContext | undefined,
|
||||
out: BashCommand[],
|
||||
): void {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (!child?.isNamed) continue;
|
||||
if (CONTROL_FLOW_STATEMENT_TYPES.has(child.type)) {
|
||||
collectCommandsInto(child, context, out);
|
||||
continue;
|
||||
}
|
||||
const nestedContext = NESTED_EXECUTION_CONTEXTS.get(child.type);
|
||||
if (nestedContext) {
|
||||
descendCommandChildren(child, nestedContext, out);
|
||||
} else {
|
||||
collectHostedCommands(child, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeUnit(
|
||||
|
||||
Reference in New Issue
Block a user