Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,15 @@ priorities.
fail atomically until their state effects are modeled. Native Bash
oracles pin nameref-deferred and integer-assignment execution, and the
executable corpus carries isolated, unknown-state, and wrapper cases.
- [x] Close Bash command-resolution mutation globally. Reject `exec` and
mutating or ambiguous `hash`, `alias`, `unalias`, `shopt`, and `enable`
forms before later occurrences can inherit a false executable identity; retain only
exact static query grammar and pin native-shell behavior, recursive
wrappers, and executable corpus cases. Fail unmodeled `time`, negation,
coprocess, and current-shell brace-group syntax closed rather than
flattening nested execution into apparent ordinary verb chains.
- [ ] Promote the Bash command-resolution mutation cases into Netclaw's strict
allow/prompt/deny matrix before the downstream approval-fatigue gate.
- [ ] Add the separately tested Bash `<<<` here-string redirect slice with
bounded operand analysis and trailing-newline semantics.

Expand Down
25 changes: 22 additions & 3 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -810,8 +810,28 @@ evaluate argument text, install deferred execution, or assign through
unproved integer, nameref, or array attributes. Recognition recursively
unwraps statically proved `command` and `builtin` dispatch; dynamic or invalid
wrapper grammar fails closed. Ordinary `printf` without `-v` remains
supported. `break`, `continue`, `return`, `exit`, and `exec` remain loop-region
failures until their transfers are implemented.
supported.

Command-resolution state is independent from variable attributes and cwd.
`exec` fails the complete parse closed globally because it replaces the shell
or makes commandless redirections persistent. Mutating or ambiguous `hash`,
`alias`, `unalias`, `shopt`, and `enable` forms likewise fail globally before a
later command can inherit an unmodeled executable identity. The only retained
forms are exact static queries: bare `hash`, `alias`, `shopt`, and `enable`;
`hash -l` without operands and `hash -t NAME...`; `alias [-p] [NAME...]`
without a definition; `shopt` option clusters without `s` or `u`; and no-name
`enable` listing flags composed only from `a`, `n`, `p`, and `s`. Dynamic or
invalid grammar fails closed, and exact `command` / `builtin` wrappers cannot
bypass the boundary. `break`, `continue`, `return`, and `exit` remain
loop-region failures until their transfers are implemented.

Unquoted `time` and `!` reserved prefixes execute the following pipeline with
current-shell state; `coproc` starts hidden concurrent execution; and
`{ ...; }` is a current-shell group. Stable v0.3 fails exact unquoted `time`,
`!`, `coproc`, `{`, or `}` in command position closed until their nested
structure and state propagation are modeled. Quoted/escaped spellings,
`/usr/bin/time`, and `command time ...` remain ordinary command identities and
do not acquire reserved-word semantics.

Substitutions and subshells inherit the current variable/cwd state but discard
their state changes on exit. Decoded Bash command wrappers inherit invocation
Expand Down Expand Up @@ -1356,7 +1376,6 @@ bash_list_item := bash_and_or
bash_and_or := bash_pipeline (("&&" | "||") bash_pipeline)*
bash_pipeline := bash_command ("|" bash_command)*
bash_command := bash_for_in
| bash_group
| bash_subshell
| bash_c_wrapper
| bash_simple_command
Expand Down
12 changes: 12 additions & 0 deletions docs/CONSUMER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,18 @@ outside the first bounded scalar grammar. The parser also downgrades a decoded
`export`; resolver-only option cloning for an exact cwd retains the independent
variable-state assertion.

ShellSyntaxTree also treats Bash command resolution as parser-owned security
state. `exec` and mutating or ambiguous `hash`, `alias`, `unalias`, `shopt`,
and `enable` forms make the complete result unparseable, including through exact `command`
or `builtin` dispatch wrappers. Only documented static query forms remain
visible, such as `hash -t name`, `alias name`, `shopt -q option`, and bare
`enable -n`. Consumers need no special fallback for rejected mutations: apply
the ordinary `IsUnparseable` prompt-or-deny rule. A parseable query is still
only syntax evidence; it does not prove the queried executable safe.
Unmodeled unquoted `time`, `!`, `coproc`, and `{ ...; }` syntax follows the
same rule because those constructs can hide nested or current-shell execution;
quoted spellings and external `/usr/bin/time` do not acquire reserved syntax.

PowerShell `foreach` value proofs require the parallel but shell-specific
assertion. `PwshInitialStateMode.Unknown` is the safe default: the parser can
still expose supported loop structure, but ambient typed, validated,
Expand Down
30 changes: 29 additions & 1 deletion openspec/changes/v0-3-structured-shell-analysis/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,35 @@ than bypassing the catalog. Ordinary `printf` without `-v` remains supported.
A later additive grammar may admit individually proved query or assignment
forms without weakening this stable boundary.

Bash command resolution is a separate state axis from variable attributes and
cwd. Stable v0.3 rejects `exec` globally because it replaces the analyzed shell
with another executable (or makes redirections persistent when no command is
supplied). It also rejects `unalias` and every mutating or ambiguous `hash`,
`alias`, `shopt`, and `enable` form before any later occurrence can inherit a
false command identity. This closes mappings such as
`hash -p /bin/rm git; git target`, newline-delimited alias activation through
`shopt -s expand_aliases`, and builtin disable/load operations.

The parser may retain only exact static query grammar: bare `hash`, `alias`,
`shopt`, and `enable`; `hash -l` without operands and `hash -t NAME...`;
`alias [-p] [NAME...]` without an equals-bearing definition; `shopt` options
that contain neither `-s` nor `-u`; and no-name `enable` listing flags composed
only from `-a`, `-n`, `-p`, and `-s`. Dynamic words, unsupported options, plain
hash names, alias definitions, any `shopt -s` / `-u`, and any enable name,
`-d`, or `-f` fail closed. Exact `command` and `builtin` wrappers are unwrapped
before this grammar is applied. Query recognition is a parser completeness
fact, not authorization for the queried builtin.

Reserved execution syntax is not a verb-chain wrapper. Unquoted `time` and `!`
run the following pipeline with current-shell state, `coproc` launches hidden
concurrent execution, and `{ ...; }` shares the current shell. Until those
constructs have typed structural nodes and recursive state analysis, an exact
unquoted `time`, `!`, `coproc`, `{`, or `}` in command position fails the
complete parse closed. Quoted/escaped spellings and `/usr/bin/time` remain
ordinary command identities; `command time ...` does not invent reserved-word
semantics. The structured Bash BNF therefore does not include a `bash_group`
production until brace-group support is actually implemented.

PowerShell needs the same explicit boundary for different reasons.
`PwshParserOptions.InitialStateMode` defaults to `Unknown`, which permits
structural discovery but withholds exact or finite `foreach` binding proofs.
Expand Down Expand Up @@ -1505,7 +1534,6 @@ bash_list_item := bash_and_or
bash_and_or := bash_pipeline (("&&" | "||") bash_pipeline)*
bash_pipeline := bash_command ("|" bash_command)*
bash_command := bash_for_in
| bash_group
| bash_subshell
| bash_c_wrapper
| bash_simple_command
Expand Down
5 changes: 5 additions & 0 deletions openspec/changes/v0-3-structured-shell-analysis/proposal.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ fail-closed behavior for incomplete analysis.
unmodeled shell builtins that can evaluate, assign through, or defer
argument text, because nameref and arithmetic attributes can otherwise turn
quoted data into hidden execution.
- Treat Bash command resolution as policy-relevant shell state. `exec` and
mutating or ambiguous `hash`, `alias`, `unalias`, shell-option, and
builtin-enable forms fail closed globally; only exact documented query forms remain visible.
Unmodeled `time`, negation, coprocess, and brace-group syntax also fails
closed rather than hiding a nested mutation in an apparent verb chain.
- Preserve resolver-relevant lexical fragments, typed expansion identity and
cardinality, operation-specific transform eligibility, opaque cause, and
consumer/binding context through decoding so escaped or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,66 @@ SHALL retain its existing behavior.
- **WHEN** Bash parses `builtin eval 'rm target.txt'`
- **THEN** the whole result is unparseable under the same rule as direct `eval`

### Requirement: Bash command-resolution mutation fails closed globally
Stable v0.3 SHALL treat command-resolution state independently from variable
attributes and cwd. Direct or statically wrapped `exec` SHALL make the whole
result unparseable. Mutating or ambiguous `hash`, `alias`, `unalias`, `shopt`,
and `enable` forms SHALL likewise fail closed before later commands can inherit
an unmodeled executable identity.

The parser MAY retain only exact static query forms: bare `hash`, `alias`,
`shopt`, and `enable`; `hash -l` without operands and `hash -t NAME...`;
`alias [-p] [NAME...]` without an equals-bearing definition; `shopt` option
clusters that contain neither `s` nor `u`; and no-name `enable` listing flags
composed only from `a`, `n`, `p`, and `s`. A dynamic word, invalid option,
plain hash operand, alias definition, `unalias`, `shopt -s` / `-u`, or enable
name / `-d` / `-f` SHALL make the complete result unparseable. Exact `command`
and `builtin` dispatch wrappers SHALL NOT bypass this boundary.

#### Scenario: Hash mapping cannot replace a later executable
- **WHEN** Bash parses `hash -p /bin/rm git; git target.txt`
- **THEN** the whole result is unparseable
- **THEN** the later `git` occurrence is not published with a false executable identity

#### Scenario: Alias activation cannot hide a later command
- **WHEN** Bash parses newline-delimited `shopt -s expand_aliases`, `alias safe=rm`, and `safe target.txt`
- **THEN** the whole result is unparseable before the alias-expanded command can be hidden

#### Scenario: Exec is a global execution boundary
- **WHEN** Bash parses `exec /bin/rm target.txt` outside a loop
- **THEN** the whole result is unparseable
- **THEN** `exec` is not published as an ordinary complete occurrence

#### Scenario: Exact query forms remain structurally visible
- **WHEN** Bash parses `hash -t git`, `alias safe`, `shopt -q expand_aliases`, or bare `enable -n`
- **THEN** the query remains parseable under its exact option grammar
- **THEN** the parser does not infer that the queried identity is safe to authorize

### Requirement: Unmodeled Bash reserved execution syntax fails closed
Stable v0.3 SHALL NOT treat unquoted `time`, `!`, `coproc`, `{`, or `}` in
command position as ordinary verb-chain elements. Until timed/negated
pipelines, coprocesses, and brace groups have typed recursive structure and
state propagation, each form SHALL make the whole result unparseable. Quoted
or escaped spellings and an external `/usr/bin/time` identity SHALL NOT invent
reserved syntax.

#### Scenario: Timed mutation remains in the current shell
- **WHEN** Bash parses `time hash -p /bin/rm git; git target.txt`
- **THEN** the whole result is unparseable
- **THEN** the hash mutation is not hidden inside a `time hash` verb chain

#### Scenario: Negated mutation remains in the current shell
- **WHEN** Bash parses `! hash -p /bin/rm git; git target.txt`
- **THEN** the whole result is unparseable even though negation changes exit status

#### Scenario: Brace group shares command-resolution state
- **WHEN** Bash parses `{ hash -p /bin/rm git; git target.txt; }`
- **THEN** the whole result is unparseable until current-scope brace groups are modeled

#### Scenario: Coprocess body is hidden concurrent execution
- **WHEN** Bash parses `coproc exec /bin/rm target.txt`
- **THEN** the whole result is unparseable until coprocess structure and timing are modeled

### Requirement: PowerShell loop proofs require an explicit initial-runspace contract
`PwshParserOptions.InitialStateMode` SHALL default to `Unknown`. In that mode,
the parser MAY expose supported `foreach` structure and command occurrences,
Expand Down
3 changes: 3 additions & 0 deletions openspec/changes/v0-3-structured-shell-analysis/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,21 @@
- [x] 3.10a Implement the simple-command argument and redirect-target slice, including comment-safe boundaries and fail-closed unsupported interiors.
- [x] 3.10b Implement the bounded expanding-heredoc slice with quote-removed delimiters, literal quoted/escaped bodies, tab stripping, exact provenance, and fail-closed unsupported header/body forms.
- [x] 3.10c Require proved Bash variable-attribute state for simple named-parameter dereferences and fail closed globally on the locked unmodeled execution-bearing builtin catalog, including exact dispatch-wrapper bypasses.
- [x] 3.10d Fail closed globally on Bash `exec`, mutating or ambiguous `hash`, alias, `shopt`, and `enable` forms, and unmodeled reserved execution prefixes/groups while retaining only exact static query grammar.
- [ ] 3.11 Implement PowerShell `$()` discovery in supported words, redirect values, foreach expressions, call-operator dynamic identities, standalone expression statements, double-quoted strings, and expandable here-strings; never invent invocation from standalone output, retain literal/escaped spellings, and fail closed on trailing command-style arguments, call-operator script blocks, or unsupported execution-bearing `@()` / `@{}` forms.
- [x] 3.11a Implement words, redirect values, call-operator dynamic identities, standalone statements, expandable strings/here-strings, and parent-versus-child host payload provenance; fail closed on arbitrary expression values and unsupported execution-bearing `@()` / `@{}` forms.
- [x] 3.12 Pin substitution parentage, authored sibling indices, innermost-first ordering, Bash-isolated versus PowerShell-current-scope state, unknown-state propagation, nesting/depth limits, and incomplete dynamic identities in direct tests.
- [x] 3.12a Pin the Bash argument/redirect slice, isolated cwd behavior, wrapper provenance, and the shared structural-depth budget.
- [x] 3.12b Pin the PowerShell simple-command slice, current-scope exact and unknown cwd propagation, parent/child wrapper provenance, expression boundaries, and the shared structural-depth budget.
- [x] 3.12c Pin expanding-heredoc sibling/nested ordering, exact spans, isolated state, delimiter modes, escape parity, depth limits, and atomic failure.
- [x] 3.12d Pin default-versus-isolated parameter dereferences, nameref and integer hidden execution, reachable mutation invalidation, substitution/subshell scope boundaries, and direct/wrapped execution-bearing builtins with native Bash oracles.
- [x] 3.12e Pin Bash command-resolution mutation, reserved-prefix/group rejection, and exact-query boundaries with direct tests, recursive wrapper cases, and native Bash oracles.
- [ ] 3.13 Promote ordinary, multiple, nested, iterator, redirect, quoted, escaped, stateful, malformed, and hidden-execution substitution cases into both executable corpora and the Netclaw approval matrix.
- [x] 3.13a Promote the Bash ordinary, multiple, nested, redirect, quoted, escaped, stateful, malformed, and hidden-execution cases into its executable corpus.
- [x] 3.13b Promote the PowerShell ordinary, multiple, nested, redirect, quoted, escaped, stateful, malformed, expression-boundary, and hidden-execution cases into its executable corpus.
- [x] 3.13c Promote expanding, literal, tab-stripped, multiple, and malformed Bash heredoc cases with full structural expectations into the executable corpus.
- [ ] 3.13d Promote sanitized nameref, unknown-state dereference, and execution-bearing builtin failures into the Bash executable corpus and Netclaw strict matrix.
- [ ] 3.13e Promote sanitized Bash hash, alias/option, exec, builtin-enable, and reserved execution syntax failures into the executable corpus and Netclaw strict matrix.
- [x] 3.14 Add `ExecutionRegionSyntax`, its four discriminant enums,
`SimpleCommandSyntax.ExecutionRegions`, and appended occurrence/ancestry enum
members to the public API and snapshot without changing existing enum values.
Expand Down
Loading