bash-classify 0.4.1__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bash_classify-0.4.1 → bash_classify-0.5.0}/.claude-plugin/marketplace.json +1 -1
- {bash_classify-0.4.1 → bash_classify-0.5.0}/PKG-INFO +1 -1
- {bash_classify-0.4.1 → bash_classify-0.5.0}/SPEC.md +19 -1
- {bash_classify-0.4.1 → bash_classify-0.5.0}/coding-agent-plugins/claude-code/.claude-plugin/plugin.json +1 -1
- {bash_classify-0.4.1 → bash_classify-0.5.0}/docs/classification-guidance.md +9 -1
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/classifier.py +47 -2
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/cli.py +10 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/pup.yaml +38 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/tee.yaml +1 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/models.py +4 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/test_classifier.py +56 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/test_cli_serialization.py +99 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/.claude/rules/command-database.md +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/.editorconfig +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/.github/workflows/ci.yml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/.github/workflows/publish.yml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/.gitignore +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/.python-version +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/CLAUDE.md +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/LICENSE +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/README.md +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/coding-agent-plugins/claude-code/hooks/classify-bash.sh +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/coding-agent-plugins/claude-code/hooks/hooks.json +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/pyproject.toml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/ruff.toml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/schemas/command.schema.json +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/__init__.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/__main__.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/agent-browser.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/apt.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/awk.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/basename.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/bash-classify.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/bash.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/black.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/brew.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/builtin.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/cargo.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/cat.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/chgrp.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/chmod.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/chown.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/column.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/comm.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/command.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/cp.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/curl.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/cut.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/date.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/df.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/diff.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/dig.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/dirname.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/docker.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/du.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/echo.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/env.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/eslint.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/expand.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/false.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/file.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/find.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/fmt.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/fold.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/free.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/gawk.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/gcloud.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/gh.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/git.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/glab-discussion.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/glab.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/go.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/gradle.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/gradlew.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/grep.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/groups.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/gunzip.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/gzip.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/hadolint.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/head.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/helm.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/hostname.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/id.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/install.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/isort.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/java.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/javac.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/join.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/journalctl.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/jq.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/kill.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/killall.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/kubectl.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/less.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/ln.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/ls.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/make.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/md5sum.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/mkdir.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/more.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/mv.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/mvn.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/mvnw.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/mypy.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/netstat.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/nice.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/nl.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/node.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/nohup.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/npm.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/npx.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/nslookup.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/paste.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/ping.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/pip.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/pip3.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/pipx.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/pkill.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/poetry.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/prettier.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/printenv.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/printf.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/ps.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/pwd.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/pytest.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/python.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/python3.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/read.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/readlink.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/realpath.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/rev.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/rg.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/rm.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/rsync.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/ruff.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/scp.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/sed.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/seq.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/sh.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/sha1sum.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/sha256sum.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/shellcheck.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/sort.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/ss.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/ssh.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/stat.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/strace.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/sudo.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/systemctl.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/tac.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/tail.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/tar.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/terraform.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/test.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/time.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/timeout.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/top.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/touch.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/tr.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/traceroute.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/true.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/tsc.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/type.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/uname.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/unexpand.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/uniq.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/unzip.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/uptime.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/uv.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/wc.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/wget.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/whereis.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/which.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/whoami.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/xargs.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/xmllint.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/yarn.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/yes.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/yq.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/zip.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/commands/zsh.yaml +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/database.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/matcher.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/parser.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/src/bash_classify/py.typed +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/__init__.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/conftest.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/test_cli.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/test_database.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/test_matcher.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/test_models.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/test_parser.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/test_real_world.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/test_schema.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/tests/test_smoke.py +0 -0
- {bash_classify-0.4.1 → bash_classify-0.5.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bash-classify
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Classify bash commands as READONLY, WRITE, DANGEROUS, or UNKNOWN
|
|
5
5
|
Project-URL: Homepage, https://github.com/fprochazka/bash-classify
|
|
6
6
|
Project-URL: Repository, https://github.com/fprochazka/bash-classify
|
|
@@ -246,7 +246,21 @@ Certain expression-level patterns elevate risk to at least `HIGH`:
|
|
|
246
246
|
- Writing to system paths (e.g. `/etc`, `/usr`)
|
|
247
247
|
- Redirecting to `/dev/tcp` or `/dev/udp`
|
|
248
248
|
- Backgrounding (`cmd &`)
|
|
249
|
-
- Output redirects to non-`/dev/null` targets
|
|
249
|
+
- Output redirects to non-`/dev/null` targets (except when all write targets are under `/tmp` or `/var/tmp`)
|
|
250
|
+
|
|
251
|
+
#### Temp path risk lowering
|
|
252
|
+
|
|
253
|
+
When all write targets from output redirects (`>`, `>>`, `2>`, `&>`, `>&`) are under `/tmp` or `/var/tmp`, the risk elevation to `MEDIUM` from output redirects is skipped. This allows commands like `cat > /tmp/foo.txt` to remain at `LOW` risk while still being classified as `LOCAL_EFFECTS`.
|
|
254
|
+
|
|
255
|
+
### File path detection
|
|
256
|
+
|
|
257
|
+
The tool extracts file paths from shell redirects:
|
|
258
|
+
|
|
259
|
+
- **`write_paths`**: Files targeted by output redirects (`>`, `>>`, `2>`, `&>`, `>&`), excluding `/dev/null`, `/dev/stdin`, `/dev/stdout`, `/dev/stderr`, and `/dev/fd/*`.
|
|
260
|
+
- **`read_paths`**: Files targeted by input redirects (`<`), excluding the same `/dev/*` paths.
|
|
261
|
+
- Heredoc operators (`<<`, `<<<`) are not included — their target is a delimiter, not a file path.
|
|
262
|
+
|
|
263
|
+
Paths are reported at both the per-command level (`commands[].write_paths`, `commands[].read_paths`) and the expression level (aggregated from all commands).
|
|
250
264
|
|
|
251
265
|
### Composite classification
|
|
252
266
|
|
|
@@ -918,6 +932,8 @@ The `commands` list is recursive — any command entry can contain `inner_comman
|
|
|
918
932
|
"expression": "string — the original input",
|
|
919
933
|
"classification": "READONLY | LOCAL_EFFECTS | EXTERNAL_EFFECTS | DANGEROUS | UNKNOWN",
|
|
920
934
|
"directories": ["string — detected directories"],
|
|
935
|
+
"write_paths": ["string — files targeted by output redirects (optional, omitted when empty)"],
|
|
936
|
+
"read_paths": ["string — files targeted by input redirects (optional, omitted when empty)"],
|
|
921
937
|
"commands": [
|
|
922
938
|
{
|
|
923
939
|
"command": ["string — binary + subcommand chain (without inner command tokens)"],
|
|
@@ -928,6 +944,8 @@ The `commands` list is recursive — any command entry can contain `inner_comman
|
|
|
928
944
|
"remaining_options": ["string — options that were not in database"],
|
|
929
945
|
"classification_reason": "string — why this classification was chosen",
|
|
930
946
|
"overriding_option": "string | null — the option that elevated classification",
|
|
947
|
+
"write_paths": ["string — files targeted by output redirects (optional, omitted when empty)"],
|
|
948
|
+
"read_paths": ["string — files targeted by input redirects (optional, omitted when empty)"],
|
|
931
949
|
"inner_commands": [
|
|
932
950
|
{
|
|
933
951
|
"delegation_mode": "string — how this inner command was extracted",
|
|
@@ -453,7 +453,15 @@ subcommands:
|
|
|
453
453
|
- Unrecognized goals (e.g. custom Maven plugin goals like `some-plugin:goal`) fall back to the command's base classification and risk
|
|
454
454
|
- Option overrides (e.g. `--dry-run`) still take precedence over goal aggregation
|
|
455
455
|
|
|
456
|
-
## 10.
|
|
456
|
+
## 10. Temp Path Risk Behavior
|
|
457
|
+
|
|
458
|
+
When a command writes to a file via output redirects (`>`, `>>`, `2>`, `&>`, `>&`), its risk is normally elevated to at least `MEDIUM`. However, if **all** write targets are under `/tmp` or `/var/tmp`, the risk elevation is skipped and the command stays at `LOW` risk (assuming no other risk elevations apply).
|
|
459
|
+
|
|
460
|
+
This means `cat > /tmp/foo.txt` is classified as `LOCAL_EFFECTS` with risk `LOW`, while `cat > ~/foo.txt` is classified as `LOCAL_EFFECTS` with risk `MEDIUM`.
|
|
461
|
+
|
|
462
|
+
The tool also reports `write_paths` and `read_paths` in the output, extracted from redirect operators. These fields are omitted from the JSON output when empty.
|
|
463
|
+
|
|
464
|
+
## 11. Common Patterns
|
|
457
465
|
|
|
458
466
|
| Pattern | Example | Classification |
|
|
459
467
|
|---------|---------|---------------|
|
|
@@ -48,6 +48,22 @@ _SAFE_PREFIXES = (
|
|
|
48
48
|
"/dev/udp",
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
+
_WRITE_OPERATORS = {">", ">>", "2>", "&>", ">&"}
|
|
52
|
+
_READ_OPERATORS = {"<"}
|
|
53
|
+
_HEREDOC_OPERATORS = {"<<", "<<<"}
|
|
54
|
+
|
|
55
|
+
_DEV_PATHS = ("/dev/null", "/dev/stdin", "/dev/stdout", "/dev/stderr", "/dev/fd/")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _is_temp_path(path: str) -> bool:
|
|
59
|
+
"""Check if a path is under a temp directory."""
|
|
60
|
+
return path.startswith("/tmp/") or path == "/tmp" or path.startswith("/var/tmp/") or path == "/var/tmp"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _is_real_file_path(path: str) -> bool:
|
|
64
|
+
"""Check if a redirect target is a real file path (not /dev/null etc)."""
|
|
65
|
+
return all(not (path == dev or path.startswith(dev)) for dev in _DEV_PATHS)
|
|
66
|
+
|
|
51
67
|
|
|
52
68
|
def _is_system_path(path: str) -> bool:
|
|
53
69
|
"""Check if a path is a system directory that should trigger DANGEROUS when written to."""
|
|
@@ -103,6 +119,20 @@ def classify_expression(
|
|
|
103
119
|
else:
|
|
104
120
|
result = match_command(invocation, database)
|
|
105
121
|
|
|
122
|
+
# Collect file paths from redirects
|
|
123
|
+
write_paths: list[str] = []
|
|
124
|
+
read_paths: list[str] = []
|
|
125
|
+
all_write_targets_are_temp = True
|
|
126
|
+
|
|
127
|
+
for redirect in invocation.redirects:
|
|
128
|
+
if redirect.operator in _WRITE_OPERATORS and _is_real_file_path(redirect.target):
|
|
129
|
+
write_paths.append(redirect.target)
|
|
130
|
+
if not _is_temp_path(redirect.target):
|
|
131
|
+
all_write_targets_are_temp = False
|
|
132
|
+
elif redirect.operator in _READ_OPERATORS and _is_real_file_path(redirect.target):
|
|
133
|
+
read_paths.append(redirect.target)
|
|
134
|
+
# Skip << and <<< (heredoc/herestring - target is delimiter, not file)
|
|
135
|
+
|
|
106
136
|
# Step 4: Apply redirect classification
|
|
107
137
|
for redirect in invocation.redirects:
|
|
108
138
|
if redirect.affects_classification:
|
|
@@ -114,8 +144,9 @@ def classify_expression(
|
|
|
114
144
|
if result.classification_reason
|
|
115
145
|
else "elevated by output redirect"
|
|
116
146
|
)
|
|
117
|
-
#
|
|
118
|
-
|
|
147
|
+
# Only elevate risk if write targets are NOT all temp paths
|
|
148
|
+
if not (write_paths and all_write_targets_are_temp):
|
|
149
|
+
result.risk = Risk.max_severity(result.risk, Risk.MEDIUM)
|
|
119
150
|
# /dev/tcp and /dev/udp redirects are network access -> DANGEROUS
|
|
120
151
|
if redirect.target.startswith("/dev/tcp/") or redirect.target.startswith("/dev/udp/"):
|
|
121
152
|
elevated = Classification.max_severity(result.classification, Classification.DANGEROUS)
|
|
@@ -165,12 +196,24 @@ def classify_expression(
|
|
|
165
196
|
) + f"; elevated to DANGEROUS: system path {system_paths_found[0]}"
|
|
166
197
|
result.risk = Risk.HIGH
|
|
167
198
|
|
|
199
|
+
result.write_paths = write_paths if write_paths else None
|
|
200
|
+
result.read_paths = read_paths if read_paths else None
|
|
201
|
+
|
|
168
202
|
command_results.append(result)
|
|
169
203
|
all_redirects.extend(invocation.redirects)
|
|
170
204
|
|
|
171
205
|
# Step 7: Collect directories
|
|
172
206
|
directories = _collect_directories(command_results)
|
|
173
207
|
|
|
208
|
+
# Step 7b: Aggregate write_paths and read_paths
|
|
209
|
+
all_write_paths: list[str] = []
|
|
210
|
+
all_read_paths: list[str] = []
|
|
211
|
+
for cmd_result in command_results:
|
|
212
|
+
if cmd_result.write_paths:
|
|
213
|
+
all_write_paths.extend(cmd_result.write_paths)
|
|
214
|
+
if cmd_result.read_paths:
|
|
215
|
+
all_read_paths.extend(cmd_result.read_paths)
|
|
216
|
+
|
|
174
217
|
# Step 8: Compute composite classification and risk
|
|
175
218
|
if not command_results and parse_warnings:
|
|
176
219
|
overall = Classification.UNKNOWN
|
|
@@ -187,6 +230,8 @@ def classify_expression(
|
|
|
187
230
|
classification=overall,
|
|
188
231
|
risk=overall_risk,
|
|
189
232
|
directories=directories,
|
|
233
|
+
write_paths=all_write_paths,
|
|
234
|
+
read_paths=all_read_paths,
|
|
190
235
|
commands=command_results,
|
|
191
236
|
redirects=all_redirects,
|
|
192
237
|
parse_warnings=parse_warnings,
|
|
@@ -57,6 +57,11 @@ def _command_to_dict(result: CommandResult) -> dict:
|
|
|
57
57
|
if result.overriding_option is not None:
|
|
58
58
|
d["overriding_option"] = result.overriding_option
|
|
59
59
|
|
|
60
|
+
if result.write_paths:
|
|
61
|
+
d["write_paths"] = result.write_paths
|
|
62
|
+
if result.read_paths:
|
|
63
|
+
d["read_paths"] = result.read_paths
|
|
64
|
+
|
|
60
65
|
d["inner_commands"] = [_inner_command_to_dict(ic) for ic in result.inner_commands]
|
|
61
66
|
|
|
62
67
|
return d
|
|
@@ -81,6 +86,11 @@ def _result_to_dict(result: ExpressionResult) -> dict:
|
|
|
81
86
|
"commands": [_command_to_dict(cmd) for cmd in result.commands],
|
|
82
87
|
}
|
|
83
88
|
|
|
89
|
+
if result.write_paths:
|
|
90
|
+
d["write_paths"] = result.write_paths
|
|
91
|
+
if result.read_paths:
|
|
92
|
+
d["read_paths"] = result.read_paths
|
|
93
|
+
|
|
84
94
|
if result.redirects:
|
|
85
95
|
d["redirects"] = [_redirect_to_dict(r) for r in result.redirects]
|
|
86
96
|
|
|
@@ -13,8 +13,10 @@ subcommands:
|
|
|
13
13
|
|
|
14
14
|
apm:
|
|
15
15
|
classification: READONLY
|
|
16
|
+
strict: false
|
|
16
17
|
audit-logs:
|
|
17
18
|
classification: READONLY
|
|
19
|
+
strict: false
|
|
18
20
|
auth:
|
|
19
21
|
classification: LOCAL_EFFECTS
|
|
20
22
|
strict: false
|
|
@@ -33,76 +35,112 @@ subcommands:
|
|
|
33
35
|
classification: LOCAL_EFFECTS
|
|
34
36
|
cloud:
|
|
35
37
|
classification: READONLY
|
|
38
|
+
strict: false
|
|
36
39
|
containers:
|
|
37
40
|
classification: READONLY
|
|
41
|
+
strict: false
|
|
38
42
|
cost:
|
|
39
43
|
classification: READONLY
|
|
44
|
+
strict: false
|
|
40
45
|
dbm:
|
|
41
46
|
classification: READONLY
|
|
47
|
+
strict: false
|
|
42
48
|
ddsql:
|
|
43
49
|
classification: READONLY
|
|
50
|
+
strict: false
|
|
44
51
|
error-tracking:
|
|
45
52
|
classification: READONLY
|
|
53
|
+
strict: false
|
|
46
54
|
events:
|
|
47
55
|
classification: READONLY
|
|
56
|
+
strict: false
|
|
48
57
|
fleet:
|
|
49
58
|
classification: READONLY
|
|
59
|
+
strict: false
|
|
50
60
|
infrastructure:
|
|
51
61
|
classification: READONLY
|
|
62
|
+
strict: false
|
|
52
63
|
llm-obs:
|
|
53
64
|
classification: READONLY
|
|
65
|
+
strict: false
|
|
54
66
|
logs:
|
|
55
67
|
classification: READONLY
|
|
68
|
+
strict: false
|
|
56
69
|
on-call:
|
|
57
70
|
classification: READONLY
|
|
71
|
+
strict: false
|
|
58
72
|
organizations:
|
|
59
73
|
classification: READONLY
|
|
74
|
+
strict: false
|
|
60
75
|
rum:
|
|
61
76
|
classification: READONLY
|
|
77
|
+
strict: false
|
|
62
78
|
scorecards:
|
|
63
79
|
classification: READONLY
|
|
80
|
+
strict: false
|
|
64
81
|
service-catalog:
|
|
65
82
|
classification: READONLY
|
|
83
|
+
strict: false
|
|
66
84
|
static-analysis:
|
|
67
85
|
classification: READONLY
|
|
86
|
+
strict: false
|
|
68
87
|
status-pages:
|
|
69
88
|
classification: READONLY
|
|
89
|
+
strict: false
|
|
70
90
|
symdb:
|
|
71
91
|
classification: READONLY
|
|
92
|
+
strict: false
|
|
72
93
|
synthetics:
|
|
73
94
|
classification: READONLY
|
|
95
|
+
strict: false
|
|
74
96
|
traces:
|
|
75
97
|
classification: READONLY
|
|
98
|
+
strict: false
|
|
76
99
|
usage:
|
|
77
100
|
classification: READONLY
|
|
101
|
+
strict: false
|
|
78
102
|
users:
|
|
79
103
|
classification: READONLY
|
|
104
|
+
strict: false
|
|
80
105
|
version:
|
|
81
106
|
classification: READONLY
|
|
107
|
+
strict: false
|
|
82
108
|
status:
|
|
83
109
|
classification: READONLY
|
|
110
|
+
strict: false
|
|
84
111
|
completions:
|
|
85
112
|
classification: READONLY
|
|
113
|
+
strict: false
|
|
86
114
|
test-optimization:
|
|
87
115
|
classification: READONLY
|
|
116
|
+
strict: false
|
|
88
117
|
code-coverage:
|
|
89
118
|
classification: READONLY
|
|
119
|
+
strict: false
|
|
90
120
|
cicd:
|
|
91
121
|
classification: READONLY
|
|
122
|
+
strict: false
|
|
92
123
|
product-analytics:
|
|
93
124
|
classification: READONLY
|
|
125
|
+
strict: false
|
|
94
126
|
data-governance:
|
|
95
127
|
classification: READONLY
|
|
128
|
+
strict: false
|
|
96
129
|
feature-flags:
|
|
97
130
|
classification: READONLY
|
|
131
|
+
strict: false
|
|
98
132
|
bits:
|
|
99
133
|
classification: READONLY
|
|
134
|
+
strict: false
|
|
100
135
|
hamr:
|
|
101
136
|
classification: READONLY
|
|
137
|
+
strict: false
|
|
102
138
|
misc:
|
|
103
139
|
classification: READONLY
|
|
140
|
+
strict: false
|
|
104
141
|
agent:
|
|
105
142
|
classification: READONLY
|
|
143
|
+
strict: false
|
|
106
144
|
|
|
107
145
|
|
|
108
146
|
monitors:
|
|
@@ -177,6 +177,8 @@ class CommandResult:
|
|
|
177
177
|
classification_reason: str | None = None
|
|
178
178
|
overriding_option: str | None = None
|
|
179
179
|
directories: list[str] | None = None
|
|
180
|
+
write_paths: list[str] | None = None
|
|
181
|
+
read_paths: list[str] | None = None
|
|
180
182
|
|
|
181
183
|
|
|
182
184
|
@dataclass
|
|
@@ -187,6 +189,8 @@ class ExpressionResult:
|
|
|
187
189
|
classification: Classification
|
|
188
190
|
risk: Risk
|
|
189
191
|
directories: list[str]
|
|
192
|
+
write_paths: list[str]
|
|
193
|
+
read_paths: list[str]
|
|
190
194
|
commands: list[CommandResult]
|
|
191
195
|
redirects: list[Redirect]
|
|
192
196
|
parse_warnings: list[str]
|
|
@@ -631,3 +631,59 @@ class TestRiskExpressionAggregation:
|
|
|
631
631
|
result = classify_expression("if then fi", database=database)
|
|
632
632
|
assert result.classification == Classification.UNKNOWN
|
|
633
633
|
assert result.risk == Risk.HIGH
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
class TestFilePathDetection:
|
|
637
|
+
"""Tests for write_paths/read_paths detection and temp path risk lowering."""
|
|
638
|
+
|
|
639
|
+
def test_write_to_tmp_stays_low_risk(self, database: dict[str, CommandDef]) -> None:
|
|
640
|
+
result = classify_expression("cat > /tmp/foo.txt", database=database)
|
|
641
|
+
assert result.classification == Classification.LOCAL_EFFECTS
|
|
642
|
+
assert result.risk == Risk.LOW
|
|
643
|
+
assert result.write_paths == ["/tmp/foo.txt"]
|
|
644
|
+
cmd = result.commands[0]
|
|
645
|
+
assert cmd.write_paths == ["/tmp/foo.txt"]
|
|
646
|
+
|
|
647
|
+
def test_write_to_var_tmp_stays_low_risk(self, database: dict[str, CommandDef]) -> None:
|
|
648
|
+
result = classify_expression("cat > /var/tmp/foo.txt", database=database)
|
|
649
|
+
assert result.classification == Classification.LOCAL_EFFECTS
|
|
650
|
+
assert result.risk == Risk.LOW
|
|
651
|
+
assert result.write_paths == ["/var/tmp/foo.txt"]
|
|
652
|
+
|
|
653
|
+
def test_write_to_home_elevates_risk(self, database: dict[str, CommandDef]) -> None:
|
|
654
|
+
result = classify_expression("cat > /home/user/foo.txt", database=database)
|
|
655
|
+
assert result.classification == Classification.LOCAL_EFFECTS
|
|
656
|
+
assert result.risk == Risk.MEDIUM
|
|
657
|
+
assert result.write_paths == ["/home/user/foo.txt"]
|
|
658
|
+
|
|
659
|
+
def test_mixed_tmp_and_home_elevates_risk(self, database: dict[str, CommandDef]) -> None:
|
|
660
|
+
# Two redirects: one temp, one not -- should elevate
|
|
661
|
+
result = classify_expression("echo hello > /tmp/a.txt > /home/user/b.txt", database=database)
|
|
662
|
+
assert result.risk == Risk.MEDIUM
|
|
663
|
+
|
|
664
|
+
def test_read_redirect_detected(self, database: dict[str, CommandDef]) -> None:
|
|
665
|
+
result = classify_expression("cat < /home/user/data.txt", database=database)
|
|
666
|
+
assert result.classification == Classification.READONLY
|
|
667
|
+
assert result.risk == Risk.LOW
|
|
668
|
+
assert result.read_paths == ["/home/user/data.txt"]
|
|
669
|
+
assert result.write_paths == []
|
|
670
|
+
|
|
671
|
+
def test_read_and_write_redirect(self, database: dict[str, CommandDef]) -> None:
|
|
672
|
+
result = classify_expression("sort < /home/user/hosts > /tmp/sorted.txt", database=database)
|
|
673
|
+
assert result.classification == Classification.LOCAL_EFFECTS
|
|
674
|
+
assert result.risk == Risk.LOW
|
|
675
|
+
assert result.write_paths == ["/tmp/sorted.txt"]
|
|
676
|
+
assert result.read_paths == ["/home/user/hosts"]
|
|
677
|
+
|
|
678
|
+
def test_dev_null_excluded_from_write_paths(self, database: dict[str, CommandDef]) -> None:
|
|
679
|
+
result = classify_expression("cat > /dev/null", database=database)
|
|
680
|
+
assert result.write_paths == []
|
|
681
|
+
|
|
682
|
+
def test_heredoc_not_in_read_paths(self, database: dict[str, CommandDef]) -> None:
|
|
683
|
+
result = classify_expression("cat << EOF\nhello\nEOF", database=database)
|
|
684
|
+
assert result.read_paths == []
|
|
685
|
+
|
|
686
|
+
def test_expression_level_aggregation(self, database: dict[str, CommandDef]) -> None:
|
|
687
|
+
result = classify_expression("cat < /home/user/data.txt | tee /tmp/out.txt > /tmp/log.txt", database=database)
|
|
688
|
+
assert "/home/user/data.txt" in result.read_paths
|
|
689
|
+
assert "/tmp/log.txt" in result.write_paths
|
|
@@ -129,6 +129,8 @@ class TestResultToDict:
|
|
|
129
129
|
classification=Classification.EXTERNAL_EFFECTS,
|
|
130
130
|
risk=Risk.MEDIUM,
|
|
131
131
|
directories=[],
|
|
132
|
+
write_paths=[],
|
|
133
|
+
read_paths=[],
|
|
132
134
|
commands=[cmd],
|
|
133
135
|
redirects=[redirect],
|
|
134
136
|
parse_warnings=["some warning"],
|
|
@@ -155,6 +157,8 @@ class TestResultToDict:
|
|
|
155
157
|
classification=Classification.READONLY,
|
|
156
158
|
risk=Risk.LOW,
|
|
157
159
|
directories=[],
|
|
160
|
+
write_paths=[],
|
|
161
|
+
read_paths=[],
|
|
158
162
|
commands=[cmd],
|
|
159
163
|
redirects=[],
|
|
160
164
|
parse_warnings=[],
|
|
@@ -238,6 +242,8 @@ class TestRiskSerialization:
|
|
|
238
242
|
classification=Classification.READONLY,
|
|
239
243
|
risk=Risk.LOW,
|
|
240
244
|
directories=[],
|
|
245
|
+
write_paths=[],
|
|
246
|
+
read_paths=[],
|
|
241
247
|
commands=[cmd],
|
|
242
248
|
redirects=[],
|
|
243
249
|
parse_warnings=[],
|
|
@@ -259,3 +265,96 @@ class TestRiskSerialization:
|
|
|
259
265
|
d = _command_to_dict(result)
|
|
260
266
|
assert d["risk"] == risk.value
|
|
261
267
|
assert isinstance(d["risk"], str)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class TestWriteReadPaths:
|
|
271
|
+
def test_write_paths_in_command_dict(self) -> None:
|
|
272
|
+
result = CommandResult(
|
|
273
|
+
command=["cat"],
|
|
274
|
+
argv=["cat"],
|
|
275
|
+
classification=Classification.LOCAL_EFFECTS,
|
|
276
|
+
risk=Risk.LOW,
|
|
277
|
+
matched_rule="cat",
|
|
278
|
+
inner_commands=[],
|
|
279
|
+
write_paths=["/tmp/foo.txt"],
|
|
280
|
+
)
|
|
281
|
+
d = _command_to_dict(result)
|
|
282
|
+
assert d["write_paths"] == ["/tmp/foo.txt"]
|
|
283
|
+
assert "read_paths" not in d
|
|
284
|
+
|
|
285
|
+
def test_read_paths_in_command_dict(self) -> None:
|
|
286
|
+
result = CommandResult(
|
|
287
|
+
command=["cat"],
|
|
288
|
+
argv=["cat"],
|
|
289
|
+
classification=Classification.READONLY,
|
|
290
|
+
risk=Risk.LOW,
|
|
291
|
+
matched_rule="cat",
|
|
292
|
+
inner_commands=[],
|
|
293
|
+
read_paths=["/etc/hosts"],
|
|
294
|
+
)
|
|
295
|
+
d = _command_to_dict(result)
|
|
296
|
+
assert d["read_paths"] == ["/etc/hosts"]
|
|
297
|
+
assert "write_paths" not in d
|
|
298
|
+
|
|
299
|
+
def test_paths_omitted_when_empty(self) -> None:
|
|
300
|
+
result = CommandResult(
|
|
301
|
+
command=["ls"],
|
|
302
|
+
argv=["ls"],
|
|
303
|
+
classification=Classification.READONLY,
|
|
304
|
+
risk=Risk.LOW,
|
|
305
|
+
matched_rule="ls",
|
|
306
|
+
inner_commands=[],
|
|
307
|
+
)
|
|
308
|
+
d = _command_to_dict(result)
|
|
309
|
+
assert "write_paths" not in d
|
|
310
|
+
assert "read_paths" not in d
|
|
311
|
+
|
|
312
|
+
def test_expression_write_read_paths(self) -> None:
|
|
313
|
+
cmd = CommandResult(
|
|
314
|
+
command=["cat"],
|
|
315
|
+
argv=["cat"],
|
|
316
|
+
classification=Classification.LOCAL_EFFECTS,
|
|
317
|
+
risk=Risk.LOW,
|
|
318
|
+
matched_rule="cat",
|
|
319
|
+
inner_commands=[],
|
|
320
|
+
write_paths=["/tmp/out.txt"],
|
|
321
|
+
read_paths=["/etc/hosts"],
|
|
322
|
+
)
|
|
323
|
+
result = ExpressionResult(
|
|
324
|
+
expression="cat < /etc/hosts > /tmp/out.txt",
|
|
325
|
+
classification=Classification.LOCAL_EFFECTS,
|
|
326
|
+
risk=Risk.LOW,
|
|
327
|
+
directories=[],
|
|
328
|
+
write_paths=["/tmp/out.txt"],
|
|
329
|
+
read_paths=["/etc/hosts"],
|
|
330
|
+
commands=[cmd],
|
|
331
|
+
redirects=[],
|
|
332
|
+
parse_warnings=[],
|
|
333
|
+
)
|
|
334
|
+
d = _result_to_dict(result)
|
|
335
|
+
assert d["write_paths"] == ["/tmp/out.txt"]
|
|
336
|
+
assert d["read_paths"] == ["/etc/hosts"]
|
|
337
|
+
|
|
338
|
+
def test_expression_paths_omitted_when_empty(self) -> None:
|
|
339
|
+
cmd = CommandResult(
|
|
340
|
+
command=["ls"],
|
|
341
|
+
argv=["ls"],
|
|
342
|
+
classification=Classification.READONLY,
|
|
343
|
+
risk=Risk.LOW,
|
|
344
|
+
matched_rule="ls",
|
|
345
|
+
inner_commands=[],
|
|
346
|
+
)
|
|
347
|
+
result = ExpressionResult(
|
|
348
|
+
expression="ls",
|
|
349
|
+
classification=Classification.READONLY,
|
|
350
|
+
risk=Risk.LOW,
|
|
351
|
+
directories=[],
|
|
352
|
+
write_paths=[],
|
|
353
|
+
read_paths=[],
|
|
354
|
+
commands=[cmd],
|
|
355
|
+
redirects=[],
|
|
356
|
+
parse_warnings=[],
|
|
357
|
+
)
|
|
358
|
+
d = _result_to_dict(result)
|
|
359
|
+
assert "write_paths" not in d
|
|
360
|
+
assert "read_paths" not in d
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{bash_classify-0.4.1 → bash_classify-0.5.0}/coding-agent-plugins/claude-code/hooks/classify-bash.sh
RENAMED
|
File without changes
|
{bash_classify-0.4.1 → bash_classify-0.5.0}/coding-agent-plugins/claude-code/hooks/hooks.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|