Project Lethe is a lightweight, dependency-free CLI for authorized WAF resilience and canonicalization testing. It combines deterministic payload mutation, safe HTTP discrepancy templates, single-request site profiling, and an optional AI planning/reporting layer.
Lethe is built for ethical hackers, bug bounty hunters, AppSec engineers, and students who need small, repeatable, explainable test sets for owned systems, labs, or explicitly authorized programs.
Project Lethe is not an exploitation framework, scanner, target attacker, or
payload pack. Generation commands do not send network traffic. The profile,
fingerprint, and test commands do make requests, but only to targets you
explicitly authorize through the scope guard (default-deny). Even when testing
actively, generated variants always flow through deterministic allowlisted
transformations.
Use it only where you have permission.
- Zero runtime dependencies; pure Python standard library.
- Deterministic mutation engine with provenance, tags, depth, and chained techniques.
- Default-deny authorization scope guard (hosts, wildcards, ports, CIDRs) for all active commands.
- Active testing that injects variants at an authorized target and classifies each response as blocked, passed, rate-limited, challenged, or origin error.
- Per-target oracle calibration, multi-sample baselines, and automatic bypass re-confirmation for trustworthy verdicts.
- Reflection-context detection (HTML body/attribute/script/JSON) with a
likely_exploitableflag. - Heuristic WAF/CDN fingerprinting (Cloudflare, Akamai, AWS, Imperva, Sucuri, ModSecurity, F5, and more) with technique suggestions.
- n-day knowledge pack mapping WAF bypass classes to profiles/techniques, surfaced during fingerprinting.
- Profiles for XSS, SQL-style strings, APIs, GraphQL, XML, redirects, headers, SSRF-style URLs, JSON, and generic normalization.
- Vendor-tuned profiles (
cloudflare,akamai,aws_waf,imperva,modsecurity,f5) thatfingerprintrecommends automatically. - Export variants as Nuclei templates or Burp BCheck scripts (
--format nuclei|bcheck). http-runexecutes HTTP/1.1 discrepancy templates over raw sockets against an authorized target.schemaingests OpenAPI/Swagger or GraphQL and recommends type-aware profiles/contexts/tags.proxyis a scope-gated HTTP mutation middleware: mutate live Burp/Intruder traffic in place, with optional HTTPS interception via a local CA.- Context-aware technique selection (
--context html_body|html_attr|js_string|url|sql|...). - Novel charset/normalization transforms: NFKC compatibility homoglyphs, overlong UTF-8, and inconsistent partial encoding.
- Engine-aware techniques and profiles: MySQL/MSSQL/Postgres SQLi, SSRF IP encodings, path-traversal slashes, and shell separators.
- Oracle-guided
discoverengine that evolves and minimizes novel WAF bypasses. - Import requests straight from a
curlcommand, raw HTTP, or a HAR file, marked with§INJECT§. - Local SQLite session store with
lethe history, and integrity-hashed run manifests for evidence. - Versioned (
schema_version) JSON/NDJSON output and documented exit codes. - Advanced novelty mode with bounded stacked mutations and seeded polymorphic padding.
- AI safety planner returning
allow,review, orblock. - AI report analysis for triage-ready explanations without generating new payloads.
- Built-in Gemini, OpenAI, Claude, Ollama, LM Studio, LocalAI, and OpenAI-compatible provider support.
- Single-request site profiling with redacted bearer, cookie, and custom-header auth.
- Short-lived local auth memory with TTL.
- HTTP/1.1 discrepancy templates, bounded local-lab stress templates, and HTTP/2 downgrade translation templates.
explainandanalyzecommands for report-ready evidence.- Smooth
STYX://PROJECT-LETHEterminal signature. - JSON, NDJSON, Markdown, raw template, and readable table output.
Project Lethe is pure Python standard library, so it runs anywhere CPython 3.10+ runs — Linux, macOS, Windows, and containers — with no third-party runtime dependencies.
git clone https://github.com/styxabhor/Project-Lethe.git
cd Project-Lethe
python -m venv .venv
# Linux/macOS:
source .venv/bin/activate
# Windows (PowerShell):
.\.venv\Scripts\Activate.ps1
pip install -e .pipx install .
# or, once published:
pipx install project-lethedocker build -t project-lethe .
docker run --rm project-lethe generate -p '<waf-test>' --profile xss --limit 12
# mount a scope file and persist the session store:
docker run --rm -v "$PWD:/work" -w /work project-lethe \
test https://example.com/s -p '<x>' --scope-file scope.txt --yespip install -e ".[completion]"
# bash:
eval "$(register-python-argcomplete lethe)"Confirm:
lethe --version
lethe --help
lethe examplesNative Windows works out of the box. If a cloned .venv was created on Linux
(it contains bin/ instead of Scripts/), recreate it with py -m venv .venv.
The auth cache and session store fall back gracefully when POSIX file
permissions are unavailable.
Generate a small XSS-context test set:
lethe generate --payload '<waf-test data-id="123">' --profile xss --limit 12Markdown output for notes:
lethe generate \
--payload '<waf-test data-id="123">' \
--profile xss \
--limit 12 \
--format markdownThe banner appears automatically only for interactive table output. Force or disable it explicitly:
lethe generate --payload '<test>' --banner
lethe generate --payload '<test>' --no-bannerUse explain to show exactly how a deterministic transformation changed a
payload:
lethe explain --payload '<waf-test>' --technique html_hex_entitiesExplain a chain:
lethe explain \
--payload '<waf-test>' \
--technique html_hex_entities \
--technique url_encode_reservedUse analyze when you need triage-ready wording: normalization hypotheses,
evidence notes, validation guidance, remediation, and a character-level diff.
lethe analyze \
--payload '<waf-test>' \
--technique html_hex_entities \
--technique url_encode_reserved \
--profile xss \
--format markdownAnalyze a copied/generated variant:
lethe analyze \
--payload '<waf-test>' \
--mutated '%3Cwaf-test%3E' \
--format markdownAdd optional AI narrative while keeping the model constrained to explanation only:
lethe analyze \
--payload '<waf-test>' \
--technique html_hex_entities \
--ai-provider ollama \
--ai-model qwen3:8b \
--format markdownDemo the AI flow without an API key:
lethe analyze \
--payload '<waf-test>' \
--technique html_hex_entities \
--ai-command 'python examples/mock_ai_analysis.py' \
--format markdownStack multiple mutation layers:
lethe generate \
--payload '<waf-test>' \
--profile xss \
--stack 3 \
--limit 75Stack depth is capped at 4 by default. For local labs, allow bounded deeper stacks up to 6:
lethe generate --payload '<test>' --stack 5 --allow-deep-stack --limit 100Explore novelty-oriented parser, Unicode, entity, recursive, and canonicalization techniques:
lethe generate --payload '<waf-test>' --explore --format jsonPreview the resolved plan:
lethe generate --payload '<waf-test>' --explore --stack 3 --dry-run --format jsonAdd bounded polymorphic padding variants:
lethe generate \
--payload '<waf-test>' \
--profile xss \
--limit 40 \
--polymorphic 10 \
--poly-seed report-001 \
--format markdownLethe can close the loop: generate variants, send them at a target you are authorized to test, and classify each response. Every active command is gated by a default-deny scope guard — nothing is sent unless the target matches an explicit scope rule.
Identify the WAF first:
lethe fingerprint https://example.com/ --scope example.comWhen a WAF is detected, Lethe suggests a vendor-tuned profile (for example
--profile cloudflare) and the relevant knowledge-pack entries. Vendor profiles
bundle the canonicalization tricks each vendor tends to be weak to.
Move variants straight into your existing automation as Nuclei templates or Burp BCheck scripts:
lethe generate -p '<waf-test>' --profile xss --format nuclei --param q --output lethe.yaml
lethe generate -p '<waf-test>' --profile xss --format bcheck --output lethe.bcheckThese are ready-to-edit starting templates — tune the matchers/target before use.
Plan a test (no traffic is sent without --yes):
lethe test https://example.com/search \
--payload '<script>alert(1)</script>' \
--profile xss \
--scope example.com \
--limit 25Confirm authorization and actually send:
lethe test https://example.com/search \
--payload '<script>alert(1)</script>' \
--profile xss \
--scope example.com \
--rate 5 \
--concurrency 4 \
--yes \
--format jsonEach variant is classified as blocked, passed, rate_limited, challenged,
origin_error, or inconclusive, with a confidence score and the signals used.
A passed verdict means the variant reached the origin — a candidate bypass.
For results you can rely on, add calibration, multi-sample baselines, and re-confirmation:
lethe test https://example.com/search -p '<script>' --scope example.com --yes \
--baseline-samples 3 \
--calibrate \
--confirm-bypass--calibratesends a few benign and known-malicious probes first to learn the target's own block/benign signature, instead of relying on generic markers.--baseline-samples Nmedians over N benign requests so a flaky origin does not poison verdicts.--confirm-bypassre-sends each apparent bypass; anything that does not reproduce is downgraded toinconclusive.
Lethe reports where a reflected payload landed — html_text, html_attribute,
script_block, json_string, and so on — and flags likely_exploitable when
the context is plausibly executable. A payload reflected in an HTML body is a
real finding; one reflected inside a JSON string usually is not.
Scope rules accept exact hosts, wildcards, ports, and CIDRs, from flags or a file:
# scope.txt
*.example.com
api.example.com:8443
10.0.0.0/8
!admin.example.com # '!' denies, and deny always wins
lethe test https://shop.example.com/q --payload '<x>' --scope-file scope.txt --yesA host rule without a port (example.com) matches any port; a rule with a port
(api.example.com:8443) matches that port only, where the URL's implicit port is
resolved from its scheme (https = 443, http = 80).
Inject into a query parameter (default), a header, or a request body:
lethe test https://example.com/api --payload '<x>' --scope example.com \
--inject body --content-type application/json --param input --yes
lethe test https://example.com/ --payload '<x>' --scope example.com \
--inject header --param X-Search --yesRoute through Burp/ZAP, and fail CI when a bypass is found:
lethe test https://example.com/search --payload '<x>' --scope example.com \
--proxy http://127.0.0.1:8080 --fail-on-bypass --yeslethe test exits 0 normally, 3 when --fail-on-bypass is set and at least
one variant passes, and 2 on a scope or setup error.
Paste a curl command or a raw HTTP request and mark the injection point with
§INJECT§. Lethe figures out whether to inject into the query, a header, or the
body:
lethe test --payload '<script>alert(1)</script>' --scope example.com --yes \
--curl "curl 'https://example.com/search?q=§INJECT§' -H 'X-Auth: token'"
lethe test --payload '<x>' --scope example.com --yes --request-file req.txtIf no §INJECT§ marker is present, the --inject/--param selection is used.
Restrict generation to techniques that make sense where the payload lands:
lethe test https://example.com/search --payload '<x>' --scope example.com \
--context html_attr --yes
lethe generate --payload '<x>' --context js_string
lethe techniques --context url
lethe contextsWhen a static technique set is fully blocked, let the oracle-guided loop evolve toward a bypass and minimize it to the smallest working chain:
lethe discover https://example.com/search \
--payload '<script>alert(1)</script>' \
--profile xss \
--scope example.com \
--rounds 3 \
--stop-on-bypass \
--yesEach round sends a population of candidate chains, keeps the survivors (anything
not blocked), and evolves the next generation. A passed chain is a candidate
bypass of that WAF config; discovered chains are reduced to a minimal primitive.
Novel chains (not seen before) are flagged. --fail-on-bypass makes it exit 3.
Lethe ships an inert knowledge pack that maps WAF/canonicalization bypass classes to profiles and techniques. It is data, never executed.
lethe feeds # browse all entries
lethe feeds --vendor Cloudflare # entries relevant to a detected WAF
lethe feeds --tag unicode
lethe feeds --search traversal --format jsonlethe fingerprint automatically annotates a detected WAF with related entries.
Point LETHE_FEEDS at your own curated JSON pack, or refresh from a trusted
source you control:
lethe feeds --update https://feeds.example.com/lethe-pack.jsonFetched content is validated as JSON and stored locally; it is never executed.
Persist runs and findings to a local SQLite store for later review and evidence:
lethe test https://example.com/search --payload '<x>' --scope example.com \
--yes --save --session hackerone-prod
lethe history # list recent runs
lethe history 3 --format json # findings for run #3The store lives at ~/.cache/project-lethe/sessions.db by default (override with
LETHE_STORE).
You can also pin scope in .lethe.toml so you never forget the guard:
[lethe]
scope_allow = ["*.example.com"]
scope_deny = ["admin.example.com"]Profile one authorized URL:
lethe profile https://example.com/account --format json --output site-profile.jsonLethe records response context such as content type, security headers, forms, input names, cookie names, technology hints, and recommended profiles/tags.
Optional reflection check with a harmless canary:
lethe profile https://example.com/search --reflectUse a saved profile during generation:
lethe generate --payload '<waf-test>' --site-profile site-profile.json --limit 12For authenticated routes, prefer environment variables:
export LETHE_TOKEN='redacted-token-here'
export LETHE_COOKIE='session=redacted'
lethe profile https://example.com/account \
--bearer-token-env LETHE_TOKEN \
--cookie-env LETHE_COOKIE \
--header 'X-Program-Scope: authorized' \
--format json \
--output site-profile.jsonSave auth material temporarily for repeated authorized profiling:
lethe auth save hackerone-prod \
--bearer-token-env LETHE_TOKEN \
--cookie-env LETHE_COOKIE \
--ttl 30m
lethe profile https://example.com/account --auth-ref hackerone-prod
lethe auth list
lethe auth clear hackerone-prodThe cache is stored outside the repo at ~/.cache/project-lethe/auth.json by
default, written with 0600 permissions when supported, and capped at 2 hours
unless --allow-long-ttl is used.
Generate safe request-shape templates for WAF/backend parser comparison:
lethe http-plan --host example.com --path /search --param q=safe --format rawInclude HTTP/2-to-HTTP/1.1 downgrade templates:
lethe http-plan \
--host example.com \
--path /search \
--param q=safe \
--include-http2 \
--format rawThese templates cover harmless canary checks for :path versus path,
:authority versus host, duplicate pseudo-header rejection,
lowercase-header enforcement, and TE/content-length downgrade consistency.
Execute the HTTP/1.1 templates against an authorized target (raw sockets, so duplicate headers and HPP go out exactly as crafted; HTTP/2 text templates are skipped):
lethe http-run https://example.com/search --scope example.com --yesFeed Lethe an OpenAPI/Swagger or GraphQL introspection file to get type-aware profile, context, and technique recommendations per endpoint and parameter:
lethe schema swagger.json
lethe schema swagger.json --format jsonString fields get encoding/Unicode folds, path parameters get path normalization, and so on — so you focus mutations where they matter.
Run a scope-gated HTTP proxy that mutates live traffic in place. Point Burp's
upstream proxy at it and every forwarded request has the chosen mutation applied
to a parameter (or a §INJECT§ marker):
lethe proxy --listen 127.0.0.1:8081 \
--param q \
--technique unicode_compat_bypass \
--scope example.com \
--yesIt enforces scope (in-scope targets only). For HTTP targets it works out of the
box. For HTTPS interception, install the optional extra and add --https:
pip install "project-lethe[mitm]"
lethe proxy --listen 127.0.0.1:8081 \
--param q --technique unicode_compat_bypass \
--scope example.com --https --yesOn first run Lethe generates a local CA under ~/.cache/project-lethe/mitm/ and
prints its path; trust that CA cert in your browser/Burp so the intercepted TLS
validates. Lethe mints a per-host leaf certificate on demand and still verifies
the upstream server normally — it only terminates TLS on the client side. Only
in-scope hosts are intercepted.
Include bounded local-lab stress templates:
lethe http-plan \
--host example.com \
--path /search \
--include-stress \
--max-padding 1024 \
--regex-length 64 \
--format markdownLethe does not transmit these templates.
Create .lethe.toml:
[ai]
provider = "gemini"
model = "gemini-2.5-flash"
api_key_env = "GEMINI_API_KEY"Keep real API keys in environment variables:
export GEMINI_API_KEY='redacted-key-here'Ask AI for a safety/context plan:
lethe plan --payload '<waf-test>' --site-profile site-profile.jsonUse AI planning during generation:
lethe generate --payload '<waf-test>' --site-profile site-profile.json --aiLocal/free provider example:
[ai]
provider = "ollama"
model = "llama3.2"Supported providers: gemini, openai, claude, ollama, lmstudio,
localai, and openai-compatible.
Copy the example:
cp .lethe.toml.example .lethe.toml
lethe configIgnore local config for one command:
lethe --no-config generate --payload '<test>'STYX://PROJECT-LETHE [trace online]
canon.map=armed | mode=authorized-lab | output=clean
sig=Avinash K A//styxabhor
Good places to use Lethe:
- Your own local lab.
- Systems you administer.
- Bug bounty scopes that explicitly allow this type of testing.
- Defensive parser/canonicalization validation.
Bad places to use Lethe:
- Random third-party targets.
- Credential attacks.
- Service exhaustion.
- Claims of impact without authorized target evidence.
See SECURITY.md for the full policy.
For detailed instructions on using Lethe within Kali Linux, see KALI_USAGE.md.
See CHANGELOG.md for notable changes per release.
MIT. See LICENSE.
