blast-scope 0.3.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.
- blast_scope-0.3.0/.claude/settings.local.json +8 -0
- blast_scope-0.3.0/.claude-plugin/marketplace.json +13 -0
- blast_scope-0.3.0/.claude-plugin/plugin.json +8 -0
- blast_scope-0.3.0/.gitignore +15 -0
- blast_scope-0.3.0/.mcp.json +10 -0
- blast_scope-0.3.0/CLAUDE.md +184 -0
- blast_scope-0.3.0/LICENSE +21 -0
- blast_scope-0.3.0/PKG-INFO +331 -0
- blast_scope-0.3.0/PUBLISHING.md +69 -0
- blast_scope-0.3.0/README.md +303 -0
- blast_scope-0.3.0/bench/README.md +79 -0
- blast_scope-0.3.0/bench/saber_eval.py +584 -0
- blast_scope-0.3.0/docs/heuristics.md +201 -0
- blast_scope-0.3.0/docs/hook.md +108 -0
- blast_scope-0.3.0/hooks/hooks.json +15 -0
- blast_scope-0.3.0/pyproject.toml +50 -0
- blast_scope-0.3.0/server.json +20 -0
- blast_scope-0.3.0/src/blast_scope/__init__.py +3 -0
- blast_scope-0.3.0/src/blast_scope/centrality.py +117 -0
- blast_scope-0.3.0/src/blast_scope/classes/__init__.py +129 -0
- blast_scope-0.3.0/src/blast_scope/classes/docker.py +281 -0
- blast_scope-0.3.0/src/blast_scope/classes/git.py +302 -0
- blast_scope-0.3.0/src/blast_scope/classes/packages.py +102 -0
- blast_scope-0.3.0/src/blast_scope/classes/sql.py +248 -0
- blast_scope-0.3.0/src/blast_scope/command_effects.py +341 -0
- blast_scope-0.3.0/src/blast_scope/command_parser.py +580 -0
- blast_scope-0.3.0/src/blast_scope/config_refs.py +157 -0
- blast_scope-0.3.0/src/blast_scope/consequences.py +101 -0
- blast_scope-0.3.0/src/blast_scope/eval.py +280 -0
- blast_scope-0.3.0/src/blast_scope/graph_resolver.py +494 -0
- blast_scope-0.3.0/src/blast_scope/hook.py +175 -0
- blast_scope-0.3.0/src/blast_scope/import_resolver.py +237 -0
- blast_scope-0.3.0/src/blast_scope/infra.py +99 -0
- blast_scope-0.3.0/src/blast_scope/recoverability.py +274 -0
- blast_scope-0.3.0/src/blast_scope/risk_scorer.py +555 -0
- blast_scope-0.3.0/src/blast_scope/server.py +310 -0
- blast_scope-0.3.0/src/blast_scope/snapshot.py +350 -0
- blast_scope-0.3.0/src/blast_scope/vcs.py +145 -0
- blast_scope-0.3.0/src/blast_scope/vendor/__init__.py +0 -0
- blast_scope-0.3.0/src/blast_scope/vendor/crg/LICENSE +21 -0
- blast_scope-0.3.0/src/blast_scope/vendor/crg/VENDORED.md +13 -0
- blast_scope-0.3.0/src/blast_scope/vendor/crg/__init__.py +17 -0
- blast_scope-0.3.0/src/blast_scope/vendor/crg/constants.py +20 -0
- blast_scope-0.3.0/src/blast_scope/vendor/crg/graph.py +1024 -0
- blast_scope-0.3.0/src/blast_scope/vendor/crg/migrations.py +246 -0
- blast_scope-0.3.0/src/blast_scope/vendor/crg/parser.py +3909 -0
- blast_scope-0.3.0/src/blast_scope/vendor/crg/tsconfig_resolver.py +257 -0
- blast_scope-0.3.0/tests/__init__.py +0 -0
- blast_scope-0.3.0/tests/fixtures/additive_commands.txt +5 -0
- blast_scope-0.3.0/tests/fixtures/destructive_commands.txt +7 -0
- blast_scope-0.3.0/tests/fixtures/eval_corpus.jsonl +38 -0
- blast_scope-0.3.0/tests/fixtures/read_commands.txt +8 -0
- blast_scope-0.3.0/tests/fixtures/sample_project/config.py +6 -0
- blast_scope-0.3.0/tests/fixtures/sample_project/db.py +7 -0
- blast_scope-0.3.0/tests/fixtures/sample_project/main.py +12 -0
- blast_scope-0.3.0/tests/test_centrality.py +60 -0
- blast_scope-0.3.0/tests/test_chain.py +210 -0
- blast_scope-0.3.0/tests/test_classes.py +192 -0
- blast_scope-0.3.0/tests/test_classes_docker_pkg_sql.py +229 -0
- blast_scope-0.3.0/tests/test_command_effects.py +85 -0
- blast_scope-0.3.0/tests/test_command_parser.py +325 -0
- blast_scope-0.3.0/tests/test_consequences.py +225 -0
- blast_scope-0.3.0/tests/test_e2e.py +171 -0
- blast_scope-0.3.0/tests/test_eval.py +51 -0
- blast_scope-0.3.0/tests/test_graph_resolver.py +124 -0
- blast_scope-0.3.0/tests/test_hook.py +117 -0
- blast_scope-0.3.0/tests/test_incremental.py +95 -0
- blast_scope-0.3.0/tests/test_recoverability.py +58 -0
- blast_scope-0.3.0/tests/test_risk_scorer.py +283 -0
- blast_scope-0.3.0/tests/test_snapshot.py +203 -0
- blast_scope-0.3.0/uv.lock +869 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "blast-scope",
|
|
3
|
+
"owner": { "name": "Atharva Jayappa", "url": "https://github.com/Atharva-Jayappa" },
|
|
4
|
+
"plugins": [
|
|
5
|
+
{
|
|
6
|
+
"name": "blast-scope",
|
|
7
|
+
"source": "./",
|
|
8
|
+
"description": "Contextual blast-radius scoring for shell commands β an advisory PreToolUse guardrail plus MCP tools (assess / snapshots / restore).",
|
|
9
|
+
"version": "0.3.0",
|
|
10
|
+
"category": "security"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "blast-scope",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Scores the blast radius of a shell command before your agent runs it β advisory, never blocking. Same command, opposite score depending on what it would actually hit.",
|
|
5
|
+
"author": { "name": "Atharva Jayappa", "url": "https://github.com/Atharva-Jayappa" },
|
|
6
|
+
"homepage": "https://github.com/Atharva-Jayappa/blast-scope",
|
|
7
|
+
"license": "MIT"
|
|
8
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# blast-scope
|
|
2
|
+
|
|
3
|
+
MCP tool that intercepts shell commands from AI agents, resolves target paths against a code dependency graph, and returns a structured risk score before execution.
|
|
4
|
+
|
|
5
|
+
The goal is contextual blast radius scoring β not pattern matching on syntax, but understanding structural consequence. `rm -rf ./logs` on an untracked folder is different from `rm -rf ./config` that 8 services import at runtime.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## what this is not
|
|
10
|
+
|
|
11
|
+
- Not a blocklist tool. We don't block commands, we score them.
|
|
12
|
+
- Not a replacement for Shellfirm. Complementary to it.
|
|
13
|
+
- Not a runtime monitor. Static analysis + filesystem state, not syscall tracing (yet).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## project structure
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
blast-scope/
|
|
21
|
+
βββ CLAUDE.md
|
|
22
|
+
βββ pyproject.toml
|
|
23
|
+
βββ README.md
|
|
24
|
+
βββ src/
|
|
25
|
+
β βββ blast_scope/
|
|
26
|
+
β βββ __init__.py
|
|
27
|
+
β βββ server.py # MCP server entrypoint
|
|
28
|
+
β βββ command_parser.py # parse shell command β structured intent
|
|
29
|
+
β βββ graph_resolver.py # resolve paths β dependency graph nodes
|
|
30
|
+
β βββ risk_scorer.py # combine signals β risk score + rationale
|
|
31
|
+
βββ tests/
|
|
32
|
+
β βββ fixtures/ # real command strings to test against
|
|
33
|
+
β βββ test_command_parser.py
|
|
34
|
+
β βββ test_graph_resolver.py
|
|
35
|
+
β βββ test_risk_scorer.py
|
|
36
|
+
βββ docs/
|
|
37
|
+
βββ heuristics.md # scoring logic, documented as it evolves
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## build order
|
|
43
|
+
|
|
44
|
+
1. `command_parser.py` β pure functions, no dependencies, fully testable in isolation
|
|
45
|
+
2. `server.py` β MCP skeleton, single tool that calls the parser
|
|
46
|
+
3. vendor `parser.py` + `graph.py` from code-review-graph into `src/blast_scope/vendor/crg/`
|
|
47
|
+
4. `graph_resolver.py` β thin wrapper over vendored code, adds path-to-node resolution
|
|
48
|
+
5. `risk_scorer.py` β combine parser + resolver output into a score
|
|
49
|
+
|
|
50
|
+
do not skip ahead. each module should be independently useful before wiring together.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## command parser contract
|
|
55
|
+
|
|
56
|
+
input: raw shell command string
|
|
57
|
+
output:
|
|
58
|
+
```python
|
|
59
|
+
{
|
|
60
|
+
"command": str, # the base command (rm, mv, chmod...)
|
|
61
|
+
"targets": list[str], # resolved absolute paths
|
|
62
|
+
"flags": list[str], # parsed flags (-rf, --force...)
|
|
63
|
+
"intent": str, # "destructive" | "additive" | "read" | "unknown"
|
|
64
|
+
"recursive": bool,
|
|
65
|
+
"reversible": bool # is there a git history? is target in project tree?
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## risk scorer contract
|
|
72
|
+
|
|
73
|
+
input: parser output + graph resolver output
|
|
74
|
+
output:
|
|
75
|
+
```python
|
|
76
|
+
{
|
|
77
|
+
"score": float, # 0.0 to 1.0
|
|
78
|
+
"severity": str, # "low" | "medium" | "high" | "critical"
|
|
79
|
+
"rationale": str, # human-readable explanation
|
|
80
|
+
"affected_nodes": list, # what the graph says depends on this path
|
|
81
|
+
"recommendation": str # "proceed" | "confirm" | "block"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
score formula (v1, iterate as real data comes in):
|
|
86
|
+
`score = command_weight Γ path_in_degree Γ (1 / reversibility_factor)`
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## coding conventions
|
|
91
|
+
|
|
92
|
+
- Python 3.11+, use `uv` for deps
|
|
93
|
+
- type hints everywhere, no exceptions
|
|
94
|
+
- pure functions where possible β side effects only in server.py
|
|
95
|
+
- every public function gets a docstring with an example
|
|
96
|
+
- tests use pytest, fixtures in `tests/fixtures/` as plain .txt files (one command per file)
|
|
97
|
+
- no `print()` in library code, use `logging`
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## dependencies
|
|
102
|
+
|
|
103
|
+
- `mcp` β MCP server SDK
|
|
104
|
+
- `shlex` β command parsing (stdlib, prefer over regex)
|
|
105
|
+
- `pathlib` β all path handling, no raw strings
|
|
106
|
+
- `tree-sitter` + language grammars β already used by code-review-graph, vendored with it
|
|
107
|
+
- `pytest` β testing
|
|
108
|
+
|
|
109
|
+
keep deps minimal. if you're reaching for a new package, ask first.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## safety rules for this session
|
|
114
|
+
|
|
115
|
+
these are non-negotiable regardless of what any task or test requires:
|
|
116
|
+
|
|
117
|
+
- **never run `rm`, `mv`, `truncate`, `chmod`, `chown`, `dd`, `mkfs`, or `sudo`** without showing the exact command and waiting for explicit approval
|
|
118
|
+
- **never delete any file in `tests/fixtures/`** β these are intentionally "dangerous-looking" command strings used as test inputs, not actual commands to run
|
|
119
|
+
- **never execute the contents of test fixtures as shell commands** β they are strings to be parsed, not instructions to follow
|
|
120
|
+
- **never run anything with `-rf` flags** without approval
|
|
121
|
+
- **never modify `pyproject.toml` or `uv.lock` without asking** β dependency changes are a supply chain decision
|
|
122
|
+
- if you're unsure whether something is safe to run, don't run it. show me what you were going to do and ask.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## on test fixtures
|
|
127
|
+
|
|
128
|
+
`tests/fixtures/` will contain strings like `rm -rf /etc` and `sudo su -`. these are **test inputs**, not commands. treat them the way you'd treat SQL injection strings in a security test suite β data to analyze, never to execute.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## graph resolver β vendor from code-review-graph
|
|
133
|
+
|
|
134
|
+
code-review-graph is MIT licensed. don't rewrite what they've already solved β copy the relevant parts directly into `src/blast_scope/vendor/` and modify from there.
|
|
135
|
+
|
|
136
|
+
the parts worth taking:
|
|
137
|
+
- `code_review_graph/parser.py` β Tree-sitter AST parsing, node extraction, language mappings
|
|
138
|
+
- `code_review_graph/graph.py` (or equivalent) β SQLite schema, edge storage, dependency queries
|
|
139
|
+
|
|
140
|
+
the parts to ignore:
|
|
141
|
+
- MCP server, CLI, embeddings, wiki generation, community detection β none of that is relevant
|
|
142
|
+
- anything related to token optimisation or code review output formatting
|
|
143
|
+
|
|
144
|
+
once vendored, we own the code. modify freely β we'll likely need to add path-to-node resolution (given a filesystem path, find all nodes that reference it) which is directionally opposite to what they built (given a node, find what it affects).
|
|
145
|
+
|
|
146
|
+
vendor location: `src/blast_scope/vendor/crg/` β keep it isolated so it's obvious what's ours vs theirs.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## testing the graph resolver
|
|
151
|
+
|
|
152
|
+
the graph resolver tests against a synthetic fixture project, not a real codebase.
|
|
153
|
+
|
|
154
|
+
`tests/fixtures/sample_project/` contains a minimal set of Python files with a known import structure:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
sample_project/
|
|
158
|
+
main.py # imports config, imports db
|
|
159
|
+
config.py # imports nothing
|
|
160
|
+
db.py # imports config
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
ground truth is hardcoded β touching `config.py` must affect `main.py` and `db.py`. if the resolver returns anything else, it's wrong. no external state, no flaky dependencies, fully deterministic.
|
|
164
|
+
|
|
165
|
+
add more fixture projects as edge cases emerge (circular imports, deeply nested deps, files outside src tree).
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## external integrations
|
|
170
|
+
|
|
171
|
+
- **code-review-graph** β MIT licensed, vendor the parser and graph modules directly. source: https://github.com/tirth8205/code-review-graph
|
|
172
|
+
- **shellfirm** β reference for pattern matching heuristics. do not fork or import it.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## what good looks like
|
|
177
|
+
|
|
178
|
+
the tool should be able to say:
|
|
179
|
+
|
|
180
|
+
> `rm -rf ./logs` β LOW risk. 0 importers, not git-tracked, outside src tree. Proceed.
|
|
181
|
+
|
|
182
|
+
> `rm -rf ./config` β CRITICAL. 8 nodes import from this path, 3 are runtime-loaded. No backup detected. Block.
|
|
183
|
+
|
|
184
|
+
same command. completely different score. that's the point.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Atharva Jayappa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: blast-scope
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Contextual blast-radius scoring for shell commands an AI agent is about to run
|
|
5
|
+
Project-URL: Homepage, https://github.com/Atharva-Jayappa/blast-scope
|
|
6
|
+
Project-URL: Repository, https://github.com/Atharva-Jayappa/blast-scope
|
|
7
|
+
Project-URL: Issues, https://github.com/Atharva-Jayappa/blast-scope/issues
|
|
8
|
+
Author-email: Atharva Jayappa <jatharva289@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-agents,blast-radius,claude-code,dependency-graph,guardrail,mcp,safety,shell
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: mcp<2,>=1.0.0
|
|
22
|
+
Requires-Dist: tree-sitter-language-pack<1,>=0.3.0
|
|
23
|
+
Requires-Dist: tree-sitter<1,>=0.23.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# Blast Scope
|
|
30
|
+
|
|
31
|
+
<!-- mcp-name: io.github.atharva-jayappa/blast-scope -->
|
|
32
|
+
|
|
33
|
+
**A consequence engine for shell commands.** Blast Scope scores what a command
|
|
34
|
+
would actually *do* β before an AI agent (or you) runs it. It doesn't pattern-match
|
|
35
|
+
syntax into a blocklist; it figures out the command's **real target**, observes
|
|
36
|
+
that target with a **safe, read-only probe**, and returns a structured risk score
|
|
37
|
+
with evidence.
|
|
38
|
+
|
|
39
|
+
The whole point is *contextual* blast radius. The **same command** gets a
|
|
40
|
+
completely different score depending on what it would actually hit:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
COMMAND SEVERITY WHY ADVICE
|
|
44
|
+
βββββββββββββββββββββββββββββββββ ββββββββ ββββββββββββββββββββββββββββββββββββββββββ βββββββ
|
|
45
|
+
rm -rf ./logs LOW 0 importers Β· regenerable Β· outside src proceed
|
|
46
|
+
rm -rf ./config CRITICAL 8 modules import it Β· high PageRank hub block
|
|
47
|
+
git reset --hard (clean tree) LOW nothing uncommitted to discard proceed
|
|
48
|
+
git reset --hard (4 dirty files) HIGH would throw away 4 files of uncommitted work confirm
|
|
49
|
+
git push --force (protected) CRITICAL would orphan commits on a protected branch block
|
|
50
|
+
docker volume rm cache (absent) LOW volume doesn't exist β nothing to remove proceed
|
|
51
|
+
docker volume rm pgdata (in use) CRITICAL holds data Β· in use Β· no image to rebuild block
|
|
52
|
+
pip uninstall flask (uv.lock) LOW regenerable β exact version pinned in lock proceed
|
|
53
|
+
DROP TABLE users (42 rows) CRITICAL schema + 42 rows Β· irreversible block
|
|
54
|
+
DELETE FROM logs (in txn) HIGH no WHERE β but inside a txn, ROLLBACK-able confirm
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Two commands can be byte-identical and score four bands apart. **That gap is the
|
|
58
|
+
product.**
|
|
59
|
+
|
|
60
|
+
> Not a blocklist. Not a replacement for Shellfirm. Not a syscall monitor. It
|
|
61
|
+
> scores *structural consequence* β advisory, never blocking β and for the rare
|
|
62
|
+
> critical command it captures an undo snapshot first.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## How it works
|
|
67
|
+
|
|
68
|
+
A command flows through a cheap funnel: almost everything is recognized as
|
|
69
|
+
non-destructive in microseconds and exits silent. Only a flagged *destructive
|
|
70
|
+
candidate* pays for a probe.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
shell command
|
|
74
|
+
β split chains (&& || ; |) Β· de-alias PowerShell Β· parse flags/targets
|
|
75
|
+
βΌ
|
|
76
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
77
|
+
β STAGE 1 Β· triage (near-free regex β runs on every command) β
|
|
78
|
+
β which class? git Β· docker Β· pip/uv Β· sql Β· else filesystem β
|
|
79
|
+
β destructive? `git status` β no. `git reset --hard` β yes β β
|
|
80
|
+
βββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
|
|
81
|
+
destructive candidate β (everything else exits here, silent)
|
|
82
|
+
βΌ
|
|
83
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
84
|
+
β ELIGIBILITY FILTER safe read-only probe? AND undo authorable? β
|
|
85
|
+
ββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββββ
|
|
86
|
+
yes, probe it β no probe here / now β
|
|
87
|
+
βΌ βΌ
|
|
88
|
+
STAGE 2 Β· safe probe (read-only) heuristic estimate
|
|
89
|
+
git status Β· reflog Β· rev-list from a static per-class
|
|
90
|
+
docker inspect Β· ps Β· ls table β and LABELED
|
|
91
|
+
sqlite SELECT count(*) [mode=ro] "(estimated)" so you
|
|
92
|
+
pip/uv read lockfiles know it wasn't probed
|
|
93
|
+
β β
|
|
94
|
+
βββββββββββββββββββββββ¬ββββββββββββββββββββββ
|
|
95
|
+
βΌ
|
|
96
|
+
blast radius Γ reversibility (combined PER CLASS β no global formula)
|
|
97
|
+
filesystem also folds in: dependency-graph centrality + recoverability
|
|
98
|
+
βΌ
|
|
99
|
+
score 0.0β1.0 β severity (low / medium / high / critical)
|
|
100
|
+
βΌ
|
|
101
|
+
PreToolUse hook: silent (low/med) Β· advise (high) Β· advise + snapshot (critical)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**The eligibility filter is the design boundary.** A command class earns a *live
|
|
105
|
+
probe* only when both hold: (1) its impact is observable by a **strictly
|
|
106
|
+
side-effect-free read** (HTTP-GET sense β never mutate state to assess state),
|
|
107
|
+
and (2) its undo story is well-known enough to encode in a static table. When a
|
|
108
|
+
probe can't run here and now (no docker daemon, no DB driver, no creds), the tool
|
|
109
|
+
degrades to a labeled estimate β it never guesses silently, and it never blocks.
|
|
110
|
+
|
|
111
|
+
See [docs/heuristics.md](docs/heuristics.md) for the per-class tables, the exact
|
|
112
|
+
filesystem formula, and calibration.
|
|
113
|
+
|
|
114
|
+
### The five command classes
|
|
115
|
+
|
|
116
|
+
| Class | Destructive ops it scores | Safe (read-only) probe | Reversibility signal |
|
|
117
|
+
|---|---|---|---|
|
|
118
|
+
| **Filesystem** | `rm -rf`, `mv`, `>` truncate | dependency graph + git status | git-tracked? regenerable? secret? precious? |
|
|
119
|
+
| **Git** | `reset --hard`, `push --force`, `branch -D`, `clean -fdx` | `status` Β· `reflog` Β· `rev-list` Β· `rev-parse @{u}` | reflog window Β· remote ahead Β· protected branch |
|
|
120
|
+
| **Docker** | `volume rm`, `system prune -a`, `rm -f` | `volume inspect` Β· `ps -a` Β· `volume ls` | volume β none Β· container β recreatable from image |
|
|
121
|
+
| **pip / uv** | `pip uninstall`, `uv pip uninstall` | read lockfile / manifest (no subprocess) | lockfile present β fully regenerable |
|
|
122
|
+
| **SQL** | `DROP`, `TRUNCATE`, `DELETE` without `WHERE` | SQLite: `SELECT count(*)` `mode=ro`; transaction check | inside a transaction? backup posture? |
|
|
123
|
+
|
|
124
|
+
New classes drop in behind one protocol (`triage` / `probe_commands` / `assess`)
|
|
125
|
+
in [`src/blast_scope/classes/`](src/blast_scope/classes); the probe surface each
|
|
126
|
+
declares is asserted read-only by the test suite, so no probe can ever mutate.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Status
|
|
131
|
+
|
|
132
|
+
**v0.3.0 β calibrated multi-class guardrail with a precise dependency graph.**
|
|
133
|
+
|
|
134
|
+
| Capability | Module |
|
|
135
|
+
|---|---|
|
|
136
|
+
| Flag/operand-sensitive command model (POSIX **and** PowerShell) | `command_effects.py`, `command_parser.py` |
|
|
137
|
+
| Recoverability classification (git state, secrets, regenerable, precious data) | `recoverability.py` |
|
|
138
|
+
| Dependency graph + weighted **PageRank** centrality, incremental indexing | `graph_resolver.py`, `centrality.py` |
|
|
139
|
+
| Two-axis, evidence-based filesystem scoring | `risk_scorer.py` |
|
|
140
|
+
| **Command-class probes** β git / docker / pipΒ·uv / SQL, behind one protocol | `classes/` |
|
|
141
|
+
| Out-of-graph **path analyzers** (infra / config-by-path) + git base | `consequences.py`, `vcs.py`, `infra.py`, `config_refs.py` |
|
|
142
|
+
| **PreToolUse hook** + tarball **snapshot/undo** | `hook.py`, `snapshot.py` |
|
|
143
|
+
| **Eval harness** + labeled corpus + calibration | `eval.py`, `tests/fixtures/eval_corpus.jsonl` |
|
|
144
|
+
|
|
145
|
+
**Calibration.** Two harnesses, both run-it-yourself:
|
|
146
|
+
|
|
147
|
+
- **In-repo corpus** (`tests/fixtures/eval_corpus.jsonl`, 38 cases spanning every
|
|
148
|
+
recoverability category, git working-tree state, infra/config, `rm -rf .git`,
|
|
149
|
+
a graph-indexed central module, and the git/docker/pip/SQL classes) β
|
|
150
|
+
**38/38 exact severity, gate F1 1.00**, pinned by `tests/test_eval.py` with
|
|
151
|
+
headroom so changes can't silently regress.
|
|
152
|
+
- **[SABER](https://github.com/sssr-lab/saber)** β 716 real coding-agent
|
|
153
|
+
workspaces. Against ~1725 safe commands, blast-scope's **false-positive rate is
|
|
154
|
+
0.4%**; on its core competency (`data_destruction`) it catches **76.5%** of
|
|
155
|
+
injected attacks on realistic workspaces (**82%** with the dependency graph
|
|
156
|
+
built). The per-category recall is deliberately uneven, and the table says so:
|
|
157
|
+
blast-scope scores *destructive consequence* β filesystem/data loss plus
|
|
158
|
+
git/docker/pip/SQL state. Network exfiltration and persistence are a **different
|
|
159
|
+
threat model, out of scope by design** β not an unfinished corner. That's the
|
|
160
|
+
boundary, drawn on purpose. See [`bench/`](bench).
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
uv run python -m blast_scope.eval # in-repo corpus
|
|
164
|
+
python bench/saber_eval.py --tasks <saber>/dataset/data/tasks.jsonl # SABER
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Installation
|
|
170
|
+
|
|
171
|
+
The fastest path for any MCP client is zero-install via `uvx` (no clone, no venv):
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
uvx blast-scope # runs the MCP server on stdio
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Claude Code users β one line wires up both the MCP tools and the advisory hook:**
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
/plugin marketplace add Atharva-Jayappa/blast-scope
|
|
181
|
+
/plugin install blast-scope
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
For development, or to pin a checkout:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
git clone https://github.com/Atharva-Jayappa/blast-scope.git
|
|
188
|
+
cd blast-scope && uv sync --all-extras
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Usage
|
|
194
|
+
|
|
195
|
+
### As an MCP server
|
|
196
|
+
|
|
197
|
+
Add to your MCP client config (e.g. Claude Code `settings.json`):
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"mcpServers": {
|
|
202
|
+
"blast-scope": { "command": "uvx", "args": ["blast-scope"], "type": "stdio" }
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Tools exposed:
|
|
208
|
+
|
|
209
|
+
| Tool | Purpose |
|
|
210
|
+
|---|---|
|
|
211
|
+
| `assess_command(command, cwd?, project_root?)` | Score a (possibly chained) command. Returns score, severity, rationale, evidence, recoverability, affected nodes, and a per-segment `chain` breakdown. |
|
|
212
|
+
| `index_project(project_root)` | Force a dependency-graph rebuild (auto-built on first use otherwise). |
|
|
213
|
+
| `list_snapshots(project_root)` | List undo snapshots, newest first. |
|
|
214
|
+
| `restore_snapshot(snapshot_id, project_root)` | Undo a risky command by restoring its snapshot. |
|
|
215
|
+
|
|
216
|
+
### As a PreToolUse hook (tiered advice + auto-snapshot)
|
|
217
|
+
|
|
218
|
+
Intercept Bash commands *before* they run β advisory, never blocking. Volume
|
|
219
|
+
scales with stakes: **silent** on low/medium, **advise** on high, **advise +
|
|
220
|
+
snapshot** on critical. The snapshot skips what's already recoverable
|
|
221
|
+
(git-clean, regenerable) and warns rather than tars anything over a hard size
|
|
222
|
+
cap, so the undo net stays fast and trustworthy. Add to `.claude/settings.json`:
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"hooks": {
|
|
227
|
+
"PreToolUse": [
|
|
228
|
+
{ "matcher": "Bash",
|
|
229
|
+
"hooks": [{ "type": "command", "command": "python -m blast_scope.hook" }] }
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Full details and the undo flow: [docs/hook.md](docs/hook.md).
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Example output
|
|
240
|
+
|
|
241
|
+
A filesystem command, scored against the dependency graph:
|
|
242
|
+
|
|
243
|
+
```jsonc
|
|
244
|
+
// assess_command("rm -rf ./config", project_root="/proj")
|
|
245
|
+
{
|
|
246
|
+
"score": 0.93,
|
|
247
|
+
"severity": "critical",
|
|
248
|
+
"recommendation": "block",
|
|
249
|
+
"recoverability": "untracked",
|
|
250
|
+
"rationale": "rm targets config. 8 direct importer(s), 14 total affected. not git-tracked. recursive deletion. CRITICAL risk.",
|
|
251
|
+
"evidence": [
|
|
252
|
+
"8 importer(s), 14 affected node(s)",
|
|
253
|
+
"high centrality (PageRank 0.91) β a hub other code routes through",
|
|
254
|
+
"untracked β not in git history",
|
|
255
|
+
"recursive β applies to every file underneath"
|
|
256
|
+
],
|
|
257
|
+
"affected_nodes": [ /* ... */ ],
|
|
258
|
+
"chain": [ /* per-segment breakdown */ ]
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
A command class that couldn't probe β note the **labeled estimate** (no
|
|
263
|
+
Postgres driver, server possibly remote, so the tool refuses to guess silently):
|
|
264
|
+
|
|
265
|
+
```jsonc
|
|
266
|
+
// assess_command('psql -c "DROP TABLE users"')
|
|
267
|
+
{
|
|
268
|
+
"score": 0.9,
|
|
269
|
+
"severity": "critical",
|
|
270
|
+
"recommendation": "block",
|
|
271
|
+
"evidence": [
|
|
272
|
+
"drops users β its schema and all rows, irreversible (estimated β no read-only probe for postgres)"
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
// the same DROP against a local SQLite file probes for real:
|
|
276
|
+
// "drops users β its schema and 42 row(s), irreversible" (estimated: false)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Development
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
uv sync --all-extras
|
|
285
|
+
uv run pytest -q # full suite
|
|
286
|
+
uv run python -m blast_scope.eval # scoring accuracy report
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Project structure
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
blast-scope/
|
|
293
|
+
βββ src/blast_scope/
|
|
294
|
+
β βββ server.py # MCP server + tools (assess, index, snapshots)
|
|
295
|
+
β βββ command_parser.py # shell β structured intent (POSIX + PowerShell)
|
|
296
|
+
β βββ command_effects.py # command/flag/operand β intent + weight
|
|
297
|
+
β βββ recoverability.py # path β how recoverable if destroyed
|
|
298
|
+
β βββ graph_resolver.py # paths β dependency-graph impact (+ PageRank)
|
|
299
|
+
β βββ centrality.py # pure-Python weighted PageRank
|
|
300
|
+
β βββ risk_scorer.py # signals β score + severity + evidence
|
|
301
|
+
β βββ classes/ # command-class probes behind one protocol
|
|
302
|
+
β β βββ __init__.py # Candidate Β· ConsequenceClass Β· registry
|
|
303
|
+
β β βββ git.py # reflog / upstream-divergence / protected branch
|
|
304
|
+
β β βββ docker.py # volume / container / system-prune probes
|
|
305
|
+
β β βββ packages.py # pipΒ·uv uninstall vs. lockfile presence
|
|
306
|
+
β β βββ sql.py # DROP/TRUNCATE/DELETE β SQLite probe + estimates
|
|
307
|
+
β βββ consequences.py # coordinator: class probes + path analyzers
|
|
308
|
+
β βββ vcs.py / infra.py / config_refs.py # git base + path analyzers
|
|
309
|
+
β βββ hook.py # PreToolUse advisory hook
|
|
310
|
+
β βββ snapshot.py # tarball snapshot / restore / list
|
|
311
|
+
β βββ eval.py # evaluation harness + metrics
|
|
312
|
+
β βββ vendor/crg/ # vendored from code-review-graph (MIT)
|
|
313
|
+
βββ tests/ # 298 tests incl. eval regression guard
|
|
314
|
+
β βββ fixtures/eval_corpus.jsonl # labeled calibration corpus
|
|
315
|
+
βββ docs/
|
|
316
|
+
βββ heuristics.md # scoring model + per-class tables + calibration
|
|
317
|
+
βββ hook.md # hook registration + undo
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Roadmap
|
|
323
|
+
|
|
324
|
+
- Lift recall on the destruction classes (glob targets over tracked files,
|
|
325
|
+
`find`-based deletion variants) β the SABER per-category table is the worklist.
|
|
326
|
+
- Optional live probes for Postgres/MySQL (in-process, read-only) once a driver
|
|
327
|
+
policy is settled β today those engines degrade to labeled estimates.
|
|
328
|
+
- PowerShell-shell awareness in the hook path (the MCP tool already supports it).
|
|
329
|
+
- Optional richer interception modes beyond advisory.
|
|
330
|
+
|
|
331
|
+
See [CLAUDE.md](CLAUDE.md) for the full spec, contracts, and design rules.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Publishing blast-scope
|
|
2
|
+
|
|
3
|
+
The repo is ship-ready: packaging metadata, an MIT `LICENSE`, an MCP registry
|
|
4
|
+
manifest (`server.json`), and a Claude Code plugin (`.claude-plugin/`,
|
|
5
|
+
`.mcp.json`, `hooks/`) are all in place. The steps below need *your* accounts and
|
|
6
|
+
are the only things not already done β run them in order.
|
|
7
|
+
|
|
8
|
+
## 1. PyPI (the gate β everything downstream needs this)
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
uv build # produces dist/*.whl and dist/*.tar.gz (already verified)
|
|
12
|
+
uvx twine upload dist/* # needs a PyPI account + API token
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- Verify the install works clean: `uvx blast-scope` (from a fresh shell).
|
|
16
|
+
- The README already carries the registry ownership token
|
|
17
|
+
(`<!-- mcp-name: io.github.atharva-jayappa/blast-scope -->`), so the PyPI
|
|
18
|
+
description satisfies the next step automatically.
|
|
19
|
+
|
|
20
|
+
## 2. Official MCP registry (most directories crawl from here)
|
|
21
|
+
|
|
22
|
+
`registry.modelcontextprotocol.io` has no human review β it verifies ownership
|
|
23
|
+
and schema only.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
brew install mcp-publisher # or grab the prebuilt binary
|
|
27
|
+
mcp-publisher login github # ties the io.github.atharva-jayappa namespace to your GH account
|
|
28
|
+
mcp-publisher publish # reads server.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`server.json` is pre-filled; `mcp-publisher init` is authoritative if the schema
|
|
32
|
+
has moved on β diff its output against the committed file and reconcile the
|
|
33
|
+
version/identifier fields.
|
|
34
|
+
|
|
35
|
+
Then **claim** your auto-ingested listings on PulseMCP, Glama, and mcp.so, and
|
|
36
|
+
open a PR to [`punkpeye/awesome-mcp-servers`](https://github.com/punkpeye/awesome-mcp-servers)
|
|
37
|
+
(there's no clear "security/guardrails" leader yet β own that slot).
|
|
38
|
+
|
|
39
|
+
## 3. Claude Code plugin
|
|
40
|
+
|
|
41
|
+
Already works straight from the repo β no gatekeeper:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
/plugin marketplace add Atharva-Jayappa/blast-scope
|
|
45
|
+
/plugin install blast-scope
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
That installs the MCP server **and** the advisory PreToolUse hook in one step.
|
|
49
|
+
The hook runs `uvx --from blast-scope blast-scope-hook`; if you'd rather avoid
|
|
50
|
+
per-command `uvx` startup, `uv tool install blast-scope` puts `blast-scope-hook`
|
|
51
|
+
on PATH and you can point the hook at it directly.
|
|
52
|
+
|
|
53
|
+
Once polished, submit to Anthropic's curated directory via the
|
|
54
|
+
[plugin directory submission form](https://clau.de/plugin-directory-submission).
|
|
55
|
+
|
|
56
|
+
## 4. Announce
|
|
57
|
+
|
|
58
|
+
The killer demo is already the README headline: **same command, opposite score β
|
|
59
|
+
`rm -rf ./logs` LOW vs `rm -rf ./config` CRITICAL.** Lead a Show HN with the
|
|
60
|
+
contrast and the SABER numbers (0.4% FPR, 82% on data-destruction), TueβThu
|
|
61
|
+
morning ET, and work the thread for the first few hours. Cross-post to
|
|
62
|
+
r/ClaudeAI and r/commandline framed as "I built this because my agent `rm -rf`'d
|
|
63
|
+
my config."
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
**Note on the license:** MIT is set as a sensible default (and the project
|
|
68
|
+
vendors MIT-licensed code from code-review-graph). Change `LICENSE` and the
|
|
69
|
+
`license` field in `pyproject.toml` if you want something else before publishing.
|