residoo 0.1.0 → 0.2.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 +225 -46
- package/SECURITY.md +29 -22
- package/package.json +1 -1
- package/src/cli.js +82 -16
- package/src/integrity.js +669 -0
- package/src/patterns.js +78 -5
- package/src/report.js +74 -7
- 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/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,149 @@ $ 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
|
+
|
|
107
|
+
## Beyond transcripts: configs and planted persistence
|
|
108
|
+
|
|
109
|
+
Transcripts leak what your agent *saw*. Config files leak what your agent was
|
|
110
|
+
*configured with*, and it turns out that is the better-measured problem.
|
|
111
|
+
GitGuardian counted 24,008 secrets inside MCP config files on public GitHub
|
|
112
|
+
(2,117 still valid when checked), and Lakera found live credentials inside
|
|
113
|
+
`.claude/settings.local.json` files shipped in roughly 30 published npm
|
|
114
|
+
packages, because Claude Code's approved-command cache quietly accumulates
|
|
115
|
+
tokens and no packaging tool ignores `.claude/` by default. So as of v0.2.0,
|
|
116
|
+
`residoo scan` includes an **agent config source** covering the home-level
|
|
117
|
+
config files of Claude Code, Claude Desktop, Cursor, Gemini CLI, Codex, and
|
|
118
|
+
Kiro. Every path is verified against a real install or published sources (one
|
|
119
|
+
disclosed exception, a stealer-target path backed by a single published
|
|
120
|
+
list, argued openly in the source header), with the full verification trail
|
|
121
|
+
written into `src/sources/agent-configs.js`.
|
|
122
|
+
|
|
123
|
+
The same files are also where the year's supply-chain campaigns planted
|
|
124
|
+
their persistence. Mini Shai-Hulud wrote a `SessionStart` hook into
|
|
125
|
+
`.claude/settings.json` and a `"runOn": "folderOpen"` task into
|
|
126
|
+
`.vscode/tasks.json`. Miasma reused both plants and added
|
|
127
|
+
`.gemini/settings.json` hooks and `.cursor/rules/setup.mdc` prompt-injection
|
|
128
|
+
files. The keyv/ChainDrop wave dropped a script literally named `setup.mjs`
|
|
129
|
+
into `.claude/` and `.vscode/`. And TrapDoor hid instructions in
|
|
130
|
+
`CLAUDE.md`/`.cursorrules` as zero-width Unicode: invisible in your editor,
|
|
131
|
+
fully visible to the agent. So every scan now also runs **integrity checks**
|
|
132
|
+
over those exact locations:
|
|
133
|
+
|
|
134
|
+
- Every auto-executing hook found in the checked locations is listed (hooks
|
|
135
|
+
run without asking; you should be able to vouch for each one). Only
|
|
136
|
+
commands matching a published campaign IOC (`setup.mjs`) or a
|
|
137
|
+
campaign-shaped behavior escalate to warnings: piping a download straight
|
|
138
|
+
into a shell, decoding base64 before executing, running repo-local scripts
|
|
139
|
+
out of dot-directories.
|
|
140
|
+
- Loose scripts in `.claude/`, and the exact planted filenames from the
|
|
141
|
+
published IOC lists, are flagged by name.
|
|
142
|
+
- `CLAUDE.md`, `.cursorrules`, and `.cursor/rules/*` are checked for
|
|
143
|
+
zero-width Unicode, with legitimate emoji/script joiners kept to an
|
|
144
|
+
informational tier so the warning count stays meaningful.
|
|
145
|
+
- `.vscode/tasks.json` is parsed (as JSONC, comments and all) for tasks that
|
|
146
|
+
execute on folder open.
|
|
147
|
+
|
|
148
|
+
The checks are read-only like everything else, warnings (not review items)
|
|
149
|
+
count toward `--fail-on-find`, project-level checks cover the directory you
|
|
150
|
+
run from, and `--no-integrity` skips the whole thing. A config that exists
|
|
151
|
+
but can't be read or parsed is reported as unverified, never silently
|
|
152
|
+
counted as clean.
|
|
153
|
+
|
|
154
|
+
## How it works
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
YOUR MACHINE · no network calls
|
|
158
|
+
┌───────────────────────────────────────────────────────────────┐
|
|
159
|
+
│ │
|
|
160
|
+
│ 42 transcript sources agent config files │
|
|
161
|
+
│ ~/.claude, Cursor, Codex… settings · MCP · memory │
|
|
162
|
+
│ │ │ │
|
|
163
|
+
│ ├──────────────┬───────────────┤ │
|
|
164
|
+
│ ▼ │ ▼ │
|
|
165
|
+
│ stream + match │ integrity checks │
|
|
166
|
+
│ 35 verified rules │ hooks · droppers · │
|
|
167
|
+
│ │ │ zero-width unicode │
|
|
168
|
+
│ ▼ ▼ │ │
|
|
169
|
+
│ redacted report (first/last 4 chars only) ◀────────────┤
|
|
170
|
+
│ │ │
|
|
171
|
+
│ ▼ --seal (only if you ask) │
|
|
172
|
+
│ AES-256-GCM vault · scrypt key · encrypted manifest │
|
|
173
|
+
│ │ │ │
|
|
174
|
+
│ ▼ unseal --restore ▼ --upload-cloudroam
|
|
175
|
+
│ SHA-256 verified copy ciphertext only ┄┄┄┄┄┄┄┄┄▶
|
|
176
|
+
│ │
|
|
177
|
+
└───────────────────────────────────────────────────────────────┘
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The `--seal` and `--upload-cloudroam` legs never run unless you pass their
|
|
181
|
+
flag. Everything above the vault happens on every scan; nothing in the
|
|
182
|
+
diagram ever modifies or deletes an existing file.
|
|
49
183
|
|
|
50
184
|
## Sealing what it finds
|
|
51
185
|
|
|
@@ -57,29 +191,31 @@ residoo scan --seal
|
|
|
57
191
|
```
|
|
58
192
|
|
|
59
193
|
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
|
-
|
|
194
|
+
directory. AES-256-GCM, key derived from your passphrase with scrypt,
|
|
195
|
+
streamed, so an 800MB transcript never touches memory whole. The vault's
|
|
196
|
+
manifest (the mapping from numbered blobs back to real paths) is itself
|
|
197
|
+
encrypted, so the vault doesn't advertise what's inside it even by name.
|
|
198
|
+
**Originals are never touched.** Once you've verified a restore works
|
|
199
|
+
(`residoo unseal <vault> --restore 0001.sealed --out /tmp/check`, verified
|
|
200
|
+
byte-identical via a recorded SHA-256), deleting the plaintext is your
|
|
201
|
+
decision, made by you, not by this tool.
|
|
67
202
|
|
|
68
203
|
Optionally, `--upload-cloudroam` (with `CLOUDROAM_API_KEY`, `--connector`,
|
|
69
204
|
`--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
|
|
205
|
+
durable, cross-cloud storage. **This is the only feature in residoo that
|
|
206
|
+
touches the network, it never runs unless you pass the flag, and only
|
|
207
|
+
ciphertext is transmitted.** The vault is sealed before upload code ever
|
|
208
|
+
executes.
|
|
73
209
|
|
|
74
210
|
## What it does not do
|
|
75
211
|
|
|
76
|
-
- **No network calls in the default path
|
|
212
|
+
- **No network calls in the default path, and none at all unless you
|
|
77
213
|
explicitly pass `--upload-cloudroam`.** A secret scanner that phones home is
|
|
78
214
|
not a tool you should trust with your secrets. Verify this yourself: the one
|
|
79
215
|
`fetch` call in the codebase is in `src/sealvault.js`, reachable only behind
|
|
80
216
|
that flag, and sends only encrypted bytes.
|
|
81
217
|
- **Nothing destructive, ever.** Scanning is read-only. Sealing creates *new*
|
|
82
|
-
files and modifies or deletes nothing
|
|
218
|
+
files and modifies or deletes nothing, not even the plaintext it just
|
|
83
219
|
encrypted a copy of. That last step is deliberately left to a human.
|
|
84
220
|
- **No telemetry, no analytics, no update-check ping.**
|
|
85
221
|
|
|
@@ -96,7 +232,10 @@ npm install -g residoo
|
|
|
96
232
|
residoo scan
|
|
97
233
|
```
|
|
98
234
|
|
|
99
|
-
Requires Node.js 18+.
|
|
235
|
+
Requires Node.js 18+. The SQLite-backed sources listed below additionally
|
|
236
|
+
need 22.5+; residoo still runs and scans every line-delimited/JSON source,
|
|
237
|
+
including Claude Code, fine without it. Zero runtime dependencies, and you
|
|
238
|
+
can check `package.json` rather than take that on faith.
|
|
100
239
|
|
|
101
240
|
## Usage
|
|
102
241
|
|
|
@@ -106,7 +245,9 @@ residoo scan [options]
|
|
|
106
245
|
--json machine-readable output (full detail, still redacted)
|
|
107
246
|
--include-noisy also run broad, false-positive-prone rules
|
|
108
247
|
--include-suppressed also show matches that looked like placeholder/example text
|
|
109
|
-
--fail-on-find exit code 1 if anything is found (for CI)
|
|
248
|
+
--fail-on-find exit code 1 if anything is found (for CI): secret
|
|
249
|
+
findings and integrity warnings count, review items don't
|
|
250
|
+
--no-integrity skip the integrity checks
|
|
110
251
|
--no-color disable ANSI colour
|
|
111
252
|
|
|
112
253
|
--seal encrypt every transcript with findings into a local vault
|
|
@@ -119,48 +260,86 @@ residoo unseal <vault-dir> --restore <n> --out <p> restore one file, hash-verif
|
|
|
119
260
|
```
|
|
120
261
|
|
|
121
262
|
The vault passphrase comes from `RESIDOO_PASSPHRASE` or a hidden interactive
|
|
122
|
-
prompt. There is no recovery if you lose it
|
|
263
|
+
prompt. There is no recovery if you lose it. That is the point of the design,
|
|
123
264
|
so pick one you keep.
|
|
124
265
|
|
|
125
266
|
## Sources supported today
|
|
126
267
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
268
|
+
43 sources as of this writing (42 transcript stores plus the agent-config
|
|
269
|
+
source described above), in two honestly-distinct tiers. See
|
|
270
|
+
`src/sources/index.js` for the full list and grouping, and each source file's
|
|
271
|
+
own header for exactly what was and wasn't checked.
|
|
272
|
+
|
|
273
|
+
**Real-install-verified.** The adapter was run against an actual, populated
|
|
274
|
+
installation and confirmed to find real content:
|
|
275
|
+
|
|
276
|
+
- **Claude Code** (`~/.claude/projects/**/*.jsonl`)
|
|
277
|
+
- **Agent config files**, for its Claude-family paths (`~/.claude.json` and
|
|
278
|
+
its `.backup`, `~/.claude/settings*.json`, Claude Desktop's
|
|
279
|
+
`claude_desktop_config.json`). Its Cursor/Gemini/Codex/Kiro paths are in
|
|
280
|
+
the tier below; `src/sources/agent-configs.js` tracks verification per
|
|
281
|
+
path, not per file.
|
|
282
|
+
|
|
283
|
+
**Multi-source-corroborated-but-unverified.** The path/schema is backed by
|
|
284
|
+
2+ independent, credible sources (official docs, the tool's own shipped
|
|
285
|
+
source code, a real community tool that reads the same files for a living,
|
|
286
|
+
or a real user's own reported install) but was **not** checked against a real
|
|
287
|
+
install of the tool on any machine this project was built on. Every adapter
|
|
288
|
+
in this tier is still built to fail loudly (`broken: true`, `status:
|
|
289
|
+
"failed"`) rather than silently report "all clear", but the path itself
|
|
290
|
+
could still be stale or wrong in a way only a real install can catch. If you
|
|
291
|
+
use one of these and can confirm `residoo scan`'s file counts look right for
|
|
292
|
+
what's actually on your disk, that report is exactly what moves a source out
|
|
293
|
+
of this tier:
|
|
294
|
+
|
|
295
|
+
Cursor, Codex CLI, OpenCode, Aider, Cline, Roo Code, Kilo Code, Windsurf,
|
|
296
|
+
PearAI, Trae, Void, Gemini CLI, Qwen Code, Continue, Open Interpreter, Goose,
|
|
297
|
+
GitHub Copilot Chat, GitHub Copilot CLI, `llm` (Simon Willison's Datasette-
|
|
298
|
+
adjacent CLI), Codebuff, Mentat, Hermes, OpenClaw, Warp, Crush, Grok Build,
|
|
299
|
+
Kiro CLI, Kiro IDE, Zed, JetBrains Junie, JetBrains AI Assistant, Sourcegraph
|
|
300
|
+
Cody, Amazon Q Developer, Qodo Gen, OpenHands, Factory Droid CLI, Devin CLI,
|
|
301
|
+
Pi, Google Antigravity, Kimi Code, and `fx`.
|
|
302
|
+
|
|
303
|
+
A few of these are SQLite-backed (Cursor, Crush, Cody, Devin CLI, Hermes,
|
|
304
|
+
Kiro CLI, `llm`, Trae, Void, Warp, Zed) and need Node.js 22.5+ for the
|
|
305
|
+
built-in `node:sqlite` module (not a dependency; see `package.json`). On an
|
|
306
|
+
older Node, `residoo scan` reports each of those as detected-but-not-scanned
|
|
307
|
+
rather than silently dropping it or crashing.
|
|
308
|
+
|
|
309
|
+
**Investigated and deliberately not included**, rather than guessed at:
|
|
310
|
+
Plandex (confirmed, from its own source, to be client-server with nothing
|
|
311
|
+
local to scan), CodeGPT and Augment Code (both account/cloud-based, no
|
|
312
|
+
evidence of a local transcript file), and Replit Agent (confirmed
|
|
313
|
+
cloud-only). Tabby, Tabnine, Zencoder, Tongyi Lingma, and Berd were
|
|
314
|
+
researched but didn't clear this project's 2-independent-source bar in the
|
|
315
|
+
time available. A verified adapter for any of these is a welcome PR.
|
|
136
316
|
|
|
137
317
|
## Adding a source
|
|
138
318
|
|
|
139
319
|
A source is a small object with four methods: `id()`, `label()`,
|
|
140
320
|
`available()`, `files()`, and `readLines(file)`. `src/sources/claude-code.js`
|
|
141
|
-
is the reference implementation
|
|
321
|
+
is the reference implementation. Copy it, point it at the real local
|
|
142
322
|
storage path for your tool, and open a PR. Two contracts scan.js actually
|
|
143
323
|
depends on, worth getting right rather than guessing from a quick skim:
|
|
144
324
|
|
|
145
325
|
- **`files()`** is a generator yielding `{ file, mtimeMs, sizeBytes, broken }`.
|
|
146
326
|
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
|
|
327
|
+
like it should be scannable but wasn't; a dangling symlink is the main
|
|
328
|
+
case. Don't just `continue` past it inside the generator. An early version
|
|
149
329
|
of the Claude Code source did exactly that, and a real, non-hypothetical
|
|
150
330
|
case (a project directory relocated via a symlink whose target no longer
|
|
151
|
-
exists) went completely invisible
|
|
331
|
+
exists) went completely invisible: not in the scan count, not in any
|
|
152
332
|
warning, nothing. Surfacing it as `broken` is what lets scan.js report it
|
|
153
333
|
instead.
|
|
154
334
|
- **`readLines(file)`** is `async`, returning `{ lines, status, bytesRead }`.
|
|
155
335
|
`status` is `"complete"`, `"partial"` (some real lines WERE read before a
|
|
156
|
-
failure partway through
|
|
336
|
+
failure partway through; return them, don't discard real content because
|
|
157
337
|
the rest of the file didn't finish cleanly), `"too-large"`, or `"failed"`.
|
|
158
338
|
Whatever you return in `lines` for a non-"complete" status still gets
|
|
159
339
|
scanned normally.
|
|
160
340
|
|
|
161
341
|
Please verify the path actually exists and holds real content before
|
|
162
|
-
submitting
|
|
163
|
-
that matters here specifically.
|
|
342
|
+
submitting. See the note above on why that matters here specifically.
|
|
164
343
|
|
|
165
344
|
## A known limitation, stated plainly
|
|
166
345
|
|
|
@@ -168,7 +347,7 @@ Shape-based detection can't tell a real secret from a realistic-looking
|
|
|
168
347
|
example in a fetched web page or a piece of documentation your agent read
|
|
169
348
|
aloud back to you. The `--include-suppressed`/placeholder-context heuristic
|
|
170
349
|
catches the common UI-hint case, not every case. Treat every finding as a
|
|
171
|
-
lead to check, not a certainty
|
|
350
|
+
lead to check, not a certainty. The same is true of every tool in this
|
|
172
351
|
category, including the well-established ones.
|
|
173
352
|
|
|
174
353
|
## License
|
|
@@ -177,9 +356,9 @@ MIT. See `LICENSE`.
|
|
|
177
356
|
|
|
178
357
|
---
|
|
179
358
|
|
|
180
|
-
Built and maintained by the team behind [CloudRoam](https://cloudroam.io)
|
|
359
|
+
Built and maintained by the team behind [CloudRoam](https://cloudroam.io),
|
|
181
360
|
client-side encrypted, cross-cloud backup. residoo has no dependency on
|
|
182
|
-
CloudRoam and never will need one to be useful
|
|
361
|
+
CloudRoam and never will need one to be useful. If a scan turns up something
|
|
183
362
|
you want stored somewhere durable and encrypted going forward, that's the
|
|
184
363
|
kind of problem CloudRoam solves, but it's an entirely separate choice from
|
|
185
364
|
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.2.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)",
|