Authorized recon tool for AI platform data leakage via public share links.
Finds secrets and PII that users accidentally exposed when sharing AI conversations, published artifacts, and exported chat histories — across live URLs, web archives, code repositories, and file-sharing platforms.
Authorization required. The tool enforces a hard gate: without
--authorized, it runs in links-only mode and makes no outbound fetch requests. All secrets are redacted before storage.
Every major AI platform (ChatGPT, Claude, Gemini, Perplexity, …) lets users share conversations via public links. Users routinely share chats containing:
- API keys, database credentials, cloud access tokens pasted into prompts
- Private keys, connection strings, MCP server configs
- Exported chat histories (
conversations.json) uploaded to GitHub or Google Drive - PII — IDs, phone numbers, tax codes — included as context for AI assistance
Critical gap since August 2025: Google de-indexed AI share links. Standard Google dorking now returns false "clean" results. These URLs remain accessible and indexed in the Wayback Machine and Common Crawl — making archive-first discovery essential.
Scope (targets: domains, orgs, keywords)
│
▼
┌─────────────────────────────────────────────────────┐
│ Discovery Engine (8 channels, iterative pivot) │
│ C1 Search ──── Google CSE, Brave, DDG │
│ C2 Archive ──── Wayback CDX, Common Crawl │ ← primary for de-indexed links
│ C3 UrlIntel ─── urlscan.io │
│ C4 Code ──────── GitHub code/gist search │
│ FileShare ─── Drive, Dropbox, Notion, Pastebin, │
│ Rentry, Scribd, Zenodo, OSF … │
│ C5 Social ─────── Reddit │
│ C6 AI Platform ── dorks per platform │
│ C7 HostIntel ──── Shodan (self-hosted AI infra) │
│ C8 Observability ─ Langfuse public traces │
│ │
│ Pivot loop: extract seeds from docs → new queries │
│ Saturation: stop when new_unique/total < 2% × 2 │
└──────────────────────────┬──────────────────────────┘
│ hits.jsonl
▼
┌─────────────────────────────────────────────────────┐
│ Fetcher (SSRF-safe, redirect-validating) │
│ ├── robots.txt cache │
│ ├── per-host rate limiter │
│ ├── 2 MB stream cap │
│ ├── Wayback fallback on 404/410 │
│ └── artifact pivot (Gist, Pastebin, Drive, …) │
└──────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Extract (HTML, PDF, DOCX, XLSX, .ipynb, OCR) │
│ Decode (BFS cascade: base64 / hex / URL / │
│ gzip / JSON-escaped PEM, depth ≤ 4) │
└──────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Scanner (94 rules, 4-tier) │
│ Tier 1: regex match │
│ Tier 2: entropy check (per-charset thresholds) │
│ Tier 3: structural validators (CRC32, checksum, │
│ PEM parse, JWT decode, expiry check) │
│ Tier 4: sensitive-content classifier │
│ │
│ public_by_design allowlist: Firebase anon key, │
│ Supabase anon, Stripe pk_*, Algolia search-key │
│ → demoted to info, no false-positive noise │
└──────────────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Triage │
│ ├── Ownership: own / third_party / unknown │
│ ├── Blast radius: exposure_days, in_common_crawl │
│ ├── Priority score (severity × confidence × │
│ │ blast × 2× if own asset) │
│ └── Diff: new / existing / resolved vs last run │
└──────────────────────────┬──────────────────────────┘
│
▼
Markdown · HTML dashboard · JSON · SARIF
Real scan against chatgpt.com / OpenAI — 1380 URLs discovered across 3 generations (499 from GitHub alone in gen 1, Common Crawl added 727 more in gen 3), unlimited fetch, 20 findings including Cloudflare API Token, Cohere API Key, MCP config files with embedded secrets, and Vietnam Social Insurance codes leaked via public GitHub repos.
Discovery phase — 3 generations, 1380 unique hits:
Scan phase — 500 docs scanned, 20 findings, reports written in MD / JSON / HTML:
pip install -e .
# Optional extras
pip install -e ".[ocr]" # image scanning via RapidOCR
pip install -e ".[fast]" # hyperscan engine for large corporaRequirements: Python ≥ 3.11
# Discovery only (no outbound fetch — safe to run first)
ai-leak-recon run example.com
ai-leak-recon run "Acme Corp" example.com acme.io
# Full scan (requires authorization confirmation)
ai-leak-recon run example.com --authorized
ai-leak-recon run "Acme Corp" example.com acme.io --authorized --out report_output/Targets are auto-classified: strings matching domain.tld → domains; anything else → orgs + keywords.
# Phase 1: discover only (enumerate URLs, no fetch)
ai-leak-recon discover example.com --out hits.jsonl
# Review hits.jsonl, then authorize and scan
ai-leak-recon scan --in hits.jsonl --authorized --format md,html,json,sarif# scope.yaml
authorized_by: "Security Team <security@example.com>"
targets:
domains: [example.com, api.example.com]
orgs: [ExampleCorp]
keywords: ["example.com", "ExampleCorp internal"]
handles: ["@examplecorp"]
limits:
delay: 1.5
max_pages: 500
channels:
google_cse_key: "${GOOGLE_CSE_API_KEY}"
google_cse_cx: "${GOOGLE_CSE_CX}"
brave_key: "${BRAVE_SEARCH_KEY}"
github_token: "${GITHUB_TOKEN}"
urlscan_key: "${URLSCAN_API_KEY}"ai-leak-recon run --scope scope.yaml --authorizedai-leak-recon run [TARGET...] [OPTIONS]
Arguments:
TARGET... One or more domains or org names (alternative to --scope)
Options:
-s, --scope PATH scope.yaml (alternative to TARGET args)
--authorized Confirm authorized scope — required to fetch pages
-o, --out PATH Output directory [default: report_output]
--format TEXT md,html,json,jsonl,sarif [default: md,html,json]
--channels TEXT Enabled channels [default: search,archive,urlintel,code,fileshare]
-g, --generations Pivot iterations [default: 3]
--max-pages INT URL fetch cap (0 = all) [default: 0]
--delay FLOAT Per-host request delay (s) [default: 1.0]
ai-leak-recon discover [TARGET...] [OPTIONS]
Arguments:
TARGET... Domains or org names (or use --scope)
Options:
-s, --scope PATH scope.yaml
-o, --out PATH hits.jsonl output [default: hits.jsonl]
--channels TEXT [default: search,archive,urlintel,code,fileshare]
-g, --generations [default: 3]
--coverage PATH coverage.json output [default: coverage.json]
ai-leak-recon scan --in hits.jsonl --authorized [OPTIONS]
Options:
-i, --in PATH hits.jsonl from discover [required]
--authorized Required to make fetch requests
-o, --out PATH Report output dir [default: report_output]
--format TEXT md,html,json,jsonl,sarif [default: md,html,json]
--max-pages INT URL fetch cap (0 = all) [default: 0]
--delay FLOAT [default: 1.0]
--concurrency INT [default: 8]
--archive-fallback Wayback snapshot for 404/410 [default: on]
--ocr Image scanning via RapidOCR [default: off]
cp scope.template.yaml scope.yaml
# Edit scope.yaml — fill in targets and API keysscope.yaml is git-ignored. Never commit it.
# ── Targets ───────────────────────────────────────────────────────────
targets:
domains:
- example.com
- api.example.com
orgs:
- "Example Corp"
keywords:
- "example" # product codenames, internal project names
handles: [] # Twitter/X handles
# ── API Keys ──────────────────────────────────────────────────────────
channels:
api_keys:
github: "ghp_xxxx" # C4 GitHub code search
urlscan: "xxxx" # C3 URL intel
shodan: "xxxx" # C7 self-hosted AI infra scan
engines:
google_cse:
api_key: "AIzaSy..." # C1 Google search
cx: "xxxxxx" # Search engine ID
brave:
api_key: "BSA..." # C1 Brave search
serpapi:
api_key: "xxxx" # C1 SerpAPI (Google backend)
# ── Limits ────────────────────────────────────────────────────────────
limits:
delay: 1.0 # seconds between requests (raise if rate-limited)
max_pages: 500
# ── Authorization ─────────────────────────────────────────────────────
authorized_by: "Security Team <security@example.com>"
authorized_scope: "Bug bounty program — example.com"- Go to console.cloud.google.com → APIs & Services → Credentials → Create Credentials → API Key
- Enable Custom Search API in the API Library
- Go to programmablesearchengine.google.com → Add → create a new engine
- In engine settings, either enable "Search the entire web" OR add specific domains:
chatgpt.com claude.ai gemini.google.com chat.deepseek.com poe.com perplexity.ai github.com gist.github.com huggingface.co colab.research.google.com - Copy the Search engine ID (cx) from the engine overview page
- Set in scope.yaml:
Or via env:
engines: google_cse: api_key: "AIzaSy..." cx: "xxxxxxxxxx"
export GOOGLE_CSE_API_KEY="AIzaSy..." export GOOGLE_CSE_CX="xxxxxxxxxx"
Note: Free tier = 100 queries/day. Pricing — $5 per 1000 queries beyond that.
- Go to api.search.brave.com → sign up → Dashboard → API Keys → Create Key
- Choose plan: Free (2000 queries/month) or Base ($3/1000 queries)
- Set in scope.yaml:
Or:
engines: brave: api_key: "BSA..."
export BRAVE_SEARCH_KEY="BSA..."
- Go to serpapi.com → sign up → Dashboard → copy API Key
- Free tier: 100 searches/month. Paid from $50/month.
- Set in scope.yaml:
Or:
engines: serpapi: api_key: "xxxx"
export SERPAPI_KEY="xxxx"
SerpAPI uses Google as backend — highest-quality results. Good fallback when Google CSE quota runs out.
Without a token, GitHub API is limited to 60 requests/hour. With token: 5000/hour.
- Go to github.com/settings/tokens → Generate new token (classic)
- Select scope:
public_repoonly (read-only, sufficient) - Set in scope.yaml:
Or:
channels: api_keys: github: "ghp_xxxx"
export GITHUB_TOKEN="ghp_xxxx"
- Go to urlscan.io → sign up → Account → API Key
- Free tier: 100 searches/hour
- Set in scope.yaml:
Or:
channels: api_keys: urlscan: "xxxx"
export URLSCAN_API_KEY="xxxx"
- Go to shodan.io → sign up → Account → API Key
- Requires paid plan for full query access (free tier very limited)
- Set in scope.yaml:
Or:
channels: api_keys: shodan: "xxxx"
export SHODAN_API_KEY="xxxx"
| Variable | Channel | Notes |
|---|---|---|
GOOGLE_CSE_API_KEY + GOOGLE_CSE_CX |
C1 Google | Both required together |
BRAVE_SEARCH_KEY |
C1 Brave | |
SERPAPI_KEY |
C1 SerpAPI | |
GITHUB_TOKEN |
C4 GitHub | Strongly recommended |
URLSCAN_API_KEY |
C3 urlscan | |
SHODAN_API_KEY |
C7 Shodan |
Minimum viable setup (no keys): DDG (no key) + Wayback CDX (no key) run automatically. Expect ~20–40% coverage vs full config.
Recommended minimum: GITHUB_TOKEN + one search engine key (SerpAPI or Google CSE).
Copy ai_leak_recon/config/own_assets_template.yaml to own_assets.yaml (git-ignored). Populate with your organization's identifiers.
Findings from your own assets get:
ownership: own- 2× priority multiplier
- Immediate escalation in report
# own_assets.yaml (never commit this)
organization: "Example Corp"
domains: [example.com]
aws_account_ids: ["123456789012"]
github_orgs: [examplecorp]
own_key_prefixes:
- "AKIA123456" # first 12 chars of your AWS AKIA keys| # | Channel | Key Source | Notes |
|---|---|---|---|
| C1 | Search engines | Google CSE, Brave, DDG, SerpAPI, Bing | Monthly date-slicing for 10-30× coverage |
| C2 | Wayback CDX | archive.org | Critical — finds de-indexed AI share links |
| C2 | Common Crawl | commoncrawl.org | 5 recent indexes; hits flagged as "in LLM training data" |
| C3 | urlscan.io | urlscan.io API | Pre-built ES queries for 34 AI platforms |
| C4 | GitHub code | GitHub API | MCP configs, .cursor/mcp.json, conversations.json exports |
| C4 | File-sharing | via C1 dorks | Drive, Dropbox, Notion, Rentry, Pastebin, Scribd, Zenodo, OSF, Mega… |
| C5 | Reddit JSON | Thread search for shared AI links | |
| C6 | AI platforms | platform dorks | Per-platform site: dorks for 34 platforms |
| C7 | Shodan | Shodan API | Self-hosted AI infra: Ollama, Langflow, Qdrant, Flowise, Open WebUI |
| C8 | Observability | synthetic hits | Langfuse public project traces |
After each generation, the tool extracts new seed terms from discovered documents:
- JIRA-style project codes (
PROJ-1234) - Internal hostnames, subdomains
- Product codenames from text
These feed back as additional seed terms for the next query generation.
107 rules across 13 categories with structural validators:
| Category | Count | Examples | Validators |
|---|---|---|---|
| LLM Providers | 21 | OpenAI sk-proj-, Anthropic sk-ant-api03-, Google AIza, Groq gsk_, xAI xai-, ElevenLabs sk_, Fireworks fw_, NVIDIA nvapi-, Cerebras csk-, Voyage pa- |
entropy |
| Cloud AWS | 4 | AKIA/ASIA access key, secret key, session token, S3 presigned URL | AWS account ID from base32; presigned expiry check |
| Cloud GCP | — | Service account JSON, GCP API key | — |
| Cloud Azure | — | Client secret, connection string | — |
| BaaS | 5 | Supabase service_role JWT (CRITICAL, bypasses RLS), Firebase admin SDK |
JWT decode + role claim check |
| DevOps / CI | 9 | GitHub PAT ghp_/ghs_, GitLab glpat-, npm npm_, PyPI pypi-AgEI |
GitHub CRC32 checksum |
| Crypto Keys | 8 | RSA/EC/OpenSSH private key, BIP39 seed phrase, ETH private key, TOTP otpauth:// |
PEM parse via cryptography |
| AI Tooling | 11 | MCP server config, Cursor .cursor/mcp.json, LangSmith ls__, Langfuse sk-lf-, W&B, Helicone sk-helicone-, LlamaIndex llx-, Dify app-, Windsurf, Continue.dev |
— |
| SaaS | 18 | Slack xoxb-, Stripe sk_live_ (CRITICAL), Discord, Telegram, SendGrid, Pinecone, Arize/Phoenix ari- |
Stripe live/test detection; Slack format |
| Databases | — | Postgres/MySQL/MongoDB connection strings with embedded passwords | — |
| Infra / K8s | — | Kubernetes service account tokens, kubeconfig | — |
| PII (Global) | — | Credit card (Luhn), IBAN, SSN | Luhn checksum |
| PII (Vietnam) | 6 | CCCD 12-digit, CMND 9-digit, VN phone, MST, BHXH, passport | Province code check |
Public-by-design allowlist — demoted to info, excluded from critical/high counts:
- Firebase web API key (
AIza…used as anonymous/public) - Supabase anon JWT
- Stripe publishable key (
pk_live_,pk_test_) - Algolia search-only API key
Secrets hidden through encoding are caught by a BFS decoder (depth ≤ 4):
- JSON unescape → HTML unescape → URL decode (2-level) → base64 / URL-safe b64 → hex → gzip-after-b64 → data URIs
\n-escaped PEM keys restored before scanning (common in JSON-serialized configs)
| Format | Purpose |
|---|---|
report.md |
Human-readable Markdown; coverage-incomplete banner; per-finding remediation steps with vendor rotation URLs |
report.html |
Self-contained single-file dashboard; dark/light theme; sortable findings table; expand-to-JSON detail rows |
report.json |
Full structured report for programmatic consumption |
report.jsonl |
One finding per line for SIEM streaming |
report.sarif |
SARIF 2.1.0 for GitHub Advanced Security / CI gate integration |
Coverage honesty: if any channel was quota-exhausted, rate-limited, or truncated, a mandatory warning banner appears at the top of every report format. Silent truncation = false "clean" result.
Without --authorized, the tool makes zero outbound fetch requests. Discovery builds a hits.jsonl list only. This allows safe scope review before any fetching occurs.
The fetcher validates every URL — including every hop in a redirect chain — before following:
- Blocks RFC 1918 (10.x, 172.16-31.x, 192.168.x), loopback, link-local
- Blocks cloud metadata endpoints (169.254.169.254, metadata.google.internal)
- Validates DNS resolution result, not just the URL string
- Re-validates after each redirect hop (not just the final destination)
Secrets are never stored in plaintext:
match_redacted:sk-pr…[redacted]…CDEF(4 chars prefix + 4 chars suffix)secret_hash: SHA-256 truncated to 32 chars for deduplication across runs- Full plaintext only available with explicit
--show-secrets(not implemented by default)
Respected by default. Bypassed only with --authorized --force.
This tool contains no credential validation or API liveness check. Liveness is handled upstream in the service that invokes this tool. A "found key" is not the same as "valid key."
# Install with dev dependencies
pip install -e ".[ocr,js]"
pip install pytest pytest-asyncio
# Run tests
pytest tests/ -v
# Key test assertions
# tests/test_ssrf.py — 169.254.169.254 blocked after redirect
# tests/test_redact.py — full secret never appears in findings
# tests/test_scanner.py — each fixture → correct detection + redaction
# tests/test_decode.py — nested base64, JSON-escaped PEM decoded
# tests/test_entropy.py — placeholders rejected, real keys pass
# tests/test_validators.py — Luhn, JWT, Stripe, presigned URL validators- Does not validate or exploit found credentials
- Does not bypass authentication or CAPTCHA
- Does not store full third-party secret values
- Does not perform brute-force or enumeration against AI platform APIs
- Does not automate account takeover
This tool is designed for authorized security assessments, bug bounty programs, and red team engagements within a signed scope agreement.
Proprietary. Authorized use only.