Skip to main content

demo

🕷️🔥 PolyglotBugHunter-X

Bro, imagine an AI that can see, hear, and read a website while hunting bugs at the same time. Yeah, that's PolyglotBugHunter-X.

HF Space HF Model HF Dataset GitHub License MIT Python 3.11+


🚀 Fire this up in one click

🎨 Try it live on the Space

no install · no account · no API key · works on a free CPU tier

Press 🚀 Scan Example and it boots a deliberately vulnerable app on 127.0.0.1, attacks it across all three modalities, and renders a full report in about five seconds: SQLi, IDOR, SSTI, XSS, SSRF, open redirect, weak cookies, exposed .env, audio polyglots and a visual prompt-injection canary — each with a CVSS v3.1 score and evidence you can re-check.

Nothing leaves the container. Nobody gets an angry email. It always works.

To scan a target you actually own, use the CLI — a browser page cannot make server requests, so real targets need a real process:

git clone https://github.com/skamy64-ux/polyglot-bughunter-x && cd polyglot-bughunter-x
./run.py scan https://staging.yourcompany.com --i-own-this

🤔 What is this?

A multimodal agent for automated web security testing — for systems you own or have written permission to test.

Modality What it does
⌨️ Text Reflected XSS, SQLi (boolean/error/UNION differentials), command injection, SSTI, path traversal, SSRF, open redirect, NoSQL/LDAP, prompt injection, IDOR, CSRF, race conditions, DOM-XSS sinks
🖼️ Image Screenshot diffing with hotspot boxes, EXIF/GPS privacy audit, alt-text prompt injection, generated near-invisible-text canary
🎙️ Audio RIFF/HTML/ZIP polyglots, silence + decode-bomb checks, spectrogram injection, tone-encoded instructions, STT-pipeline prompt injection
👁️ Passive CSP/HSTS/nosniff/XFO, cookie flags, CORS reflection with credentials, mixed content, TLS, exposed .env/.git/wp-config//actuator/env, fingerprinting, debug leaks

43 payloads across 8 bug classes — 24 of them polyglot. A polyglot payload is a single string that is simultaneously plausible HTML, JS, SQL and shell, so it escapes whatever context your app dropped it into. That's the house style.

Why not just call a transformer?

Because a scanner that hallucinates a finding is worse than no scanner.

Every verdict here comes from a reproducible comparison — a captured baseline response versus an injected one — and every finding ships the exact request, response snippet and signal that produced it. {{7*7}} coming back as 49 is proof. "This looks exploitable" is labelled needs-manual-confirm and says so.

Scoring is real CVSS v3.1 base vectors, cross-checked against RedHat's cvss library over 5000 random vectors: 0 mismatches.

🌍 Twelve languages

🇬🇧 EN · 🇨🇳 中文 · 🇯🇵 日本語 · 🇰🇷 한국어 · 🇮🇩 ID · 🇪🇸 ES · 🇸🇦 AR · 🇷🇺 RU · 🇩🇪 DE · 🇫🇷 FR · 🇵🇹 PT · 🇮🇳 HI

160 keys each, 100% coverage, no blanks. Auto-detected from navigator.language; a dropdown re-renders the entire UI and the report. Arabic gets proper RTL.

Translations live in /locales/ — English is the master, the rest are translations
locales/
├── en.json   160 keys  ← MASTER
├── zh.json   160 keys  中文
├── ja.json   160 keys  日本語
├── ko.json   160 keys  한국어
├── id.json   160 keys  Bahasa Indonesia
├── es.json   160 keys  Español
├── ar.json   160 keys  العربية  (RTL)
├── ru.json   160 keys  Русский
├── de.json   160 keys  Deutsch
├── fr.json   160 keys  Français
├── pt.json   160 keys  Português
└── hi.json   160 keys  हिन्दी

Missing keys fall back to English rather than showing raw snake_case to a user.

⚡ Quick start

Install

# from a release tag. works today, needs no account and no token.
pip install "polyglot-bug-hunter-x @ git+https://github.com/skamy64-ux/polyglot-bughunter-x@v1.0.1"

# from a clone
git clone https://github.com/skamy64-ux/polyglot-bughunter-x
cd polyglot-bughunter-x
pip install .

pip install polyglot-bug-hunter-x from PyPI is not available yet. The publishing workflow is in place and every gate passes; the remaining step is a one-time browser confirmation on pypi.org, which needs an account.

The tag is a tag on purpose. Installing from a branch means a notebook or a script you published last month can start running different code without any diff to review.

The scanning core is stdlib-only. pip install pulls in nothing. Every real dependency is an extra, and all of them degrade gracefully:

pip install "polyglot-bug-hunter-x[http]"      # requests, nicer TLS handling
pip install "polyglot-bug-hunter-x[image]"     # pillow, EXIF and pixel analysis
pip install "polyglot-bug-hunter-x[browser]"   # playwright, screenshot proof
pip install "polyglot-bug-hunter-x[duckdb]"    # Parquet round-trip
pip install "polyglot-bug-hunter-x[all]"
pip install "polyglot-bug-hunter-x[space]"     # gradio, only for the 4-tab UI

Nothing installed at all

git clone https://github.com/skamy64-ux/polyglot-bughunter-x
cd polyglot-bughunter-x
./run.py                # menu
./run.py demo           # boots its own vulnerable target on localhost, scans it

No venv, no pip install, no dependencies. run.py puts src/ on the path itself. Pick 4 from the menu to get the same UI as the live Static Space on http://127.0.0.1:8000.

As a command

pip install -e .        # installs the `pbhx` command, still zero dependencies

pbhx demo                          # offline scan + md/html/json/sarif report
pbhx scan https://staging.example --i-own-this --active
pbhx serve                         # the 4-tab Gradio UI on 127.0.0.1:7860
pbhx canary -o canary.png          # visual prompt-injection canary
pbhx audio -o suite/               # 8 adversarial audio files
pbhx payload "{{7*7}}"             # explain a payload against every detector
pbhx targets                       # hosts that exist to be scanned
pbhx capabilities                  # which optional extras are installed

Exit codes are meant for CI: 0 clean, 1 findings at or above --fail-on, 2 refused or bad usage.

pbhx demo --fail-on high          # non-zero if the demo finds anything high+
pbhx scan "$URL" --i-own-this --json | jq '.findings[] | select(.severity=="critical")'

--json puts only JSON on stdout, so it pipes cleanly. The human report goes to stderr alongside it.

As a library

from polyglot_bug_hunter import Hunter, ScanPolicy

# 1. the demo. boots its own vulnerable target on localhost. no auth needed.
report = Hunter.demo()
print(report.risk_score, report.counts())

# 2. your own infrastructure. the authorization tick is mandatory.
policy = ScanPolicy(
    authorization_confirmed=True,
    authorization_note="staging.mycompany.com, ticket SEC-1234",
    active_probing=True,
)
report = Hunter(policy, modes=["text", "image", "audio"], lang="auto").scan(
    "https://staging.mycompany.com"
)

for f in report.sorted_findings()[:5]:
    print(f.severity.value, f.cvss.score, f.title)

Run it locally:

python -m polyglot_bug_hunter.demo_target 8787   # poke the vulnerable app yourself
python tools/build_dataset.py                    # regenerate the HF dataset
python tools/build_space.py                      # assemble the Space folder
python -m pytest -q                              # the test suite

Why the CLI exists

The Static Space on Hugging Face is a browser page, so it cannot scan a real target: there is no server to make requests from. The demo, payload lab, canary and audio suite all work there because they run entirely in the tab. Anything that touches a real host needs the CLI, which is why it is the primary interface rather than a wrapper around the Space.

🗂️ Project layout

polyglot-bughunter-x/
├── src/polyglot_bug_hunter/
│   ├── cli.py            the `pbhx` command: demo, scan, serve, payload, canary, audio
│   ├── safety.py         5 gates: auth, network range, scheme, payload, rate limit
│   ├── hunter.py         the orchestrator. one class, .scan(url) -> report
│   ├── demo_target.py    an intentionally vulnerable app we host ourselves
│   ├── models.py         Finding / Evidence / Asset / ScanReport + CVSS v3.1 math
│   ├── payloads.py       43 payloads, param-name routing, safety-classified
│   ├── net.py            stdlib HTTP client + `replace_param` (see below)
│   ├── htmlx.py          HTML parsing, link/param ranking, DOM-XSS sink audit
│   ├── config.py         ScanConfig / ModalityConfig
│   ├── i18n.py           12 locales, auto-detect, RTL
│   ├── scanner/
│   │   ├── text.py       differential injection analysis
│   │   ├── passive.py    headers, cookies, CORS, TLS, exposure
│   │   ├── image.py      PNG encode/decode, pixel diff, visual canary
│   │   ├── audio.py      wav synthesis, spectrogram attack, container audit
│   │   ├── access.py     IDOR walk
│   │   └── race.py       TOCTOU probes + CSRF assessment
│   ├── report/render.py  markdown, self-contained HTML, JSON, SARIF
│   └── storage/db.py     DuckDB or sqlite3, same API
├── hf_space/         → upload as a Space (4 tabs, 12 languages, CPU-only)
├── hf_model/         → upload as a Model (config.json + payload vocabulary)
├── hf_dataset/       → upload as a Dataset (JSONL + Parquet)
├── hf_static_space/  → upload as a Static Space (the free one, no PRO needed)
├── run.py            → one-command launcher, no install required
├── notebooks/hf_demo.ipynb
├── locales/          → 12 JSON locale files
├── tools/            → build_space.py, build_dataset.py, build_notebook.py
└── tests/

🛡️ The safety model

This tool fires requests at someone else's machine, so the first import in the package is safety.py and nothing touches the network until a ScanPolicy says yes.

Gate Behaviour
Authorization authorization_confirmed=False → AuthorizationError before any byte moves
Network range loopback, RFC1918, link-local (so cloud metadata at 169.254.169.254), CGNAT, multicast, reserved → refused by default. DNS resolved by us, so DNS-rebinding can't sneak past
Scheme file://, gopher://, javascript: → refused
Payload DROP TABLE, DELETE FROM, system(, sleep(, xp_cmdshell, rm -rf → blocked at the boundary, for community payloads too
Methods POST/PUT/PATCH/DELETE refused unless explicitly enabled
Rate limit mandatory, 5–120 req/min, plus a hard total request budget

Time-based blind SQLi is deliberately not implemented — sleep() is on the blocklist, and a scanner that can hang a database is a scanner that can be used as a DoS tool.

⚠️ Only test systems you own or have explicit written permission to test. Unauthorised scanning is illegal in most jurisdictions (CFAA 18 U.S.C. §1030, UK CMA 1990 s.1, id. UU ITEA Pasal 35-51). You are 100% responsible for every target you point this at.

📦 The Hugging Face family

Repo What it is
🎨 polyglot-bughunter-x-static The Space. Static, 4 tabs, 12 languages, 100% in-browser, free tier, no cold start.
🐍 polyglot-bughunter-x The full Gradio Space. Needs HF PRO (HF blocks new Gradio Spaces on the free tier).
🧠 polyglot-bughunter-x Model repo: config.json, 43-payload vocabulary, CVSS + scoring profile.
🗃️ polyglot-bug-patterns Dataset: payloads + real detector output, JSONL and Parquet.
📓 hf_demo.ipynb Runnable walkthrough. Works on HF Jupyter, Colab, Kaggle.
💻 GitHub Source, issues, PRs.

Kaggle

Repo What it is
📊 polyglot-bug-patterns The dataset: 11 files, JSONL and Parquet, public.
📓 polyglot-bug-patterns-demo A Kernel that runs the whole detector end to end on Kaggle's free CPU.

The Kernel inlines the package as a base64 wheel inside the notebook. That is not a stylistic choice: Kaggle uploads the notebook and nothing else, so a pip install ./package fails with File './package' does not exist and a side-by-side .whl leaves glob empty. Rebuild and push with:

python tools/build_kaggle.py            # dataset
python tools/build_kaggle_kernel.py     # kernel (builds a wheel, inlines it)
kaggle datasets version -p kaggle_dataset
kaggle kernels push -p kaggle_kernel

Auth comes from ~/.kaggle/access_token (one line, chmod 600) or ~/.kaggle/kaggle.json or KAGGLE_API_TOKEN — the CLI checks all three, so no environment variable is needed once the file exists.

from datasets import load_dataset
ds = load_dataset("Kicaulah/polyglot-bug-patterns", "payloads")
polyglots = ds["train"].filter(lambda r: r["polyglot"])
print(len(polyglots), "polyglot payloads")

🚀 Releasing

Five published surfaces, and a release that lands on four of them is worse than no release script, because it looks done. One command runs the gate, rebuilds every artifact, checks for drift, pushes, then re-reads every destination back:

python tools/release.py --all          # check, build, publish, verify
python tools/release.py --check        # the gate only, nothing uploaded
python tools/release.py --build        # rebuild every artifact
python tools/release.py --verify       # re-read all six URLs
python tools/release.py --publish --only kaggle-dataset,kernel
python tools/release.py --all --dry-run
Guard What it stops
The gate Publishing on a red test suite. It runs ruff, pytest, JS/Python parity, app-level and browser checks.
Drift check A version bump reaching four surfaces out of five. Compares the wheel shipped in the Kaggle dataset against the one inlined in the kernel, and both against pyproject.
Dirty-tree refusal Uploading artifacts that do not match the commit. This is only meaningful because the build is reproducible.
Anonymous verify A broken publish reading as a success. With a token loaded both hosts answer 200 for private repos, so the verifier uses no credential at all.

Two things that verification taught, both of which report a healthy thing as broken: Kaggle does not implement HEAD — it 404s every HEAD request whether or not the repo exists — and it 404s any User-Agent that does not look like a browser. GET with a browser agent is the only combination that tells the truth.

🧱 Engineering notes

A few decisions worth knowing about, because they're the difference between a tool and a demo:

  • The core is stdlib-only. No requests, no bs4, no pillow, no numpy. A Space that needs a 400 MB wheel download loses every visitor to a cold start. Optional deps are adapters that return empty results instead of raising.
  • replace_param() rebuilds the URL. Appending to a URL that already has the parameter produces /x?id=1&id=2, and most servers honour the first one — so every differential test silently comes back "clean". This one function is the difference between a working scanner and a scanner that finds nothing.
  • The PNG codec is hand-rolled (zlib + struct). 120 lines to generate a canary with hidden pixels and to decode a screenshot for the pixel diff. No Pillow on the critical path.
  • Findings dedupe by scope. A missing CSP is one finding with "affects 8 pages", not eight identical rows. Injection findings key on path + parameter, so ?q=a and ?q=b are the same bug.
  • The demo target simulates, it doesn't execute. It reproduces the four responses a real injectable app gives. Findings against it prove the detector.

📝 Changelog

v1.0.0 — first flight 🚀

  • 4-tab Gradio Space, 12 languages, 100% CPU, one-click offline demo.
  • 43 payloads / 8 classes, 24 polyglot, param-name routing.
  • CVSS v3.1 validated against an independent implementation (5000 vectors, 0 mismatches).
  • Markdown / HTML / JSON / SARIF reports, DuckDB-or-sqlite history.
  • findings, scans, class_index dataset configs in JSONL + Parquet.
  • 5-gate safety model, destructive-payload blocklist, mandatory rate limiting.

MIT licensed · 🕷️ PolyglotBugHunter-X · authorized security testing only

Metadata

Release files for polyglot-bug-hunter-x 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for polyglot-bug-hunter-x 1.0.1
File Size Uploaded
polyglot_bug_hunter_x-1.0.1.tar.gz 142.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for polyglot-bug-hunter-x 1.0.1
File Interpreter ABI Platform
polyglot_bug_hunter_x-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 253.5 kB

Release files / polyglot_bug_hunter_x-1.0.1.tar.gz

Download URL polyglot_bug_hunter_x-1.0.1.tar.gz
Size 142.5 kB
Tags Source
SHA-256 checksum
How to use checksums
17c1988edbb27cd11308fe257dc6bbee46110177d5631811d25fe89217c9e233
BLAKE2b-256 checksum
How to use checksums
6e0d97ee4b8ca4e13415e36fccbb469ccc039ef48bbf1e050ecb72aa796076a5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / polyglot_bug_hunter_x-1.0.1-py3-none-any.whl

Download URL polyglot_bug_hunter_x-1.0.1-py3-none-any.whl
Size 110.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0ada469e118fa5ea0ba14a5a8a5407bac3dbea8376543f144c0cb36b07884dad
BLAKE2b-256 checksum
How to use checksums
461ef62a8fc69e3ba510a560aaeb424a8a1fb66ebc4ac4e3ac40f3f7e9a5a483
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page