Skip to content

Repository files navigation

ai-leak-recon

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.


The Problem

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.


Architecture

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

Live Results

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:

Discovery output

Scan phase — 500 docs scanned, 20 findings, reports written in MD / JSON / HTML:

Scan output


Installation

pip install -e .

# Optional extras
pip install -e ".[ocr]"    # image scanning via RapidOCR
pip install -e ".[fast]"   # hyperscan engine for large corpora

Requirements: Python ≥ 3.11


Quick Start

Simple: domain or org name as argument

# 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.tlddomains; anything else → orgs + keywords.

Two-phase workflow

# 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

Advanced: scope file (complex configs, multiple targets, API keys)

# 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 --authorized

CLI Reference

run — all-in-one (primary command)

ai-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]

discover — enumeration only, no fetch

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]

scan — fetch + scan from existing hits file

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]

Configuration

Quickstart: copy the scope template

cp scope.template.yaml scope.yaml
# Edit scope.yaml — fill in targets and API keys

scope.yaml is git-ignored. Never commit it.


scope.yaml structure

# ── 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"

API Keys — where to get them

C1 Google CSE (best coverage for AI share links)

  1. Go to console.cloud.google.comAPIs & ServicesCredentialsCreate CredentialsAPI Key
  2. Enable Custom Search API in the API Library
  3. Go to programmablesearchengine.google.comAdd → create a new engine
  4. 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
    
  5. Copy the Search engine ID (cx) from the engine overview page
  6. Set in scope.yaml:
    engines:
      google_cse:
        api_key: "AIzaSy..."
        cx: "xxxxxxxxxx"
    Or via env:
    export GOOGLE_CSE_API_KEY="AIzaSy..."
    export GOOGLE_CSE_CX="xxxxxxxxxx"

Note: Free tier = 100 queries/day. Pricing — $5 per 1000 queries beyond that.


C1 Brave Search API

  1. Go to api.search.brave.com → sign up → DashboardAPI KeysCreate Key
  2. Choose plan: Free (2000 queries/month) or Base ($3/1000 queries)
  3. Set in scope.yaml:
    engines:
      brave:
        api_key: "BSA..."
    Or: export BRAVE_SEARCH_KEY="BSA..."

C1 SerpAPI (Google backend, high-volume)

  1. Go to serpapi.com → sign up → Dashboard → copy API Key
  2. Free tier: 100 searches/month. Paid from $50/month.
  3. Set in scope.yaml:
    engines:
      serpapi:
        api_key: "xxxx"
    Or: export SERPAPI_KEY="xxxx"

SerpAPI uses Google as backend — highest-quality results. Good fallback when Google CSE quota runs out.


C4 GitHub Token (strongly recommended)

Without a token, GitHub API is limited to 60 requests/hour. With token: 5000/hour.

  1. Go to github.com/settings/tokensGenerate new token (classic)
  2. Select scope: public_repo only (read-only, sufficient)
  3. Set in scope.yaml:
    channels:
      api_keys:
        github: "ghp_xxxx"
    Or: export GITHUB_TOKEN="ghp_xxxx"

C3 urlscan.io

  1. Go to urlscan.io → sign up → AccountAPI Key
  2. Free tier: 100 searches/hour
  3. Set in scope.yaml:
    channels:
      api_keys:
        urlscan: "xxxx"
    Or: export URLSCAN_API_KEY="xxxx"

C7 Shodan (self-hosted AI infra discovery)

  1. Go to shodan.io → sign up → AccountAPI Key
  2. Requires paid plan for full query access (free tier very limited)
  3. Set in scope.yaml:
    channels:
      api_keys:
        shodan: "xxxx"
    Or: export SHODAN_API_KEY="xxxx"

Environment Variables (alternative to scope.yaml)

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).


Ownership Attribution

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

Discovery Channels

# 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 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

Pivot Loop

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.


Secret Detection Rules

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

Decoder Cascade

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)

Output Formats

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.


Security Design

Authorization Gate

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.

SSRF Protection

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)

Redaction

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)

robots.txt

Respected by default. Bypassed only with --authorized --force.

No Liveness Probe

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."


Development

# 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

Non-Goals

  • 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.


License

Proprietary. Authorized use only.

About

Recon tool to discover leaked secrets and sensitive data exposed via AI platforms (ChatGPT, Claude, Gemini, Copilot, DeepSeek, and 45+ more). Searches Wayback CDX, urlscan, GitHub, Shodan, and 8 discovery channels. 107 detection rules. Authorized bug bounty / red team use.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages