whatileaked 0.5.3 → 0.6.1
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 +10 -4
- package/dist/cli.js +84 -20
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Your coding agent saves every conversation to your disk. If you ever pasted an
|
|
6
6
|
API key into a chat, or asked the agent to read a `.env` file, that key is now
|
|
7
|
-
sitting in a log file on your laptop — and it was uploaded to Anthropic
|
|
8
|
-
OpenAI the moment you sent the message.
|
|
7
|
+
sitting in a log file on your laptop — and it was uploaded to Anthropic,
|
|
8
|
+
OpenAI or Google the moment you sent the message.
|
|
9
9
|
|
|
10
10
|
Secret scanners point at repositories and CI. You can aim one at `~/.claude` by
|
|
11
11
|
hand, but nobody does — it is not a repo, it is not in CI, and it does not look
|
|
@@ -211,6 +211,7 @@ npx whatileaked scan || echo "found something"
|
|
|
211
211
|
| Codex | `~/.codex/sessions/` · `~/.codex/archived_sessions/` · `~/.codex/history.jsonl` · `~/.codex/AGENTS.md` · `~/.codex/AGENTS.override.md` |
|
|
212
212
|
| Cursor | `~/.cursor/projects/*/agent-transcripts/` |
|
|
213
213
|
| opencode | `~/.local/share/opencode/opencode.db` |
|
|
214
|
+
| Gemini CLI | `~/.gemini/tmp/*/chats/` · `~/.gemini/tmp/*/memory/` · `~/.gemini/GEMINI.md` |
|
|
214
215
|
|
|
215
216
|
opencode keeps its sessions in a SQLite database. Reading it needs Node's
|
|
216
217
|
built-in `node:sqlite`, which loads without a flag from Node 22.13. On an older
|
|
@@ -224,6 +225,11 @@ scanned yet. Agent transcripts are covered; Ask history is not.
|
|
|
224
225
|
|
|
225
226
|
Claude Code transcripts it set aside (`*.jsonl.superseded-*`) are scanned too.
|
|
226
227
|
|
|
228
|
+
If you moved Claude Code's folder with `CLAUDE_CONFIG_DIR`, Codex's with
|
|
229
|
+
`CODEX_HOME`, or Gemini CLI's with `GEMINI_CLI_HOME`, the folder it names is
|
|
230
|
+
scanned as well as the default one, which still holds every session from before
|
|
231
|
+
the move.
|
|
232
|
+
|
|
227
233
|
Transcripts record credentials that were already sent. So does prompt history,
|
|
228
234
|
which keeps every prompt you typed and every long paste after the session itself
|
|
229
235
|
has been cleaned up. Instruction and memory files are worse: they are read again
|
|
@@ -231,8 +237,8 @@ at the start of the next session, so a credential in one keeps leaking until the
|
|
|
231
237
|
file is edited. All of them are scanned, and `wipe` rewrites all of them.
|
|
232
238
|
|
|
233
239
|
Nowhere else. It does not read your repos, your shell history, or your
|
|
234
|
-
environment
|
|
235
|
-
welcome.
|
|
240
|
+
environment — beyond those three variables, to know where to look. Adding
|
|
241
|
+
support for another agent is one small file — pull requests welcome.
|
|
236
242
|
|
|
237
243
|
---
|
|
238
244
|
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/main.ts
|
|
4
4
|
import { homedir } from "os";
|
|
5
|
+
import { join as join8 } from "path";
|
|
5
6
|
|
|
6
7
|
// src/commands.ts
|
|
7
8
|
var Command = {
|
|
@@ -154,7 +155,7 @@ var TerminalReporter = class {
|
|
|
154
155
|
const onDisk = distinct(totals, FileKind.memory);
|
|
155
156
|
this.#write(
|
|
156
157
|
dim(
|
|
157
|
-
` scanned ${
|
|
158
|
+
` scanned ${plural(stats.transcripts, "transcript")} \xB7 ${plural(stats.entries, "message")} \xB7 ${plural(stats.memoryFiles, "memory file")}`
|
|
158
159
|
)
|
|
159
160
|
);
|
|
160
161
|
this.#write("");
|
|
@@ -206,7 +207,9 @@ var TerminalReporter = class {
|
|
|
206
207
|
#notScanned(stats) {
|
|
207
208
|
if (stats.unscannable > 0) {
|
|
208
209
|
this.#write(
|
|
209
|
-
yellow(
|
|
210
|
+
yellow(
|
|
211
|
+
`${plural(stats.unscannable, "message")} could not be scanned and ${stats.unscannable === 1 ? "is" : "are"} not counted.`
|
|
212
|
+
)
|
|
210
213
|
);
|
|
211
214
|
}
|
|
212
215
|
for (const file of stats.unread) {
|
|
@@ -2432,6 +2435,7 @@ async function listDirectories(root) {
|
|
|
2432
2435
|
}
|
|
2433
2436
|
var FileExtension = {
|
|
2434
2437
|
jsonl: ".jsonl",
|
|
2438
|
+
json: ".json",
|
|
2435
2439
|
markdown: ".md",
|
|
2436
2440
|
text: ".txt",
|
|
2437
2441
|
database: ".db"
|
|
@@ -2607,6 +2611,7 @@ var SourceName = {
|
|
|
2607
2611
|
"claude-code": "claude-code",
|
|
2608
2612
|
codex: "codex",
|
|
2609
2613
|
cursor: "cursor",
|
|
2614
|
+
"gemini-cli": "gemini-cli",
|
|
2610
2615
|
opencode: "opencode"
|
|
2611
2616
|
};
|
|
2612
2617
|
|
|
@@ -2626,20 +2631,22 @@ function objectField(source, key) {
|
|
|
2626
2631
|
// src/sources/claude-code-source.ts
|
|
2627
2632
|
var ClaudeCodeSource = class {
|
|
2628
2633
|
name = SourceName["claude-code"];
|
|
2629
|
-
#
|
|
2630
|
-
|
|
2631
|
-
|
|
2634
|
+
#dir;
|
|
2635
|
+
/** `dir` is where Claude Code keeps its state: `~/.claude`, or wherever
|
|
2636
|
+
* `CLAUDE_CONFIG_DIR` moved it. */
|
|
2637
|
+
constructor(home2, dir = join2(home2, ".claude")) {
|
|
2638
|
+
this.#dir = dir;
|
|
2632
2639
|
}
|
|
2633
2640
|
async *discover() {
|
|
2634
|
-
const instructions = await resolveFile(join2(this.#
|
|
2641
|
+
const instructions = await resolveFile(join2(this.#dir, "CLAUDE.md"));
|
|
2635
2642
|
if (instructions !== null) {
|
|
2636
2643
|
yield { source: this.name, kind: FileKind.memory, path: instructions, project: this.name };
|
|
2637
2644
|
}
|
|
2638
|
-
const history = await resolveFile(join2(this.#
|
|
2645
|
+
const history = await resolveFile(join2(this.#dir, "history.jsonl"));
|
|
2639
2646
|
if (history !== null) {
|
|
2640
2647
|
yield { source: this.name, kind: FileKind.transcript, path: history, project: this.name };
|
|
2641
2648
|
}
|
|
2642
|
-
const pastes = join2(this.#
|
|
2649
|
+
const pastes = join2(this.#dir, "paste-cache");
|
|
2643
2650
|
for (const file of await listFiles(pastes, FileExtension.text)) {
|
|
2644
2651
|
yield {
|
|
2645
2652
|
source: this.name,
|
|
@@ -2648,7 +2655,7 @@ var ClaudeCodeSource = class {
|
|
|
2648
2655
|
project: this.name
|
|
2649
2656
|
};
|
|
2650
2657
|
}
|
|
2651
|
-
const root = join2(this.#
|
|
2658
|
+
const root = join2(this.#dir, "projects");
|
|
2652
2659
|
for (const slug of await listDirectories(root)) {
|
|
2653
2660
|
const dir = join2(root, slug);
|
|
2654
2661
|
let project = slug;
|
|
@@ -2692,23 +2699,25 @@ async function firstCwd(path) {
|
|
|
2692
2699
|
import { basename as basename2, join as join3 } from "path";
|
|
2693
2700
|
var CodexSource = class {
|
|
2694
2701
|
name = SourceName.codex;
|
|
2695
|
-
#
|
|
2696
|
-
|
|
2697
|
-
|
|
2702
|
+
#dir;
|
|
2703
|
+
/** `dir` is where Codex keeps its state: `~/.codex`, or wherever
|
|
2704
|
+
* `CODEX_HOME` moved it. */
|
|
2705
|
+
constructor(home2, dir = join3(home2, ".codex")) {
|
|
2706
|
+
this.#dir = dir;
|
|
2698
2707
|
}
|
|
2699
2708
|
async *discover() {
|
|
2700
2709
|
for (const name of ["AGENTS.override.md", "AGENTS.md"]) {
|
|
2701
|
-
const instructions = await resolveFile(join3(this.#
|
|
2710
|
+
const instructions = await resolveFile(join3(this.#dir, name));
|
|
2702
2711
|
if (instructions !== null) {
|
|
2703
2712
|
yield { source: this.name, kind: FileKind.memory, path: instructions, project: this.name };
|
|
2704
2713
|
}
|
|
2705
2714
|
}
|
|
2706
|
-
const history = await resolveFile(join3(this.#
|
|
2715
|
+
const history = await resolveFile(join3(this.#dir, "history.jsonl"));
|
|
2707
2716
|
if (history !== null) {
|
|
2708
2717
|
yield { source: this.name, kind: FileKind.transcript, path: history, project: this.name };
|
|
2709
2718
|
}
|
|
2710
2719
|
for (const root of ["sessions", "archived_sessions"]) {
|
|
2711
|
-
for (const dir of await walkDirectories(join3(this.#
|
|
2720
|
+
for (const dir of await walkDirectories(join3(this.#dir, root))) {
|
|
2712
2721
|
for (const file of await listFiles(dir, FileExtension.jsonl)) {
|
|
2713
2722
|
const path = join3(dir, file);
|
|
2714
2723
|
const cwd = await firstCwd2(path);
|
|
@@ -2790,8 +2799,42 @@ async function isDirectory(path) {
|
|
|
2790
2799
|
}
|
|
2791
2800
|
}
|
|
2792
2801
|
|
|
2802
|
+
// src/sources/gemini-cli-source.ts
|
|
2803
|
+
import { join as join5 } from "path";
|
|
2804
|
+
var GeminiCliSource = class {
|
|
2805
|
+
name = SourceName["gemini-cli"];
|
|
2806
|
+
#dir;
|
|
2807
|
+
/** `dir` is `~/.gemini`, or `$GEMINI_CLI_HOME/.gemini` when that is set. */
|
|
2808
|
+
constructor(home2, dir = join5(home2, ".gemini")) {
|
|
2809
|
+
this.#dir = dir;
|
|
2810
|
+
}
|
|
2811
|
+
async *discover() {
|
|
2812
|
+
const instructions = await resolveFile(join5(this.#dir, "GEMINI.md"));
|
|
2813
|
+
if (instructions !== null) {
|
|
2814
|
+
yield { source: this.name, kind: FileKind.memory, path: instructions, project: this.name };
|
|
2815
|
+
}
|
|
2816
|
+
const root = join5(this.#dir, "tmp");
|
|
2817
|
+
for (const id of await listDirectories(root)) {
|
|
2818
|
+
const project = legacyHash.test(id) ? this.name : id;
|
|
2819
|
+
for (const dir of await walkDirectories(join5(root, id, "chats"))) {
|
|
2820
|
+
for (const extension of [FileExtension.jsonl, FileExtension.json]) {
|
|
2821
|
+
for (const file of await listFiles(dir, extension)) {
|
|
2822
|
+
yield { source: this.name, kind: FileKind.transcript, path: join5(dir, file), project };
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
for (const dir of await walkDirectories(join5(root, id, "memory"))) {
|
|
2827
|
+
for (const file of await listFiles(dir, FileExtension.markdown)) {
|
|
2828
|
+
yield { source: this.name, kind: FileKind.memory, path: join5(dir, file), project };
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
};
|
|
2834
|
+
var legacyHash = /^[0-9a-f]{64}$/;
|
|
2835
|
+
|
|
2793
2836
|
// src/sources/opencode-source.ts
|
|
2794
|
-
import { basename as basename4, join as
|
|
2837
|
+
import { basename as basename4, join as join6 } from "path";
|
|
2795
2838
|
var OpencodeSource = class {
|
|
2796
2839
|
name = SourceName.opencode;
|
|
2797
2840
|
#home;
|
|
@@ -2801,9 +2844,9 @@ var OpencodeSource = class {
|
|
|
2801
2844
|
this.#load = load;
|
|
2802
2845
|
}
|
|
2803
2846
|
async *discover() {
|
|
2804
|
-
const dir =
|
|
2847
|
+
const dir = join6(this.#home, ".local", "share", "opencode");
|
|
2805
2848
|
for (const file of await listFiles(dir, FileExtension.database)) {
|
|
2806
|
-
const path =
|
|
2849
|
+
const path = join6(dir, file);
|
|
2807
2850
|
const sqlite = await this.#load();
|
|
2808
2851
|
yield {
|
|
2809
2852
|
source: this.name,
|
|
@@ -2857,6 +2900,17 @@ async function importSqlite() {
|
|
|
2857
2900
|
}
|
|
2858
2901
|
}
|
|
2859
2902
|
|
|
2903
|
+
// src/sources/state-dirs.ts
|
|
2904
|
+
import { join as join7, resolve } from "path";
|
|
2905
|
+
function stateDirs(home2, folder, moved) {
|
|
2906
|
+
const dirs = [join7(home2, folder)];
|
|
2907
|
+
if (moved !== void 0 && moved !== "") {
|
|
2908
|
+
const dir = resolve(moved);
|
|
2909
|
+
if (!dirs.includes(dir)) dirs.push(dir);
|
|
2910
|
+
}
|
|
2911
|
+
return dirs;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2860
2914
|
// src/wipe/confirm.ts
|
|
2861
2915
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
2862
2916
|
var CONFIRMATION = "wipe";
|
|
@@ -3021,9 +3075,19 @@ async function main(argv) {
|
|
|
3021
3075
|
return 0;
|
|
3022
3076
|
}
|
|
3023
3077
|
const sources = [
|
|
3024
|
-
|
|
3025
|
-
|
|
3078
|
+
...stateDirs(homedir(), ".claude", process.env.CLAUDE_CONFIG_DIR).map(
|
|
3079
|
+
(dir) => new ClaudeCodeSource(homedir(), dir)
|
|
3080
|
+
),
|
|
3081
|
+
...stateDirs(homedir(), ".codex", process.env.CODEX_HOME).map(
|
|
3082
|
+
(dir) => new CodexSource(homedir(), dir)
|
|
3083
|
+
),
|
|
3026
3084
|
new CursorSource(homedir()),
|
|
3085
|
+
// GEMINI_CLI_HOME stands in for the home directory, not for `.gemini`.
|
|
3086
|
+
...stateDirs(
|
|
3087
|
+
homedir(),
|
|
3088
|
+
".gemini",
|
|
3089
|
+
process.env.GEMINI_CLI_HOME ? join8(process.env.GEMINI_CLI_HOME, ".gemini") : void 0
|
|
3090
|
+
).map((dir) => new GeminiCliSource(homedir(), dir)),
|
|
3027
3091
|
new OpencodeSource(homedir())
|
|
3028
3092
|
];
|
|
3029
3093
|
const scanner = new Scanner(new KeywordIndex(compileRules(SECRET_RULE_SOURCES)));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whatileaked",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Scan your local Claude Code, Codex, Cursor and
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "Scan your local Claude Code, Codex, Cursor, opencode and Gemini CLI transcripts for credentials you already sent to a model provider.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"imports": {
|
|
7
7
|
"#scripts/*": "./scripts/*.ts",
|