whatileaked 0.4.0 → 0.5.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 +19 -11
- package/dist/cli.js +203 -48
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -205,18 +205,26 @@ npx whatileaked scan || echo "found something"
|
|
|
205
205
|
|
|
206
206
|
| Agent | Folder |
|
|
207
207
|
| --- | --- |
|
|
208
|
-
| Claude Code | `~/.claude/projects/` · `~/.claude/CLAUDE.md` |
|
|
209
|
-
| Codex | `~/.codex/sessions/` · `~/.codex/archived_sessions/` · `~/.codex/AGENTS.md` |
|
|
208
|
+
| Claude Code | `~/.claude/projects/` · `~/.claude/history.jsonl` · `~/.claude/paste-cache/` · `~/.claude/CLAUDE.md` |
|
|
209
|
+
| Codex | `~/.codex/sessions/` · `~/.codex/archived_sessions/` · `~/.codex/history.jsonl` · `~/.codex/AGENTS.md` |
|
|
210
210
|
| Cursor | `~/.cursor/projects/*/agent-transcripts/` |
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
211
|
+
| opencode | `~/.local/share/opencode/opencode.db` |
|
|
212
|
+
|
|
213
|
+
opencode keeps its sessions in a SQLite database. Reading it needs Node's
|
|
214
|
+
built-in `node:sqlite`, which loads without a flag from Node 22.13. On an older
|
|
215
|
+
Node the scan still finds the file and says, in one line, that it was not
|
|
216
|
+
scanned, so a skipped database never looks clean. Only the messages table is
|
|
217
|
+
read, and `wipe` does not rewrite the database: delete those sessions in
|
|
218
|
+
opencode, and rotate.
|
|
219
|
+
|
|
220
|
+
Cursor keeps its older Ask-mode chats in SQLite as well, and those are not
|
|
221
|
+
scanned yet. Agent transcripts are covered; Ask history is not.
|
|
222
|
+
|
|
223
|
+
Transcripts record credentials that were already sent. So does prompt history,
|
|
224
|
+
which keeps every prompt you typed and every long paste after the session itself
|
|
225
|
+
has been cleaned up. Instruction and memory files are worse: they are read again
|
|
226
|
+
at the start of the next session, so a credential in one keeps leaking until the
|
|
227
|
+
file is edited. All of them are scanned, and `wipe` rewrites all of them.
|
|
220
228
|
|
|
221
229
|
Nowhere else. It does not read your repos, your shell history, or your
|
|
222
230
|
environment. Adding support for another agent is one small file — pull requests
|
package/dist/cli.js
CHANGED
|
@@ -161,6 +161,8 @@ var TerminalReporter = class {
|
|
|
161
161
|
if (totals.length === 0) {
|
|
162
162
|
this.#write(green("No credentials found."));
|
|
163
163
|
this.#write("");
|
|
164
|
+
this.#unread(stats);
|
|
165
|
+
if (stats.unread.length > 0) this.#write("");
|
|
164
166
|
this.#write(dim(DISCLOSURE));
|
|
165
167
|
this.#share(sent, stats);
|
|
166
168
|
return;
|
|
@@ -191,6 +193,7 @@ var TerminalReporter = class {
|
|
|
191
193
|
yellow(`${plural(stats.unscannable, "message")} could not be scanned and are not counted.`)
|
|
192
194
|
);
|
|
193
195
|
}
|
|
196
|
+
this.#unread(stats);
|
|
194
197
|
this.#write("");
|
|
195
198
|
this.#write(dim("The 8-character code is a fingerprint, never the secret: a truncated"));
|
|
196
199
|
this.#write(dim("SHA-256, so one credential shows the same code everywhere it leaked."));
|
|
@@ -199,6 +202,13 @@ var TerminalReporter = class {
|
|
|
199
202
|
this.#write(dim(DISCLOSURE));
|
|
200
203
|
this.#share(sent, stats);
|
|
201
204
|
}
|
|
205
|
+
/** One plain line per file found and not read. Printed under a clean result
|
|
206
|
+
* too, which is exactly where leaving it out would mislead. */
|
|
207
|
+
#unread(stats) {
|
|
208
|
+
for (const file of stats.unread) {
|
|
209
|
+
this.#write(yellow(`Found ${shorten(file.path)} but did not scan it: ${file.reason}.`));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
202
212
|
/**
|
|
203
213
|
* The one line meant to be pasted somewhere public. Everything above it names
|
|
204
214
|
* projects, paths and fingerprints, so nobody shares it; this is counts only,
|
|
@@ -2398,6 +2408,50 @@ async function* readLines(path) {
|
|
|
2398
2408
|
}
|
|
2399
2409
|
}
|
|
2400
2410
|
|
|
2411
|
+
// src/sources/listing.ts
|
|
2412
|
+
import { readdir, realpath, stat } from "fs/promises";
|
|
2413
|
+
import { join } from "path";
|
|
2414
|
+
async function listDirectories(root) {
|
|
2415
|
+
try {
|
|
2416
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
2417
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2418
|
+
} catch {
|
|
2419
|
+
return [];
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
var FileExtension = {
|
|
2423
|
+
jsonl: ".jsonl",
|
|
2424
|
+
markdown: ".md",
|
|
2425
|
+
text: ".txt",
|
|
2426
|
+
database: ".db"
|
|
2427
|
+
};
|
|
2428
|
+
function isJsonl(path) {
|
|
2429
|
+
return path.endsWith(FileExtension.jsonl);
|
|
2430
|
+
}
|
|
2431
|
+
async function listFiles(dir, extension) {
|
|
2432
|
+
try {
|
|
2433
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
2434
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(extension)).map((entry) => entry.name);
|
|
2435
|
+
} catch {
|
|
2436
|
+
return [];
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
async function resolveFile(path) {
|
|
2440
|
+
try {
|
|
2441
|
+
const real = await realpath(path);
|
|
2442
|
+
return (await stat(real)).isFile() ? real : null;
|
|
2443
|
+
} catch {
|
|
2444
|
+
return null;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
async function walkDirectories(root) {
|
|
2448
|
+
const found = [root];
|
|
2449
|
+
for (const name of await listDirectories(root)) {
|
|
2450
|
+
found.push(...await walkDirectories(join(root, name)));
|
|
2451
|
+
}
|
|
2452
|
+
return found;
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2401
2455
|
// src/transcript/reader.ts
|
|
2402
2456
|
import { createReadStream as createReadStream2 } from "fs";
|
|
2403
2457
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -2430,6 +2484,7 @@ var TranscriptReader = class {
|
|
|
2430
2484
|
};
|
|
2431
2485
|
|
|
2432
2486
|
// src/scan-run.ts
|
|
2487
|
+
var NEEDS_SQLITE = "reading it needs Node 22.13 or newer";
|
|
2433
2488
|
var ScanRun = class {
|
|
2434
2489
|
#sources;
|
|
2435
2490
|
#scanner;
|
|
@@ -2450,12 +2505,19 @@ var ScanRun = class {
|
|
|
2450
2505
|
memoryFiles: 0,
|
|
2451
2506
|
entries: 0,
|
|
2452
2507
|
skipped: 0,
|
|
2453
|
-
unscannable: 0
|
|
2508
|
+
unscannable: 0,
|
|
2509
|
+
unread: []
|
|
2454
2510
|
};
|
|
2455
2511
|
for (const source of this.#sources) {
|
|
2456
2512
|
for await (const file of source.discover()) {
|
|
2457
|
-
if (file.
|
|
2458
|
-
stats
|
|
2513
|
+
if (file.messages !== void 0) {
|
|
2514
|
+
await this.#readDatabase(file, file.messages, findings, stats);
|
|
2515
|
+
this.#progress.advance(stats.transcripts + stats.memoryFiles, file.project);
|
|
2516
|
+
continue;
|
|
2517
|
+
}
|
|
2518
|
+
if (file.kind === FileKind.transcript) stats.transcripts++;
|
|
2519
|
+
else stats.memoryFiles++;
|
|
2520
|
+
if (isJsonl(file.path)) {
|
|
2459
2521
|
const reader = new TranscriptReader();
|
|
2460
2522
|
for await (const entry of reader.read(file.path)) {
|
|
2461
2523
|
this.#collect(file, entry.line, entry.index, findings, stats);
|
|
@@ -2463,7 +2525,7 @@ var ScanRun = class {
|
|
|
2463
2525
|
stats.entries += reader.stats.entries;
|
|
2464
2526
|
stats.skipped += reader.stats.skipped;
|
|
2465
2527
|
} else {
|
|
2466
|
-
stats.
|
|
2528
|
+
if (file.kind === FileKind.transcript) stats.entries++;
|
|
2467
2529
|
for await (const line of readLines(file.path)) {
|
|
2468
2530
|
this.#collect(file, line.text, line.number, findings, stats);
|
|
2469
2531
|
}
|
|
@@ -2475,6 +2537,24 @@ var ScanRun = class {
|
|
|
2475
2537
|
this.#reporter.report(findings, stats);
|
|
2476
2538
|
return findings.length;
|
|
2477
2539
|
}
|
|
2540
|
+
/** A database counts as one transcript once it has been read. One it could
|
|
2541
|
+
* not open is listed instead, so a scan that skipped it never reads as clean. */
|
|
2542
|
+
async #readDatabase(file, messages, findings, stats) {
|
|
2543
|
+
if (messages === null) {
|
|
2544
|
+
stats.unread.push({ path: file.path, reason: NEEDS_SQLITE });
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
let index = 0;
|
|
2548
|
+
try {
|
|
2549
|
+
for await (const message of messages()) {
|
|
2550
|
+
stats.entries++;
|
|
2551
|
+
this.#collect({ ...file, project: message.project }, message.text, index++, findings, stats);
|
|
2552
|
+
}
|
|
2553
|
+
stats.transcripts++;
|
|
2554
|
+
} catch {
|
|
2555
|
+
stats.unread.push({ path: file.path, reason: "it could not be opened" });
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2478
2558
|
/** One entry the engine chokes on must not cost the other 700,000. */
|
|
2479
2559
|
#collect(file, text, at, findings, stats) {
|
|
2480
2560
|
let matches = [];
|
|
@@ -2502,47 +2582,12 @@ var ScanRun = class {
|
|
|
2502
2582
|
// src/sources/claude-code-source.ts
|
|
2503
2583
|
import { basename, join as join2 } from "path";
|
|
2504
2584
|
|
|
2505
|
-
// src/sources/listing.ts
|
|
2506
|
-
import { readdir, realpath, stat } from "fs/promises";
|
|
2507
|
-
import { join } from "path";
|
|
2508
|
-
async function listDirectories(root) {
|
|
2509
|
-
try {
|
|
2510
|
-
const entries = await readdir(root, { withFileTypes: true });
|
|
2511
|
-
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2512
|
-
} catch {
|
|
2513
|
-
return [];
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
var FileExtension = { jsonl: ".jsonl", markdown: ".md" };
|
|
2517
|
-
async function listFiles(dir, extension) {
|
|
2518
|
-
try {
|
|
2519
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
2520
|
-
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(extension)).map((entry) => entry.name);
|
|
2521
|
-
} catch {
|
|
2522
|
-
return [];
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
async function resolveFile(path) {
|
|
2526
|
-
try {
|
|
2527
|
-
const real = await realpath(path);
|
|
2528
|
-
return (await stat(real)).isFile() ? real : null;
|
|
2529
|
-
} catch {
|
|
2530
|
-
return null;
|
|
2531
|
-
}
|
|
2532
|
-
}
|
|
2533
|
-
async function walkDirectories(root) {
|
|
2534
|
-
const found = [root];
|
|
2535
|
-
for (const name of await listDirectories(root)) {
|
|
2536
|
-
found.push(...await walkDirectories(join(root, name)));
|
|
2537
|
-
}
|
|
2538
|
-
return found;
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2541
2585
|
// src/transcript/entry.ts
|
|
2542
2586
|
var SourceName = {
|
|
2543
2587
|
"claude-code": "claude-code",
|
|
2544
2588
|
codex: "codex",
|
|
2545
|
-
cursor: "cursor"
|
|
2589
|
+
cursor: "cursor",
|
|
2590
|
+
opencode: "opencode"
|
|
2546
2591
|
};
|
|
2547
2592
|
|
|
2548
2593
|
// src/transcript/shape.ts
|
|
@@ -2570,6 +2615,19 @@ var ClaudeCodeSource = class {
|
|
|
2570
2615
|
if (instructions !== null) {
|
|
2571
2616
|
yield { source: this.name, kind: FileKind.memory, path: instructions, project: this.name };
|
|
2572
2617
|
}
|
|
2618
|
+
const history = await resolveFile(join2(this.#home, ".claude", "history.jsonl"));
|
|
2619
|
+
if (history !== null) {
|
|
2620
|
+
yield { source: this.name, kind: FileKind.transcript, path: history, project: this.name };
|
|
2621
|
+
}
|
|
2622
|
+
const pastes = join2(this.#home, ".claude", "paste-cache");
|
|
2623
|
+
for (const file of await listFiles(pastes, FileExtension.text)) {
|
|
2624
|
+
yield {
|
|
2625
|
+
source: this.name,
|
|
2626
|
+
kind: FileKind.transcript,
|
|
2627
|
+
path: join2(pastes, file),
|
|
2628
|
+
project: this.name
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2573
2631
|
const root = join2(this.#home, ".claude", "projects");
|
|
2574
2632
|
for (const slug of await listDirectories(root)) {
|
|
2575
2633
|
const dir = join2(root, slug);
|
|
@@ -2623,6 +2681,10 @@ var CodexSource = class {
|
|
|
2623
2681
|
if (instructions !== null) {
|
|
2624
2682
|
yield { source: this.name, kind: FileKind.memory, path: instructions, project: this.name };
|
|
2625
2683
|
}
|
|
2684
|
+
const history = await resolveFile(join3(this.#home, ".codex", "history.jsonl"));
|
|
2685
|
+
if (history !== null) {
|
|
2686
|
+
yield { source: this.name, kind: FileKind.transcript, path: history, project: this.name };
|
|
2687
|
+
}
|
|
2626
2688
|
for (const root of ["sessions", "archived_sessions"]) {
|
|
2627
2689
|
for (const dir of await walkDirectories(join3(this.#home, ".codex", root))) {
|
|
2628
2690
|
for (const file of await listFiles(dir, FileExtension.jsonl)) {
|
|
@@ -2706,6 +2768,73 @@ async function isDirectory(path) {
|
|
|
2706
2768
|
}
|
|
2707
2769
|
}
|
|
2708
2770
|
|
|
2771
|
+
// src/sources/opencode-source.ts
|
|
2772
|
+
import { basename as basename4, join as join5 } from "path";
|
|
2773
|
+
var OpencodeSource = class {
|
|
2774
|
+
name = SourceName.opencode;
|
|
2775
|
+
#home;
|
|
2776
|
+
#load;
|
|
2777
|
+
constructor(home2, load = loadSqlite) {
|
|
2778
|
+
this.#home = home2;
|
|
2779
|
+
this.#load = load;
|
|
2780
|
+
}
|
|
2781
|
+
async *discover() {
|
|
2782
|
+
const dir = join5(this.#home, ".local", "share", "opencode");
|
|
2783
|
+
for (const file of await listFiles(dir, FileExtension.database)) {
|
|
2784
|
+
const path = join5(dir, file);
|
|
2785
|
+
const sqlite = await this.#load();
|
|
2786
|
+
yield {
|
|
2787
|
+
source: this.name,
|
|
2788
|
+
kind: FileKind.transcript,
|
|
2789
|
+
path,
|
|
2790
|
+
project: this.name,
|
|
2791
|
+
messages: sqlite === null ? null : () => readMessages(sqlite, path, this.name)
|
|
2792
|
+
};
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
async function* readMessages(sqlite, path, fallback) {
|
|
2797
|
+
const db = new sqlite.DatabaseSync(path, { readOnly: true });
|
|
2798
|
+
try {
|
|
2799
|
+
const directories = /* @__PURE__ */ new Map();
|
|
2800
|
+
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (?, ?)").all("session", "session_v2");
|
|
2801
|
+
for (const { name } of tables) {
|
|
2802
|
+
for (const row of db.prepare(`SELECT id, directory FROM "${String(name)}"`).iterate()) {
|
|
2803
|
+
if (typeof row.id === "string" && typeof row.directory === "string") {
|
|
2804
|
+
directories.set(row.id, row.directory);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
for (const row of db.prepare("SELECT session_id, data FROM session_message").iterate()) {
|
|
2809
|
+
if (typeof row.data !== "string") continue;
|
|
2810
|
+
const directory = typeof row.session_id === "string" ? directories.get(row.session_id) : null;
|
|
2811
|
+
yield { project: directory == null ? fallback : basename4(directory), text: row.data };
|
|
2812
|
+
}
|
|
2813
|
+
} finally {
|
|
2814
|
+
db.close();
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
var loading = null;
|
|
2818
|
+
function loadSqlite() {
|
|
2819
|
+
loading ??= importSqlite();
|
|
2820
|
+
return loading;
|
|
2821
|
+
}
|
|
2822
|
+
async function importSqlite() {
|
|
2823
|
+
const emitWarning = process.emitWarning;
|
|
2824
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
2825
|
+
if (String(warning).includes("SQLite")) return;
|
|
2826
|
+
Reflect.apply(emitWarning, process, [warning, ...rest]);
|
|
2827
|
+
});
|
|
2828
|
+
try {
|
|
2829
|
+
const specifier = "node:sqlite";
|
|
2830
|
+
return await import(specifier);
|
|
2831
|
+
} catch {
|
|
2832
|
+
return null;
|
|
2833
|
+
} finally {
|
|
2834
|
+
process.emitWarning = emitWarning;
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2709
2838
|
// src/wipe/confirm.ts
|
|
2710
2839
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
2711
2840
|
var CONFIRMATION = "wipe";
|
|
@@ -2749,16 +2878,16 @@ var WipeRun = class {
|
|
|
2749
2878
|
this.#write = write;
|
|
2750
2879
|
}
|
|
2751
2880
|
async run() {
|
|
2752
|
-
const plans = await this.#plan();
|
|
2881
|
+
const { plans, untouched } = await this.#plan();
|
|
2753
2882
|
if (plans.length === 0) {
|
|
2754
|
-
return { planned: [], filesRewritten: 0, secretsRedacted: 0, confirmed: false };
|
|
2883
|
+
return { planned: [], untouched, filesRewritten: 0, secretsRedacted: 0, confirmed: false };
|
|
2755
2884
|
}
|
|
2756
2885
|
this.#preview(plans);
|
|
2757
2886
|
const answer = await this.#prompt.ask(
|
|
2758
2887
|
` Type ${bold(red(CONFIRMATION))} to rewrite these files, or anything else to stop: `
|
|
2759
2888
|
);
|
|
2760
2889
|
if (answer !== CONFIRMATION) {
|
|
2761
|
-
return { planned: plans, filesRewritten: 0, secretsRedacted: 0, confirmed: false };
|
|
2890
|
+
return { planned: plans, untouched, filesRewritten: 0, secretsRedacted: 0, confirmed: false };
|
|
2762
2891
|
}
|
|
2763
2892
|
let filesRewritten = 0;
|
|
2764
2893
|
let secretsRedacted = 0;
|
|
@@ -2766,12 +2895,18 @@ var WipeRun = class {
|
|
|
2766
2895
|
secretsRedacted += await this.#rewrite(plan.file);
|
|
2767
2896
|
filesRewritten++;
|
|
2768
2897
|
}
|
|
2769
|
-
return { planned: plans, filesRewritten, secretsRedacted, confirmed: true };
|
|
2898
|
+
return { planned: plans, untouched, filesRewritten, secretsRedacted, confirmed: true };
|
|
2770
2899
|
}
|
|
2771
2900
|
async #plan() {
|
|
2772
2901
|
const plans = [];
|
|
2902
|
+
const untouched = [];
|
|
2773
2903
|
for (const source of this.#sources) {
|
|
2774
2904
|
for await (const file of source.discover()) {
|
|
2905
|
+
if (file.messages !== void 0) {
|
|
2906
|
+
if (!await this.#holdsSecret(file.messages)) continue;
|
|
2907
|
+
untouched.push(file.path);
|
|
2908
|
+
continue;
|
|
2909
|
+
}
|
|
2775
2910
|
let lines = 0;
|
|
2776
2911
|
let secrets = 0;
|
|
2777
2912
|
for await (const text of this.#textOf(file)) {
|
|
@@ -2785,12 +2920,25 @@ var WipeRun = class {
|
|
|
2785
2920
|
}
|
|
2786
2921
|
}
|
|
2787
2922
|
}
|
|
2788
|
-
return plans;
|
|
2923
|
+
return { plans, untouched };
|
|
2924
|
+
}
|
|
2925
|
+
/** True for a database that cannot be read: it may hold one, and saying
|
|
2926
|
+
* nothing about it would let a wipe pass for complete. */
|
|
2927
|
+
async #holdsSecret(messages) {
|
|
2928
|
+
if (messages == null) return true;
|
|
2929
|
+
try {
|
|
2930
|
+
for await (const message of messages()) {
|
|
2931
|
+
if (this.#scanner.secretsOf(message.text).size > 0) return true;
|
|
2932
|
+
}
|
|
2933
|
+
return false;
|
|
2934
|
+
} catch {
|
|
2935
|
+
return true;
|
|
2936
|
+
}
|
|
2789
2937
|
}
|
|
2790
2938
|
/** The rewrite below is textual and kind-blind; only counting needs to know
|
|
2791
2939
|
* which reader gets the file. */
|
|
2792
2940
|
async *#textOf(file) {
|
|
2793
|
-
if (file.
|
|
2941
|
+
if (!isJsonl(file.path)) {
|
|
2794
2942
|
for await (const line of readLines(file.path)) yield line.text;
|
|
2795
2943
|
return;
|
|
2796
2944
|
}
|
|
@@ -2853,7 +3001,8 @@ async function main(argv) {
|
|
|
2853
3001
|
const sources = [
|
|
2854
3002
|
new ClaudeCodeSource(homedir()),
|
|
2855
3003
|
new CodexSource(homedir()),
|
|
2856
|
-
new CursorSource(homedir())
|
|
3004
|
+
new CursorSource(homedir()),
|
|
3005
|
+
new OpencodeSource(homedir())
|
|
2857
3006
|
];
|
|
2858
3007
|
const scanner = new Scanner(new KeywordIndex(compileRules(SECRET_RULE_SOURCES)));
|
|
2859
3008
|
return command === Command.wipe ? await wipe(sources, scanner, write) : await scan(sources, scanner, write);
|
|
@@ -2872,7 +3021,13 @@ async function wipe(sources, scanner, write) {
|
|
|
2872
3021
|
write(dim(" replacing credentials in your local transcripts with a placeholder"));
|
|
2873
3022
|
write("");
|
|
2874
3023
|
const outcome = await new WipeRun(sources, scanner, new TerminalPrompt(), write).run();
|
|
3024
|
+
for (const path of outcome.untouched) {
|
|
3025
|
+
write(yellow(` Not rewritten: ${shorten(path)} is a database, and wipe only rewrites text.`));
|
|
3026
|
+
write(dim(" Delete those sessions in the agent itself, and rotate what they held."));
|
|
3027
|
+
write("");
|
|
3028
|
+
}
|
|
2875
3029
|
if (outcome.planned.length === 0) {
|
|
3030
|
+
if (outcome.untouched.length > 0) return 1;
|
|
2876
3031
|
write(green(" No credentials found. Nothing to wipe."));
|
|
2877
3032
|
return 0;
|
|
2878
3033
|
}
|
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 and
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Scan your local Claude Code, Codex, Cursor and opencode transcripts for credentials you already sent to a model provider.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"imports": {
|
|
7
7
|
"#scripts/*": "./scripts/*.ts",
|