ranwhat 0.1.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.
- ranwhat-0.1.0/LICENSE +21 -0
- ranwhat-0.1.0/PKG-INFO +247 -0
- ranwhat-0.1.0/README.md +220 -0
- ranwhat-0.1.0/pyproject.toml +54 -0
- ranwhat-0.1.0/ranwhat/__init__.py +3 -0
- ranwhat-0.1.0/ranwhat/__main__.py +7 -0
- ranwhat-0.1.0/ranwhat/catalog.py +250 -0
- ranwhat-0.1.0/ranwhat/clean.py +570 -0
- ranwhat-0.1.0/ranwhat/cli.py +248 -0
- ranwhat-0.1.0/ranwhat/demo/support-copilot.json +60 -0
- ranwhat-0.1.0/ranwhat/demo/well-configured.json +39 -0
- ranwhat-0.1.0/ranwhat/html_report.py +263 -0
- ranwhat-0.1.0/ranwhat/introspect.py +148 -0
- ranwhat-0.1.0/ranwhat/report.py +94 -0
- ranwhat-0.1.0/ranwhat/score.py +420 -0
- ranwhat-0.1.0/ranwhat/usage.py +334 -0
- ranwhat-0.1.0/ranwhat/watch.py +903 -0
- ranwhat-0.1.0/ranwhat.egg-info/PKG-INFO +247 -0
- ranwhat-0.1.0/ranwhat.egg-info/SOURCES.txt +28 -0
- ranwhat-0.1.0/ranwhat.egg-info/dependency_links.txt +1 -0
- ranwhat-0.1.0/ranwhat.egg-info/entry_points.txt +2 -0
- ranwhat-0.1.0/ranwhat.egg-info/top_level.txt +1 -0
- ranwhat-0.1.0/setup.cfg +4 -0
- ranwhat-0.1.0/tests/test_clean.py +219 -0
- ranwhat-0.1.0/tests/test_coverage.py +119 -0
- ranwhat-0.1.0/tests/test_openclaw.py +105 -0
- ranwhat-0.1.0/tests/test_packaging.py +58 -0
- ranwhat-0.1.0/tests/test_robustness.py +137 -0
- ranwhat-0.1.0/tests/test_security.py +162 -0
- ranwhat-0.1.0/tests/test_watch.py +273 -0
ranwhat-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matija Mikulinec
|
|
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.
|
ranwhat-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ranwhat
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Flight recorder and authority scanner for AI agents. Reads locally, transmits nothing.
|
|
5
|
+
Author: Matija Mikulinec
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://ranwhat.com
|
|
8
|
+
Project-URL: Source, https://github.com/MatijaMiki/ranwhat
|
|
9
|
+
Project-URL: Issues, https://github.com/MatijaMiki/ranwhat/issues
|
|
10
|
+
Keywords: ai,agents,security,audit,observability,least-privilege
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Classifier: Topic :: System :: Monitoring
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# ranwhat
|
|
29
|
+
|
|
30
|
+
**A flight recorder for AI agents, and a scanner for the authority they hold.**
|
|
31
|
+
Reads locally. Transmits nothing. No dependencies.
|
|
32
|
+
|
|
33
|
+
Your coding agent has your shell, your keys and your repo. `ranwhat` reads
|
|
34
|
+
what it actually ran and surfaces the handful of irreversible actions worth
|
|
35
|
+
knowing about.
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
$ ranwhat watch --days 90
|
|
39
|
+
|
|
40
|
+
ranwhat watch · local agent flight recorder
|
|
41
|
+
--------------------------------------------------------------
|
|
42
|
+
4 source(s) over 90 days
|
|
43
|
+
|
|
44
|
+
1 critical 3 high
|
|
45
|
+
|
|
46
|
+
* Credential material accessed 18:13 Bash
|
|
47
|
+
cat ~/.ssh/id_rsa
|
|
48
|
+
-> Whatever it read is now in a model context you do not control.
|
|
49
|
+
|
|
50
|
+
* Bulk or recursive deletion 14:42 Bash
|
|
51
|
+
mv '@/components/'*.tsx src/components/ ; rm -rf '@'
|
|
52
|
+
-> Recursive deletion. Recoverable only if something else was
|
|
53
|
+
backing it up.
|
|
54
|
+
|
|
55
|
+
--------------------------------------------------------------
|
|
56
|
+
Read locally. Nothing was transmitted.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uvx --from git+https://github.com/MatijaMiki/ranwhat ranwhat watch
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or put it on your path:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pipx install git+https://github.com/MatijaMiki/ranwhat
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Python 3.9+. Installing with plain `pip`? Upgrade it first, because the pip macOS
|
|
72
|
+
ships cannot read this project's metadata and will silently build a wheel
|
|
73
|
+
named `UNKNOWN-0.0.0` that installs fine and gives you no command.
|
|
74
|
+
|
|
75
|
+
## Two tools
|
|
76
|
+
|
|
77
|
+
### `ranwhat watch`: what your agents did
|
|
78
|
+
|
|
79
|
+
Reads transcripts your agents already wrote to disk. No wrapper, no proxy,
|
|
80
|
+
nothing in your critical path.
|
|
81
|
+
|
|
82
|
+
| Source | Location | Format |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| Claude Code | `~/.claude/projects/*/*.jsonl` | JSONL |
|
|
85
|
+
| OpenClaw | `$OPENCLAW_STATE_DIR/agents/*/agent/*.sqlite` | SQLite |
|
|
86
|
+
|
|
87
|
+
Nine rules: credential access, secret literals in commands, package
|
|
88
|
+
publishing, cloud destruction, financial API calls, log tampering, destructive
|
|
89
|
+
git, recursive deletion, exfiltration-shaped pipes.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
ranwhat watch --days 30
|
|
93
|
+
ranwhat watch --source openclaw
|
|
94
|
+
ranwhat watch --json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `ranwhat scan`: what they're allowed to do next
|
|
98
|
+
|
|
99
|
+
Reads the credentials an agent holds, read-only, and scores the three things
|
|
100
|
+
that determine exposure.
|
|
101
|
+
|
|
102
|
+
| Axis | Question |
|
|
103
|
+
|---|---|
|
|
104
|
+
| Authority | What is it allowed to do? |
|
|
105
|
+
| Observability | Can you reconstruct a named past action? |
|
|
106
|
+
| Reversibility | Can a wrong action be undone? |
|
|
107
|
+
|
|
108
|
+
Observability vetoes the overall verdict. An agent that cannot reconstruct its
|
|
109
|
+
own tool calls is indistinguishable from the worst case.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
ranwhat demo # see it on a worked example
|
|
113
|
+
ranwhat scan profile.json --html report.html
|
|
114
|
+
ranwhat live --github "$GH_TOKEN" # read-only introspection
|
|
115
|
+
ranwhat scan profile.json --pull-usage --stripe "$STRIPE_KEY"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Capability catalogues for Google, GitHub, Slack, Stripe and AWS. Unrecognised
|
|
119
|
+
scopes are classified by action verb and flagged unclassified, never assumed
|
|
120
|
+
safe.
|
|
121
|
+
|
|
122
|
+
### `ranwhat clean`: secrets sitting in your transcripts
|
|
123
|
+
|
|
124
|
+
When an agent runs `cat .env`, the **output** is written into the transcript:
|
|
125
|
+
your database password, your JWT secret, your provider tokens, in plaintext,
|
|
126
|
+
in a file that is never rotated and gets read again by agents later.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
ranwhat clean # report, then open a review session
|
|
130
|
+
ranwhat clean --apply # mask everything without asking
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Scanning a real history takes a while, so the session stays open on what it
|
|
134
|
+
just found rather than making you re-scan to act on it:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
ranwhat> list the findings again
|
|
138
|
+
ranwhat> show 3 where it appears, and what to roll it at
|
|
139
|
+
ranwhat> mask 3 mask just that one
|
|
140
|
+
ranwhat> mask all mask everything listed
|
|
141
|
+
ranwhat> keep 3 leave it alone
|
|
142
|
+
ranwhat> rotate what to rotate, grouped by provider
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Each finding says where it came from, the file it was read out of and the
|
|
146
|
+
project that file belongs to, because a 64-character string is useless
|
|
147
|
+
without knowing which `.env` it escaped:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
* AWS access key ID AKI…WB 20 chars seen 8x
|
|
151
|
+
read from api/.env
|
|
152
|
+
in /Users/you/Desktop/app
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Redaction is not remediation.** Masking a value here does not un-expose it.
|
|
156
|
+
It was already on disk and already sat in a model context you do not control.
|
|
157
|
+
The rotation is the fix; masking only stops it leaking a second time. The
|
|
158
|
+
report says so rather than implying safety.
|
|
159
|
+
|
|
160
|
+
Only masks a value when the key beside it names it as a secret or the value
|
|
161
|
+
carries a recognisable credential shape. Placeholders, template files and
|
|
162
|
+
ordinary config are left alone. Backups go to `~/.ranwhat/backups`, and the
|
|
163
|
+
rewritten file is parsed back before it replaces the original.
|
|
164
|
+
|
|
165
|
+
## Precision is the feature
|
|
166
|
+
|
|
167
|
+
A watcher that cries wolf gets muted in a day, and a muted watcher records
|
|
168
|
+
nothing anyone reads. So these are **not** treated as actions:
|
|
169
|
+
|
|
170
|
+
| Not an action | Why |
|
|
171
|
+
|---|---|
|
|
172
|
+
| `grep "rm -rf" src/` | Searching for a string isn't running it |
|
|
173
|
+
| `python3 -c "print('rm -rf /')"` | The payload is Python source, not shell |
|
|
174
|
+
| `echo "rm -rf /"` | An echo argument is literal text |
|
|
175
|
+
| `cat > f.sh <<'EOF' … EOF` | A heredoc body is data being written |
|
|
176
|
+
| `git rm --cached x` | Unstages; never touches the working tree |
|
|
177
|
+
| `# rm -rf ~/x` | A comment |
|
|
178
|
+
| `rm -rf build` `rm -rf /tmp/x` | Deleting build output is not an incident |
|
|
179
|
+
|
|
180
|
+
`bash -c` is the exception: its payload really is shell, so the parser
|
|
181
|
+
recurses into it. And severity follows the **target**, not the verb:
|
|
182
|
+
`rm -rf /tmp/x` is silent, `rm -rf ~/Documents` is high, `rm -rf /` is
|
|
183
|
+
critical.
|
|
184
|
+
|
|
185
|
+
Every row above came from running the tool against a real machine and finding
|
|
186
|
+
it wrong. On that machine the first build reported 15 findings; 3 were false
|
|
187
|
+
positives and 11 were true deletions of build directories that nobody would
|
|
188
|
+
want to read. It reports 4 now, and all four are real.
|
|
189
|
+
|
|
190
|
+
## Handling credentials
|
|
191
|
+
|
|
192
|
+
Pass tokens through the environment, not the command line. Anything in argv is
|
|
193
|
+
readable by every user on the machine through the process table, and is written
|
|
194
|
+
to your shell history.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
export RANWHAT_STRIPE_TOKEN="rk_live_..."
|
|
198
|
+
ranwhat scan profile.json --pull-usage
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`export` it first, because `VAR=x ranwhat ...` on one line still puts the value in
|
|
202
|
+
that shell's own command line. `--stripe env:MY_VAR` and `--stripe -` (read one
|
|
203
|
+
line from stdin) also work. Passing a token as a flag value still works and
|
|
204
|
+
prints a warning saying why it shouldn't.
|
|
205
|
+
|
|
206
|
+
Reports are written mode `600` and never through a symlink: a report maps an
|
|
207
|
+
agent's entire authority surface, which is useful to somebody other than you.
|
|
208
|
+
|
|
209
|
+
## Nothing leaves the machine
|
|
210
|
+
|
|
211
|
+
Not a policy but an architecture:
|
|
212
|
+
|
|
213
|
+
- Credentials are held in memory for one call and never written down
|
|
214
|
+
- Live introspection talks only to the credential's own issuer
|
|
215
|
+
- Scans never exercise a permission and never need a write-scoped token
|
|
216
|
+
- No runtime dependencies, so there is nothing to audit before you point this at your keys
|
|
217
|
+
|
|
218
|
+
## Say what you don't know
|
|
219
|
+
|
|
220
|
+
Usage evidence has three states, reported distinctly, because collapsing them
|
|
221
|
+
makes the report contradict itself:
|
|
222
|
+
|
|
223
|
+
| State | Meaning |
|
|
224
|
+
|---|---|
|
|
225
|
+
| Verified | Pulled from the provider's own audit trail |
|
|
226
|
+
| Self-attested | Declared in the profile, not independently pulled |
|
|
227
|
+
| Unverified | No evidence at all, and scopes are not assumed safe |
|
|
228
|
+
|
|
229
|
+
Usage pulls: AWS IAM service-last-accessed, Stripe events, Google Admin SDK,
|
|
230
|
+
GitHub org audit log. Slack has no usable API below Enterprise Grid and says
|
|
231
|
+
so rather than returning an empty set.
|
|
232
|
+
|
|
233
|
+
## Status
|
|
234
|
+
|
|
235
|
+
Alpha, honestly. The local tools ship and are tested; hosted collection and
|
|
236
|
+
evidence retention are not built yet.
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
python3 -m unittest discover -s tests -v
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
102 tests, written as invariants rather than expected output. Most of them
|
|
243
|
+
exist because something on this page was once wrong.
|
|
244
|
+
|
|
245
|
+
## Licence
|
|
246
|
+
|
|
247
|
+
MIT. Built by cenner.
|
ranwhat-0.1.0/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# ranwhat
|
|
2
|
+
|
|
3
|
+
**A flight recorder for AI agents, and a scanner for the authority they hold.**
|
|
4
|
+
Reads locally. Transmits nothing. No dependencies.
|
|
5
|
+
|
|
6
|
+
Your coding agent has your shell, your keys and your repo. `ranwhat` reads
|
|
7
|
+
what it actually ran and surfaces the handful of irreversible actions worth
|
|
8
|
+
knowing about.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
$ ranwhat watch --days 90
|
|
12
|
+
|
|
13
|
+
ranwhat watch · local agent flight recorder
|
|
14
|
+
--------------------------------------------------------------
|
|
15
|
+
4 source(s) over 90 days
|
|
16
|
+
|
|
17
|
+
1 critical 3 high
|
|
18
|
+
|
|
19
|
+
* Credential material accessed 18:13 Bash
|
|
20
|
+
cat ~/.ssh/id_rsa
|
|
21
|
+
-> Whatever it read is now in a model context you do not control.
|
|
22
|
+
|
|
23
|
+
* Bulk or recursive deletion 14:42 Bash
|
|
24
|
+
mv '@/components/'*.tsx src/components/ ; rm -rf '@'
|
|
25
|
+
-> Recursive deletion. Recoverable only if something else was
|
|
26
|
+
backing it up.
|
|
27
|
+
|
|
28
|
+
--------------------------------------------------------------
|
|
29
|
+
Read locally. Nothing was transmitted.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uvx --from git+https://github.com/MatijaMiki/ranwhat ranwhat watch
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or put it on your path:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pipx install git+https://github.com/MatijaMiki/ranwhat
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Python 3.9+. Installing with plain `pip`? Upgrade it first, because the pip macOS
|
|
45
|
+
ships cannot read this project's metadata and will silently build a wheel
|
|
46
|
+
named `UNKNOWN-0.0.0` that installs fine and gives you no command.
|
|
47
|
+
|
|
48
|
+
## Two tools
|
|
49
|
+
|
|
50
|
+
### `ranwhat watch`: what your agents did
|
|
51
|
+
|
|
52
|
+
Reads transcripts your agents already wrote to disk. No wrapper, no proxy,
|
|
53
|
+
nothing in your critical path.
|
|
54
|
+
|
|
55
|
+
| Source | Location | Format |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| Claude Code | `~/.claude/projects/*/*.jsonl` | JSONL |
|
|
58
|
+
| OpenClaw | `$OPENCLAW_STATE_DIR/agents/*/agent/*.sqlite` | SQLite |
|
|
59
|
+
|
|
60
|
+
Nine rules: credential access, secret literals in commands, package
|
|
61
|
+
publishing, cloud destruction, financial API calls, log tampering, destructive
|
|
62
|
+
git, recursive deletion, exfiltration-shaped pipes.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
ranwhat watch --days 30
|
|
66
|
+
ranwhat watch --source openclaw
|
|
67
|
+
ranwhat watch --json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### `ranwhat scan`: what they're allowed to do next
|
|
71
|
+
|
|
72
|
+
Reads the credentials an agent holds, read-only, and scores the three things
|
|
73
|
+
that determine exposure.
|
|
74
|
+
|
|
75
|
+
| Axis | Question |
|
|
76
|
+
|---|---|
|
|
77
|
+
| Authority | What is it allowed to do? |
|
|
78
|
+
| Observability | Can you reconstruct a named past action? |
|
|
79
|
+
| Reversibility | Can a wrong action be undone? |
|
|
80
|
+
|
|
81
|
+
Observability vetoes the overall verdict. An agent that cannot reconstruct its
|
|
82
|
+
own tool calls is indistinguishable from the worst case.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
ranwhat demo # see it on a worked example
|
|
86
|
+
ranwhat scan profile.json --html report.html
|
|
87
|
+
ranwhat live --github "$GH_TOKEN" # read-only introspection
|
|
88
|
+
ranwhat scan profile.json --pull-usage --stripe "$STRIPE_KEY"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Capability catalogues for Google, GitHub, Slack, Stripe and AWS. Unrecognised
|
|
92
|
+
scopes are classified by action verb and flagged unclassified, never assumed
|
|
93
|
+
safe.
|
|
94
|
+
|
|
95
|
+
### `ranwhat clean`: secrets sitting in your transcripts
|
|
96
|
+
|
|
97
|
+
When an agent runs `cat .env`, the **output** is written into the transcript:
|
|
98
|
+
your database password, your JWT secret, your provider tokens, in plaintext,
|
|
99
|
+
in a file that is never rotated and gets read again by agents later.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
ranwhat clean # report, then open a review session
|
|
103
|
+
ranwhat clean --apply # mask everything without asking
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Scanning a real history takes a while, so the session stays open on what it
|
|
107
|
+
just found rather than making you re-scan to act on it:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
ranwhat> list the findings again
|
|
111
|
+
ranwhat> show 3 where it appears, and what to roll it at
|
|
112
|
+
ranwhat> mask 3 mask just that one
|
|
113
|
+
ranwhat> mask all mask everything listed
|
|
114
|
+
ranwhat> keep 3 leave it alone
|
|
115
|
+
ranwhat> rotate what to rotate, grouped by provider
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Each finding says where it came from, the file it was read out of and the
|
|
119
|
+
project that file belongs to, because a 64-character string is useless
|
|
120
|
+
without knowing which `.env` it escaped:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
* AWS access key ID AKI…WB 20 chars seen 8x
|
|
124
|
+
read from api/.env
|
|
125
|
+
in /Users/you/Desktop/app
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Redaction is not remediation.** Masking a value here does not un-expose it.
|
|
129
|
+
It was already on disk and already sat in a model context you do not control.
|
|
130
|
+
The rotation is the fix; masking only stops it leaking a second time. The
|
|
131
|
+
report says so rather than implying safety.
|
|
132
|
+
|
|
133
|
+
Only masks a value when the key beside it names it as a secret or the value
|
|
134
|
+
carries a recognisable credential shape. Placeholders, template files and
|
|
135
|
+
ordinary config are left alone. Backups go to `~/.ranwhat/backups`, and the
|
|
136
|
+
rewritten file is parsed back before it replaces the original.
|
|
137
|
+
|
|
138
|
+
## Precision is the feature
|
|
139
|
+
|
|
140
|
+
A watcher that cries wolf gets muted in a day, and a muted watcher records
|
|
141
|
+
nothing anyone reads. So these are **not** treated as actions:
|
|
142
|
+
|
|
143
|
+
| Not an action | Why |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `grep "rm -rf" src/` | Searching for a string isn't running it |
|
|
146
|
+
| `python3 -c "print('rm -rf /')"` | The payload is Python source, not shell |
|
|
147
|
+
| `echo "rm -rf /"` | An echo argument is literal text |
|
|
148
|
+
| `cat > f.sh <<'EOF' … EOF` | A heredoc body is data being written |
|
|
149
|
+
| `git rm --cached x` | Unstages; never touches the working tree |
|
|
150
|
+
| `# rm -rf ~/x` | A comment |
|
|
151
|
+
| `rm -rf build` `rm -rf /tmp/x` | Deleting build output is not an incident |
|
|
152
|
+
|
|
153
|
+
`bash -c` is the exception: its payload really is shell, so the parser
|
|
154
|
+
recurses into it. And severity follows the **target**, not the verb:
|
|
155
|
+
`rm -rf /tmp/x` is silent, `rm -rf ~/Documents` is high, `rm -rf /` is
|
|
156
|
+
critical.
|
|
157
|
+
|
|
158
|
+
Every row above came from running the tool against a real machine and finding
|
|
159
|
+
it wrong. On that machine the first build reported 15 findings; 3 were false
|
|
160
|
+
positives and 11 were true deletions of build directories that nobody would
|
|
161
|
+
want to read. It reports 4 now, and all four are real.
|
|
162
|
+
|
|
163
|
+
## Handling credentials
|
|
164
|
+
|
|
165
|
+
Pass tokens through the environment, not the command line. Anything in argv is
|
|
166
|
+
readable by every user on the machine through the process table, and is written
|
|
167
|
+
to your shell history.
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
export RANWHAT_STRIPE_TOKEN="rk_live_..."
|
|
171
|
+
ranwhat scan profile.json --pull-usage
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`export` it first, because `VAR=x ranwhat ...` on one line still puts the value in
|
|
175
|
+
that shell's own command line. `--stripe env:MY_VAR` and `--stripe -` (read one
|
|
176
|
+
line from stdin) also work. Passing a token as a flag value still works and
|
|
177
|
+
prints a warning saying why it shouldn't.
|
|
178
|
+
|
|
179
|
+
Reports are written mode `600` and never through a symlink: a report maps an
|
|
180
|
+
agent's entire authority surface, which is useful to somebody other than you.
|
|
181
|
+
|
|
182
|
+
## Nothing leaves the machine
|
|
183
|
+
|
|
184
|
+
Not a policy but an architecture:
|
|
185
|
+
|
|
186
|
+
- Credentials are held in memory for one call and never written down
|
|
187
|
+
- Live introspection talks only to the credential's own issuer
|
|
188
|
+
- Scans never exercise a permission and never need a write-scoped token
|
|
189
|
+
- No runtime dependencies, so there is nothing to audit before you point this at your keys
|
|
190
|
+
|
|
191
|
+
## Say what you don't know
|
|
192
|
+
|
|
193
|
+
Usage evidence has three states, reported distinctly, because collapsing them
|
|
194
|
+
makes the report contradict itself:
|
|
195
|
+
|
|
196
|
+
| State | Meaning |
|
|
197
|
+
|---|---|
|
|
198
|
+
| Verified | Pulled from the provider's own audit trail |
|
|
199
|
+
| Self-attested | Declared in the profile, not independently pulled |
|
|
200
|
+
| Unverified | No evidence at all, and scopes are not assumed safe |
|
|
201
|
+
|
|
202
|
+
Usage pulls: AWS IAM service-last-accessed, Stripe events, Google Admin SDK,
|
|
203
|
+
GitHub org audit log. Slack has no usable API below Enterprise Grid and says
|
|
204
|
+
so rather than returning an empty set.
|
|
205
|
+
|
|
206
|
+
## Status
|
|
207
|
+
|
|
208
|
+
Alpha, honestly. The local tools ship and are tested; hosted collection and
|
|
209
|
+
evidence retention are not built yet.
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
python3 -m unittest discover -s tests -v
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
102 tests, written as invariants rather than expected output. Most of them
|
|
216
|
+
exist because something on this page was once wrong.
|
|
217
|
+
|
|
218
|
+
## Licence
|
|
219
|
+
|
|
220
|
+
MIT. Built by cenner.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ranwhat"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Flight recorder and authority scanner for AI agents. Reads locally, transmits nothing."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "Matija Mikulinec" }]
|
|
13
|
+
keywords = ["ai", "agents", "security", "audit", "observability", "least-privilege"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Intended Audience :: System Administrators",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Security",
|
|
26
|
+
"Topic :: System :: Monitoring",
|
|
27
|
+
]
|
|
28
|
+
# Deliberately empty. This is pointed at your own credentials; a dependency
|
|
29
|
+
# tree is something a reviewer has to audit before that is reasonable.
|
|
30
|
+
dependencies = []
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://ranwhat.com"
|
|
34
|
+
Source = "https://github.com/MatijaMiki/ranwhat"
|
|
35
|
+
Issues = "https://github.com/MatijaMiki/ranwhat/issues"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
ranwhat = "ranwhat.cli:main"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools]
|
|
41
|
+
packages = ["ranwhat"]
|
|
42
|
+
include-package-data = false
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.package-data]
|
|
45
|
+
ranwhat = ["demo/*.json"]
|
|
46
|
+
|
|
47
|
+
# Named so `uv publish --index testpypi` resolves. TestPyPI is a separate
|
|
48
|
+
# service with its own account and its own tokens; a real release goes to the
|
|
49
|
+
# default index and needs no --index at all.
|
|
50
|
+
[[tool.uv.index]]
|
|
51
|
+
name = "testpypi"
|
|
52
|
+
url = "https://test.pypi.org/simple/"
|
|
53
|
+
publish-url = "https://test.pypi.org/legacy/"
|
|
54
|
+
explicit = true
|