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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ Sed selects the longest regex match at the leftmost position, including for in-p

For simple commands, rshell expands only enough words to identify the command, then applies command, elevation, and mode policy before expanding the remaining arguments, inline assignments, or redirects. A rejected command therefore cannot trigger command substitutions or redirect side effects through its unused arguments. Once a command is authorized, redirects retain normal shell semantics: they are established before execution and remain effective even if the command later exits nonzero.

Script text is bounded before parsing: at most 5 MiB of source, and a nesting depth (parentheses, substitutions, brace groups, pipelines, logical lists, and `if`/`while`/`for`/`case` compounds) of at most 5000. Both are enforced before and during AST validation so that a deeply nested but sub-5-MiB script cannot overflow the parser's or interpreter's recursion and crash the embedding process.

Shell expansion is bounded independently of the execution timeout: a command may receive at most 16,384 expanded arguments totaling 10 MiB, and one `Run` may produce at most 64 MiB of expanded argument, assignment, redirect, and heredoc text across all loops, subshells, and pipeline stages. The argument cap is enforced before an unquoted variable or command substitution is field-split, not after the fields are built. Individual variable values remain capped at 1 MiB and heredocs at 10 MiB. The variables a script holds at once are capped at 1 MiB of values plus 1 MiB of names and fixed per-variable overhead (about 16k variables), shared by every concurrently running subshell and pipeline stage; a subshell that overrides or clears an inherited variable is still charged for the copy its parent holds.

Redirect targets undergo shell expansion and must resolve to exactly one field; ambiguous targets fail before the file is opened. Quoting preserves literal spaces and wildcard characters.
Expand Down
2 changes: 1 addition & 1 deletion SHELL_FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ the Go error return.

| Surface | Static check |
|---|---|
| Shell syntax | Same `ParseScript` size limit and restricted AST validation as execution |
| Shell syntax | Same `ParseScript` size and nesting-depth limits, and restricted AST validation, as execution |
| Commands | Exact `AllowedCommands`, builtin registration, remediation-only metadata, selective elevation, and prohibition of elevation in pipelines |
| Flags | Same builtin flag factories, normalization, and parsing as execution; handler-level semantic checks and embedded AWK/sed/jq programs are not validated |
| Paths | Explicit redirects and modeled builtin operands; local checks use sandbox metadata for containment and write restrictions; remote checks use only lexical grant parsing, containment, and access precedence, leaving target metadata indeterminate |
Expand Down
5 changes: 5 additions & 0 deletions interp/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,11 @@ func ParseScript(script, name string) (*syntax.File, error) {
return nil, fmt.Errorf("script size %d bytes exceeds maximum of %d bytes (%d MiB); split the script into smaller pieces",
len(script), MaxScriptBytes, MaxScriptBytes/(1024*1024))
}
// Reject deeply nested scripts before the recursive-descent parser can
// overflow the goroutine stack on them. See [MaxNestingDepth].
if err := checkScriptNestingDepth(script); err != nil {
return nil, err
}
return syntax.NewParser().Parse(strings.NewReader(script), name)
}

Expand Down
Loading
Loading