Find and assess exposed Google Gemini API keys in web assets and Android apps.
geminiHunter discovers candidate keys in JavaScript, source maps, archived assets, and APK/XAPK files. It deduplicates findings, validates access to the Gemini API, and reports source locations and observed restrictions. For forbidden keys, an optional bounded bypass engine tests request variations and records the outcome.
- Web discovery: HTML scripts, linked JavaScript, JSON manifests, framework assets, source maps, webpack chunks, and Wayback snapshots.
- Android discovery: jadx decompilation when available, with ZIP and binary-string scanning as a fallback.
- Extraction: plain keys, concatenated strings, arrays, template literals, reversed strings, hex escapes, and Base64.
- Reporting: readable terminal output, JSON, and shell-quoted curl evidence.
Use only within an authorized testing scope. This is an active scanner: API validation, bypass probes, and intelligence gathering contact Google, and some probes can generate content and consume quota. Reports and evidence can contain complete credentials. See SECURITY.md.
Requires Python 3.11+. Install directly from GitHub with pipx:
pipx install git+https://github.com/devploit/geminiHunter.git
geminihunter --versionUpdate with pipx upgrade geminihunter. Alternatively, install into a virtual environment:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install git+https://github.com/devploit/geminiHunter.gitOn Windows, activate with .venv\Scripts\Activate.ps1 in PowerShell. The module entry point is also available as python -m geminihunter.
For full APK decompilation, install jadx and make it available on PATH. On macOS: brew install jadx. Without it, ZIP and binary-string extraction still work.
Replace example.com and the local filenames below with inputs you are authorized to test.
# Discover keys in a web target
geminihunter example.com
# Check previously collected keys without exposing them in command arguments
geminihunter --key-file keys.txt
# Scan an Android app
geminihunter --apk app.apk
# Save a structured report, including an empty report if no keys are found
geminihunter example.com -o results.json# Multiple domains or full HTTP(S) URLs
geminihunter app.example.com https://example.com/app/
# One target per line; blank lines and # comments are ignored
geminihunter -f targets.txt
# Read targets from a pipe
cat targets.txt | geminihunter --json
# Import a gengar-style JSON scan
geminihunter --scan-json scan.json
# Read keys from stdin instead of targets
cat keys.txt | geminihunter -k -
# XAPK bundles and split APKs
geminihunter --apk app.xapk
geminihunter --apk base.apk --apk split_config.apk
# Combine discovery with existing keys
geminihunter --apk app.apk -f targets.txt --key-file keys.txtScan JSON accepts services objects with string url and host fields, subdomains objects with a string domain, or scan.targets as an array of strings. Services take precedence; HTTPS is preferred for duplicate hosts.
Direct keys can also be supplied with -k KEY or -k KEY1,KEY2; these values can appear in shell history and process listings. Neither direct key input nor APK input suppresses target stdin: reserve stdin for keys explicitly with -k -.
geminihunter example.com --depth 3 --no-wayback
geminihunter example.com --no-sourcemaps --no-bypass
geminihunter -f targets.txt --concurrency 5 --rate-limit 2 --delay 0.5
geminihunter example.com --timeout 20 --proxy http://127.0.0.1:8080
geminihunter example.com --proxy proxies.txt --user-agent "MyAuthorizedScanner/1.0"| Option | Default | Meaning |
|---|---|---|
--depth |
2 |
Maximum page-link depth; 0 includes the initial page and its assets |
--wayback / --no-wayback |
Enabled | Fetch archived JavaScript through Wayback |
--sourcemaps / --no-sourcemaps |
Enabled | Follow source maps |
--bypass / --no-bypass |
Enabled | Try request variations after a 403 response |
--rate-limit |
10 |
Token refill rate per second; permits an initial burst |
--delay |
0 |
Delay before each request, in seconds |
--timeout |
15 |
HTTP timeout; discovery phases use shorter caps |
--concurrency |
20 |
Concurrency setting for pipeline stages and connection pools |
--user-agent |
rotate |
Rotating user agent, or a fixed custom value |
--insecure |
Disabled | Disable TLS certificate verification |
The concurrency setting is not a strict global in-flight request limit: some stages perform nested probes. Proxies rotate when a session client is created. Retry attempts also consume rate-limit tokens; Retry-After waits are capped at 60 seconds.
Discovery can fetch linked assets on external hosts. Wayback queries expand targets to an inferred parent domain, so review that scope before enabling them. Parent-domain inference is heuristic, not a full public-suffix lookup.
# JSON on stdout; diagnostic output stays on stderr
geminihunter example.com --json
# File extension selects JSON; other extensions receive a text report
geminihunter example.com -o results.json
geminihunter example.com -o results.txt
# Include curl evidence in terminal output
geminihunter example.com --evidence
# Suppress banner and progress
geminihunter example.com -q --json--json always selects JSON, regardless of the output filename. With -o, the report goes to the file; without it, JSON goes to stdout and terminal reports go to stderr. JSON includes curl evidence automatically. Existing output files are overwritten.
For the complete option list, run geminihunter --help. Add -v for diagnostic logs; treat those logs as sensitive.
| Status | Meaning |
|---|---|
valid |
The initial model-list request returned 200 |
bypassed |
A forbidden key subsequently returned 200 with a recorded request variation |
rate_limited |
A 429 response was observed; a working generation call is not confirmed |
forbidden |
Access remains forbidden, including cases where the error reason changed |
invalid |
Validation returned 400 or 401 |
unknown |
Transport failure or an unexpected endpoint response prevented validation |
A 429 candidate from the bypass engine is rechecked before being labeled bypassed. Permission progress from a referrer restriction to SERVICE_DISABLED remains forbidden. See bypass behavior and extension points.
For valid and bypassed keys, the tool probes model listings, tuned-model listings, project identifiers, quota headers, and restrictions. These are observations, not a complete cloud-account audit:
- A successful generation request does not establish paid billing: Gemini also offers a free tier.
billing_enabledremainsnullunless a billing-disabled response is explicitly identified. - A successful request with an arbitrary referrer cannot rule out IP or API restrictions. The tool records
no_referrer_restriction_observedinstead of declaring the key unrestricted. See Google's restriction types. - Model lists reflect the returned API page; pagination is not currently followed. Tuned-model and quota information may be unavailable. A tuned-model listing does not establish access to training data.
- Some existing probes use fixed model names and API versions, which may become unavailable. A failed probe is not proof that all model access is blocked.
Top-level JSON fields include targets_scanned, sources_crawled, keys_found, per-status counts, duration_seconds, phase_timings, and results. Each result includes key, status, sources, target_domain, optional bypass details, intelligence fields, and curl_commands.
# Print statuses and source locations without printing the keys
geminihunter -f targets.txt -q --json | jq '.results[] | {status, sources}'
# Inspect permission progress
geminihunter --key-file keys.txt --json | jq '.results[] | select(.bypass) | {status, bypass: .bypass.technique, code: .bypass.bypass_status_code}'| Exit code | Meaning |
|---|---|
0 |
At least one valid, bypassed, or rate-limited key |
1 |
No such findings, or an operational error reported on stderr |
2 |
Invalid arguments, input structure, or configuration |
130 |
Interrupted by the user |
A valid JSON report is emitted even when discovery finds no keys. Use the status fields to distinguish confirmed access from rate limiting; exit code 0 alone does not establish unrestricted access.
Create .geminihunterrc or .geminihunter.toml in the working directory or home directory:
depth = 2
wayback = true
sourcemaps = true
bypass = true
rate-limit = 5.0
delay = 0.2
timeout = 20.0
concurrency = 10
user-agent = "rotate"
insecure = false
evidence = falseThe first file found is used: working directory before home, and .geminihunterrc before .geminihunter.toml. Files are not merged. Explicit CLI options override file values. Malformed TOML, unsupported settings, and invalid option values produce an error.
Archive inputs are validated before decompilation or extraction. Unsafe paths and symbolic-link entries are rejected. Each archive is limited to 10,000 entries and 512 MiB of declared uncompressed data; an APK inside an XAPK is limited to 256 MiB. Individual source files above 10 MiB are skipped. XAPK processing extracts nested APKs and reads supported metadata, without unpacking unrelated assets.
jadx runs with a 180-second timeout; unavailable or unsuccessful decompilation falls back to ZIP scanning. These limits are guardrails, not a sandbox for a third-party decompiler.
See CONTRIBUTING.md for setup, tests, and package verification. CHANGELOG.md tracks changes.
src/geminihunter/
cli.py Input, configuration, and exit codes
pipeline.py Discovery → extraction → validation → reporting
discovery/ Web, archive, source-map, and APK discovery
extraction/ Key patterns and deobfuscation
validation/ API validation and bypass orchestration
intelligence/ Post-validation probes
network/ Sessions, retries, rate limiting, transport errors
output/ Terminal, JSON, and curl evidence
Licensed under the MIT License.
