residoo 0.1.0 → 0.3.0
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.
- package/README.md +334 -46
- package/SECURITY.md +29 -22
- package/package.json +1 -1
- package/src/cli.js +249 -17
- package/src/integrity.js +689 -0
- package/src/patterns.js +78 -5
- package/src/report.js +188 -8
- package/src/rotation.js +834 -0
- package/src/sources/agent-configs.js +308 -0
- package/src/sources/aider.js +361 -0
- package/src/sources/amazon-q.js +199 -0
- package/src/sources/antigravity-cli.js +155 -0
- package/src/sources/cline.js +208 -0
- package/src/sources/codebuff.js +295 -0
- package/src/sources/codex-cli.js +258 -0
- package/src/sources/cody.js +325 -0
- package/src/sources/continue.js +408 -0
- package/src/sources/copilot-chat.js +272 -0
- package/src/sources/copilot-cli.js +300 -0
- package/src/sources/crush.js +364 -0
- package/src/sources/cursor.js +374 -0
- package/src/sources/devin-cli.js +241 -0
- package/src/sources/factory-droid.js +153 -0
- package/src/sources/fx.js +136 -0
- package/src/sources/gemini-cli.js +242 -0
- package/src/sources/goose.js +366 -0
- package/src/sources/grok-cli.js +267 -0
- package/src/sources/hermes.js +282 -0
- package/src/sources/index.js +172 -8
- package/src/sources/jetbrains-ai-assistant.js +343 -0
- package/src/sources/jetbrains-junie.js +292 -0
- package/src/sources/kilo-code.js +430 -0
- package/src/sources/kimi-code.js +147 -0
- package/src/sources/kiro-cli.js +393 -0
- package/src/sources/kiro-ide.js +230 -0
- package/src/sources/llm.js +328 -0
- package/src/sources/mentat.js +143 -0
- package/src/sources/open-interpreter.js +224 -0
- package/src/sources/openclaw.js +218 -0
- package/src/sources/opencode.js +379 -0
- package/src/sources/openhands.js +181 -0
- package/src/sources/pearai.js +151 -0
- package/src/sources/pi-agent.js +130 -0
- package/src/sources/project-artifacts.js +355 -0
- package/src/sources/qodo-gen.js +189 -0
- package/src/sources/qwen-code.js +244 -0
- package/src/sources/roo-code.js +239 -0
- package/src/sources/trae.js +294 -0
- package/src/sources/void.js +273 -0
- package/src/sources/warp.js +395 -0
- package/src/sources/windsurf.js +256 -0
- package/src/sources/zed.js +374 -0
package/README.md
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/logo-dark.svg">
|
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/logo-light.svg">
|
|
6
|
+
<img src="docs/logo-light.svg" alt="residoo" width="280">
|
|
7
|
+
</picture>
|
|
2
8
|
|
|
3
9
|
**Find secrets leaking through your AI coding agent's session history.**
|
|
4
10
|
|
|
11
|
+
[](https://www.npmjs.com/package/residoo)
|
|
12
|
+
[](https://github.com/dandovdub/residoo/actions/workflows/ci.yml)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
[](package.json)
|
|
15
|
+
[](package.json)
|
|
16
|
+
|
|
17
|
+
<img src="docs/demo.svg" alt="residoo scan terminal output: 17 potential secrets found across 3 files, 87 files scanned (1.2 GB), values redacted to first/last 4 characters, no network calls" width="760">
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
5
21
|
Every time Claude Code, Cursor, or a similar tool reads a file, runs a command, or
|
|
6
|
-
browses a page on your behalf, it writes a transcript of the whole session to
|
|
7
|
-
including the contents of whatever it touched. If that ever included a
|
|
8
|
-
a config with a real key in it, or a login token captured during
|
|
9
|
-
credential is now sitting in plaintext, indefinitely, in a place
|
|
10
|
-
thinks to check.
|
|
22
|
+
browses a page on your behalf, it writes a transcript of the whole session to
|
|
23
|
+
disk, including the contents of whatever it touched. If that ever included a
|
|
24
|
+
`.env` file, a config with a real key in it, or a login token captured during
|
|
25
|
+
testing, that credential is now sitting in plaintext, indefinitely, in a place
|
|
26
|
+
almost nobody thinks to check.
|
|
11
27
|
|
|
12
28
|
residoo scans those transcripts and tells you what's in them.
|
|
13
29
|
|
|
@@ -21,31 +37,163 @@ $ residoo scan
|
|
|
21
37
|
16 [high] AWS Access Key ID (1 distinct value, re-exposed 15× across tool output)
|
|
22
38
|
1 [high] Private key block
|
|
23
39
|
|
|
24
|
-
Values are redacted in this report
|
|
25
|
-
here left your machine; residoo makes no network calls.
|
|
40
|
+
Values are redacted in this report (first/last 4 characters only). Nothing
|
|
41
|
+
scanned here left your machine; residoo makes no network calls.
|
|
26
42
|
```
|
|
27
43
|
|
|
28
44
|
## Why this, and not a git secret scanner
|
|
29
45
|
|
|
30
|
-
Tools like `gitleaks` and `trufflehog` are excellent at what they do
|
|
46
|
+
Tools like `gitleaks` and `trufflehog` are excellent at what they do, and what
|
|
31
47
|
they do is scan **commits**. That's a different, well-covered space. Nobody was
|
|
32
48
|
looking at the **conversation transcripts** these agents leave behind, which
|
|
33
49
|
contain a superset of everything a commit does: not just code, but file
|
|
34
50
|
contents, terminal output, and whatever got pasted into a prompt.
|
|
35
51
|
|
|
52
|
+
Two newer categories are adjacent but solve a different problem, worth being
|
|
53
|
+
precise about rather than lumping together:
|
|
54
|
+
|
|
55
|
+
- **Real-time hooks** (e.g. GitGuardian's `ggshield` AI hook, GitHub's secret
|
|
56
|
+
scanning via its MCP server) intercept a prompt or a code change *as it
|
|
57
|
+
happens*, going forward, in the session that has the hook installed. They
|
|
58
|
+
do nothing for the months of transcripts already sitting on disk, or for
|
|
59
|
+
any session run without the hook active. residoo scans **retroactively, at
|
|
60
|
+
rest**: every file already there, from every past session.
|
|
61
|
+
- **agentsweep** is a genuine, welcome peer covering similar ground. Broader,
|
|
62
|
+
in fact: 31 agent sources and 209 detection rules to residoo's smaller set,
|
|
63
|
+
plus in-place redaction, SARIF output, and a pre-commit hook. The tradeoffs
|
|
64
|
+
are worth naming precisely rather than either dismissing it or copying it
|
|
65
|
+
blindly. It needs Python 3.11+ and three pip packages (all clean ones, on
|
|
66
|
+
inspection; no known CVEs), where residoo needs nothing beyond Node. Its
|
|
67
|
+
own README documents that its in-place redaction leaves the pre-redaction
|
|
68
|
+
original sitting in a **plaintext** `.bak` file, and its issue tracker shows
|
|
69
|
+
the real cost of that design: a merged fix
|
|
70
|
+
([PR #13](https://github.com/Ishannaik/agent-sweep/pull/13)) for a case
|
|
71
|
+
where redacting a WAL-mode SQLite database left the secret recoverable from
|
|
72
|
+
a leftover journal file. residoo's `--seal` takes a different tradeoff
|
|
73
|
+
(encrypt a copy, touch nothing, never claim a file is "cleaned") precisely
|
|
74
|
+
to avoid that failure class. Its tracker also shows several real,
|
|
75
|
+
since-fixed false-*clean* reports: schema drift and malformed lines
|
|
76
|
+
silently skipped, `--root` pointed at a file scanning nothing and exiting
|
|
77
|
+
0. That is the exact failure mode residoo's `broken`/`partial` status
|
|
78
|
+
contract (see `CONTRIBUTING.md`) exists to make structurally hard to
|
|
79
|
+
reproduce. None of this makes agentsweep bad; it makes for a legitimately
|
|
80
|
+
different set of choices, and its README is honest about its own tradeoffs
|
|
81
|
+
too. Worth a look if broader source coverage matters more to you than a
|
|
82
|
+
minimal dependency footprint.
|
|
83
|
+
|
|
84
|
+
This isn't a gap Anthropic is planning to close upstream, either: a
|
|
85
|
+
[request to scrub secrets from `~/.claude/projects` natively](https://github.com/anthropics/claude-code/issues/50014)
|
|
86
|
+
was filed and closed as **not planned**. Whatever scans this directory, it
|
|
87
|
+
won't be built into the tool that writes it.
|
|
88
|
+
|
|
36
89
|
## What it does
|
|
37
90
|
|
|
38
91
|
- Scans your local AI-agent session transcripts for high-confidence secret
|
|
39
|
-
patterns
|
|
40
|
-
database connection strings, and more
|
|
92
|
+
patterns: cloud provider keys, private key blocks, OAuth/API tokens,
|
|
93
|
+
database connection strings, and more (see `src/patterns.js`).
|
|
41
94
|
- Redacts everything in its own output. You get a shape and a first/last-4
|
|
42
|
-
preview, never the real value
|
|
95
|
+
preview, never the real value, including in `--json` mode.
|
|
43
96
|
- Tells you how many **distinct** secrets it found versus how many times one
|
|
44
97
|
got echoed back across tool calls, so the headline number reflects real
|
|
45
98
|
exposure, not repetition.
|
|
46
99
|
- Flags likely placeholder/example matches (an HTML form's
|
|
47
100
|
`placeholder="AKIA..."` hint, a doc's example key) separately from real
|
|
48
101
|
findings, rather than either hiding them or inflating the count with them.
|
|
102
|
+
- Scans agent **config** files too (settings, MCP server configs, memory
|
|
103
|
+
files), and checks the places the 2026 supply-chain campaigns planted
|
|
104
|
+
persistence: hooks, dropper scripts, auto-run tasks, invisible Unicode.
|
|
105
|
+
See the next section.
|
|
106
|
+
- Attaches a **rotation runbook** to every finding: the vendor's real
|
|
107
|
+
revocation path, verified against their own docs, plus a local
|
|
108
|
+
acknowledgement ledger so "found it" can become "closed it". See
|
|
109
|
+
[Rotation](#rotation-from-found-to-closed).
|
|
110
|
+
- Scans a **repository checkout** instead of the machine with
|
|
111
|
+
`--project <dir>`: committed transcripts, agent configs, and root `.env`
|
|
112
|
+
files, built for CI and pre-commit. See
|
|
113
|
+
[CI and pre-commit](#ci-and-pre-commit).
|
|
114
|
+
|
|
115
|
+
## Beyond transcripts: configs and planted persistence
|
|
116
|
+
|
|
117
|
+
Transcripts leak what your agent *saw*. Config files leak what your agent was
|
|
118
|
+
*configured with*, and it turns out that is the better-measured problem.
|
|
119
|
+
GitGuardian counted 24,008 secrets inside MCP config files on public GitHub
|
|
120
|
+
(2,117 still valid when checked), and Lakera found live credentials inside
|
|
121
|
+
`.claude/settings.local.json` files shipped in roughly 30 published npm
|
|
122
|
+
packages, because Claude Code's approved-command cache quietly accumulates
|
|
123
|
+
tokens and no packaging tool ignores `.claude/` by default. So as of v0.2.0,
|
|
124
|
+
`residoo scan` includes an **agent config source** covering the home-level
|
|
125
|
+
config files of Claude Code, Claude Desktop, Cursor, Gemini CLI, Codex, and
|
|
126
|
+
Kiro. Every path is verified against a real install or published sources (one
|
|
127
|
+
disclosed exception, a stealer-target path backed by a single published
|
|
128
|
+
list, argued openly in the source header), with the full verification trail
|
|
129
|
+
written into `src/sources/agent-configs.js`.
|
|
130
|
+
|
|
131
|
+
The same files are also where the year's supply-chain campaigns planted
|
|
132
|
+
their persistence. Mini Shai-Hulud wrote a `SessionStart` hook into
|
|
133
|
+
`.claude/settings.json` and a `"runOn": "folderOpen"` task into
|
|
134
|
+
`.vscode/tasks.json`. Miasma reused both plants and added
|
|
135
|
+
`.gemini/settings.json` hooks and `.cursor/rules/setup.mdc` prompt-injection
|
|
136
|
+
files. The keyv/ChainDrop wave dropped a script literally named `setup.mjs`
|
|
137
|
+
into `.claude/` and `.vscode/`. And TrapDoor hid instructions in
|
|
138
|
+
`CLAUDE.md`/`.cursorrules` as zero-width Unicode: invisible in your editor,
|
|
139
|
+
fully visible to the agent. So every scan now also runs **integrity checks**
|
|
140
|
+
over those exact locations:
|
|
141
|
+
|
|
142
|
+
- Every auto-executing hook found in the checked locations is listed (hooks
|
|
143
|
+
run without asking; you should be able to vouch for each one). Only
|
|
144
|
+
commands matching a published campaign IOC (`setup.mjs`) or a
|
|
145
|
+
campaign-shaped behavior escalate to warnings: piping a download straight
|
|
146
|
+
into a shell, decoding base64 before executing, running repo-local scripts
|
|
147
|
+
out of dot-directories.
|
|
148
|
+
- Loose scripts in `.claude/`, and the exact planted filenames from the
|
|
149
|
+
published IOC lists, are flagged by name.
|
|
150
|
+
- `CLAUDE.md`, `.cursorrules`, and `.cursor/rules/*` are checked for
|
|
151
|
+
zero-width Unicode, with legitimate emoji/script joiners kept to an
|
|
152
|
+
informational tier so the warning count stays meaningful.
|
|
153
|
+
- `.vscode/tasks.json` is parsed (as JSONC, comments and all) for tasks that
|
|
154
|
+
execute on folder open.
|
|
155
|
+
|
|
156
|
+
The checks are read-only like everything else, warnings (not review items)
|
|
157
|
+
count toward `--fail-on-find`, project-level checks cover the directory you
|
|
158
|
+
run from, and `--no-integrity` skips the whole thing. A config that exists
|
|
159
|
+
but can't be read or parsed is reported as unverified, never silently
|
|
160
|
+
counted as clean.
|
|
161
|
+
|
|
162
|
+
## How it works
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
YOUR MACHINE · no network calls
|
|
166
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
167
|
+
│ │
|
|
168
|
+
│ 42 transcript sources agent config files │
|
|
169
|
+
│ ~/.claude, Cursor, Codex… settings · MCP · memory │
|
|
170
|
+
│ (--project <dir>: a repo checkout instead of the machine) │
|
|
171
|
+
│ │ │ │
|
|
172
|
+
│ ├──────────────┬───────────────┤ │
|
|
173
|
+
│ ▼ │ ▼ │
|
|
174
|
+
│ stream + match │ integrity checks │
|
|
175
|
+
│ 35 verified rules │ hooks · droppers · │
|
|
176
|
+
│ │ │ zero-width unicode │
|
|
177
|
+
│ ▼ ▼ │ │
|
|
178
|
+
│ redacted report (first/last 4 chars only) ◀────────────┤
|
|
179
|
+
│ │ │
|
|
180
|
+
│ ├─▶ rotation hints per finding · explain / ack │
|
|
181
|
+
│ │ ledger: ~/.residoo/rotations.json │
|
|
182
|
+
│ │ │
|
|
183
|
+
│ ▼ --seal (only if you ask) │
|
|
184
|
+
│ AES-256-GCM vault · scrypt key · encrypted manifest │
|
|
185
|
+
│ │ │ │
|
|
186
|
+
│ ▼ unseal --restore ▼ --upload-cloudroam
|
|
187
|
+
│ SHA-256 verified copy ciphertext only ┄┄┄┄┄┄┄┄┄▶
|
|
188
|
+
│ │
|
|
189
|
+
└───────────────────────────────────────────────────────────────┘
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The `--seal` and `--upload-cloudroam` legs never run unless you pass their
|
|
193
|
+
flag. Everything above the vault happens on every scan; nothing in the
|
|
194
|
+
diagram ever modifies or deletes an existing file. The one exception, stated
|
|
195
|
+
in the open: `residoo ack` writes residoo's own rotation ledger at
|
|
196
|
+
`~/.residoo/rotations.json` (atomic, redacted, never a user file).
|
|
49
197
|
|
|
50
198
|
## Sealing what it finds
|
|
51
199
|
|
|
@@ -57,29 +205,101 @@ residoo scan --seal
|
|
|
57
205
|
```
|
|
58
206
|
|
|
59
207
|
Every transcript that carried a finding is encrypted into a local vault
|
|
60
|
-
directory
|
|
61
|
-
|
|
62
|
-
mapping from numbered blobs back to real paths
|
|
63
|
-
vault doesn't advertise what's inside it even by name.
|
|
64
|
-
touched
|
|
65
|
-
0001.sealed --out /tmp/check
|
|
66
|
-
|
|
208
|
+
directory. AES-256-GCM, key derived from your passphrase with scrypt,
|
|
209
|
+
streamed, so an 800MB transcript never touches memory whole. The vault's
|
|
210
|
+
manifest (the mapping from numbered blobs back to real paths) is itself
|
|
211
|
+
encrypted, so the vault doesn't advertise what's inside it even by name.
|
|
212
|
+
**Originals are never touched.** Once you've verified a restore works
|
|
213
|
+
(`residoo unseal <vault> --restore 0001.sealed --out /tmp/check`, verified
|
|
214
|
+
byte-identical via a recorded SHA-256), deleting the plaintext is your
|
|
215
|
+
decision, made by you, not by this tool.
|
|
67
216
|
|
|
68
217
|
Optionally, `--upload-cloudroam` (with `CLOUDROAM_API_KEY`, `--connector`,
|
|
69
218
|
`--bucket`) copies the sealed vault to [CloudRoam](https://cloudroam.io) for
|
|
70
|
-
durable, cross-cloud storage. **This is the only feature in residoo that
|
|
71
|
-
the network, it never runs unless you pass the flag, and only
|
|
72
|
-
transmitted
|
|
219
|
+
durable, cross-cloud storage. **This is the only feature in residoo that
|
|
220
|
+
touches the network, it never runs unless you pass the flag, and only
|
|
221
|
+
ciphertext is transmitted.** The vault is sealed before upload code ever
|
|
222
|
+
executes.
|
|
223
|
+
|
|
224
|
+
## Rotation: from found to closed
|
|
225
|
+
|
|
226
|
+
Detection without rotation is theater, and the field's own numbers say so:
|
|
227
|
+
64% of secrets leaked publicly in 2022 were still valid years later, 88% of
|
|
228
|
+
re-verified leaked AWS keys still authenticated, and the median time to
|
|
229
|
+
remediate a GitHub-leaked secret is 94 days. A scanner that stops at "found
|
|
230
|
+
it" leaves all of that untouched. So every finding in a residoo report comes
|
|
231
|
+
with the way out:
|
|
232
|
+
|
|
233
|
+
- **A rotation hint per finding**, from a per-rule guidance map covering all
|
|
234
|
+
35 detection rules (plus the opt-in noisy ones). Where a rotation URL is
|
|
235
|
+
shown, that exact URL was fetched and confirmed to document rotating or
|
|
236
|
+
revoking that credential type; where a vendor's docs are login-walled or
|
|
237
|
+
unfetchable, the report gives the console path in words instead of a link
|
|
238
|
+
it could not verify. Generic shapes (a JWT, a bearer header) get honest
|
|
239
|
+
generic guidance that says how to identify the issuer, never a pretend
|
|
240
|
+
vendor.
|
|
241
|
+
- **`residoo explain <rule-id>`** prints the full runbook for one credential
|
|
242
|
+
type: where to revoke, the steps, and what revocation actually does at
|
|
243
|
+
that vendor. `residoo explain --list` shows the whole catalogue.
|
|
244
|
+
- **`residoo ack <fingerprint>`** records that you rotated one finding.
|
|
245
|
+
Every finding carries a stable fingerprint (derived only from
|
|
246
|
+
already-redacted material, so the ledger can never leak), shown in the
|
|
247
|
+
report and in `--json`. Acknowledged findings are reported as such on the
|
|
248
|
+
next scan instead of re-alarming forever. The ledger lives at
|
|
249
|
+
`~/.residoo/rotations.json`: residoo's own file, written atomically, ack
|
|
250
|
+
notes redacted through the same pipeline as previews.
|
|
251
|
+
- **Order matters, and the report says so when it does.** The ChainDrop
|
|
252
|
+
campaign (Aug 2026) shipped a token monitor that fires an attacker payload
|
|
253
|
+
the moment the stolen GitHub token is revoked. When one scan finds both
|
|
254
|
+
integrity warnings and leaked credentials, the report tells you to remove
|
|
255
|
+
the planted persistence first and rotate second, because "rotate
|
|
256
|
+
everything now" advice can itself trigger the damage.
|
|
257
|
+
|
|
258
|
+
Acks change what the report says, never what CI does: `--fail-on-find`
|
|
259
|
+
fails on every finding, acknowledged or not, unless you explicitly pass
|
|
260
|
+
`--allow-acked` (integrity warnings always fail either way).
|
|
261
|
+
|
|
262
|
+
## CI and pre-commit
|
|
263
|
+
|
|
264
|
+
`residoo scan --project <dir>` scans a repository checkout instead of the
|
|
265
|
+
machine it runs on: committed agent transcripts (Claude Code `.jsonl`
|
|
266
|
+
trees, Codex `rollout-*.jsonl`, SpecStory histories), agent config and
|
|
267
|
+
rules files at any depth, and root-level `.env` files, plus the integrity
|
|
268
|
+
checks anchored at that directory. It deliberately does not touch the
|
|
269
|
+
machine's home-level sources, so a clean CI run means the checkout is
|
|
270
|
+
clean and claims nothing about anyone's laptop.
|
|
271
|
+
|
|
272
|
+
As a GitHub Action (this repository doubles as a composite action):
|
|
273
|
+
|
|
274
|
+
```yaml
|
|
275
|
+
steps:
|
|
276
|
+
- uses: actions/checkout@v4
|
|
277
|
+
- uses: dandovdub/residoo@v0.3.0
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
As a pre-commit hook:
|
|
281
|
+
|
|
282
|
+
```yaml
|
|
283
|
+
repos:
|
|
284
|
+
- repo: https://github.com/dandovdub/residoo
|
|
285
|
+
rev: v0.3.0
|
|
286
|
+
hooks:
|
|
287
|
+
- id: residoo
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Or with no integration at all: `npx --yes residoo scan --project . --fail-on-find`.
|
|
291
|
+
Exit codes, inputs, and exactly what project mode does and does not see are
|
|
292
|
+
documented in [docs/ci.md](docs/ci.md).
|
|
73
293
|
|
|
74
294
|
## What it does not do
|
|
75
295
|
|
|
76
|
-
- **No network calls in the default path
|
|
296
|
+
- **No network calls in the default path, and none at all unless you
|
|
77
297
|
explicitly pass `--upload-cloudroam`.** A secret scanner that phones home is
|
|
78
298
|
not a tool you should trust with your secrets. Verify this yourself: the one
|
|
79
299
|
`fetch` call in the codebase is in `src/sealvault.js`, reachable only behind
|
|
80
300
|
that flag, and sends only encrypted bytes.
|
|
81
301
|
- **Nothing destructive, ever.** Scanning is read-only. Sealing creates *new*
|
|
82
|
-
files and modifies or deletes nothing
|
|
302
|
+
files and modifies or deletes nothing, not even the plaintext it just
|
|
83
303
|
encrypted a copy of. That last step is deliberately left to a human.
|
|
84
304
|
- **No telemetry, no analytics, no update-check ping.**
|
|
85
305
|
|
|
@@ -96,7 +316,24 @@ npm install -g residoo
|
|
|
96
316
|
residoo scan
|
|
97
317
|
```
|
|
98
318
|
|
|
99
|
-
|
|
319
|
+
A Homebrew formula ships in this repo at `packaging/homebrew/`. It installs
|
|
320
|
+
the exact tarball published to npm (same bits, sha256 verified), so Homebrew
|
|
321
|
+
is a second door to the same release, not a second build. Once the tap
|
|
322
|
+
repository (`dandovdub/homebrew-residoo`) is published, installation is:
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
brew tap dandovdub/residoo
|
|
326
|
+
brew install residoo
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Until the tap is up, npm above is the way in. The formula always points at
|
|
330
|
+
the latest *published* npm release (its sha256 is computed from the real
|
|
331
|
+
tarball), so it can lag a fresh tag by one publish cycle.
|
|
332
|
+
|
|
333
|
+
Requires Node.js 18+. The SQLite-backed sources listed below additionally
|
|
334
|
+
need 22.5+; residoo still runs and scans every line-delimited/JSON source,
|
|
335
|
+
including Claude Code, fine without it. Zero runtime dependencies, and you
|
|
336
|
+
can check `package.json` rather than take that on faith.
|
|
100
337
|
|
|
101
338
|
## Usage
|
|
102
339
|
|
|
@@ -104,9 +341,15 @@ Requires Node.js 18+. Zero runtime dependencies — check `package.json`.
|
|
|
104
341
|
residoo scan [options]
|
|
105
342
|
|
|
106
343
|
--json machine-readable output (full detail, still redacted)
|
|
344
|
+
--project [dir] scan a repository checkout instead of this machine
|
|
345
|
+
(committed transcripts, agent configs, root .env)
|
|
107
346
|
--include-noisy also run broad, false-positive-prone rules
|
|
108
347
|
--include-suppressed also show matches that looked like placeholder/example text
|
|
109
|
-
--fail-on-find exit code 1 if anything is found (for CI)
|
|
348
|
+
--fail-on-find exit code 1 if anything is found (for CI): secret
|
|
349
|
+
findings and integrity warnings count, review items don't
|
|
350
|
+
--allow-acked with --fail-on-find: acknowledged findings no longer
|
|
351
|
+
fail the run (pending ones and warnings still do)
|
|
352
|
+
--no-integrity skip the integrity checks
|
|
110
353
|
--no-color disable ANSI colour
|
|
111
354
|
|
|
112
355
|
--seal encrypt every transcript with findings into a local vault
|
|
@@ -114,53 +357,98 @@ residoo scan [options]
|
|
|
114
357
|
--upload-cloudroam also upload the sealed vault (needs CLOUDROAM_API_KEY,
|
|
115
358
|
--connector <id>, --bucket <name>; ciphertext only)
|
|
116
359
|
|
|
360
|
+
residoo explain <rule-id> full rotation runbook for one rule
|
|
361
|
+
residoo explain --list every rule id and label
|
|
362
|
+
residoo ack <fingerprint> [--note <text>] mark one finding rotated
|
|
363
|
+
|
|
117
364
|
residoo unseal <vault-dir> list a vault's contents
|
|
118
365
|
residoo unseal <vault-dir> --restore <n> --out <p> restore one file, hash-verified
|
|
119
366
|
```
|
|
120
367
|
|
|
121
368
|
The vault passphrase comes from `RESIDOO_PASSPHRASE` or a hidden interactive
|
|
122
|
-
prompt. There is no recovery if you lose it
|
|
369
|
+
prompt. There is no recovery if you lose it. That is the point of the design,
|
|
123
370
|
so pick one you keep.
|
|
124
371
|
|
|
125
372
|
## Sources supported today
|
|
126
373
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
374
|
+
43 sources as of this writing (42 transcript stores plus the agent-config
|
|
375
|
+
source described above), in two honestly-distinct tiers. Project mode
|
|
376
|
+
(`--project`) adds one more, opt-in source (`src/sources/project-artifacts.js`)
|
|
377
|
+
that scans a repository checkout rather than the machine and never
|
|
378
|
+
participates in a default scan. See
|
|
379
|
+
`src/sources/index.js` for the full list and grouping, and each source file's
|
|
380
|
+
own header for exactly what was and wasn't checked.
|
|
381
|
+
|
|
382
|
+
**Real-install-verified.** The adapter was run against an actual, populated
|
|
383
|
+
installation and confirmed to find real content:
|
|
384
|
+
|
|
385
|
+
- **Claude Code** (`~/.claude/projects/**/*.jsonl`)
|
|
386
|
+
- **Agent config files**, for its Claude-family paths (`~/.claude.json` and
|
|
387
|
+
its `.backup`, `~/.claude/settings*.json`, Claude Desktop's
|
|
388
|
+
`claude_desktop_config.json`). Its Cursor/Gemini/Codex/Kiro paths are in
|
|
389
|
+
the tier below; `src/sources/agent-configs.js` tracks verification per
|
|
390
|
+
path, not per file.
|
|
391
|
+
|
|
392
|
+
**Multi-source-corroborated-but-unverified.** The path/schema is backed by
|
|
393
|
+
2+ independent, credible sources (official docs, the tool's own shipped
|
|
394
|
+
source code, a real community tool that reads the same files for a living,
|
|
395
|
+
or a real user's own reported install) but was **not** checked against a real
|
|
396
|
+
install of the tool on any machine this project was built on. Every adapter
|
|
397
|
+
in this tier is still built to fail loudly (`broken: true`, `status:
|
|
398
|
+
"failed"`) rather than silently report "all clear", but the path itself
|
|
399
|
+
could still be stale or wrong in a way only a real install can catch. If you
|
|
400
|
+
use one of these and can confirm `residoo scan`'s file counts look right for
|
|
401
|
+
what's actually on your disk, that report is exactly what moves a source out
|
|
402
|
+
of this tier:
|
|
403
|
+
|
|
404
|
+
Cursor, Codex CLI, OpenCode, Aider, Cline, Roo Code, Kilo Code, Windsurf,
|
|
405
|
+
PearAI, Trae, Void, Gemini CLI, Qwen Code, Continue, Open Interpreter, Goose,
|
|
406
|
+
GitHub Copilot Chat, GitHub Copilot CLI, `llm` (Simon Willison's Datasette-
|
|
407
|
+
adjacent CLI), Codebuff, Mentat, Hermes, OpenClaw, Warp, Crush, Grok Build,
|
|
408
|
+
Kiro CLI, Kiro IDE, Zed, JetBrains Junie, JetBrains AI Assistant, Sourcegraph
|
|
409
|
+
Cody, Amazon Q Developer, Qodo Gen, OpenHands, Factory Droid CLI, Devin CLI,
|
|
410
|
+
Pi, Google Antigravity, Kimi Code, and `fx`.
|
|
411
|
+
|
|
412
|
+
A few of these are SQLite-backed (Cursor, Crush, Cody, Devin CLI, Hermes,
|
|
413
|
+
Kiro CLI, `llm`, Trae, Void, Warp, Zed) and need Node.js 22.5+ for the
|
|
414
|
+
built-in `node:sqlite` module (not a dependency; see `package.json`). On an
|
|
415
|
+
older Node, `residoo scan` reports each of those as detected-but-not-scanned
|
|
416
|
+
rather than silently dropping it or crashing.
|
|
417
|
+
|
|
418
|
+
**Investigated and deliberately not included**, rather than guessed at:
|
|
419
|
+
Plandex (confirmed, from its own source, to be client-server with nothing
|
|
420
|
+
local to scan), CodeGPT and Augment Code (both account/cloud-based, no
|
|
421
|
+
evidence of a local transcript file), and Replit Agent (confirmed
|
|
422
|
+
cloud-only). Tabby, Tabnine, Zencoder, Tongyi Lingma, and Berd were
|
|
423
|
+
researched but didn't clear this project's 2-independent-source bar in the
|
|
424
|
+
time available. A verified adapter for any of these is a welcome PR.
|
|
136
425
|
|
|
137
426
|
## Adding a source
|
|
138
427
|
|
|
139
428
|
A source is a small object with four methods: `id()`, `label()`,
|
|
140
429
|
`available()`, `files()`, and `readLines(file)`. `src/sources/claude-code.js`
|
|
141
|
-
is the reference implementation
|
|
430
|
+
is the reference implementation. Copy it, point it at the real local
|
|
142
431
|
storage path for your tool, and open a PR. Two contracts scan.js actually
|
|
143
432
|
depends on, worth getting right rather than guessing from a quick skim:
|
|
144
433
|
|
|
145
434
|
- **`files()`** is a generator yielding `{ file, mtimeMs, sizeBytes, broken }`.
|
|
146
435
|
Set `broken: true` (other fields can be omitted) for an entry that looked
|
|
147
|
-
like it should be scannable but wasn't
|
|
148
|
-
case. Don't just `continue` past it inside the generator
|
|
436
|
+
like it should be scannable but wasn't; a dangling symlink is the main
|
|
437
|
+
case. Don't just `continue` past it inside the generator. An early version
|
|
149
438
|
of the Claude Code source did exactly that, and a real, non-hypothetical
|
|
150
439
|
case (a project directory relocated via a symlink whose target no longer
|
|
151
|
-
exists) went completely invisible
|
|
440
|
+
exists) went completely invisible: not in the scan count, not in any
|
|
152
441
|
warning, nothing. Surfacing it as `broken` is what lets scan.js report it
|
|
153
442
|
instead.
|
|
154
443
|
- **`readLines(file)`** is `async`, returning `{ lines, status, bytesRead }`.
|
|
155
444
|
`status` is `"complete"`, `"partial"` (some real lines WERE read before a
|
|
156
|
-
failure partway through
|
|
445
|
+
failure partway through; return them, don't discard real content because
|
|
157
446
|
the rest of the file didn't finish cleanly), `"too-large"`, or `"failed"`.
|
|
158
447
|
Whatever you return in `lines` for a non-"complete" status still gets
|
|
159
448
|
scanned normally.
|
|
160
449
|
|
|
161
450
|
Please verify the path actually exists and holds real content before
|
|
162
|
-
submitting
|
|
163
|
-
that matters here specifically.
|
|
451
|
+
submitting. See the note above on why that matters here specifically.
|
|
164
452
|
|
|
165
453
|
## A known limitation, stated plainly
|
|
166
454
|
|
|
@@ -168,7 +456,7 @@ Shape-based detection can't tell a real secret from a realistic-looking
|
|
|
168
456
|
example in a fetched web page or a piece of documentation your agent read
|
|
169
457
|
aloud back to you. The `--include-suppressed`/placeholder-context heuristic
|
|
170
458
|
catches the common UI-hint case, not every case. Treat every finding as a
|
|
171
|
-
lead to check, not a certainty
|
|
459
|
+
lead to check, not a certainty. The same is true of every tool in this
|
|
172
460
|
category, including the well-established ones.
|
|
173
461
|
|
|
174
462
|
## License
|
|
@@ -177,9 +465,9 @@ MIT. See `LICENSE`.
|
|
|
177
465
|
|
|
178
466
|
---
|
|
179
467
|
|
|
180
|
-
Built and maintained by the team behind [CloudRoam](https://cloudroam.io)
|
|
468
|
+
Built and maintained by the team behind [CloudRoam](https://cloudroam.io),
|
|
181
469
|
client-side encrypted, cross-cloud backup. residoo has no dependency on
|
|
182
|
-
CloudRoam and never will need one to be useful
|
|
470
|
+
CloudRoam and never will need one to be useful. If a scan turns up something
|
|
183
471
|
you want stored somewhere durable and encrypted going forward, that's the
|
|
184
472
|
kind of problem CloudRoam solves, but it's an entirely separate choice from
|
|
185
473
|
running this tool.
|
package/SECURITY.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Reporting a vulnerability
|
|
4
4
|
|
|
5
|
-
If you find a security issue in residoo
|
|
5
|
+
If you find a security issue in residoo, including "this finding shouldn't
|
|
6
6
|
have been suppressed," "this output leaked more than it should have," or
|
|
7
|
-
anything in the redaction logic
|
|
7
|
+
anything in the redaction logic, please report it privately rather than as
|
|
8
8
|
a public issue. Open a [GitHub Security Advisory](../../security/advisories/new)
|
|
9
9
|
on this repository, or email the maintainer listed in `package.json`.
|
|
10
10
|
|
|
@@ -13,45 +13,47 @@ Please include:
|
|
|
13
13
|
- Whether real secret material was involved (if so, a redacted/synthetic
|
|
14
14
|
reproduction is preferred over the real value)
|
|
15
15
|
|
|
16
|
-
You'll get an acknowledgment within a few days. There's no bug bounty
|
|
17
|
-
this is a small open-source tool, not a funded program
|
|
16
|
+
You'll get an acknowledgment within a few days. There's no bug bounty
|
|
17
|
+
(this is a small open-source tool, not a funded program), but every report
|
|
18
18
|
gets read and taken seriously, and credited in the fix unless you'd rather
|
|
19
19
|
stay anonymous.
|
|
20
20
|
|
|
21
21
|
## What's already been checked, and how
|
|
22
22
|
|
|
23
23
|
This isn't a claim taken on faith. Every property below was tested, not
|
|
24
|
-
just asserted
|
|
24
|
+
just asserted. See the git history for the actual commands run:
|
|
25
25
|
|
|
26
26
|
- **No network calls in the scan path.** Grepped for every network-capable
|
|
27
27
|
primitive (`http`, `https`, `fetch`, `child_process`, etc.) across the
|
|
28
28
|
scanning code. The codebase's single `fetch` lives in `src/sealvault.js`,
|
|
29
29
|
is reachable only behind the explicit `--upload-cloudroam` flag, and
|
|
30
|
-
transmits ciphertext only
|
|
30
|
+
transmits ciphertext only. The vault is fully sealed before that code
|
|
31
31
|
can run.
|
|
32
32
|
- **Scanning is read-only.** Grepped for every filesystem write/delete
|
|
33
33
|
primitive in the scan path. Sealing (`--seal`) writes NEW files into a
|
|
34
34
|
vault directory it creates; nothing in the codebase modifies or deletes
|
|
35
|
-
an existing file, including the plaintext originals a seal just
|
|
36
|
-
|
|
35
|
+
an existing file, including the plaintext originals a seal just
|
|
36
|
+
encrypted. Removing those is deliberately left to the human.
|
|
37
37
|
- **Output can't leak more than it shows.** The one raw matched value is
|
|
38
38
|
used in exactly two places: an in-memory dedup count (never serialized)
|
|
39
39
|
and the redaction function. Verified with a crafted input containing a
|
|
40
|
-
raw ANSI escape sequence that a real terminal would execute
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
raw ANSI escape sequence that a real terminal would execute. It rendered
|
|
41
|
+
live (a working clear-screen) before the fix, and doesn't after. The
|
|
42
|
+
integrity checker applies the same discipline to attacker-controlled
|
|
43
|
+
config content: control characters are stripped and invisible Unicode is
|
|
44
|
+
rewritten as visible escapes before anything reaches your terminal.
|
|
43
45
|
- **Not vulnerable to regex denial-of-service.** Every pattern checked
|
|
44
46
|
against the nested-quantifier shape behind real, dated CVEs in adjacent
|
|
45
47
|
tooling (e.g. CVE-2026-0621, a ReDoS in Anthropic's own MCP SDK from
|
|
46
48
|
catastrophic backtracking on an exploded template pattern). Also stress-
|
|
47
49
|
tested directly against multi-megabyte adversarial inputs.
|
|
48
50
|
- **No supply-chain surface.** Zero runtime dependencies, zero
|
|
49
|
-
pre/post-install lifecycle scripts
|
|
51
|
+
pre/post-install lifecycle scripts. Check `package.json` yourself;
|
|
50
52
|
there's nothing to hide behind a `postinstall` hook.
|
|
51
53
|
|
|
52
54
|
## Verifying you have the real thing
|
|
53
55
|
|
|
54
|
-
Fake clones of security tools are a real, active pattern
|
|
56
|
+
Fake clones of security tools are a real, active pattern, not a
|
|
55
57
|
hypothetical. In the last year alone: a self-propagating npm worm that
|
|
56
58
|
typosquatted common package names and injected malicious config into AI
|
|
57
59
|
coding tools' own settings files; a fake installer for a well-known AI
|
|
@@ -61,15 +63,20 @@ history to smuggle malware behind a README download link. A tool whose
|
|
|
61
63
|
entire premise is "trust me with what I find in your secrets" is exactly
|
|
62
64
|
the kind of thing worth impersonating.
|
|
63
65
|
|
|
64
|
-
- The
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
- The canonical repository is **`github.com/dandovdub/residoo`**, the one
|
|
67
|
+
named in this package's own `repository` field. A GitHub account named
|
|
68
|
+
"residoo" exists and is NOT this project.
|
|
69
|
+
- The only npm package is **`residoo`**. Releases are published from this
|
|
70
|
+
repository through CI with npm's provenance attestation, which
|
|
71
|
+
cryptographically ties each release to the exact repo and workflow that
|
|
72
|
+
built it; check the provenance badge on the npm page. One honest
|
|
73
|
+
exception, stated rather than hidden: the very first release (v0.1.0)
|
|
74
|
+
was a manual upload to claim the name, so provenance starts at the first
|
|
75
|
+
CI-published version after it.
|
|
76
|
+
- The only PyPI package is **`residoo`**: a thin official launcher whose
|
|
77
|
+
entire job is running the npm CLI via `npx`. Its source lives in this
|
|
78
|
+
repository under `pypi/`. It exists partly so nobody else can hold the
|
|
79
|
+
name.
|
|
73
80
|
- Nothing here needs a postinstall script, a config change to another
|
|
74
81
|
tool, or elevated permissions. If a "residoo" you found asks for any of
|
|
75
82
|
those, it isn't this project.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "residoo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Find secrets leaking through your AI coding agent's session history. Zero network calls in the scan path, zero dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "CloudRoam (https://cloudroam.io)",
|