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 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.
@@ -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
@@ -0,0 +1,3 @@
1
+ """ranwhat -- flight recorder and authority scanner for AI agents."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,7 @@
1
+ """Allow `python -m ranwhat` as well as the installed `ranwhat` script."""
2
+ import sys
3
+
4
+ from .cli import main
5
+
6
+ if __name__ == "__main__":
7
+ sys.exit(main())