Adler
Named for Irene Adler β "the Woman", the one who outwitted Sherlock Holmes. Where Sherlock searched, Adler outsmarts.
OSINT username search across ~3,000 sites, in Rust. Honest verdicts and built to reach the hard ones β Cloudflare-walled, TLS-fingerprinted, geo-restricted, login-walled.
Documentation
- π adler-docs β the user manual: install, the access engine, the web UI, embedding, FAQ.
- π¦ docs.rs/adler-core β Rust API reference.
- πΊοΈ PLAN.md β roadmap and the design behind the access-engine phases.
This README covers the elevator pitch (compare table, detection rate data) plus install / quickstart; deeper material lives on the docs site so it can evolve independently of crate releases.
How Adler compares
Open-source username-search tools that OSINT operators actually consider, on the dimensions that matter when sites push back:
| Sherlock | Maigret | Blackbird | Snoop | Adler | |
|---|---|---|---|---|---|
| Approx. sites | 400 | 3,000 | 600 | 5,400 | 3,000 [^cmp-1] |
| Verdict model | Found / NotFound | Found / NotFound | Found / NotFound | Found / NotFound | Found / NotFound / Uncertain(reason) |
| Bot-protected sites (Instagram, X, β¦) | β | β | β | β | headless Chrome via --browser-backend |
| TLS-fingerprint blocking | β | β | β | β | Chrome 134 handshake via --features impersonate |
| Proxy routing | one global | one global + Tor + I2P | β | β | one global or per-site policy via --proxy-pool |
| Cookies / sessions | β | global cookies.txt |
β | β | per-site named sessions via --sessions |
| Registry self-heal | β | β | β | β | --doctor --fix diffs responses, proposes new signatures |
| Web UI | β | yes (results graph, reports) | β | β | --web β live SSE-streaming SolidJS SPA + JSON API |
| Output formats | text / CSV / XLSX / JSON | text / JSON / CSV / HTML / PDF / XMind / D3 | text / CSV / PDF | text / CSV / HTML | text / JSON / NDJSON / CSV / HTML |
| Embeddable library | β | yes (Python async) | β | β | adler-core on crates.io (Rust) |
| Runtime / packaging | Python | Python | Python | Python | Rust β single static binary, cargo binstall |
[^cmp-1]: Sherlock + Maigret + WhatsMyName lineages combined; see Site registry.
Adler's thesis: honest verdicts plus access for the sites that matter. A
NotFound from a Python-HTTP-only tool on a Cloudflare-walled, TLS-
fingerprinted, geo-restricted, or login-walled site is often just "I gave up
at the first wall." Adler reports Uncertain(reason) when it couldn't verify,
and ships the transports you need to break the wall yourself β headless
browser, Chrome handshake emulation, per-site geo / IP-type egress, operator-
supplied sessions. We do not solve CAPTCHAs or evade human-verification (see
Ethics & responsible use).
Detection rate
Recall depends on where you scan from. A --doctor pass on 2026-05-26
against the bundled registry (411 sites):
| Scan source | Sites where a known-existing account is found | Recall |
|---|---|---|
| Datacenter IP (Hetzner / Leaseweb DE) | 282 / 411 | 68.6% |
| US residential proxy pool (DECODO) | 305 / 411 | 74.2% |
The residential lift is real: ~40 sites swap their verdict between
Uncertain (datacenter) and Found (residential) β most are
Cloudflare-walled or geo-restricted (RU-segment, plus platforms like
Reddit, Imgur, Patreon). The remaining ~26% breaks down roughly as:
- Bot-protected sites tagged
bot-protected(Instagram and X/Twitter today) β these serve a JS login wall to a plain HTTP request; a clean IP doesn't help, you need a browser backend. Exclude them with--exclude-tag bot-protected. - Stale Sherlock-imported
known_presentaccounts that no longer exist on the live site. The--doctor --suggest-known-presenttool (new in v0.4.0) probes a small candidate pool (the site's brand name, plustorvalds/octocat/admin/ β¦) and prints a paste-ready snippet for any site where it finds a live account. Discovery surfaced 19 healable entries on the most recent sweep; the remaining placeholders need either a contributor-found candidate or a deeper repair via--doctor --fix. - Sites whose detection rule fires for every username β
signal repair territory, not username repair.
--doctor --fixdiffs the responses and proposes a tighter signal. - Sites that don't reliably distinguish found from not-found for unauthenticated requests at all β investigated and not added rather than ship false-positive entries: Reddit, TikTok, Pinterest, and Threads. See issues #11β#14 for the specific failure modes and what would unblock each.
Run the same check yourself: adler --doctor (uses your current IP)
or adler --doctor --proxy <url> (via your own proxy). With
--browser-backend browserbase the doctor's --fix mode routes
bot-protected sites through a real Chrome session, so the diff sees
real profile pages rather than two identical login walls. With
--suggest-known-present you get an OVERRIDES block per healable
site.
Crates
| Crate | Kind | Purpose |
|---|---|---|
adler-core |
lib | Detection engine, site registry, executor. |
adler-server |
lib | HTTP API + SSE streaming + scan persistence; embeds the SolidJS web UI via rust-embed. |
adler-mcp |
lib | Model Context Protocol server (rmcp 1.7); exposes the OSINT surface to AI agents over stdio + Streamable HTTP+SSE. |
adler-cli |
bin | adler command-line interface; --web launches the embedded server + UI in-process; --mcp / --mcp-http launch the MCP server. |
Install
From crates.io (compiles locally, ~1β2 min):
Pre-built binary from the GitHub release (instant, no compile):
From source:
Requires Rust β₯ 1.85. The installed binary is adler. The library
(adler-core) is published separately
for embedding the engine in your own tools β see the
Library section below.
Verify release artifacts
Every platform archive attached to a GitHub Release is signed with
Sigstore cosign using the GitHub
Actions OIDC identity β no long-lived keys are kept, the signing
certificate is short-lived and bound to the exact workflow that
produced it (visible in the Rekor
transparency log). The signature (.sig) and certificate (.pem) are
uploaded alongside each archive on the release page.
TAG=v0.11.3 # or whichever release
ARCHIVE=adler-x86_64-unknown-linux-gnu.tar.gz
# Pull the archive + its signature + certificate from the release.
# Verify the signature is bound to this repo's release.yml workflow.
A successful verification prints Verified OK. The identity-regex
pins the signer to this repository's release.yml at a SemVer tag β
a forged archive uploaded under a different workflow won't satisfy it.
Build & run
Logging is controlled by the ADLER_LOG env var (defaults to adler=info):
ADLER_LOG=adler=debug
Usage
adler <username> scans the embedded registry; everything else is a
knob. Text output shows Found and Uncertain by default and hides
NotFound β pass --all for the full list. Results stream into a
terminal as they resolve; piped output is collected and ordered. Exit
codes: 0 found, 1 nothing found, 2 error.
A few of the most common knobs:
β Complete flag reference, grouped by intent (filtering / output /
network & sessions / browser & cache / batch & enrichment), is on the
Usage page.
adler --help lists every flag with its short doc; the docs page adds
the bigger picture.
Web UI
adler --web boots a small in-process HTTP server and serves a SolidJS
SPA from the same binary β live SSE-streamed scans, persisted history,
side-by-side diff against an earlier run with a picker for which
historical scan to diff against, a read-only access-engine panel,
per-scan egress subset selection when a --proxy-pool is loaded, and
a single/batch tab pair so you can paste a list of usernames into the
hero and watch them queue through one at a time.
Warning β the default bind is loopback. Switching to
0.0.0.0exposes the JSON API to your network. Adler is not built to face the open internet; put auth in front of any non-loopback bind.
β The
Web UI page
covers the full feature set, the /api/* surface, and the deployment
notes (the SPA is rust-embed'd into the binary; rebuild from source
with npm ci && npm run build in adler-server/web/).
MCP server
Adler exposes its OSINT surface to AI assistants over the
Model Context Protocol. Five
tools the agent can call (list_sites, scan_username with
streamed progress, scan_batch, doctor_check, get_scan_history),
five resources it can browse (adler://registry/{sites,tags, disabled}, adler://scans/recent, adler://scans/{id} template),
and three prompts with templated OSINT workflows
(investigate_username, audit_registry_health,
correlate_accounts). Two transports β pick whichever fits how the
agent runs.
The HTTP transport inherits rmcp's loopback allowed_hosts
DNS-rebind guard out of the box; non-loopback binds expose the API
without authentication, so only do it on a trusted network. The
instructions block sent on initialize restates the project's
ethical bound (authorised security testing / OSINT research /
defensive work only; no harassment, doxxing, or unauthorised
surveillance) so the agent's first peek at the server names what's
in scope.
β The Usage
page lists every tool / resource / prompt with its arguments and
return shape. adler-mcp/examples/ ships two hand-runnable probes
(stdio + HTTP) that double as reference implementations of a
minimal MCP client.
Access engine
Adler ships a transport ladder for sites a plain HTTP client can't see β that's the whole reason it scores ahead of Sherlock / Maigret on the hard subset of the registry:
- Browser backend (
--browser-backend local/browserbase) β real headless Chrome for sites taggedbot-protected(Instagram, X / Twitter today). Bounded by--browser-budgetso a misconfigured flag can't burn a quota. - TLS-fingerprint impersonation (
cargo install --features impersonate) β in-process Chrome 134 BoringSSL handshake for sites taggedprotection: tls-fingerprint. Much cheaper than a real browser. - Egress pool (
--proxy-pool <file>) β per-site geo / IP-type routing. Sites with anaccesspolicy pick a matching proxy; sites without stay on the default egress.region:XXtags auto-populate a softprefer_geo(since v0.12) so 685 region- tagged sites get a recall lift when a matching egress is configured, and fall back to the default when one isn't β no hardUncertain(GeoUnavailable). - Sessions (
--sessions <file>) β operator-supplied cookies / tokens for login-walled sites. Per-site[name]tables; values redacted from logs. - Automatic escalation (
--escalation-budget N/--no-escalation) β when the cheap path returnsUncertain(cloudflare_challenge | rate_limited), the router automatically retries through the browser backend. Bounded by its own budget. Outcomes carrytransportandescalationstelemetry so it's clear which path produced each verdict.adler --doctor --suggest-protection(since v0.13) reads that telemetry across runs and flags sites that consistently escalate as candidates for addingprotection: cloudflareup front.
β Full guide with the TOML formats, guardrails, and trade-offs lives at Access engine.
Library
adler-core is the runtime-agnostic engine that powers the CLI,
published separately on crates.io
for embedding in your own Rust tools β a Discord bot that checks
usernames, a security tool that flags exposed identities across a
watchlist, a CI gate that asserts a name isn't claimed elsewhere.
[]
= "0.10"
= { = "1", = ["macros", "rt-multi-thread"] }
β Minimal worked example, the notable ClientBuilder knobs, and the
per-version breaking-change log are on the
Embedding
page. The complete API reference is on
docs.rs/adler-core.
Site registry
The default registry (adler-core/data/sites.json, ~2.5k sites) is
generated from MIT-licensed upstream data β Sherlock + Maigret + an
opt-in WhatsMyName tranche (CC BY-SA 4.0; pass --no-wmn to drop it
when redistributing scan output under MIT only). Detections are imported
unverified β adler --doctor validates every signal, --doctor --fix proposes corrected ones, and --doctor --fix --apply --sites <path> (since v0.12) patches them straight into the JSON file with
an atomic sibling-*.tmp rewrite.
β Detailed lineage, schema, signal model, and doctor workflow live in Site registry.
Troubleshooting
Common questions ("Why is everything Uncertain?", "Why does Adler find fewer accounts than Sherlock?", "How do I scan Instagram?", β¦) are covered in the FAQ on the docs site.
For CI / contributor-facing commands (cargo fmt, cargo clippy,
cargo test), see CONTRIBUTING.md.
Ethics & responsible use
Adler aggregates publicly reachable profile URLs, but aggregation makes intrusion easy β please use it responsibly.
Intended uses: checking your own accounts; authorized penetration tests and bug-bounty engagements; security research; and OSINT investigations with a lawful basis. Do not use Adler to stalk, harass, dox, or surveil people without authorization, or to mass-target individuals.
Detect, never circumvent. Adler reports anti-bot responses (rate limits,
Cloudflare challenges, captchas) as Uncertain β it does not solve captchas
or bypass access controls. It rate-limits per host, supports --max-rps and
--respect-robots, and writes an optional --audit-log of every request.
See SECURITY.md and CODE_OF_CONDUCT.md.
License
The Adler code is licensed under the MIT License.
The default site registry (adler-core/data/sites.json) is also under MIT
β it is derived from the Sherlock project (MIT) and the Maigret project
(MIT). See the file's _comment header and the corresponding importer
scripts in scripts/ for attribution.
The supplementary registry (adler-core/data/sites_wmn.json, included
by default; opt-out with adler --no-wmn) is derived from WhatsMyName
and licensed CC BY-SA 4.0. Adler's MIT licence
does not cover this file; downstream redistribution must preserve
attribution and the ShareAlike obligation on derivative data.