tankslab.com/recall/source

bin/inspect_store.py

138 lines, exactly as they ship in the download.

""" inspect_store.py - say what is inside a leftover store, before asking about it. WHY THIS IS THE WHOLE POINT --------------------------- Uninstalling a memory tool does not remove what it harvested. The program goes; its copy of your work stays. Most people do not know that, and nobody deletes a folder because a tool suggested it. They act on the second line: this database holds 1,204 sessions and 25 passwords or keys in plain text it has been on your disk since 12 July That is the informed part of informed consent. We can say it truthfully because the redactor already recognises those patterns, so the number is one a person can check by eye in their own file. TWO RULES, both absolute: · COUNT AND KIND, NEVER THE VALUE. This prints "9 chat tokens". It does not print a token. A tool that displayed the secrets it found in order to warn you about them would be the joke it deserves to be. · READ-ONLY. Everything here opens the file read-only. Inspecting a stranger's database must not be able to change it. """ from __future__ import annotations import sqlite3 import sys from datetime import datetime, timezone from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from redact import scrub # noqa: E402 # Plain words for what was found. The rule ids are ours; these are for a person who # just wants to know whether it matters. PLAIN = { "private-key": "private keys", "anthropic-key": "API keys", "openai-key": "API keys", "api-key-sk": "API keys", "google-key": "API keys", "github-token": "code-host tokens", "slack-token": "chat tokens", "telegram-bot": "chat tokens", "aws-akid": "cloud keys", "jwt": "session tokens", "bearer": "authorisation headers", "basic": "authorisation headers", "apikey-header": "authorisation headers", "url-cred": "web addresses with logins in them", "assigned-secret": "passwords", "cloudflare": "cloud keys", } # Text columns worth scanning, per table. Anything not listed is ids and timestamps. SCAN = { "observations": ("text", "title", "subtitle", "facts", "narrative", "concepts"), "session_summaries": ("request", "investigated", "learned", "completed", "next_steps", "notes"), "user_prompts": ("prompt_text",), "sdk_sessions": ("user_prompt", "custom_title"), } def inspect(db: Path, sample_limit: int = 0) -> dict: """Count what is in there. Never returns a secret, only kinds and counts.""" con = sqlite3.connect(f"file:{db}?mode=ro", uri=True) con.row_factory = sqlite3.Row names = {r[0] for r in con.execute("SELECT name FROM sqlite_master WHERE type='table'")} rows = {} kinds: dict[str, int] = {} scanned = 0 for table, cols in SCAN.items(): if table not in names: continue have = {r[1] for r in con.execute(f"PRAGMA table_info({table})")} use = [c for c in cols if c in have] rows[table] = con.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0] if not use: continue q = f"SELECT {','.join(use)} FROM {table}" if sample_limit: q += f" LIMIT {int(sample_limit)}" for r in con.execute(q): for v in r: if isinstance(v, str) and v: scanned += 1 _, n, labels = scrub(v) for lab in labels: kinds[lab] = kinds.get(lab, 0) + 1 con.close() st = db.stat() plain: dict[str, int] = {} for lab, n in kinds.items(): plain[PLAIN.get(lab, lab)] = plain.get(PLAIN.get(lab, lab), 0) + n return { "path": db, "size": st.st_size, "since": datetime.fromtimestamp(st.st_mtime, timezone.utc), "rows": rows, "secrets_total": sum(kinds.values()), "secrets_by_kind": dict(sorted(plain.items(), key=lambda kv: -kv[1])), "fields_scanned": scanned, } def sentence(info: dict) -> list[str]: """The two lines a person actually decides on.""" sessions = info["rows"].get("sdk_sessions") or 0 obs = info["rows"].get("observations") or 0 n = info["secrets_total"] body = f"{sessions:,} sessions and {obs:,} entries" out = [] if n: out.append(f" This database holds {body}, and {n} password" f"{'s' if n != 1 else ''} or key{'s' if n != 1 else ''} in plain text.") for kind, c in info["secrets_by_kind"].items(): out.append(f" {c:>4} {kind}") else: out.append(f" This database holds {body}. The redactor found no secrets in it.") out.append(f" It has been on your disk since {info['since'].strftime('%d %B %Y')}" f" and is {info['size']/1e6:.1f} MB.") if n: out.append(" The kinds are listed; the values are not shown and never will be.") return out if __name__ == "__main__": if len(sys.argv) < 2: print("usage: inspect_store.py <database>", file=sys.stderr) raise SystemExit(2) info = inspect(Path(sys.argv[1])) print("\n".join(sentence(info)))