whatileaked 0.5.3 → 0.6.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.
Files changed (3) hide show
  1. package/README.md +10 -4
  2. package/dist/cli.js +80 -18
  3. 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 or
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. Adding support for another agent is one small file — pull requests
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 = {
@@ -2432,6 +2433,7 @@ async function listDirectories(root) {
2432
2433
  }
2433
2434
  var FileExtension = {
2434
2435
  jsonl: ".jsonl",
2436
+ json: ".json",
2435
2437
  markdown: ".md",
2436
2438
  text: ".txt",
2437
2439
  database: ".db"
@@ -2607,6 +2609,7 @@ var SourceName = {
2607
2609
  "claude-code": "claude-code",
2608
2610
  codex: "codex",
2609
2611
  cursor: "cursor",
2612
+ "gemini-cli": "gemini-cli",
2610
2613
  opencode: "opencode"
2611
2614
  };
2612
2615
 
@@ -2626,20 +2629,22 @@ function objectField(source, key) {
2626
2629
  // src/sources/claude-code-source.ts
2627
2630
  var ClaudeCodeSource = class {
2628
2631
  name = SourceName["claude-code"];
2629
- #home;
2630
- constructor(home2) {
2631
- this.#home = home2;
2632
+ #dir;
2633
+ /** `dir` is where Claude Code keeps its state: `~/.claude`, or wherever
2634
+ * `CLAUDE_CONFIG_DIR` moved it. */
2635
+ constructor(home2, dir = join2(home2, ".claude")) {
2636
+ this.#dir = dir;
2632
2637
  }
2633
2638
  async *discover() {
2634
- const instructions = await resolveFile(join2(this.#home, ".claude", "CLAUDE.md"));
2639
+ const instructions = await resolveFile(join2(this.#dir, "CLAUDE.md"));
2635
2640
  if (instructions !== null) {
2636
2641
  yield { source: this.name, kind: FileKind.memory, path: instructions, project: this.name };
2637
2642
  }
2638
- const history = await resolveFile(join2(this.#home, ".claude", "history.jsonl"));
2643
+ const history = await resolveFile(join2(this.#dir, "history.jsonl"));
2639
2644
  if (history !== null) {
2640
2645
  yield { source: this.name, kind: FileKind.transcript, path: history, project: this.name };
2641
2646
  }
2642
- const pastes = join2(this.#home, ".claude", "paste-cache");
2647
+ const pastes = join2(this.#dir, "paste-cache");
2643
2648
  for (const file of await listFiles(pastes, FileExtension.text)) {
2644
2649
  yield {
2645
2650
  source: this.name,
@@ -2648,7 +2653,7 @@ var ClaudeCodeSource = class {
2648
2653
  project: this.name
2649
2654
  };
2650
2655
  }
2651
- const root = join2(this.#home, ".claude", "projects");
2656
+ const root = join2(this.#dir, "projects");
2652
2657
  for (const slug of await listDirectories(root)) {
2653
2658
  const dir = join2(root, slug);
2654
2659
  let project = slug;
@@ -2692,23 +2697,25 @@ async function firstCwd(path) {
2692
2697
  import { basename as basename2, join as join3 } from "path";
2693
2698
  var CodexSource = class {
2694
2699
  name = SourceName.codex;
2695
- #home;
2696
- constructor(home2) {
2697
- this.#home = home2;
2700
+ #dir;
2701
+ /** `dir` is where Codex keeps its state: `~/.codex`, or wherever
2702
+ * `CODEX_HOME` moved it. */
2703
+ constructor(home2, dir = join3(home2, ".codex")) {
2704
+ this.#dir = dir;
2698
2705
  }
2699
2706
  async *discover() {
2700
2707
  for (const name of ["AGENTS.override.md", "AGENTS.md"]) {
2701
- const instructions = await resolveFile(join3(this.#home, ".codex", name));
2708
+ const instructions = await resolveFile(join3(this.#dir, name));
2702
2709
  if (instructions !== null) {
2703
2710
  yield { source: this.name, kind: FileKind.memory, path: instructions, project: this.name };
2704
2711
  }
2705
2712
  }
2706
- const history = await resolveFile(join3(this.#home, ".codex", "history.jsonl"));
2713
+ const history = await resolveFile(join3(this.#dir, "history.jsonl"));
2707
2714
  if (history !== null) {
2708
2715
  yield { source: this.name, kind: FileKind.transcript, path: history, project: this.name };
2709
2716
  }
2710
2717
  for (const root of ["sessions", "archived_sessions"]) {
2711
- for (const dir of await walkDirectories(join3(this.#home, ".codex", root))) {
2718
+ for (const dir of await walkDirectories(join3(this.#dir, root))) {
2712
2719
  for (const file of await listFiles(dir, FileExtension.jsonl)) {
2713
2720
  const path = join3(dir, file);
2714
2721
  const cwd = await firstCwd2(path);
@@ -2790,8 +2797,42 @@ async function isDirectory(path) {
2790
2797
  }
2791
2798
  }
2792
2799
 
2800
+ // src/sources/gemini-cli-source.ts
2801
+ import { join as join5 } from "path";
2802
+ var GeminiCliSource = class {
2803
+ name = SourceName["gemini-cli"];
2804
+ #dir;
2805
+ /** `dir` is `~/.gemini`, or `$GEMINI_CLI_HOME/.gemini` when that is set. */
2806
+ constructor(home2, dir = join5(home2, ".gemini")) {
2807
+ this.#dir = dir;
2808
+ }
2809
+ async *discover() {
2810
+ const instructions = await resolveFile(join5(this.#dir, "GEMINI.md"));
2811
+ if (instructions !== null) {
2812
+ yield { source: this.name, kind: FileKind.memory, path: instructions, project: this.name };
2813
+ }
2814
+ const root = join5(this.#dir, "tmp");
2815
+ for (const id of await listDirectories(root)) {
2816
+ const project = legacyHash.test(id) ? this.name : id;
2817
+ for (const dir of await walkDirectories(join5(root, id, "chats"))) {
2818
+ for (const extension of [FileExtension.jsonl, FileExtension.json]) {
2819
+ for (const file of await listFiles(dir, extension)) {
2820
+ yield { source: this.name, kind: FileKind.transcript, path: join5(dir, file), project };
2821
+ }
2822
+ }
2823
+ }
2824
+ for (const dir of await walkDirectories(join5(root, id, "memory"))) {
2825
+ for (const file of await listFiles(dir, FileExtension.markdown)) {
2826
+ yield { source: this.name, kind: FileKind.memory, path: join5(dir, file), project };
2827
+ }
2828
+ }
2829
+ }
2830
+ }
2831
+ };
2832
+ var legacyHash = /^[0-9a-f]{64}$/;
2833
+
2793
2834
  // src/sources/opencode-source.ts
2794
- import { basename as basename4, join as join5 } from "path";
2835
+ import { basename as basename4, join as join6 } from "path";
2795
2836
  var OpencodeSource = class {
2796
2837
  name = SourceName.opencode;
2797
2838
  #home;
@@ -2801,9 +2842,9 @@ var OpencodeSource = class {
2801
2842
  this.#load = load;
2802
2843
  }
2803
2844
  async *discover() {
2804
- const dir = join5(this.#home, ".local", "share", "opencode");
2845
+ const dir = join6(this.#home, ".local", "share", "opencode");
2805
2846
  for (const file of await listFiles(dir, FileExtension.database)) {
2806
- const path = join5(dir, file);
2847
+ const path = join6(dir, file);
2807
2848
  const sqlite = await this.#load();
2808
2849
  yield {
2809
2850
  source: this.name,
@@ -2857,6 +2898,17 @@ async function importSqlite() {
2857
2898
  }
2858
2899
  }
2859
2900
 
2901
+ // src/sources/state-dirs.ts
2902
+ import { join as join7, resolve } from "path";
2903
+ function stateDirs(home2, folder, moved) {
2904
+ const dirs = [join7(home2, folder)];
2905
+ if (moved !== void 0 && moved !== "") {
2906
+ const dir = resolve(moved);
2907
+ if (!dirs.includes(dir)) dirs.push(dir);
2908
+ }
2909
+ return dirs;
2910
+ }
2911
+
2860
2912
  // src/wipe/confirm.ts
2861
2913
  import { createInterface as createInterface3 } from "readline/promises";
2862
2914
  var CONFIRMATION = "wipe";
@@ -3021,9 +3073,19 @@ async function main(argv) {
3021
3073
  return 0;
3022
3074
  }
3023
3075
  const sources = [
3024
- new ClaudeCodeSource(homedir()),
3025
- new CodexSource(homedir()),
3076
+ ...stateDirs(homedir(), ".claude", process.env.CLAUDE_CONFIG_DIR).map(
3077
+ (dir) => new ClaudeCodeSource(homedir(), dir)
3078
+ ),
3079
+ ...stateDirs(homedir(), ".codex", process.env.CODEX_HOME).map(
3080
+ (dir) => new CodexSource(homedir(), dir)
3081
+ ),
3026
3082
  new CursorSource(homedir()),
3083
+ // GEMINI_CLI_HOME stands in for the home directory, not for `.gemini`.
3084
+ ...stateDirs(
3085
+ homedir(),
3086
+ ".gemini",
3087
+ process.env.GEMINI_CLI_HOME ? join8(process.env.GEMINI_CLI_HOME, ".gemini") : void 0
3088
+ ).map((dir) => new GeminiCliSource(homedir(), dir)),
3027
3089
  new OpencodeSource(homedir())
3028
3090
  ];
3029
3091
  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.5.3",
4
- "description": "Scan your local Claude Code, Codex, Cursor and opencode transcripts for credentials you already sent to a model provider.",
3
+ "version": "0.6.0",
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",