Async multi-provider API key security reconnaissance. Find out what an exposed key can actually do: exfiltrate data, burn billing quota, manipulate auth services, or move laterally through cloud infrastructure.
Features Β· Quick start Β· How it works Β· Safety Β· CLI Β· Exit codes
| Capability | Detail | |
|---|---|---|
| π | 14 providers | Google Cloud, Azure, OpenAI, AWS, GitHub, Anthropic, HuggingFace, Stripe, Twilio, Slack, Discord, SendGrid, GitLab, DigitalOcean |
| β‘ | Async & concurrent | asyncio + aiohttp, bounded concurrency and request rate as separate limits |
| π·οΈ | Honest outcomes | ACCESSIBLE / RESTRICTED / UNAUTHORIZED / NOT_FOUND / BAD_REQUEST / INCONCLUSIVE / ERROR / SKIPPED β 200 alone is never called a confirmed vulnerability |
| π | 5 report formats | Console, JSON, HTML dashboard, CSV, SARIF 2.1.0 (GitHub Code Scanning ready) |
| π | Key auto-detection | Regex-based provider identification (probe-based detection disabled for safety) |
| π§© | Plugin auto-discovery | Drop a new scanners/<provider>_scanner.py in β it loads itself |
| π | Credential masking | Prefix-only masking everywhere (console, reports, filenames) β suffixes never shown |
| ποΈ | Encrypted vault | Optional per-run AES-256-GCM credential vault with operator-only ACL |
| π¦ | Batch + extract | Multi-key scans with manifest reconciliation, and codebase key extraction |
| π | Notifications | Slack & Discord webhooks with structured delivery outcomes, redacted URLs |
Important
Breaking change (B04, Nov 2026): status_text values changed β "VULNERABLE" β "ACCESSIBLE",
network error severity CRITICAL β ERROR. Reports and notifiers use the outcome field as the
machine contract. Active checks run by default; use --no-active-checks to disable.
Requires Python 3.10+.
# install
pip install -e .
# scan a single key (provider auto-detected)
python apiKeyScan.py scan -k AIzaSy...
# explicit provider + all report formats
python apiKeyScan.py scan -p github -k ghp_xxxx --all-formats
# batch scan from a keyfile
python apiKeyScan.py scan --keyfile keys.txt --html --sarif
# extract leaked keys from a codebase
python apiKeyScan.py extract ./repo/βΈ More examples (AWS pair, notifications, config presets, vault)
# AWS requires the secret pair
python apiKeyScan.py scan -p aws -k AKIAXXXXXX --secret YYYYYYYY --all-formats
# notifications
python apiKeyScan.py scan -p stripe -k sk_live_... --notify-slack $URL
# YAML config preset
python apiKeyScan.py scan --config preset.yaml
# encrypted credential vault (interactive terminal only)
python apiKeyScan.py scan -p google -k AIzaSy... --vault-dir ./vaults
# open a stored vault (no provider contact)
python apiKeyScan.py vault-open <vault.akv>
# list available checks
python apiKeyScan.py list-checks --provider googleflowchart LR
A["π Input<br/>key / keyfile / codebase"] --> B["𧬠Detect<br/>regex patterns<br/><i>probe disabled</i>"]
B --> C["π‘οΈ Prerequisite<br/>validation<br/><i>no request on bad input</i>"]
C --> D["π‘ Checks<br/>async Β· rate-limited<br/>retry-safe"]
D --> E{"Outcome"}
E -->|"200 + data"| F["ACCESSIBLE"]
E -->|"200 + error / 403"| G["RESTRICTED"]
E -->|"401"| H["UNAUTHORIZED"]
E -->|"empty / malformed"| I["INCONCLUSIVE"]
E -->|"network"| J["ERROR"]
D -.->|"--no-active-checks"| K["SKIPPED<br/><i>with reason</i>"]
F & G & H & I & J & K --> L["π§Ύ Sanitized evidence<br/>prefix-masked Β· truncation marked"]
L --> M["π Reports<br/>JSON Β· HTML Β· CSV Β· SARIF + manifest"]
Every execution ends in exactly one recorded outcome per check β planned = completed + failed + skipped,
reconciled in the batch manifest. Evidence is sanitized before it touches any output path.
Kapan: sebelum scan dengan credential Β· Siapa: operator.
- Active checks run by default. This tool exists to validate capabilities, not merely list them.
Mutating/billable checks (account creation, message/push send, generation, mail send) execute when
credentials are provided β that is the point. Use
--no-active-checksto disable them; they are recorded asSKIPPEDwith an explicit reason, never silently dropped. --active-checksremains accepted as a no-op for backward compatibility.--force-probeis deprecated and rejected (exit code 2) β probe-based provider detection sends credentials to provider endpoints and stays disabled. Specify the provider with-pinstead.- Rate limiting is enforced per attempt, including retries. POST (non-idempotent) is never retried after an ambiguous result like a timeout.
Warning
Running scans with real keys creates real effects: accounts may be created, messages sent, and billing consumed. Scan only keys you are authorized to test.
Kapan: sebelum scan yang sudah diotorisasi Β· Siapa: operator.
Masking. Keys and tokens β₯ 16 characters show as their first 8 characters + ....;
shorter values, passwords and Basic credentials are hidden completely. No suffix is ever shown.
Hosts, paths, field names, statuses and reasons stay readable. This is not a PII filter.
Kapan: otomatis sebelum deteksi/probe/scan dalam mode vault Β· Siapa: runtime.
Vault. Opt in with --vault-dir <existing-parent> to store the run's credentials in a single
encrypted .akv file (AES-256-GCM, scrypt N=131072/r=8/p=1, random salt+nonce per checkpoint,
16 MiB cap). The password is generated per run, shown once on the controlling terminal, and
saved by you in a separate password manager. Without a controlling terminal or operator-only ACL
the vault fails closed. Reports carry opaque vault_run/vault_record references only.
Kapan: saat operator perlu membuka hasil tersimpan Β· Siapa: operator.
Open locally with python apiKeyScan.py vault-open <vault.akv> β no-echo password input, output only
to the controlling terminal, zero provider contact. A lost password cannot be recovered; keep backups
and passwords separately.
Kapan: saat konfigurasi scan Β· Siapa: operator.
| Option | Description |
|---|---|
--no-active-checks |
Disable mutating/billable checks; recorded as SKIPPED with reason |
--no-color |
Strip ANSI from every output path |
--quiet |
Suppress routine output (scan errors and exit codes still reported) |
--verbose |
Show URL and reason per result |
--severity-filter ERROR |
Display-only filter β full results are always stored |
--overwrite |
Overwrite existing report files (default: refuse) |
--concurrency N |
Max concurrent checks |
--connect-timeout S / --read-timeout S |
Transport timeouts in seconds |
--account-sid SID |
Twilio account SID (identifier, not a secret) |
--config FILE |
YAML preset β precedence: defaults < config < explicit CLI flags |
--vault-dir DIR |
Enable the encrypted credential vault |
| Code | scan |
extract |
|---|---|---|
β
0 |
Completed β no accessible findings, no errors | No candidates found (inspection completeness message shown) |
π‘ 1 |
At least one ACCESSIBLE finding |
Candidate keys found |
π΄ 2 |
Error: input, scan failure, partial failure, all-unauthorized | Inspection failure or --scan rejected |
Note
A scan where zero checks executed, all requests failed, or every result is UNAUTHORIZED
never reports clean β it exits 2. Error wins over findings.
apiKeyScan/
βββ apiKeyScan.py # async entry point Β· subcommands scan/extract/list-checks/vault-open
βββ core/
β βββ base_scanner.py # BaseScanner Β· ScanResult outcome contract Β· evidence pipeline
β βββ config.py # YAML schema + validation Β· default < config < CLI precedence
β βββ key_detector.py # regex detection (offline) + safe key extraction
β βββ notifier.py # Slack/Discord Β· structured NotificationResult Β· redacted URLs
β βββ plugin_loader.py # dynamic scanner discovery Β· failures surfaced, never silent
β βββ redaction.py # shared sanitizer Β· prefix masking Β· cookie/session aliases
β βββ report_engine.py # console + JSON/HTML/CSV/SARIF + batch manifest
β βββ transport.py # rate limiter Β· retry/backoff Β· Retry-After handling
β βββ vault.py # encrypted per-run credential vault (AES-256-GCM)
βββ scanners/ # 14 providers β google 24 checks Β· openai 9 Β· aws 9 Β· github 9 Β· β¦
Adding a scanner: create scanners/<provider>_scanner.py inheriting BaseScanner, define
PROVIDER_NAME and CHECKS, implement async def scan(). The plugin loader finds it automatically.
Kapan: saat eksekusi terisolasi via container Β· Siapa: operator. Status: Dockerfile dan Compose disediakan; build/runtime belum dieksekusi dalam sesi pengembangan ini β acceptance canary build dan verifikasi permission volume masih terbuka.
docker-compose run apikeyscan scan -p google -k AIzaSy...python:3.10-slim, non-root user (UID 1000), read-only rootfs + tmpfs /tmp, reports to the named
volume apikeyscan-reports. Build context excludes credentials, reports, VCS and caches (.dockerignore).
pytest # 669 tests Β· network guard active by default
pytest --cov=core --cov=scanners --cov=apiKeyScan --cov-branch # coverage (baseline 78%)
ruff check . && ruff format --check . # lint + format (ruff==0.8.4, pinned)
mypy core/ scanners/ apiKeyScan.py # types (mypy==2.4.0, pinned)The test suite cannot reach the network: tests/conftest.py blocks non-loopback sockets and DNS
before collection, and tests/test_network_guard.py locks that guard in place. All fixtures are
synthetic β they are not evidence of current provider API behavior.
βΈ Known limitations (open items)
- Docker build-context canary and Compose volume permission verification β requires a Docker-enabled session.
- Fully clean-room wheel install (no pre-existing deps) is not tested β network is blocked by design.
- 9 of 14 scanners have thin HTTP-path coverage (shared infrastructure is 88%+) β post-stabilization fixture work.
- CI matrix (3.10β3.13) is configured; older-interpreter runs are verified by the CI pipeline itself.
Educational and authorized security use only. Scan only keys and systems you are permitted to test.
MIT License Β· Built with asyncio, aiohttp, and a stubborn respect for honest evidence.