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 SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ The current public command analyzer returns `null` when it allows a command and

Standard, Strict, and Paranoid are safety presets that supply defaults. Standard mode is best-effort protection for recognizable destructive commands and intentionally allows dynamic executables, guarded command structure assembled through substitution, unverifiable recursive-delete targets, and metadata-only checks of built-in sensitive paths (`ls`, `stat`, `test -e`/`-f`, `[ -e`/`-f ]`, `git check-ignore`, `find` without actions, `wc` counts with only `-l`/`-w`/`-c`/`-m` or their long forms, and `grep`/`rg` limited to file names, counts, or exit status by `-l`, `-L` (grep only), `-c`, `-q`, or their long forms, without a `-f` pattern file, an `rg --pre` preprocessor, or a `--json` or `--format` output option), alone or as one segment of a compound command; a `wc`, `grep`, or `rg` whose output can reach a pipe (a pipe later in the command, including one after an enclosing subshell, brace group, conditional, loop, or called function, or any pipe beside a heredoc read as shell), whose output is captured by command substitution or by an interpreter's execution call, or whose command holds a coprocess (`coproc` or `|&`), is still checked, and a `grep` names-only flag counts only before the first operand, because a non-permuting `grep` reads a later `-l` as a file. For built-in sensitive-path rules standard mode also does not treat as a read: a secret file name that names an existing directory, a word containing whitespace that neither starts like a path nor exists on disk (command or prose text, such as `for c in 'cat ~/.aws/credentials'`), an output redirection that creates a secret-named file where none exists, a `mkdir` or `touch` operand, including one holding an unresolved expansion (every operand of a `touch` given `-r`/`--reference` and every redirection target is still checked), a `tailscale cert` `--cert-file` or `--key-file` value, also after `sudo`/`env` or as `timeout <n> tailscale cert`, the input file of `openssl x509 -in`, which prints only certificate data whatever the file holds (inputs of other `openssl` subcommands such as `rsa`, `pkey`, or `pkcs12`, and every other `x509` file option, stay checked; because `tailscale` prints the names it writes and `x509` reports a failed input name, both stay checked where their output can reach a pipe, a coprocess, or a capture, unless every command in the command line is `cd`, `grep`, `head`, `tail`, `openssl`, or `tailscale`, also after `timeout <n>`, and it holds no command or process substitution), or a `for` list word whose loop variable reaches no other checked word, such as `for t in .env.local; do grep -c -- "$t" notes.md; done`. A list word stays checked when the variable, a `${!` indirection, or a word equal to its name (a nameref) appears in another checked word (a reader operand, redirection, assignment value, substitution body, inline interpreter code, or nested loop list), when the loop or a segment naming the variable comes before a pipe, or when the command holds process substitution or a coprocess; a name the loop writes to a file that a later command reads back, or copies with `printf -v`, is not followed, like the same commands without the loop. A display command such as `echo` after `do`, `then`, `elif`, `else`, `if`, `while`, `until`, or `!` is judged as it is at the start of a command. A command holding process substitution gets neither the `mkdir`/`touch` nor the `wc`/`grep`/`rg` relaxation. Home-directory and coding-CLI credential rules are never relaxed for directories, file creation, or certificate inputs. Standard mode also allows `eval` and `source` of a single fully literal local generator command, such as `eval "$(ssh-agent -s)"`, `eval $(opam env)`, or `source <(kubectl completion bash)`, after the substitution body itself passes analysis. Only the generator command's shape is verified — the shell it emits at runtime is executed unverified, so this extends trust to the output of every literal local command, including forms like `eval "$(cat somefile)"`; any dynamic word, compound or redirected body, remote-fetching head such as `curl` or `wget`, or shell or wrapper head stays blocked, and strict and paranoid modes deny every dynamic shell source unconditionally. Sensitive content access and user-configured deny paths, including their descendants, remain blocked. Strict and paranoid modes fail closed on those forms and metadata-only sensitive-path discovery, and are required when commands may come from prompt injection or other adversarial context.

In every safety level, disk-device destruction is a protected category: `dd` with `of=` a device, the `mkfs`, `mke2fs`, and `newfs` families on a `/dev/` target, `diskutil` erase and partition verbs, `wipefs -a`/`-o`, `sgdisk` writes, `parted` label and partition edits, and output redirects onto a literal disk device path are blocked; further disk tools are added on field evidence.

In every safety level, built-in extension rules such as `.pem` or `.asc` do not apply to URL query text: a word holding a query parameter (`?key=` or `&key=`) that names no existing file, or the value after a query's `=`. A file with such a name that exists stays protected. A curl data or form value (`-d`, `--data`, `--data-ascii`, `--data-binary`, `--data-urlencode`, `-F`, `--form`) is checked only as the `@file` or `<file` upload it names.

For every non-shell interpreter's inline, heredoc, or stdin code, standard mode treats string literals as inert diagnostic or test data when the code as a whole contains no recognizable filesystem, command-execution, or eval marker. When the code holds such a marker, Python and JavaScript literals are judged by their position: a list, set, or tuple element, a dict key or value, an object property value, a subscript key, or an operand of `in`, `not in`, `==`, or `!=` is data, and every other position, such as a call argument, an assigned value, or a return value, keeps the literal as a candidate. Positions are not read, so every literal stays a candidate, when the code holds a comment containing a bracket, a `/*` comment, unpacking or spreading of a bracketed collection (`*(`, `*[`, `...[`), or a method call named like a keyword, such as `.of(`. When the code names a command-execution function or module (an identifier part starting with `exec` or `spawn`, or `popen`, `system`, or `subprocess`), list and tuple elements stay candidates too. A literal that only names the file a write creates is relaxed like an output redirection, only where no such file exists: a direct `Path(...)` or `pathlib.Path(...)` argument immediately written with `.write_text(` or `.write_bytes(`, the first argument of a bare `open(` whose mode is a literal holding `w`, `x`, or `a` without `r` or `+`, or the first argument of `writeFile`, `writeFileSync`, `appendFile`, `appendFileSync`, or `Bun.write`; the same name anywhere else in the code that is not data keeps it a candidate. Data and write literals are exempt only from built-in file-name rules such as `.env`, `*.pem`, or `credentials`; home-directory and coding-CLI credential paths remain candidates in any position, and so does a literal an execution call receives. Standard mode does not follow a data literal to a later read, so a file-name secret held in a collection and opened afterwards, such as `for f in {'.env'}: open(f)`, is allowed. In other interpreters' code one marker anywhere keeps every literal as a candidate. Standard mode walks a literal as shell text only when an execution call receives it: every literal when the call's first argument is or holds a name, otherwise the literals inside the call's parentheses or after a parenthesis-less `system`/`exec`. A first argument that is a list or tuple of only plain literals and names, whose first element (the program) is a literal, counts as holding no name when no element is a shell or interpreter name or a `-c`, `-e`, `-r`, `-E`, `--eval`, or `--exec` flag, such as `subprocess.run(['ffprobe', '-v', 'error', f])`; a program that runs an argument as a command line itself, such as `['watch', cmd]` or `['ssh', host, cmd]`, is not followed, like `watch` and `ssh` in shell commands. Destructive text uses the name boundary without that list exception: a dangerous command written as a string literal blocks only when an execution call can receive it, or when the code holds backticks, `%x`, or `qx`. Strict and paranoid modes keep every literal unconditionally and also inspect inside literal text. Configured deny paths and their descendants are never relaxed, and opaque or custom access wrappers remain outside standard mode's non-adversarial guarantee.
Expand Down
86 changes: 43 additions & 43 deletions dist/amp/cc-safety-net/index.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/api.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading