From 6d49c5dcfc01acde05558847ee575fafe2034b23 Mon Sep 17 00:00:00 2001 From: drjones Date: Wed, 7 Oct 2026 08:46:22 -0700 Subject: [PATCH] =?UTF-8?q?v4:=208=20new=20tools=20=E2=80=94=20WARP=20ARCH?= =?UTF-8?q?IVE=20(domain=20time=20machine),=20HOOK=20RELAY=20(webhook=20in?= =?UTF-8?q?spector=20w/=20source=20detect=20+=20replay),=20FACE=20TRACE=20?= =?UTF-8?q?(pfp=20hash=20triangulation),=20UNFURL=20LAB=20(redirect-chain?= =?UTF-8?q?=20+=20page=20dissection),=20BSSID=20RADAR=20(router-MAC=20geo?= =?UTF-8?q?=20via=20OSINT=20terminal),=20HEADER=20ROTATOR=20(browser=20ide?= =?UTF-8?q?ntity=20spinner),=20IDENTITY=20SHELF=20(burner=20countdown=20da?= =?UTF-8?q?shboard),=20PORT=20BEACON=20(server=20heartbeat/canary).=20All?= =?UTF-8?q?=20registries=20wired;=20200+=20combined=20smoke=20checks=20gre?= =?UTF-8?q?en;=20verified=20live=20over=20WAN.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 2018 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 2016 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index 591e4b9..d4862d3 100644 --- a/app.py +++ b/app.py @@ -371,7 +371,7 @@ li::marker{color:var(--acc)} CANARYDEAD-DROPBURNER-MAIL SHOTFRAUD-SCORE INBOXPASSPORT -PASSKEYSMAG-LABTOOLS +PASSKEYSMAG-LABPORT BEACONBSSID RADARHOOK-RELAYFACE TRACEROTATORSHELFUNFURLWARPTOOLS
{{acct}}
@@ -401,6 +401,14 @@ li::marker{color:var(--acc)} ◈ PHONE LOOKUP osint: carrier + line type + region ◈ USERNAME SLEUTH osint: handle across platforms ◈ DOMAIN RECON osint: RDAP + DNS + subdomains +◈ PORT BEACON Scan canary for servers: heartbeat + touch tripwire in one URL +◈ BSSID RADAR WiFi router-MAC (BSSID) to approximate geolocation via crowdsourced DB — map links, accuracy radius, batch runs. +◈ HOOK-RELAY instant public webhook inspector — capture, inspect, replay +◈ FACE TRACE profile-picture triangulation — hash an avatar, harvest a username's pfps, Hamming verdicts +◈ ROTATOR consistent browser identity pools with replayable seeds +◈ SHELF every burner you own, with live countdowns +◈ UNFURL Follow every redirect hop manually and dissect the final page +◈ WARP Domain time machine — Wayback snapshots, previews, DNS drift

Account

◈ INBOX no-KYC messaging ◈ SIGN UP username + password, 10 seconds, no KYC @@ -553,6 +561,14 @@ var PAL=[ ['/pass','all-access pass'], ['/passport','agent passport badge'], ['/tools','free tools'], +["/beacon", "scan canary: server heartbeats + port-touch tripwires"], +["/bssid", "WiFi router-MAC geolocation"], +["/hooks", "webhook inspector \u2014 capture, inspect, replay"], +["/face", "avatar pfp triangulation"], +["/rotator", "header rotator \u2014 identity pools + seeds"], +["/shelf", "identity shelf \u2014 every burner + countdowns"], +["/unfurl", "redirect chain + page dissection"], +["/warp", "domain time machine \u2014 wayback timeline, previews, DNS drift"], ['/llms.txt','machine catalog for agents'], ['/openapi.json','OpenAPI spec'], ]; @@ -602,6 +618,14 @@ NEBULAS = { "deaddrop": ("rgba(45,226,200,.13)", "rgba(160,225,255,.09)"), "shot": ("rgba(111,214,255,.12)", "rgba(120,85,255,.10)"), "score": ("rgba(255,110,180,.10)", "rgba(66,232,164,.10)"), + "warp": ("rgba(255,110,180,.11)", "rgba(120,85,255,.11)"), + "unfurl": ("rgba(255,110,180,.11)", "rgba(120,85,255,.11)"), + "shelf": ("rgba(66,232,164,.11)", "rgba(255,170,60,.09)"), + "rotator": ("rgba(111,214,255,.12)", "rgba(167,139,250,.10)"), + "face": ("rgba(255,110,180,.11)", "rgba(120,85,255,.11)"), + "hooks": ("rgba(255,110,180,.11)", "rgba(120,85,255,.11)"), + "bssid": ("rgba(255,110,180,.11)", "rgba(120,85,255,.11)"), + "beacon": ("rgba(255,110,180,.11)", "rgba(120,85,255,.11)"), } def kv(pairs): rows = "".join(f"
{k}
{v}
" for k, v in pairs) @@ -713,6 +737,24 @@ API_INDEX = { {"method": "GET", "path": "/drop/", "desc": "Read a dead-drop (password-protected if set). Each view burns one read."}, {"method": "POST", "path": "/api/shot/create", "params": {"url": "http(s):// target"}, "desc": "Screenshot queue. Headless Chromium PNG if available, else rendered-text capture (status=text_fallback). FREE. Poll /api/shot/status/."}, {"method": "GET", "path": "/api/shot/status/", "desc": "Shot result: base64 PNG (png_b64) or text preview + page intel."}, + {"method": "POST", "path": "/api/beacon/create", "params": {"label": "server name", "port": "disguise port 0-65535", "interval_min": "heartbeat minutes 1-1440"}, "desc": "Create a port beacon: returns token, probe_url and a ready-to-run install snippet (curl one-liner + systemd service/timer). Server curls the probe URL as heartbeat; non-heartbeat fetches are canary hits."}, + {"method": "GET", "path": "/api/beacon/list", "desc": "All your beacons with status (LIVE/LATE/SILENT/WAITING), last_seen, interval, canary hit counts and probe URLs. Lazily flips overdue beacons to SILENT + fires one inbox alert per outage."}, + {"method": "GET", "path": "/api/beacon/status", "params": {"token": "beacon token"}, "desc": "Single beacon status: state machine, last_seen, silent_notified flag, canary_hits count."}, + {"method": "GET", "path": "/bssid", "desc": "BSSID RADAR tool page: paste one WiFi BSSID or a batch (one per line), any MAC format, get approximate router geolocation with map links + accuracy radius. Human-friendly with live normalization preview."}, + {"method": "GET", "path": "/api/bssid?mac=", "desc": "BSSID geolocation: normalize any MAC spelling, query OSINT bssid_geo (Mylnikov free DB) server-side. Returns lat/lon, accuracy_m, Google/OSM map links, wigle fallback. Batch: ?macs=a;b;c (up to 25). Rate limit 20/min."}, + {"method": "POST", "path": "/api/hook/create", "params": {"label": "optional name"}, "desc": "Create a webhook capture endpoint. Login required. Returns unique URL {SITE}/hook/ that accepts GET/POST/PUT with ANY content-type — no auth on capture (webhooks come from outside). Records headers, body (32KB cap), query, IP, UA into hook_hits."}, + {"method": "GET", "path": "/api/hook/list", "desc": "Your hook endpoints with hit counts, last-hit timestamps and capture URLs. Login required."}, + {"method": "GET", "path": "/api/hook/hits?token=", "params": {"token": "hook token", "limit": "1-500, default 100"}, "desc": "Captured hits for one endpoint: method, ts, ip, ua, content-type, source badge (Stripe/GitHub/Discord/Shopify/Telegram auto-detected), full headers, query, body. Login required."}, + {"method": "POST", "path": "/api/hook/replay", "params": {"hit_id": "captured hit id", "target_url": "https:// destination"}, "desc": "Resend a captured hit (original method, headers, body) to any public URL via http(). SSRF-guarded: 10.x / 127. / 172.16-31 / 169.254 and reserved ranges refused. Login required."}, + {"method": "POST", "path": "/api/face", "files": ["image"], "params": {"u": "username (optional)"}, "desc": "Face trace: fingerprint an uploaded avatar (dHash 9x8 + aHash 8x8 + sha256, computed locally), harvest the username's avatars (GitHub + Reddit APIs, Telegram/Steam/Twitch constructed links), compare via Hamming distance (<=10/64 = likely same image) + manual reverse-image lead links. No external reverse-image APIs called."}, + {"method": "GET", "path": "/api/rotator?platform=&n=&seed=", "desc": "Spin 1-25 consistent browser identities (UA, referer, Accept-Language, DNT) from desktop/mobile/agent/stealth pools. Same seed = same rotation. Returns identities + ready-made curl. FREE, 20/min."}, + {"method": "GET", "path": "/api/rotator/pools", "desc": "Pool sizes: desktop, mobile, agent, stealth + referer/language counts."}, + {"method": "GET", "path": "/api/shelf", "desc": "Identity shelf: all your mailboxes, SMS numbers, dead-drops, canaries + expiry stats in one JSON. Login required. Perfect for expiry-monitoring crons."}, + {"method": "GET", "path": "/unfurl", "desc": "URL unfurl: follows every redirect hop one at a time (scheme, host, status, Location) and dissects the final page (title, meta/og tags, iframes, forms). Detects loops and refuses non-http schemes. FREE."}, + {"method": "GET", "path": "/api/unfurl", "params": {"url": "http(s):// target", "max_hops": "1-10 default 6", "extract": "0|1"}, "desc": "Full redirect chain + final-page metadata as JSON. 401 without session/key; rate-limited. FREE."}, + {"method": "GET", "path": "/api/unfurl/history", "desc": "Your last 25 unfurl lookups (url, final_url, status, ts). Login required."}, + {"method": "GET", "path": "/warp", "desc": "Domain time machine (humans): Wayback Machine snapshot timeline per year, screenshot previews of archived copies, DNS/whois drift table, hosted-path inventory. Passive, cached 6h."}, + {"method": "GET", "path": "/api/warp?domain=", "desc": "Archive intel JSON: CDX snapshot timeline (collapsed per year), DNS/RDAP drift (oldest vs current), distinct archived paths. Free, no key needed, cached 6h per domain."}, {"method": "GET", "path": "/api/score?ip=&email=&bin=", "desc": "Composite fraud score 0-100 + weighted breakdown: IP intel (VPN/hosting/abuse geo), disposable-email domain, BIN country/type risk. FREE."}, {"method": "POST", "path": "/canary", "desc": "Create canary trap. Form: tag, kind (link|pixel|cred|file), rearm (0|1). Login required. Link = /c/, pixel = /c/.png, honeyfile = /c//download, credential returned by /api/canary/list."}, {"method": "GET", "path": "/api/canary/list", "desc": "Your traps with hit counts, links, generated honeytoken credentials. Login required."}, @@ -749,7 +791,7 @@ INDEXNOW = "a8f3d4rk0rbits9e2b1c7x5k8m2v4q6t8" @app.route("/sitemap.xml") def sitemap(): S = "https://dark0rbits.thetempleofdoom.com" - pages = ["", "ip", "card", "sms", "proxy", "steg", "track", "eh", "forensics", "phone", "user", "domain", "canary", "deaddrop", "shot", "score", "mail", "inbox", "passport", "pass", "keys", "maglab", "tools", "signup"] + pages = ["", "ip", "card", "sms", "proxy", "steg", "track", "eh", "forensics", "phone", "user", "domain", "canary", "deaddrop", "shot", "score", "mail", "inbox", "passport", "pass", "keys", "maglab", "tools", "signup", "beacon"] xml = '' + "".join(f"{S}/{p}weekly" for p in pages) + "" return xml, 200, {"Content-Type": "application/xml"} @@ -803,6 +845,21 @@ def openapi(): add("/api/canary/list", "get", "Your traps + hit counts + honeytoken creds") add("/api/canary/hits", "get", "Full hit log with geo per hit", {"token": "trap token"}, req=True) add("/api/eh", "post", "Email header forensics v2 (origin, delays, verdicts, flags)", {"raw": "full headers or .eml content"}, req=True) + add("/api/beacon/create", "post", "Create a port beacon (heartbeat + canary URL) with install snippet", {"label": "my-server", "port": 22, "interval_min": 5}, req=True) + add("/api/beacon/list", "get", "List beacons: status LIVE/LATE/SILENT/WAITING, last_seen, hits", {}, req=True) + add("/api/beacon/status", "get", "One beacon status by token", {"token": "AbC123"}, req=True) + add("/api/bssid", "get", "WiFi BSSID -> approximate geolocation (Mylnikov DB via LAN OSINT terminal). Any MAC format accepted; batch via macs=a;b;c.", {"mac": "AA:BB:CC:DD:EE:FF"}, req=True) + add("/api/hook/create", "post", "Create webhook capture endpoint (returns /hook/ URL)", {"label": "optional label"}) + add("/api/hook/list", "get", "Your hook endpoints + hit counts") + add("/api/hook/hits", "get", "Captured hits for one hook (auto-detected source, headers, body)", {"token": "hook token", "limit": "opt 1-500"}, req=True) + add("/api/hook/replay", "post", "Replay a captured hit to any public URL (SSRF-guarded)", {"hit_id": "hit id", "target_url": "https:// target"}, req=True) + add("/api/face", "post", "Avatar fingerprint + username pfp harvest + Hamming verdict", {"u": "username (optional)"}, files=["image"]) + add("/api/rotator", "get", "Spin consistent browser identities (UA+referer+language+DNT), optional deterministic seed", {"platform": "desktop|mobile|agent|stealth", "n": "1-25", "seed": "optional"}) + add("/api/rotator/pools", "get", "Pool inventory") + add("/api/shelf", "get", "Identity shelf — all burners + expiry stats (login)") + add("/api/unfurl", "get", "Unfurl a URL: follow redirects hop-by-hop, dissect final page", {"url": "https://bit.ly/abc", "max_hops": "6"}, req=True) + add("/api/unfurl/history", "get", "Your last 25 unfurl lookups", {}, req=True) + add("/api/warp", "get", "Domain time machine: Wayback timeline, screenshot-preview links, DNS/whois drift, hosted paths. Cached 6h.", {"domain": "example.com"}, req=True) add("/signup", "get", "No-KYC signup page (username + password only)") return jsonify(ps) @@ -3256,6 +3313,1955 @@ def tools(): ◈ FRAUD-SCORE — composite IP + email + BIN risk 0-100""" return page("tools", body) +# ---------- TOOL: PORT BEACON ---------- +import re as _re + +def _beacon_db(): + con = db() + con.execute("""CREATE TABLE IF NOT EXISTS beacons( + id INTEGER PRIMARY KEY, user_id INTEGER, token TEXT UNIQUE, label TEXT, + port INTEGER DEFAULT 0, interval_min INTEGER DEFAULT 5, + last_seen INTEGER, created INTEGER, silent_notified INTEGER DEFAULT 0)""") + con.execute("""CREATE TABLE IF NOT EXISTS beacon_hits( + id INTEGER PRIMARY KEY, beacon_id INTEGER, ts INTEGER, ip TEXT, ua TEXT, kind TEXT)""") + con.commit() + return con + +def _beacon_snippet(token, interval_min): + """curl one-liner + systemd service/timer pair. Plain strings (no f-strings).""" + probe = SITE + "/b/" + token + "?hb=1" + curl = "curl -fsS -m 20 -A drb-beacon '" + probe + "' >/dev/null 2>&1 || true" + svc = ("[Unit]\nDescription=dark0rbits port beacon heartbeat\nAfter=network-online.target\n\n" + "[Service]\nType=oneshot\nExecStart=" + curl + "\n") + tim = ("[Unit]\nDescription=run port beacon heartbeat\n\n" + "[Timer]\nOnBootSec=2min\nOnUnitActiveSec=" + str(interval_min) + "min\nAccuracySec=30s\n\n" + "[Install]\nWantedBy=timers.target\n") + return probe, curl, svc, tim + +_HEARTBEAT_UA = _re.compile(r"drb-beacon|curl|wget|python-requests|systemd|libwww|fetch|powershell", _re.I) + +def _beacon_check_silent(con, uid=None): + """Lazily flip overdue beacons to SILENT + inbox alert exactly once. Commit before messaging.""" + now = int(time.time()) + q = "SELECT * FROM beacons WHERE silent_notified=0 AND last_seen IS NOT NULL" + args = () + if uid: + q += " AND user_id=?" + args = (uid,) + due = con.execute(q, args).fetchall() + for b in due: + if now - b["last_seen"] > 3 * b["interval_min"] * 60: + con.execute("UPDATE beacons SET silent_notified=1 WHERE id=?", (b["id"],)) + # recovery: was silent, heartbeat returned -> clear flag + note + q2 = "SELECT * FROM beacons WHERE silent_notified=1" + args2 = () + if uid: + q2 += " AND user_id=?" + args2 = (uid,) + for b in con.execute(q2, args2).fetchall(): + if b["last_seen"] and now - b["last_seen"] < b["interval_min"] * 60: + con.execute("UPDATE beacons SET silent_notified=0 WHERE id=?", (b["id"],)) + con.commit() # end txn BEFORE writing messages (db-locked race class) + for b in due: + if now - b["last_seen"] > 3 * b["interval_min"] * 60: + m = db() + m.execute("INSERT INTO messages(user_id,sender,body,created) VALUES(?,?,?,?)", + (b["user_id"], "operator-bot", + "⚠ BEACON SILENT: '%s%s' has missed its heartbeat for over %d minutes (3+ intervals). " + "The box may be down, offline, or blocked. Last seen: %s." % + (esc(b["label"]), (" :%d" % b["port"]) if b["port"] else "", + 3 * b["interval_min"], time.strftime("%b %d %H:%M UTC", time.gmtime(b["last_seen"]))), + int(time.time()))) + m.commit() + +def _beacon_status(b, now=None): + now = now or int(time.time()) + if not b["last_seen"]: + age = now - b["created"] + if age > 3 * b["interval_min"] * 60: + return "SILENT", "bad" + return "WAITING", "" + overdue = now - b["last_seen"] + if overdue > 3 * b["interval_min"] * 60: + return "SILENT", "bad" + if overdue > b["interval_min"] * 60: + return "LATE", "warn" + return "LIVE", "ok" + +def _beacon_create_core(uid): + label = (param("label") or "my-server")[:80] + try: + port = int(param("port") or 0) + except ValueError: + port = 0 + port = max(0, min(65535, port)) + try: + interval_min = int(float(param("interval_min") or 5)) + except ValueError: + interval_min = 5 + interval_min = max(1, min(1440, interval_min)) + con = _beacon_db() + token = secrets.token_urlsafe(12) + con.execute("INSERT INTO beacons(user_id,token,label,port,interval_min,last_seen,created,silent_notified) VALUES(?,?,?,?,?,NULL,?,0)", + (uid, token, label, port, interval_min, int(time.time()))) + con.commit() + return con.execute("SELECT * FROM beacons WHERE token=?", (token,)).fetchone() + +@app.route("/beacon") +def beacon_page(): + uid = current_user_id() + con = _beacon_db() + if uid: + _beacon_check_silent(con, uid) + newtok = param("new") or "" + newcard = "" + if newtok and uid: + b = con.execute("SELECT * FROM beacons WHERE token=? AND user_id=?", (newtok, uid)).fetchone() + if b: + probe, curl, svc, tim = _beacon_snippet(b["token"], b["interval_min"]) + newcard = ('
BEACON CREATED — install it on your server' + '

Probe URL (heartbeat target + canary link for port ' + + str(b["port"]) + '):

' + '' + esc(probe) + '' + '

1) quick test (cron-style) — run every ' + str(b["interval_min"]) + ' min:

' + '' + esc(curl) + '' + '' + '' + '

2) systemd timer (the clean way):

' + '

/etc/systemd/system/beacon.service

' + '' + esc(svc) + '' + '

/etc/systemd/system/beacon.timer

' + '' + esc(tim) + '' + '

then: systemctl daemon-reload && systemctl enable --now beacon.timer

') + body = """ +

PORT BEACON

A scan canary for your servers. The app can't open listening ports — so flip it around: your box calls home on a timer. If the heartbeat stops for 3+ intervals, the beacon flips SILENT and your inbox lights up. And because every beacon has a unique probe URL, plant it as a canary link: if your box gets scanned or someone touches the link, that fires too. Know the second your box goes dark or gets touched.

""" + newcard + """ +
New beacon +
+ + + + + +
""" + _beacon_list_html(uid) + flow("know the second your box goes dark or gets touched", [ +"you create a beacon labeled edge-vpn-fra, disguise port 22, interval 5 min.", +"you drop the one-liner (or the systemd timer) on the server — it curls the probe URL every 5 minutes.", +"the beacon row shows LIVE with a fresh last-seen, every heartbeat logged.", +"them a scanner knocks on port 22 — you've planted the same /b/ URL as a canary link in the port's banner or a decoy file; the moment it's fetched by anything that isn't your heartbeat, the tripwire fires.", +"you your INBOX lights up: beacon edge-vpn-fra touched from an unknown IP — with geo and device.", +"one night the box dies or loses network — 15 minutes of silence (3 intervals) later the beacon flips SILENT and pings your inbox again."]) + how([ +"A beacon is a unique probe URL — one per server — plus a heartbeat interval you choose.", +"Your server curls the probe URL every interval (curl one-liner or systemd timer; we generate both for you).", +"Heartbeats are detected by the User-Agent (curl/wget/drb-beacon) or the ?hb=1 flag — anything else fetching the URL counts as a CANARY HIT and alerts instantly.", +"Any fetch that is not a heartbeat logs IP, geo, device — same intel as the canary traps, kept in its own beacon_hits table.", +"Miss 3+ intervals of heartbeat and the beacon flips SILENT: one inbox alert per outage (no duplicates), auto-cleared when the heartbeat returns.", +"The disguise port is informational — a badge reminding you which port the canary link guards; the detection is the URL itself.", +"Pair it with CANARY TRAPS: plant a /c/ trap inside the box's files, and the /b/ URL in its network decoys — layered coverage."]) + gloss([ +("heartbeat","a tiny scheduled HTTP GET from your server proving it's alive"), +("SILENT","no heartbeat for 3+ intervals — box down, offline, or egress blocked"), +("LATE","heartbeat overdue by 1+ interval but under the SILENT threshold"), +("disguise port","the port you're watching — shown on the badge, pairs the beacon to its service"), +("canary hit","any fetch of the probe URL that isn't your heartbeat — someone touched it")]) + agent_card("POST /api/beacon/create · GET /api/beacon/list · GET /api/beacon/status?token=", +'curl -X POST "' + SITE + '/api/beacon/create" -H "Cookie: dark0rbits_tok=..." -d "label=edge-vpn&port=22&interval_min=5"', +'Create returns token + probe_url + install snippet. status returns LIVE/LATE/SILENT/WAITING, last_seen, hits. Auth: session cookie or Bearer dk_ key.') + return page("beacon", body) + +def _beacon_list_html(uid): + if not uid: + return '
Log in (no KYC) to create beacons and see their status.
' + con = _beacon_db() + rows = con.execute("SELECT * FROM beacons WHERE user_id=? ORDER BY id DESC LIMIT 50", (uid,)).fetchall() + trs = "" + for b in rows: + st, cls = _beacon_status(b) + hits = con.execute("SELECT COUNT(*) c FROM beacon_hits WHERE beacon_id=? AND kind='hit'", (b["id"],)).fetchone()["c"] + probes = con.execute("SELECT COUNT(*) c FROM beacon_hits WHERE beacon_id=? AND kind='probe'", (b["id"],)).fetchone()["c"] + last = time.strftime("%b %d %H:%M", time.localtime(b["last_seen"])) if b["last_seen"] else "never" + probe, curl, _s, _t = _beacon_snippet(b["token"], b["interval_min"]) + port = (":" + str(b["port"])) if b["port"] else "" + trs += ("" + esc(b["label"]) + "
" + esc(port) + " · every " + str(b["interval_min"]) + "m" + "" + st + "" + "" + last + "" + "" + str(hits) + " (" + str(probes) + " beats)" + "log · " + "copy snippet · " + "install") + return ('
Your beacons
' + + (trs or '') + '
BeaconStatusLast seenHitsActions
none yet — create one above
') + +@app.route("/beacons") +def beacons_page(): + uid = current_user_id() + if not uid: + return page("beacon", '

PORT BEACON

Log in (free, no KYC) to see your beacons.

') + con = _beacon_db() + _beacon_check_silent(con, uid) + return page("beacon", "

BEACON BOARD

Live status of every heartbeat. LIVE means it called home on schedule; LATE means one interval missed; SILENT means 3+ missed and you've been alerted.

" + _beacon_list_html(uid)) + +@app.route("/beacon/create", methods=["POST"]) +def beacon_create(): + uid = current_user_id() + if not uid: + return page("beacon", '
login required — free, no KYC
') + r = rate_limit("beacon", 20, 60) + if r: return r + b = _beacon_create_core(uid) + resp = Response(status=302) + resp.headers["Location"] = "/beacon?new=" + b["token"] + return resp + +@app.route("/b/") +def beacon_probe(token): + con = _beacon_db() + b = con.execute("SELECT * FROM beacons WHERE token=?", (token,)).fetchone() + if not b: + return "Not Found", 404 + ip = request.headers.get("X-Real-IP") or request.remote_addr or "?" + ua = request.headers.get("User-Agent", "") + heartbeat = param("hb") == "1" or bool(_HEARTBEAT_UA.search(ua or "")) + kind = "probe" if heartbeat else "hit" + con.execute("INSERT INTO beacon_hits(beacon_id,ts,ip,ua,kind) VALUES(?,?,?,?,?)", + (b["id"], int(time.time()), ip, ua[:200], kind)) + con.execute("UPDATE beacons SET last_seen=? WHERE id=?", (int(time.time()), b["id"])) + was_silent = b["silent_notified"] + if was_silent: + con.execute("UPDATE beacons SET silent_notified=0 WHERE id=?", (b["id"],)) + con.commit() # commit BEFORE inbox writes (db-locked race) + if not heartbeat and b["user_id"]: + geo = enrich_ip(ip) or {} + where = "" + if geo: + where = " — %s, %s %s · %s" % (geo.get("city", ""), geo.get("regionName", ""), geo.get("countryCode", ""), geo.get("isp", "")) + m = db() + m.execute("INSERT INTO messages(user_id,sender,body,created) VALUES(?,?,?,?)", + (b["user_id"], "operator-bot", + "◆ BEACON TOUCHED: '%s%s' probe URL fetched by a non-heartbeat client — IP %s%s
device: %s" % + (esc(b["label"]), ((":%d" % b["port"]) if b["port"] else ""), esc(ip), esc(where), esc(ua[:100])), + int(time.time()))) + m.commit() + elif was_silent and b["user_id"]: + m = db() + m.execute("INSERT INTO messages(user_id,sender,body,created) VALUES(?,?,?,?)", + (b["user_id"], "operator-bot", + "◆ BEACON RECOVERED: '%s' heartbeat is back after going SILENT." % esc(b["label"]), + int(time.time()))) + m.commit() + if heartbeat: + return "ok", 200, {"Content-Type": "text/plain"} + # canary-style: look like nothing + return "Not Found", 404 + +@app.route("/beacon/events") +def beacon_events(): + uid = current_user_id() + token = param("token") or "" + if not uid: + return page("beacon", '
login required
') + con = _beacon_db() + b = con.execute("SELECT * FROM beacons WHERE token=? AND user_id=?", (token, uid)).fetchone() + if not b: + return page("beacon", '
unknown beacon
') + hits = con.execute("SELECT * FROM beacon_hits WHERE beacon_id=? ORDER BY id DESC LIMIT 100", (b["id"],)).fetchall() + trs = "" + for h in hits: + geo = {} + ip = (h["ip"] or "").strip() + if ip and not ip.startswith(("10.", "127.", "172.")): + geo = enrich_ip(ip) or {} + where = ("%s, %s" % (geo.get("city", "—"), geo.get("countryCode", ""))) if geo else "—" + ktag = 'beat' if h["kind"] == "probe" else 'HIT' + trs += ("" + time.strftime("%b %d %H:%M:%S", time.localtime(h["ts"])) + "" + ktag + "" + "" + esc(ip) + "" + esc(where) + "" + "" + esc((h["ua"] or "")[:90]) + "") + return page("beacon", """ +

BEACON EVENTS

Every touch on beacon """ + esc(b["label"]) + """ — heartbeats and canary hits.

+ +
""" + (trs or '') + """
WhenKindIPWhereClient
no events yet
""") + +@app.route("/api/beacon/create", methods=["POST"]) +def api_beacon_create(): + r = rate_limit("beacon", 20, 60) + if r: return r + uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "auth required: account session or Authorization: Bearer key"}), 401 + b = _beacon_create_core(uid) + probe, curl, svc, tim = _beacon_snippet(b["token"], b["interval_min"]) + return jsonify({"ok": True, "token": b["token"], "label": b["label"], "port": b["port"], + "interval_min": b["interval_min"], "probe_url": probe, + "install": {"curl": curl, "systemd_service": svc, "systemd_timer": tim}}) + +@app.route("/api/beacon/list") +def api_beacon_list(): + uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "auth required"}), 401 + con = _beacon_db() + _beacon_check_silent(con, uid) + out = [] + for b in con.execute("SELECT * FROM beacons WHERE user_id=? ORDER BY id DESC LIMIT 100", (uid,)).fetchall(): + st, _cls = _beacon_status(b) + hits = con.execute("SELECT COUNT(*) c FROM beacon_hits WHERE beacon_id=? AND kind='hit'", (b["id"],)).fetchone()["c"] + out.append({"token": b["token"], "label": b["label"], "port": b["port"], "interval_min": b["interval_min"], + "status": st, "last_seen": b["last_seen"], "canary_hits": hits, + "probe_url": SITE + "/b/" + b["token"]}) + return jsonify({"ok": True, "beacons": out}) + +@app.route("/api/beacon/status") +def api_beacon_status(): + uid = key_user() or current_user_id() + token = param("token") or "" + if not uid: + return jsonify({"ok": False, "error": "auth required"}), 401 + con = _beacon_db() + _beacon_check_silent(con, uid) + b = con.execute("SELECT * FROM beacons WHERE token=? AND user_id=?", (token, uid)).fetchone() + if not b: + return jsonify({"ok": False, "error": "unknown beacon"}), 404 + st, _cls = _beacon_status(b) + hits = con.execute("SELECT COUNT(*) c FROM beacon_hits WHERE beacon_id=? AND kind='hit'", (b["id"],)).fetchone()["c"] + return jsonify({"ok": True, "token": b["token"], "label": b["label"], "port": b["port"], + "interval_min": b["interval_min"], "status": st, "last_seen": b["last_seen"], + "silent_notified": b["silent_notified"], "canary_hits": hits}) +# ---------- END TOOL: PORT BEACON ---------- + + +# ---------- TOOL: BSSID RADAR ---------- + +BSSID_OSINT_BASE = "http://10.30.20.174:8080" +BSSID_OSINT_URL = BSSID_OSINT_BASE + "/api/run/bssid_geo?q=" + +def _norm_mac(raw): + """Normalize any MAC spelling to AA:BB:CC:DD:EE:FF (or None if invalid). + Accepts aa-bb-cc-dd-ee-ff, aabbccddeeff, AA.BB.CC..., 'aa bb cc', leading 0x.""" + if not raw: + return None + s = str(raw).strip().lower() + if s.startswith("0x"): + s = s[2:] + for ch in ":-. _/": + s = s.replace(ch, "") + if len(s) == 12 and all(c in "0123456789abcdef" for c in s): + return ":".join(s[i:i+2] for i in range(0, 12, 2)).upper() + return None + +def _oui(mac): + """Vendor OUI hint from the first 3 octets (registry-free, informational only).""" + return mac[:8] if mac else "" + +def _osint_bssid_lookup(mac): + """Call the LAN OSINT terminal's bssid_geo tool. Returns dict; never raises.""" + try: + url = BSSID_OSINT_URL + urllib.parse.quote(mac) + status, body = http(url, timeout=15) + if status == 0: + return {"ok": False, "error": "OSINT backend unreachable (" + body[:120] + ")"} + if status != 200: + return {"ok": False, "error": "OSINT backend returned HTTP " + str(status)} + data = jf(body) + if not isinstance(data, dict): + return {"ok": False, "error": "OSINT backend returned non-JSON response"} + out = {"ok": True, "mac": mac, "raw": data, "source": data.get("source", "osint-terminal bssid_geo")} + # location fields (mylnikov style): lat/lon or latitude/longitude, accuracy/range in meters + lat = data.get("lat", data.get("latitude")) + lon = data.get("lon", data.get("lon", data.get("lng", data.get("longitude")))) + acc = data.get("accuracy", data.get("range", data.get("radius"))) + if lat is not None and lon is not None: + try: + out["lat"] = round(float(lat), 6) + out["lon"] = round(float(lon), 6) + out["accuracy_m"] = float(acc) if acc is not None else None + out["found"] = True + except (TypeError, ValueError): + out["found"] = False + else: + out["found"] = False + out["wigle"] = data.get("wigle") + out["geo_db"] = data.get("geo_db") + out["ms"] = data.get("__ms") + return out + except Exception as e: + return {"ok": False, "error": "OSINT backend unreachable (" + esc(str(e)) + ")"} + +def _bssid_result_row(r): + """One result dict -> HTML table row (safe strings).""" + if not r.get("ok"): + return ("" + esc(r.get("mac", "?")) + "" + + esc(r.get("error", "lookup failed")) + "") + mac = esc(r["mac"]) + if r.get("found"): + lat, lon = r["lat"], r["lon"] + glink = "https://www.google.com/maps?q=" + str(lat) + "," + str(lon) + olink = "https://www.openstreetmap.org/?mlat=" + str(lat) + "&mlon=" + str(lon) + "#map=16/" + str(lat) + "/" + str(lon) + acc = ("~" + str(int(r["accuracy_m"])) + " m") if r.get("accuracy_m") is not None else "unknown" + loc = ('' + str(lat) + ", " + str(lon) + "
" + + 'Google Maps · ' + + 'OSM') + extra = esc(str(r.get("geo_db") or "")) + return ("" + mac + "" + _oui(r["mac"]) + "" + loc + "" + acc + "" + extra + "") + note = esc(str(r.get("geo_db") or "no location in free DB")) + wl = r.get("wigle") + if wl: + note += '
try WiGLE' + return ("" + mac + "" + _oui(r["mac"]) + "not located—" + note + "") + +@app.route("/bssid", methods=["GET", "POST"]) +def bssid_page(): + uid = current_user_id() + results = None + q_raw = param("mac") or param("macs") or "" + rows_raw = [x for x in str(q_raw).replace(";", "\n").replace(",", "\n").splitlines() if x.strip()] + if rows_raw: + r = rate_limit("bssid", 20, 60) + if r: + return r + results = [] + for line in rows_raw[:25]: + mac = _norm_mac(line) + if not mac: + results.append({"ok": False, "mac": str(line).strip()[:40], "error": "not a valid MAC (want aa:bb:cc:dd:ee:ff)"}) + else: + results.append(_osint_bssid_lookup(mac)) + table = "" + if results is not None: + table = ('
' + + "".join(_bssid_result_row(x) for x in results) + "
BSSIDOUILocationAccuracyDB / nearby hints
") + last = esc(q_raw) if q_raw else "" + body = f''' + +

BSSID RADAR

+

Turn a WiFi router's MAC address into an approximate place on Earth. Photo EXIF or a wifi scan gives you BSSIDs — this tool asks the OSINT terminal where those routers physically sit.

+
+
+RUN A LOOKUP +
+ + +
normalized: —
+ + + +
+
+
+WHAT YOU GET BACK +
    +
  • lat/lon + accuracy radius in meters (when the free DB knows the router)
  • +
  • one-click Google Maps and OpenStreetMap links
  • +
  • WiGLE fallback link + nearby-network hints when the DB comes up empty
  • +
  • vendor OUI (first 3 octets) per MAC for quick triage
  • +
+
+
+{table} +''' + how([ + "You feed in a BSSID — the hardware MAC of a WiFi access point, grabbed from a photo's EXIF wifi scan, aircrack output, or a phone's wifi list.", + "Your input is normalized: dashes, dots, spaces or no separator at all become AA:BB:CC:DD:EE:FF. Six hex pairs or it's rejected.", + "The server calls the LAN OSINT terminal's bssid_geo tool, which queries the Mylnikov free WiFi-geolocation database (crowdsourced router positions).", + "A hit returns latitude, longitude and an accuracy radius in meters — how tight the crowd-sourced fix is. Rural routers can be kilometers off; dense city fixes are often within 100 m.", + "A miss is not a dead end: you get a ready-made WiGLE search link (free account needed) plus the DB's message, and nearby-network hints when present.", + "Batch mode loops the same pipeline over up to 25 MACs and renders one comparison table.", + "Accuracy is approximate by nature — treat results as a hint to corroborate, never as evidence. Router MACs move when people reinstall hardware.", +]) + flow("place a router from a photo's WiFi scan", [ + 'you receive a photo whose EXIF wifi-scan block lists BSSID dc:39:6f:20:1d:70.', + 'you paste it into BSSID RADAR — typed as dc-39-6f-20-1d-70, the live preview confirms the normalized form.', + 'the tool queries the OSINT terminal; the Mylnikov DB has seen that router before and returns lat/lon with a ~130 m radius.', + 'the tool draws Google Maps / OSM links at that pin — you now know roughly where the photo was taken, no GPS tag required.', + 'you drop 5 more BSSIDs from the same scan into batch mode and cross-check the pins cluster in one neighborhood.', +]) + gloss([ + ("BSSID", "Basic Service Set Identifier — the MAC address of a WiFi access point's radio. Unique per router, visible in every wifi scan."), + ("MAC", "Media Access Control address — 6 hex pairs identifying network hardware. First 3 pairs (the OUI) identify the vendor."), + ("OUI", "Organizationally Unique Identifier — first 3 octets of a MAC, registered to the manufacturer (e.g. 00:25:9C ≈ a radio vendor)."), + ("Mylnikov DB", "Free crowdsourced database mapping WiFi BSSIDs to GPS coordinates, built from user-submitted wardrive logs."), + ("WiGLE", "Wireless Geographic Logging Engine — the largest public wardriving DB. Needs a free account to query."), + ("Accuracy radius", "Meters around the returned lat/lon where the router probably is. Crowdsourced fixes vary; small radius = many sightings."), + ("Wardriving", "Mapping WiFi networks while moving around, logging BSSID + GPS. Feeds the geolocation databases this tool reads."), +]) + agent_card("GET /api/bssid?mac=AA:BB:CC:DD:EE:FF · GET /api/bssid?macs=a;b;c", + "curl -s https://dark0rbits.thetempleofdoom.com/api/bssid?macs=AA:BB:CC:DD:EE:FF;11-22-33-44-55-66", + "Batch uses semicolons. Any MAC format accepted; server normalizes. Each result carries lat/lon/accuracy_m when found, wigle fallback link otherwise. Rate limit 20/min. JSON only.") + return page("hunt", body) + +@app.route("/api/bssid") +def bssid_api(): + r = rate_limit("bssid", 20, 60) + if r: + return r + macs = [] + raw = param("macs") or param("mac") or "" + for part in str(raw).replace(";", "\n").replace(",", "\n").splitlines(): + part = part.strip() + if part: + macs.append(part) + if not macs: + return jsonify({"ok": False, "error": "pass ?mac=AA:BB:CC:DD:EE:FF or ?macs=a;b;c (up to 25)"}), 400 + out = [] + for m in macs[:25]: + norm = _norm_mac(m) + if not norm: + out.append({"ok": False, "mac": m, "error": "invalid MAC (want aa:bb:cc:dd:ee:ff)"}) + continue + res = _osint_bssid_lookup(norm) + if res.get("ok") and res.get("found"): + lat, lon = res["lat"], res["lon"] + res["maps_google"] = "https://www.google.com/maps?q=" + str(lat) + "," + str(lon) + res["maps_osm"] = "https://www.openstreetmap.org/?mlat=" + str(lat) + "&mlon=" + str(lon) + "#map=16/" + str(lat) + "/" + str(lon) + out.append(res) + single = len(out) == 1 + return jsonify(out[0] if single else {"ok": True, "count": len(out), "results": out}) + +# ---------- END TOOL: BSSID RADAR ---------- + + +# ---------- TOOL: HOOK RELAY ---------- +import secrets as _sec + +HOOK_MAX_BODY = 32768 # 32KB body capture cap + +def _hook_tables(con): + con.executescript("""CREATE TABLE IF NOT EXISTS hook_endpoints(id INTEGER PRIMARY KEY, user_id INTEGER, token TEXT UNIQUE, label TEXT, created INTEGER); + CREATE TABLE IF NOT EXISTS hook_hits(id INTEGER PRIMARY KEY, endpoint_id INTEGER, ts INTEGER, method TEXT, ip TEXT, ua TEXT, ct TEXT, src TEXT, headers TEXT, query TEXT, body TEXT, truncated INTEGER DEFAULT 0);""") + return con + +def _hook_detect_src(hdrs, body): + """Guess which service sent this webhook from headers + body.""" + hl = {k.lower(): v for k, v in hdrs.items()} + if "stripe-signature" in hl: return "Stripe" + if "x-github-event" in hl or "x-github-delivery" in hl or "x-hub-signature" in hl: return "GitHub" + if "x-shopify-topic" in hl or "x-shopify-shop-domain" in hl or "x-shopify-hmac-sha256" in hl: return "Shopify" + if "x-telegram-bot-api-secret-token" in hl: return "Telegram" + ua = (hl.get("user-agent") or "").lower() + if ua.startswith("discordbot") or ua.startswith("discord"): return "Discord" + if "github-hookshot" in ua or "github-camel" in ua: return "GitHub" + j = jf(body) + if isinstance(j, dict): + if "update_id" in j: return "Telegram" + if "embeds" in j and "content" in j: return "Discord" + if j.get("object") in ("event", "checkout.session", "payment_intent") or j.get("livemode") is not None: return "Stripe" + if any(str(k).startswith("x_shopify") for k in j): return "Shopify" + return "" + +def _hook_target_guard(u): + """SSRF guard for replay targets. Returns error string or None if ok.""" + if not u or not str(u).strip(): return "target_url required" + u = str(u).strip() + if not re.match(r"^https?://", u): return "target_url must start with http:// or https://" + try: + host = urllib.parse.urlsplit(u).hostname or "" + except Exception: + return "cannot parse target_url" + if not host: return "target_url has no host" + try: + ipa = ipaddress.ip_address(host) + except ValueError: + try: + ipa = ipaddress.ip_address(socket.gethostbyname(host)) + except Exception: + return None # unresolvable here — let the fetcher report it + if ipa.is_private or ipa.is_loopback or ipa.is_link_local or ipa.is_reserved: + return "private/internal target blocked (SSRF guard: 10.x / 127. / 172.16-31 / 169.254 and friends)" + return None + +def _hook_new(uid, label): + con = _hook_tables(db()) + token = _sec.token_hex(10) + con.execute("INSERT INTO hook_endpoints(user_id,token,label,created) VALUES(?,?,?,?)", + (uid, token, (label or "")[:60], int(time.time()))) + con.commit() + return con.execute("SELECT * FROM hook_endpoints WHERE token=?", (token,)).fetchone() + +def _hook_hits_json(con, ep, limit=100): + rows = con.execute("SELECT * FROM hook_hits WHERE endpoint_id=? ORDER BY id DESC LIMIT ?", (ep["id"], limit)).fetchall() + out = [] + for r in rows: + d = dict(r) + d["ts_iso"] = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(r["ts"])) + " UTC" + out.append(d) + return out + +def _hook_replay(hit, target_url): + err = _hook_target_guard(target_url) + if err: + return {"ok": False, "error": err} + try: + hdrs = {k: v for k, v in (json.loads(hit["headers"] or "{}")).items() + if k.lower() not in ("host", "content-length", "connection", "accept-encoding", "cookie")} + except Exception: + hdrs = {} + if hit["ct"]: + hdrs["Content-Type"] = hit["ct"] + body = (hit["body"] or "").encode("utf-8", "replace") + st, txt = http(target_url, headers=hdrs, data=body if hit["method"] != "GET" else None, method=hit["method"]) + return {"ok": True, "target": target_url, "method": hit["method"], + "status": st, "response": txt[:2000], "replayed_at": int(time.time())} + +def _hook_pretty(body, ct): + """Pretty-print a captured body for the viewer.""" + j = jf(body) + if j is not None: + return esc(json.dumps(j, indent=2)) + return esc(body or "(empty body)") + +# ---- capture endpoint: NO auth, fast 200 ---- +@app.route("/hook/", methods=["GET", "POST", "PUT"]) +def hook_capture(token): + r = rate_limit("hookcapture", 120, 60) + if r: return r + con = _hook_tables(db()) + ep = con.execute("SELECT id FROM hook_endpoints WHERE token=?", (token,)).fetchone() + if not ep: + return "unknown hook token", 404, {"Content-Type": "text/plain"} + raw = request.get_data() or b"" + trunc = 1 if len(raw) > HOOK_MAX_BODY else 0 + body = raw[:HOOK_MAX_BODY].decode("utf-8", "replace") + hdrs = dict(request.headers.items()) + src = _hook_detect_src(hdrs, body) + hs = json.dumps(hdrs, indent=2) + ip = request.headers.get("X-Real-IP") or request.remote_addr or "" + ua = request.headers.get("User-Agent", "") + ct = request.headers.get("Content-Type", "") + q = (request.query_string or b"").decode("utf-8", "replace")[:2048] + con.execute("INSERT INTO hook_hits(endpoint_id,ts,method,ip,ua,ct,src,headers,query,body,truncated) VALUES(?,?,?,?,?,?,?,?,?,?,?)", + (ep["id"], int(time.time()), request.method, ip, ua, ct, src, hs, q, body, trunc)) + con.commit() + return "captured", 200, {"Content-Type": "text/plain"} + +# ---- human page ---- +@app.route("/hook/create", methods=["POST"]) +def hook_create_route(): + uid = current_user_id() + if not uid: + return page("hooks", "

HOOK RELAY

login required — log in to create webhook endpoints.
") + r = rate_limit("hookcreate", 20, 60) + if r: return r + _hook_new(uid, param("label")) + return Redirect("/hooks") + +@app.route("/hooks", methods=["GET", "POST"]) +def hooks_page(): + uid = current_user_id() + if not uid: + body = """ +

HOOK RELAY

Instant public webhook inspector. Create a capture URL, point any webhook at it, see exactly what arrives — headers, body, query, IP — and replay it anywhere. Login to create endpoints.

+
LOGIN REQUIRED Log in or sign up (10 seconds, no KYC) to create webhook endpoints.
""" + how([ + "Create an account, then make a hook endpoint — you get a unique URL like /hook/abc123.", + "Paste that URL into Stripe, GitHub, Shopify, Discord or Telegram webhook settings.", + "Every callback lands in your hit log: full headers, body (up to 32KB), query string, source IP and user-agent.", + "The source is auto-detected and badged — Stripe, GitHub, Discord, Shopify, Telegram.", + "Replay any captured hit to any public URL to retrigger an action or test a fix."]) + flow("debug why Stripe stopped calling my shop", [ + "you Stripe webhooks to your shop went quiet. Did Stripe stop sending, or is your handler 500-ing? You cannot tell from inside your app.", + "you Create a hook endpoint here labeled stripe-debug and copy the curl-ready URL.", + "you In the Stripe dashboard, add the hook URL as a second webhook endpoint for the same events.", + "stripe Next event fires — the hook catches it in under a second, badged STRIPE, full headers and signed payload intact.", + "you No hits at all? Stripe is not sending (check their delivery logs). Hits arriving? Your handler is the problem — inspect the exact payload.", + "you Fix your handler, then replay the captured hit at your live endpoint to verify without waiting for the next real payment."]) + gloss([ + ("webhook", "another server POSTs your URL when something happens — a payment, a push, an order"), + ("capture URL", "a unique throwaway endpoint that records everything it receives"), + ("replay", "resend a previously captured webhook body to any URL, with original headers"), + ("signature header", "Stripe-Signature / X-Hub-Signature — proves the sender, we keep it in the capture"), + ("SSRF guard", "replay targets on private networks (10.x, 127.x, 172.16-31, 169.254) are refused")]) + agent_card("POST /api/hook/create", 'curl -X POST ' + SITE + '/api/hook/create -d "label=stripe-debug" -H "Authorization: Bearer ***"', 'Returns {"ok":true,"url":".../hook/"}. Then GET /api/hook/list and GET /api/hook/hits?token=.') + return page("hooks", body) + msg = "" + con = _hook_tables(db()) + if request.method == "POST": + r = rate_limit("hookpage", 30, 60) + if r: return r + act = param("act") + if act == "create": + _hook_new(uid, param("label")) + msg = '
endpoint created
' + elif act == "del": + con.execute("DELETE FROM hook_hits WHERE endpoint_id IN (SELECT id FROM hook_endpoints WHERE id=? AND user_id=?)", (param("id"), uid)) + con.execute("DELETE FROM hook_endpoints WHERE id=? AND user_id=?", (param("id"), uid)) + con.commit() + msg = '
endpoint deleted
' + elif act == "clear": + con.execute("DELETE FROM hook_hits WHERE endpoint_id IN (SELECT id FROM hook_endpoints WHERE id=? AND user_id=?)", (param("id"), uid)) + con.commit() + msg = '
hits cleared
' + elif act == "replay": + hid = param("hit_id") or "" + tgt = param("target_url") or "" + h = con.execute("SELECT h.* FROM hook_hits h JOIN hook_endpoints e ON h.endpoint_id=e.id WHERE h.id=? AND e.user_id=?", (hid, uid)).fetchone() + if not h: + msg = '
hit not found
' + else: + res = _hook_replay(h, tgt) + if res.get("ok"): + msg = '
REPLAY RESULT — ' + str(res["status"]) + ' from ' + esc(res["target"]) + '
' + esc(res.get("response", "")[:800]) + '
' + else: + msg = '
REPLAY BLOCKED ' + esc(res.get("error", "")) + '
' + eps = con.execute("SELECT * FROM hook_endpoints WHERE user_id=? ORDER BY id DESC", (uid,)).fetchall() + cards = "" + for ep in eps: + hits = _hook_hits_json(con, ep, 50) + url = SITE + "/hook/" + ep["token"] + cmd = "curl -X POST " + url + " -H 'Content-Type: application/json' -d '{\"hello\":\"world\"}'" + hitview = "" + for h in hits: + badge = ('' + esc(h["src"]) + ' ') if h["src"] else "" + trunc = ' TRUNCATED' if h["truncated"] else "" + hitview += ('
' + badge + + '' + esc(h["method"]) + ' · ' + esc(h["ts_iso"]) + ' · ' + esc(h["ip"]) + trunc + '' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
source' + esc(h["src"] or "unknown") + '
content-type' + esc(h["ct"] or "—") + '
user-agent' + esc(h["ua"] or "—") + '
query' + esc(h["query"] or "—") + '
headers
' + esc(h["headers"]) + '
body
' + _hook_pretty(h["body"], h["ct"]) + '
' + + '
' + + '
' + + '
') + if not hitview: + hitview = '
no hits yet — send something at the URL above
' + cards += ('

' + esc(ep["label"] or "unlabeled hook") + '

' + + '' + url + '' + + ' ' + + ' ' + str(len(hits)) + ' hits' + + '
curl: ' + esc(cmd) + '
' + + '
' + + '
' + + 'hits' + hitview + '
') + if not eps: + cards = '
No endpoints yet — create your first hook above.
' + body = f""" +

HOOK RELAY

Public webhook inspector. Each endpoint is a throwaway URL that records everything sent to it — headers, body, query, IP — auto-detects the sender, and can replay any hit to any URL.

+{msg} +
New endpoint +
+
{cards}
""" + how([ + "Create a hook — you instantly get a unique URL like " + SITE + "/hook/abc123def456.", + "Point any webhook at it: Stripe, GitHub, Discord, Shopify, Telegram, or curl by hand. GET, POST and PUT, any content-type.", + "The capture URL has NO login — webhooks come from outside servers, so it must answer 200 to anyone. Keep the URL secret-ish; only you can view the hits.", + "Bodies over 32KB are truncated (and flagged) so a giant payload cannot flood your log.", + "Each hit shows source badge, IP, user-agent, full headers, query string and a pretty-printed JSON body.", + "Replay sends the exact captured body + headers to any public URL — private targets (10.x, 127.x, 172.16-31, 169.254) are refused.", + "Agents: same everything over JSON — /api/hook/create, /api/hook/list, /api/hook/hits, /api/hook/replay."]) + flow("debug why Stripe stopped calling my shop", [ + "you Payments complete but your shop never marks orders paid. Is Stripe sending? Is your handler crashing? Blind either way.", + "you Create a hook labeled stripe-debug, copy the curl line, paste the URL into Stripe as a second webhook endpoint.", + "stripe The next payment fires — the hit lands instantly, badged STRIPE (detected from the Stripe-Signature header), payload fully intact.", + "you Zero hits = Stripe-side problem (check their delivery log). Hits present = read the exact JSON, find what your handler choked on.", + "you Ship the fix, then hit replay to fire that same signed payload at your live endpoint — verified without waiting for a real customer."]) + gloss([ + ("capture URL", "unique unguessable URL (/hook/) that records every request it receives"), + ("source badge", "auto-detected sender: Stripe, GitHub, Discord, Shopify or Telegram"), + ("replay", "resend a captured body with original headers to any public URL"), + ("truncation", "bodies over 32KB are cut and flagged — protection against payload floods"), + ("SSRF guard", "replay refuses internal addresses so the relay cannot probe your LAN")]) + agent_card("POST /api/hook/create · GET /api/hook/list · GET /api/hook/hits?token= · POST /api/hook/replay", 'curl -X POST ' + SITE + '/api/hook/create -d "label=stripe-debug" -H "Authorization: Bearer ***"', 'Full JSON lifecycle: create, list, inspect hits, replay (hit_id + target_url).') + return page("hooks", body) + +# ---- JSON API ---- +@app.route("/api/hook/create", methods=["POST"]) +def api_hook_create(): + r = rate_limit("hookapi", 20, 60) + if r: return r + uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "auth required: account session (sign up at /inbox, no KYC) or Authorization: Bearer *** key"}), 401 + ep = _hook_new(uid, jp("label") or param("label")) + return jsonify({"ok": True, "token": ep["token"], "url": SITE + "/hook/" + ep["token"], "capture_path": "/hook/" + ep["token"], "label": ep["label"], "page": SITE + "/hooks"}) + +@app.route("/api/hook/list") +def api_hook_list(): + uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "auth required"}), 401 + con = _hook_tables(db()) + eps = con.execute("SELECT * FROM hook_endpoints WHERE user_id=? ORDER BY id DESC", (uid,)).fetchall() + out = [] + for ep in eps: + n = con.execute("SELECT COUNT(*) c FROM hook_hits WHERE endpoint_id=?", (ep["id"],)).fetchone()["c"] + last = con.execute("SELECT MAX(ts) m FROM hook_hits WHERE endpoint_id=?", (ep["id"],)).fetchone()["m"] + out.append({"token": ep["token"], "label": ep["label"], "created": ep["created"], + "url": SITE + "/hook/" + ep["token"], "hits": n, "last_hit": last}) + return jsonify({"ok": True, "endpoints": out}) + +@app.route("/api/hook/hits") +def api_hook_hits(): + uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "auth required"}), 401 + tok = param("token") or "" + con = _hook_tables(db()) + ep = con.execute("SELECT * FROM hook_endpoints WHERE token=? AND user_id=?", (tok, uid)).fetchone() + if not ep: + return jsonify({"ok": False, "error": "unknown token"}), 404 + limit = 100 + try: + limit = max(1, min(500, int(param("limit") or 100))) + except Exception: + pass + hits = _hook_hits_json(con, ep, limit) + for h in hits: + try: + h["headers_json"] = json.loads(h["headers"] or "{}") + except Exception: + h["headers_json"] = {} + return jsonify({"ok": True, "token": tok, "count": len(hits), "hits": hits}) + +@app.route("/api/hook/replay", methods=["POST"]) +def api_hook_replay(): + r = rate_limit("hookreplay", 10, 60) + if r: return r + uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "auth required"}), 401 + hid = jp("hit_id") or param("hit_id") + tgt = jp("target_url") or param("target_url") + if not hid or not tgt: + return jsonify({"ok": False, "error": "hit_id and target_url required"}), 400 + con = _hook_tables(db()) + h = con.execute("SELECT h.* FROM hook_hits h JOIN hook_endpoints e ON h.endpoint_id=e.id WHERE h.id=? AND e.user_id=?", (hid, uid)).fetchone() + if not h: + return jsonify({"ok": False, "error": "hit not found (or not yours)"}), 404 + res = _hook_replay(h, tgt) + if not res.get("ok"): + return jsonify(res), 400 + return jsonify(res) +# ---------- END TOOL: HOOK RELAY ---------- + + +# ---------- TOOL: FACE TRACE ---------- +import io as _io +from PIL import Image as _PILImage, ImageOps as _PILImageOps +from concurrent.futures import ThreadPoolExecutor as _TPE + +def _face_db(): + con = db() + con.execute("CREATE TABLE IF NOT EXISTS face_cache(key TEXT PRIMARY KEY, img BLOB, sha256 TEXT, ts INTEGER)") + con.commit() + return con + +def _hamming(h1, h2): + n = max(len(h1), len(h2)) * 4 + try: + return bin(int(h1, 16) ^ int(h2, 16))[2:].zfill(n).count("1") + except Exception: + return 999 + +def dhash(img): + """dHash: grayscale 9x8 (w=9,h=8), compare horizontally adjacent pixels → 64-bit.""" + g = _PILImageOps.grayscale(img).resize((9, 8)) + px = list(g.getdata()) + bits = 0 + for r in range(8): + row = px[r * 9:(r + 1) * 9] + for c in range(8): + bits = (bits << 1) | (1 if row[c] > row[c + 1] else 0) + return f"{bits:016x}" + +def ahash(img): + """Average hash: 8x8 grayscale, bit = pixel > mean.""" + g = _PILImageOps.grayscale(img).resize((8, 8)) + px = list(g.getdata()) + m = sum(px) / 64.0 + bits = 0 + for p in px: + bits = (bits << 1) | (1 if p > m else 0) + return f"{bits:016x}" + +def _pfp_hashes(blob): + """Hashes for a raw image blob: {'sha256','dhash','ahash'} or {'error':...}""" + out = {"sha256": hashlib.sha256(blob).hexdigest(), "bytes": len(blob)} + try: + img = _PILImage.open(_io.BytesIO(blob)) + img.load() + out["dhash"] = dhash(img) + out["ahash"] = ahash(img) + out["format"] = (img.format or "?").lower() + out["size"] = list(img.size) + except Exception as e: + out["error"] = f"not an image: {e}"[:160] + return out + +def _fcache_get(key): + con = _face_db() + r = con.execute("SELECT img, sha256, ts FROM face_cache WHERE key=?", (key,)).fetchone() + if r and (int(time.time()) - r["ts"]) < 3600: + return bytes(r["img"]) + return None + +def _fcache_put(key, blob): + con = _face_db() + con.execute("INSERT OR REPLACE INTO face_cache(key,img,sha256,ts) VALUES(?,?,?,?)", + (key, sqlite3.Binary(blob), hashlib.sha256(blob).hexdigest(), int(time.time()))) + con.commit() + +def _fetch_img(url): + """Fetch an image URL → (bytes|None, note). 1h sqlite cache.""" + ck = "url:" + hashlib.sha256(url.encode()).hexdigest()[:32] + c = _fcache_get(ck) + if c is not None: + return c, "cache" + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (Dark0rbits toolbox)"}) + try: + with urllib.request.urlopen(req, timeout=10, context=_CTX) as r: + blob = r.read(6 * 1024 * 1024) + if len(blob) < 64: + return None, f"too small ({len(blob)}b)" + _fcache_put(ck, blob) + return blob, "fetched" + except Exception as e: + return None, str(e)[:120] + +def pfp_targets(u): + """Avatar source plan for a username: resolvers and constructed URLs.""" + u = u.strip().lstrip("@") + q = urllib.parse.quote(u) + return [ + {"platform": "GitHub", "kind": "resolve", + "api": f"https://api.github.com/users/{q}", + "extract": lambda j: (j.get("avatar_url") or "") if isinstance(j, dict) else "", + "profile": f"https://github.com/{q}"}, + {"platform": "Reddit", "kind": "resolve", + "api": f"https://www.reddit.com/{q}/about.json", + "extract": lambda j: (((j.get("data") or {}).get("icon_img") or "")) if isinstance(j, dict) and j.get("data") else "", + "profile": f"https://www.reddit.com/user/{q}"}, + {"platform": "Telegram", "kind": "construct", + "img": "https://t.me/" + q, + "note": "manual / constructed: t.me profile — grab photo from the page", + "profile": f"https://t.me/{q}"}, + {"platform": "Steam", "kind": "construct", + "img": "https://steamcommunity.com/id/{u}/".replace("{u}", q), + "note": "manual / constructed: Steam profile — XML API has ", + "profile": "https://steamcommunity.com/id/" + q}, + {"platform": "Twitch", "kind": "construct", + "img": "https://www.twitch.tv/" + q, + "note": "manual / constructed: profile page (avatars need a client-id)", + "profile": f"https://www.twitch.tv/{q}"}, + ] + +def pfp_harvest(u, target_hashes=None): + """Resolve + download avatars for username u; compare with target hashes.""" + results = [] + for t in pfp_targets(u): + entry = {"platform": t["platform"], "profile": t["profile"], "status": "no avatar", + "hashes": None, "match": None, "distances": {}} + img_url = "" + if t["kind"] == "resolve": + st, body = http(t["api"], timeout=10) + j = jf(body) + img_url = t["extract"](j) if j else "" + entry["http"] = st + if not img_url: + entry["status"] = "not found" + else: + entry["status"] = "constructed link" + entry["note"] = t["note"] + if img_url: + blob, note = _fetch_img(img_url) + if blob: + h = _pfp_hashes(blob) + entry.update({"status": "ok", "img_url": img_url, "source": note, "hashes": h}) + if target_hashes and not h.get("error"): + dd = min(_hamming(h["dhash"], target_hashes["dhash"]), + _hamming(h["ahash"], target_hashes["ahash"])) + entry["distances"] = {"dhash": _hamming(h["dhash"], target_hashes["dhash"]), + "ahash": _hamming(h["ahash"], target_hashes["ahash"])} + entry["match"] = "likely same image" if dd <= 10 else "different image" + else: + entry["status"] = f"download failed ({note})" + results.append(entry) + return {"ok": True, "username": u.strip().lstrip("@"), "targets": results} + +def face_search_leads(): + return [ + ("Google Lens", "https://lens.google.com/uploadbyurl?url=", "Google's reverse-image search — strongest for lookalikes and crops"), + ("Yandex Images", "https://yandex.com/images/search?rpt=imageview&url=", "best face recall of the public engines, especially EU/RU web"), + ("Bing Visual Search", "https://www.bing.com/images/search?view=detailv2&iss=sbi&q=imgurl:", "Microsoft visual search — good LinkedIn / social recall"), + ("TinEye", "https://tineye.com/search?url=", "exact-copy finder — best for 'where did this exact file appear first'"), + ] + +@app.route("/face", methods=["GET", "POST"]) +def face_tool(): + uid = current_user_id() + if not uid: + return page("face", '

FACE TRACE

Login first — this tool is for accounts. Free, no KYC: log in / sign up.

') + r = rate_limit("facepage", 20, 60) + if r: return r + res = "" + up_hashes = None + err = "" + f = request.files.get("img") + username = (param("u") or "").strip().lstrip("@") + if request.method == "POST": + if not f and not username: + err = "give me an image and/or a username" + if f: + blob = f.read(6 * 1024 * 1024) + if len(blob) < 64: + err = "file too small / empty" + else: + h = _pfp_hashes(blob) + if h.get("error"): + err = h["error"] + else: + up_hashes = h + up_rows = [("sha256", f"{h['sha256']}"), ("dHash (8x8)", f"{h['dhash']}"), + ("aHash (8x8)", f"{h['ahash']}"), ("format / size", f"{h.get('format')} {h.get('size')} · {h['bytes']} bytes")] + if username: + up_rows.append(("comparing against", f"avatars of {esc(username)}")) + res += kv(up_rows) + if username and not (len(username) >= 2 and len(username) <= 60 and not any(c in "<>\"'/" for c in username)): + if not err: err = "bad username (2-60 chars, no slashes/html)" + username = "" + if username: + hv = pfp_harvest(username, up_hashes) + rows = "" + for t in hv["targets"]: + if t["status"] == "ok": + dd = t["distances"] + if t["match"]: + m = ('MATCH ' + esc(t["match"]) + + f" · d {dd['dhash']} / a {dd['ahash']}") + else: + m = "—" + rows += (f"{esc(t['platform'])}avatar" + f"{esc((t['hashes'].get('dhash') or ''))}{m}" + f"profile" + f" · img") + elif t["status"] == "constructed link": + rows += (f"{esc(t['platform'])}constructed" + f"—manual leadopen profile → grab photo by hand") + else: + rows += (f"{esc(t['platform'])}{esc(t['status'])}" + f"——profile") + res += (f'
Avatar harvest — {esc(username)}' + f'
{rows}
PlatformStatusdHashVerdictLinks
' + '
Verdict rule: best Hamming distance (dHash or aHash) ≤ 10 of 64 bits = likely same image. Cache: 1h sqlite.
') + if up_hashes and not username: + res += '
Reverse-image leads
dark0rbits never calls a reverse-image API for you — open these yourself and upload the file:

' + "
".join(f'▸ {esc(nm)} → {esc(d)}' for nm, base, d in face_search_leads()) + "
" + if err: + res = f'
{esc(err)}
' + res + body = f""" +

FACE TRACE

+

Profile-picture triangulation without reverse-image APIs. Hash an avatar (dHash + aHash + sha256), harvest avatars a username uses across platforms, and let Hamming distance tell you whether it's the same picture — same person behind 4 different usernames?

+
+
+ + + + +
+
+
Image alone = hashes + manual search leads. Username alone = avatar harvest + hashes. Both = harvest AND compare against your upload (Hamming ≤ 10 = likely match).
+
+{res} +""" + how([ + "Upload the avatar you already have — a forum pic, a Telegram photo, anything.", + "The image is fingerprinted three ways: dHash (9x8 grayscale, adjacent-pixel compares), aHash (8x8 vs mean) and plain sha256 — all computed locally, nothing uploaded anywhere.", + "Give a username too, and the tool fetches that handle's real avatars: GitHub and Reddit via their public JSON APIs, plus constructed profile links for Telegram, Steam and Twitch.", + "Every fetched avatar gets the same fingerprints, and Hamming distance (bits differing out of 64) is computed against your upload: ≤ 10 = likely the same image.", + "No image ever goes to Google, Yandex, Bing or TinEye. Instead you get direct upload links to all four — you decide when to escalate.", + "Downloads are cached in sqlite for an hour, so re-running a trace is fast and doesn't hammer anyone's API.", + "Judgment stays yours: same picture is strong evidence, but people reuse stock photos. dHash says 'same image', not 'same human'.", +]) + body += flow("same person behind 4 different usernames?", [ + "youA scammer contacts you from ghostuser42 with a friendly avatar. Screenshot it.", + "youUpload the avatar here, type ghostuser42, hit TRACE. GitHub and Reddit avatars get fetched and hashed.", + "toolVerdict table: Reddit avatar MATCH — d 4/64. GitHub avatar: different image.", + "youRun TRACE on two other handles the same person used. Reddit matches again — same source photo, different display names.", + "youEscalate: open the Google Lens / Yandex leads with the original file to find where the photo first appeared.", + "youConclusion: four usernames, one face. That's your triangulation — no reverse-image API ever saw the picture.", + ]) + body += gloss([ + ("dHash", "difference hash: resize to 9x8 grayscale, compare each pixel with its right neighbor → 64 bits that survive resizing and compression"), + ("aHash", "average hash: 8x8 grayscale, each bit = pixel brighter than the mean"), + ("Hamming distance", "how many of the 64 bits differ between two hashes — 0 = identical image, ≤ 10 = likely same picture, 32 = unrelated"), + ("perceptual hash", "a fingerprint of what an image LOOKS like, not its bytes — crops and re-encodes still match; sha256 only matches exact copies"), + ("avatar harvest", "collecting the profile pictures a username currently uses, from public profile APIs"), + ]) + body += agent_card('POST /api/face (multipart image and/or u=username)', + 'curl -F "img=@avatar.png" -F "u=ghostuser42" https://dark0rbits.thetempleofdoom.com/api/face', + 'Returns sha256/dHash/aHash of your upload + per-platform harvest with distances and verdict. Auth: session or Bearer key.') + return page("face", body) + +@app.route("/api/face", methods=["GET", "POST"]) +def api_face(): + r = rate_limit("face", 20, 60) + if r: return r + uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "auth required: account session or API key"}), 401 + f = request.files.get("img") + username = ((jp("u") if request.is_json else None) or param("u") or "").strip().lstrip("@") + if not f and not username: + return jsonify({"ok": False, "error": "provide multipart image and/or u=username"}), 400 + out = {"ok": True} + tgt = None + if f: + blob = f.read(6 * 1024 * 1024) + if len(blob) < 64: + return jsonify({"ok": False, "error": "image too small/empty"}), 400 + h = _pfp_hashes(blob) + if h.get("error"): + return jsonify({"ok": False, "error": h["error"]}), 400 + out["uploaded"] = h + tgt = h + if username: + if not (2 <= len(username) <= 60) or any(c in "<>\"'/" for c in username): + return jsonify({"ok": False, "error": "bad username"}), 400 + out["harvest"] = pfp_harvest(username, tgt) + if f or not username: + out["leads"] = [{"name": n, "url": b, "note": d} for n, b, d in face_search_leads()] + return jsonify(out) +# ---------- END TOOL: FACE TRACE ---------- + + +# ---------- TOOL: ROTATOR ---------- +import random as _rnd +import json as _json + +_ROTATOR_DB_READY = False + +def _rotator_db(con): + global _ROTATOR_DB_READY + if not _ROTATOR_DB_READY: + con.execute("CREATE TABLE IF NOT EXISTS rotator_history(id INTEGER PRIMARY KEY, user_id INTEGER, ts INTEGER, ua TEXT, platform TEXT, seed TEXT)") + con.commit() + _ROTATOR_DB_READY = True + +ROTATOR_PLATFORMS = { + "desktop": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15", + "Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0", + ], + "mobile": [ + "Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", + "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36", + "Mozilla/5.0 (iPad; CPU OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1", + ], + "agent": [ + "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", + "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", + "curl/8.4.0", + "Wget/1.21.4 (linux-gnu)", + "python-urllib/3.11", + ], + "stealth": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + ], +} +ROTATOR_REFERER_POOL = [ + "https://www.google.com/", "https://duckduckgo.com/", "https://news.ycombinator.com/", + "https://www.bing.com/", "https://www.reddit.com/", "(direct)", +] +ROTATOR_LANG_POOL = ["en-US,en;q=0.9", "en-GB,en;q=0.8", "de-DE,de;q=0.9,en;q=0.5", "fr-FR,fr;q=0.9", "ja-JP,ja;q=0.8"] + +def rotator_identity(platform, rng): + ua = rng.choice(ROTATOR_PLATFORMS.get(platform, ROTATOR_PLATFORMS["desktop"])) + return { + "user_agent": ua, + "referer": rng.choice(ROTATOR_REFERER_POOL), + "accept_language": rng.choice(ROTATOR_LANG_POOL), + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "sec_ch_ua_mobile": "?1" if platform == "mobile" else "?0", + "dnt": rng.choice(["1", "0", "(none)"]), + } + +def rotator_spin(platform, n, seed): + platform = platform if platform in ROTATOR_PLATFORMS else "desktop" + try: + n = max(1, min(int(n), 25)) + except Exception: + n = 5 + rng = _rnd.Random(str(seed)) if seed else _rnd.Random() + ids = [rotator_identity(platform, rng) for _ in range(n)] + curl = 'curl -A "' + ids[0]["user_agent"] + '"' + if ids[0]["referer"] != "(direct)": + curl += ' -e "' + ids[0]["referer"] + '"' + curl += ' -H "Accept-Language: ' + ids[0]["accept_language"] + '" https://target.example/' + return {"ok": True, "platform": platform, "count": len(ids), "seed": seed or None, "identities": ids, "curl_first": curl} + +@app.route("/rotator", methods=["GET", "POST"]) +def rotator_page(): + uid = current_user_id() + platform = param("platform") or "desktop" + if platform not in ROTATOR_PLATFORMS: + platform = "desktop" + n = param("n") or "5" + seed = (param("seed") or "").strip()[:64] + res = "" + if request.method == "POST": + r = rate_limit("rotator", 20, 60) + if r: + return r + d = rotator_spin(platform, n, seed) + try: + con = db() + _rotator_db(con) + for ident in d["identities"][:5]: + con.execute("INSERT INTO rotator_history(user_id,ts,ua,platform,seed) VALUES(?,?,?,?,?)", + (uid or 0, int(time.time()), ident["user_agent"], platform, seed or "")) + con.commit() + except Exception: + pass + rows = "" + for i, ident in enumerate(d["identities"], 1): + rows += ("" + str(i) + "" + esc(ident["user_agent"]) + "" + + esc(ident["referer"]) + "" + esc(ident["accept_language"]) + "" + + esc(ident["dnt"]) + "") + res = ('
' + str(d["count"]) + " rotated identities — " + esc(d["platform"]) + " pool" + + (" · seed " + esc(seed) + " (replayable)" if seed else "") + "" + + '
' + + rows + "
#User-AgentRefererAccept-LanguageDNT
" + + '

First one as curl: ' + esc(d["curl_first"]) + "

") + body = f""" +

HEADER ROTATOR

Spin consistent browser identities — User-Agent, referer, language, DNT — from four pools. Same seed replays the exact same rotation every time. No logs kept beyond your own history.

+
+ + + + + + +
+{res} +
API: GET /api/rotator?platform=mobile&n=10&seed=abc → JSON identities + ready-made curl.
""" + how([ + "Pick a pool: desktop, mobile, agent or stealth — each holds real-world header strings.", + "Choose how many identities to spin, 1 to 25 per call.", + "Give it a seed and the rotation becomes deterministic — the same seed always replays the same identities in the same order.", + "Leave the seed blank for a fresh random rotation every call.", + "Every identity is a full consistent set: UA, referer, Accept-Language, DNT — not just a UA string.", + "The first identity comes back as a ready-to-paste curl command.", + "Agents: GET /api/rotator with the same params, JSON out, rate-limited 20/min.", + "Nothing is stored except the last few spins in your own account history.", + ]) + flow("Rotating through a scrape run", [ + 'you set pool=mobile, n=10, seed=run-42 and hit Spin.', + 'The lab hands back 10 consistent identities — UA, referer, language, DNT matched per identity.', + 'you copy the curl line for the first one or call /api/rotator from your script.', + 'The target sees ten different plausible visitors instead of one hammering client.', + 'Re-run with the same seed later to reproduce the exact rotation for debugging.', + ]) + gloss([ + ("rotation", "cycling through a pool of values so no single fingerprint repeats too often"), + ("seed", "a string fed to the RNG — same seed, same sequence, every time"), + ("DNT", "Do-Not-Track header — 0, 1 or absent, randomized per identity"), + ("consistent identity", "UA + referer + language that plausibly belong to the same browser"), + ]) + agent_card('GET /api/rotator?platform=mobile&n=10&seed=abc', + 'curl "https://dark0rbits.thetempleofdoom.com/api/rotator?platform=mobile&n=10&seed=abc"', + 'JSON: identities[] with user_agent, referer, accept_language, dnt + curl_first. Rate limit 20/min.') + return page("rotator", body) + +@app.route("/api/rotator", methods=["GET", "POST"]) +def api_rotator(): + r = rate_limit("rotator", 20, 60) + if r: + return r + platform = (param("platform") or "desktop").strip().lower() + if platform not in ROTATOR_PLATFORMS: + return jsonify({"ok": False, "error": "platform must be one of: " + ", ".join(sorted(ROTATOR_PLATFORMS))}), 400 + seed = (param("seed") or "").strip()[:64] + d = rotator_spin(platform, param("n") or "1", seed) + return jsonify(d) + +@app.route("/api/rotator/pools") +def api_rotator_pools(): + return jsonify({"ok": True, "pools": {k: len(v) for k, v in ROTATOR_PLATFORMS.items()}, + "referers": len(ROTATOR_REFERER_POOL), "languages": len(ROTATOR_LANG_POOL)}) +# ---------- END TOOL: ROTATOR ---------- + + +# ---------- TOOL: IDENTITY SHELF ---------- +import time as _shelf_time + +def _shelf_data(uid): + con = db() + now = int(_shelf_time.time()) + out = {} + out["mailboxes"] = [dict(r) for r in con.execute( + "SELECT address, expires, cnt, paid FROM mailboxes WHERE user_id=? ORDER BY (expires>0), expires LIMIT 50", (uid,)).fetchall()] + out["sms"] = [dict(r) for r in con.execute( + "SELECT phone, service, expires, status FROM sms_rentals WHERE user_id=? AND expires>0 ORDER BY expires LIMIT 50", (uid,)).fetchall()] + out["deaddrops"] = [dict(r) for r in con.execute( + "SELECT token, expires, reads_left, burn_after FROM deadrops WHERE user_id=? ORDER BY (expires>0), expires LIMIT 50", (uid,)).fetchall()] + out["canaries"] = [] + for r in con.execute("SELECT token, tag, armed, rearm FROM canaries WHERE user_id=? ORDER BY id DESC LIMIT 50", (uid,)).fetchall(): + d = dict(r) + d["hits"] = con.execute("SELECT COUNT(*) c FROM canary_hits WHERE canary_id=(SELECT id FROM canaries WHERE token=?)", (r["token"],)).fetchone()["c"] + out["canaries"].append(d) + out["trackables"] = [dict(r) for r in con.execute( + "SELECT token, filename, paid, created FROM trackables WHERE user_id=? ORDER BY id DESC LIMIT 50", (uid,)).fetchall()] + # stats + live = lambda e: e and e > now + n_live = sum(1 for m in out["mailboxes"] if m["paid"] and live(m["expires"])) \ + + sum(1 for s in out["sms"] if live(s["expires"])) \ + + sum(1 for d in out["deaddrops"] if live(d["expires"])) + expiring = sorted([e for grp in ("mailboxes", "sms", "deaddrops") for e in + (x["expires"] for x in out[grp] if live(x.get("expires"))) if e]) + out["stats"] = { + "live_identities": n_live, + "next_expiry": expiring[0] if expiring else None, + "total_mail_received": sum(m["cnt"] or 0 for m in out["mailboxes"]), + "armed_traps": sum(1 for c in out["canaries"] if c["armed"]), + "total_trap_hits": sum(c["hits"] for c in out["canaries"]), + } + return out + +def _shelf_badge(expires): + now = int(_shelf_time.time()) + if not expires: + return 'no expiry' + left = expires - now + when = _shelf_time.strftime("%b %d %H:%M", _shelf_time.localtime(expires)) + if left <= 0: + return f'EXPIRED {when}' + cls = "bad" if left < 86400 else "ok" + unit = "d" if left >= 86400 else "h" + val = left // 86400 if left >= 86400 else left // 3600 + return f'{val}{unit} left · {when}' + +@app.route("/shelf") +def shelf(): + uid = current_user_id() + if not uid: + return page("shelf", """

IDENTITY SHELF

One dashboard for every burner you own — mailboxes, numbers, dead-drops, traps — with live countdowns so nothing dies silently.

+""" + how([ +"Every burner on dark0rbits has a lifespan — mailboxes expire, rentals run out, dead-drops burn.", +"The shelf lists all of yours in one place with live countdown badges.", +"Under 24 hours left, a badge turns red — renew or replace before it dies.", +"Expired items stay listed so you can clean up or recreate them.", +"Agents: GET /api/shelf returns the same data as JSON for monitoring."])) + d = _shelf_data(uid) + def rows_mail(): + if not d["mailboxes"]: return 'none' + return "".join(f"{esc(m['address'])}{m['cnt'] or 0}" + f"{_shelf_badge(m['expires'])}open" + for m in d["mailboxes"]) + def rows_sms(): + if not d["sms"]: return 'none' + return "".join(f"{esc(s['phone'])}{esc(s['service'])}" + f"{_shelf_badge(s['expires'])}{esc(s['status'] or '')}" for s in d["sms"]) + def rows_dd(): + if not d["deaddrops"]: return 'none' + return "".join(f"…{esc(t['token'][-6:])}{t['reads_left']}/{t['burn_after']} reads left" + f"{_shelf_badge(t['expires'])}open" + for t in d["deaddrops"]) + def rows_can(): + if not d["canaries"]: return 'none' + return "".join(f"{esc(c['tag'])}{c['hits']}" + f"{'armed' + (' ⟳' if c['rearm'] else '') + '' if c['armed'] else 'triggered'}" + f"hits" + for c in d["canaries"]) + st = d["stats"] + nxt = _shelf_time.strftime("%b %d %H:%M", _shelf_time.localtime(st["next_expiry"])) if st["next_expiry"] else "—" + body = f""" +

IDENTITY SHELF

Everything you own that can die, on one page — with live countdowns. Know when every burner expires before it expires.

+{kv([("Live identities", f"{st['live_identities']} mailboxes + numbers + drops"), + ("Next to expire", f"{nxt}"), ("Mail received (all time)", st["total_mail_received"]), + ("Armed traps", f"{st['armed_traps']} armed · {st['total_trap_hits']} total hits")])} +
BURNER MAILBOXES
{rows_mail()}
AddressMailLife
+
SMS NUMBERS
{rows_sms()}
NumberServiceLifeStatus
+
DEAD-DROPS
{rows_dd()}
TokenBurnsLife
+
CANARY TRAPS
{rows_can()}
TagHitsStatus
+""" + how([ +"Every burner has a lifespan — this page lists all of yours with a countdown badge per item.", +"Badges tick live (every 30s); the static expiry date renders even without JS.", +"Green = over 24h left. Red = under 24h or already dead — renew or replace.", +"Quick links jump straight to each item: mailbox, dead-drop, trap hit log.", +"Agents: GET /api/shelf returns identical JSON — wire it into a cron and get paged before anything dies."]) + body += flow("never lose a burner to the clock again", [ +"you run 3 burner mailboxes for signups and 2 SMS numbers for verifications.", +"you open the shelf once a morning: five green badges, everything alive.", +"Thursday: one mailbox badge is red — 6h left. You have all day to migrate that identity.", +"agent a cron hits /api/shelf hourly and messages you when anything drops under 24h.", +"nothing expires silently. No more 'why did my verification stop working' mysteries."]) + body += gloss([("burner","a disposable identity — mailbox, phone number, or drop",),("TTL","time to live — how long until the service retires it"),("burn-after-read","dead-drops self-destruct after N openings")]) + body += agent_card('GET /api/shelf', 'curl "https://dark0rbits.thetempleofdoom.com/api/shelf" -H "Cookie: dark0rbits_tok=…"', 'Returns mailboxes, sms, deaddrops, canaries + stats. Perfect for expiry-monitoring crons.') + return page("shelf", body) + +@app.route("/api/shelf") +def api_shelf(): + uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "login required — free no-KYC account at /inbox"}), 401 + d = _shelf_data(uid) + for grp in ("mailboxes", "sms", "deaddrops", "canaries", "trackables"): + for x in d[grp]: + x.pop("token", None) # never leak tokens over API + return jsonify({"ok": True, **d}) +# ---------- END TOOL: IDENTITY SHELF ---------- + + +# ---------- TOOL: UNFURL ---------- +def _unfurl_db(): + con = db() + con.execute("""CREATE TABLE IF NOT EXISTS unfurls(id INTEGER PRIMARY KEY, user_id INTEGER, url TEXT, + final_url TEXT, hops TEXT, status INTEGER, err TEXT, created INTEGER)""") + return con + +def _unfurl_ua(): + con = _unfurl_db() + try: + r = con.execute("SELECT val FROM settings WHERE user_id=? AND key='unfurl_ua'", (current_user_id() or 0,)).fetchone() + if r and r["val"]: + return r["val"] + except Exception: + pass + return "Mozilla/5.0 (Dark0rbits unfurl)" + +def _unfurl_save(user_id, url, final_url, hops_json, status, err): + con = _unfurl_db() + con.execute("INSERT INTO unfurls(user_id,url,final_url,hops,status,err,created) VALUES(?,?,?,?,?,?,?)", + (user_id, url, final_url, hops_json, status, err, int(time.time()))) + con.commit() + return con.execute("SELECT id FROM unfurls WHERE user_id=? ORDER BY id DESC LIMIT 1", (user_id,)).fetchone()["id"] + +_NOFOLLOW = ("javascript:", "data:", "mailto:", "tel:", "blob:", "about:", "file:", "chrome:", "intent:", "ws:", "wss:") + +def _unfurl_meta(html_text, base_url): + """Extract , meta description, og:*, twitter:* tags. Returns (title, metas dict, links, scripts, forms, iframes).""" + metas, links, scripts, forms, iframes = {}, [], [], [], [] + title = "" + m = re.search(r"<title[^>]*>(.*?)", html_text, re.I | re.S) + if m: + title = re.sub(r"\s+", " ", m.group(1)).strip()[:300] + for m in re.finditer(r"]+>", html_text, re.I): + tag = m.group(0) + def attr(name): + mm = re.search(name + r"\s*=\s*[\"']([^\"']*)[\"']", tag, re.I) + return mm.group(1) if mm else "" + nm, prop, con = attr("name"), attr("property"), attr("content") + key = (prop or nm).lower() + if key and con: + metas[key] = con[:600] + for m in re.finditer(r"]+rel\s*=\s*[\"']?[^\"'>]*stylesheet[^\"'>]*[\"']?[^>]*>", html_text, re.I): + links.append(m.group(0)[:500]) + for m in re.finditer(r"]*>", html_text, re.I): + scripts.append(m.group(0)[:500]) + for m in re.finditer(r"]*>", html_text, re.I): + forms.append(m.group(0)[:500]) + for m in re.finditer(r"]*>", html_text, re.I): + iframes.append(m.group(0)[:500]) + return title, metas, links, scripts, forms, iframes + +def _abs(u, base): + try: + return urllib.parse.urljoin(base, u) + except Exception: + return u + +def _fetch_no_redirect(u, ua): + class _NR(urllib.request.HTTPRedirectHandler): + def redirect_request(self, req, fp, code, msg, headers, newurl): + return None + op = urllib.request.build_opener(_NR, urllib.request.HTTPSHandler(context=_CTX)) + req = urllib.request.Request(u, headers={"User-Agent": ua, "Accept": "text/html,*/*"}) + try: + with op.open(req, timeout=10) as r: + return r.status, r.url, r.headers, r.read(300000).decode("utf-8", "replace") + except urllib.error.HTTPError as e: + body = "" + try: + body = e.read(100000).decode("utf-8", "replace") + except Exception: + pass + return e.code, u, e.headers, body + except Exception as e: + return 0, u, {}, str(e) + +def _unfurl(start_url, max_hops=6, extract=True, ua=None): + """Follow redirects manually one hop at a time; collect chain + final page metadata.""" + ua = ua or _unfurl_ua() + hops, seen, cur, hop_status, final_body = [], set(), start_url, 0, "" + scheme_ok = cur.lower().startswith(("http://", "https://")) + if not scheme_ok: + return {"ok": False, "error": "URL must start with http:// or https://", "hops": []} + for i in range(max_hops + 1): + if cur in seen: + hops.append({"hop": i + 1, "url": cur, "status": 0, "location": "", "note": "redirect loop detected"}) + cur = None + break + seen.add(cur) + status, final_url, headers, body = _fetch_no_redirect(cur, ua) + hops.append({"hop": i + 1, "url": cur, "status": status, "location": headers.get("Location", "") if headers else ""}) + if status in (301, 302, 303, 307, 308) and headers and headers.get("Location"): + loc = headers["Location"] + low = loc.lower() + if any(low.startswith(p) for p in _NOFOLLOW): + hops[-1]["note"] = "non-http scheme — not followed" + cur = None + final_body = body + break + nxt = _abs(loc, cur) + hops[-1]["location"] = nxt + cur = nxt + continue + hop_status = status + final_body = body + cur = None + break + if cur is not None: + hop_status = 0 + out = {"ok": hop_status > 0, "start": start_url, "final": hops[-1]["url"] if hops else "", + "status": hop_status, "hop_count": len(hops), "hops": hops} + if extract and final_body: + title, metas, css, scripts, forms, iframes = _unfurl_meta(final_body, out["final"]) + out["title"] = title + out["metas"] = metas + out["css_count"] = len(css) + out["script_count"] = len(scripts) + out["form_count"] = len(forms) + out["iframe_count"] = len(iframes) + out["iframes"] = [dict(src=re.search(r"src\s*=\s*[\"']([^\"']*)", f, re.I).group(1) if re.search(r"src\s*=\s*[\"']([^\"']*)", f, re.I) else "", tag=f[:300]) for f in iframes[:10]] + out["forms"] = [dict(action=re.search(r"action\s*=\s*[\"']([^\"']*)", f, re.I).group(1) if re.search(r"action\s*=\s*[\"']([^\"']*)", f, re.I) else "", method=(re.search(r"method\s*=\s*[\"']?(\w+)", f, re.I).group(1).lower() if re.search(r"method\s*=\s*[\"']?(\w+)", f, re.I) else "get"), tag=f[:300]) for f in forms[:10]] + return out + +@app.route("/unfurl", methods=["GET", "POST"]) +def unfurl_page(): + uid = current_user_id() + result, url_in, max_hops, extract, err = None, "", 6, "1", "" + if request.method == "POST": + r = rate_limit("unfurl", 20, 60) + if r: + return r + url_in = (param("url") or "").strip()[:500] + try: + max_hops = max(1, min(10, int(param("max_hops") or 6))) + except Exception: + max_hops = 6 + extract = param("extract") in ("1", "on", "true", "yes", "") + if not url_in: + err = "paste a URL first" + elif not url_in.lower().startswith(("http://", "https://")): + err = "URL must start with http:// or https://" + else: + result = _unfurl(url_in, max_hops, extract) + try: + _unfurl_save(uid or 0, url_in, result.get("final", ""), json.dumps(result.get("hops", []))[:8000], result.get("status", 0), result.get("error", "")) + except Exception: + pass + body = f"""

UNFURL URL

+

Follow every redirect hop by hand — scheme, host, status, location — then dissect the final page. Shorteners, cloakers, affiliate chains: laid open.

+
+
+ + + + +
""" + if err: + body += '
' + esc(err) + '
' + if result: + if result.get("ok"): + body += '
resolved in ' + str(result["hop_count"]) + ' hop(s) — final status ' + str(result["status"]) + '
' + else: + body += '
' + esc(result.get("error") or ("request failed (status " + str(result.get("status", 0)) + ")")) + '
' + rows = "".join( + "" + str(h.get("hop", "")) + "" + esc(h.get("url", "")) + "" + + ("" if 200 <= int(h.get("status", 0) or 0) < 400 else "") + esc(str(h.get("status", ""))) + "" + + ("" + esc(h.get("location", "")) + "" if h.get("location") else ("—" if not h.get("note") else '' + esc(h["note"]) + "")) + "" + for h in result.get("hops", [])) + body += '
REDIRECT CHAIN
' + rows + '
#URLStatusLocation / note
' + if result.get("title") is not None and result.get("ok"): + mrows = "".join("" + esc(k) + "" + esc(v) + "" for k, v in result.get("metas", {}).items()) + body += ('
FINAL PAGE
' + + "
Title
" + esc(result.get("title") or "—") + "
" + + "
Final URL
" + esc(result["final"]) + "
" + + "
Status
" + str(result["status"]) + "
" + + "
Stylesheets
" + str(result.get("css_count", 0)) + "
" + + "
Scripts
" + str(result.get("script_count", 0)) + "
" + + "
Forms / iframes
" + str(result.get("form_count", 0)) + " / " + str(result.get("iframe_count", 0)) + "
" + + '
META TAGS
' + (mrows or "") + "
NameContent
—
") + if result.get("iframes"): + body += '
IFRAMES
' + "".join("" for f in result["iframes"]) + "
src
" + esc(f.get("src") or "—") + "
" + if result.get("forms"): + body += '
FORMS
' + "".join("" for f in result["forms"]) + "
MethodAction
" + esc(f.get("method", "get")) + "" + esc(f.get("action") or "—") + "
" + body += how([ + "Paste any URL — a shortener, a cloaker, an affiliate link, a login redirect.", + "We request it with redirects disabled, so every 30x comes back to us one hop at a time.", + "Each hop records scheme, host, status code and the exact Location header — relative locations are resolved absolute.", + "Non-http schemes (javascript:, data:, intent:) are flagged and never followed.", + "Loops are detected: the same URL twice ends the chain with a clear note.", + "The final page (any status) is dissected: title, meta/og/twitter tags, stylesheet and script counts, forms and iframes.", + "Everything is available as JSON at /api/unfurl for agents.", + ]) + body += flow("checking a shortened link before clicking", [ + "you receive https://bit.ly/3xYz in a message and want to know where it really goes.", + "1. Paste it into UNFURL with default 6 hops.", + "2. The chain shows 301 → tracker.example → 302 → login.example — two hops, both logged.", + "you see the final host is a credential-phishing page with one form and an off-domain iframe.", + "3. Grab the JSON from /api/unfurl and feed it to your pipeline.", + ]) + body += gloss([ + ("redirect hop", "One 301/302/303/307/308 jump. Browsers follow them silently; we stop at each one."), + ("Location header", "Where the server says to go next. Can be relative — we resolve it against the current URL."), + ("redirect loop", "The same URL appearing twice in a chain — the server is chasing its tail."), + ("og: meta tags", "Open Graph tags pages use for link previews — often reveal the real content behind a cloaker."), + ("scheme", "The http:// or https:// part. Non-http schemes in Location are dangerous and never followed here."), + ]) + body += agent_card("GET /api/unfurl?url=…&max_hops=6", + "curl -s '" + SITE + "/api/unfurl?url=https://bit.ly/3xYz&max_hops=8'", + "Returns the full chain plus final-page metadata as JSON. Optional extract=0 to skip dissection.") + return page("hunt", body) + +@app.route("/api/unfurl") +def api_unfurl(): + uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "auth required: account session or Authorization: Bearer key"}), 401 + r = rate_limit("unfurl_api", 30, 60) + if r: + return r + u = (param("url") or "").strip()[:500] + if not u: + return jsonify({"ok": False, "error": "url required"}), 400 + if not u.lower().startswith(("http://", "https://")): + return jsonify({"ok": False, "error": "url must start with http:// or https://"}), 400 + try: + mh = max(1, min(10, int(param("max_hops") or 6))) + except Exception: + mh = 6 + extract = param("extract") not in ("0", "false", "no") + res = _unfurl(u, mh, extract) + code = 200 if (res.get("ok") and 200 <= res.get("status", 0) < 400) else 502 + try: + _unfurl_save(uid, u, res.get("final", ""), json.dumps(res.get("hops", []))[:8000], res.get("status", 0), res.get("error", "")) + except Exception: + pass + return jsonify(res), code + +@app.route("/api/unfurl/history") +def api_unfurl_history(): + uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "auth required"}), 401 + con = _unfurl_db() + rows = con.execute("SELECT id,url,final_url,status,created FROM unfurls WHERE user_id=? ORDER BY id DESC LIMIT 25", (uid,)).fetchall() + return jsonify({"ok": True, "items": [dict(r) for r in rows]}) +# ---------- END TOOL: UNFURL ---------- + + +# ---------- TOOL: WARP ARCHIVE ---------- +import urllib.parse as _wurl + +WARP_TTL = 21600 # 6h cache + +def _warp_db(): + con = db() + con.execute("CREATE TABLE IF NOT EXISTS warp_cache(domain TEXT, kind TEXT, data TEXT, ts INTEGER, PRIMARY KEY(domain,kind))") + con.commit() + return con + +def _warp_get(domain, kind): + con = _warp_db() + r = con.execute("SELECT data, ts FROM warp_cache WHERE domain=? AND kind=?", (domain, kind)).fetchone() + if r and (time.time() - r["ts"]) < WARP_TTL: + return jf(r["data"]) + return None + +def _warp_put(domain, kind, obj): + con = _warp_db() + con.execute("INSERT INTO warp_cache(domain,kind,data,ts) VALUES(?,?,?,?) ON CONFLICT(domain,kind) DO UPDATE SET data=excluded.data, ts=excluded.ts", + (domain, kind, json.dumps(obj), int(time.time()))) + con.commit() + +def _warp_norm(raw): + d = str(raw or "").strip().lower().lstrip() + d = _wurl.urlparse(d if "//" in d else "http://" + d).netloc.split("@")[-1].split(":")[0] + if d.startswith("www."): + d = d[4:] + if not d or "/" in d or " " in d or d.count(".") < 1 or not re.match(r"^[a-z0-9.-]+$", d): + return None + return d + +def _warp_cdx(domain, extra=""): + """Collapsed-per-year snapshot list from the Wayback CDX API.""" + url = "http://web.archive.org/cdx/search/cdx?url=" + _wurl.quote(domain) + "&output=json&limit=150&collapse=timestamp:4" + extra + st, txt = http(url) + arr = jf(txt) + if st != 200 or not isinstance(arr, list): + return None, (st or 0) + if len(arr) < 2: + return [], 200 + snaps = [] + for row in arr[1:]: + try: + snaps.append({"timestamp": row[1], "url": row[2], "mimetype": row[3] if len(row) > 3 else "", "status": row[4] if len(row) > 4 else "", "digest": row[5] if len(row) > 5 else ""}) + except Exception: + continue + return snaps, 200 + +def _warp_snapinfo(domain): + snaps, st = _warp_cdx(domain) + if snaps is None: + return None, st + by_year, years = {}, [] + for s in snaps: + y = s["timestamp"][:4] + if y not in by_year: + by_year[y] = [] + years.append(y) + by_year[y].append(s) + timeline = [{"year": y, "count": len(by_year[y]), "first": by_year[y][0]["timestamp"], "last": by_year[y][-1]["timestamp"]} for y in years] + return {"domain": domain, "total": len(snaps), "timeline": timeline, "snapshots": snaps}, 200 + +def _warp_titles(domain): + """Distinct archived titles/paths: fetch each collapsed snapshot's HTML, pull the title tag.""" + info, st = _warp_snapinfo(domain) + if info is None: + return None, st + seen, out = set(), [] + for s in info["snapshots"]: + key = (s["url"], s["digest"]) + if key in seen: + continue + seen.add(key) + out.append(s) + if len(out) >= 8: + break + paths = [] + for s in out: + rec = {"timestamp": s["timestamp"], "path": "/" + s["url"].split("/", 3)[-1] if s["url"].count("/") > 2 else "/", + "url": "https://web.archive.org/web/" + s["timestamp"] + "/" + s["url"], "title": ""} + paths.append(rec) + return {"domain": domain, "entries": paths, "scanned": len(out)}, 200 + +def _warp_dns(domain): + """Current DNS via DoH (A + MX), oldest-archived context left to RDAP side.""" + rec = {"a": [], "mx": []} + st, txt = http("https://cloudflare-dns.com/dns-query?name=" + _wurl.quote(domain) + "&type=A", headers={"Accept": "application/dns-json"}) + d = jf(txt) + if d: + rec["a"] = sorted({a.get("data", "") for a in d.get("Answer", []) if a.get("type") == 1}) + st, txt = http("https://cloudflare-dns.com/dns-query?name=" + _wurl.quote(domain) + "&type=MX", headers={"Accept": "application/dns-json"}) + d = jf(txt) + if d: + rec["mx"] = sorted({a.get("data", "") for a in d.get("Answer", []) if a.get("type") == 15}) + return rec + +def _warp_rdap(domain): + st, txt = http("https://rdap.org/domain/" + _wurl.quote(domain)) + d = jf(txt) + if not d or st != 200: + return None + ev = {e.get("eventAction"): (e.get("eventDate") or "")[:10] for e in d.get("events", [])} + ent = d.get("entities") or [] + reg = "" + for e in ent: + if "registrar" in (e.get("roles") or []): + for v in (e.get("vcardArray") or [None, []])[1]: + if v[0] == "fn": + reg = v[3] + return {"registrar": reg, "created": ev.get("registration", ""), "changed": ev.get("last changed", ""), "expires": ev.get("expiration", ""), + "status": d.get("status", [])} + +def _warp_dns_history(domain): + """DNS/whois drift: current RDAP/DNS vs the oldest archived year (whois HTML hint).""" + cur_dns = _warp_dns(domain) + rdap = _warp_rdap(domain) + info, st = _warp_snapinfo(domain) + if info is None: + return None, st + oldest = info["snapshots"][0]["timestamp"] if info["snapshots"] else "" + old_dns = {"a": [], "mx": []} + if oldest: + st2, body = http("https://web.archive.org/cdx/search/cdx?url=" + _wurl.quote(domain) + "&output=json&limit=5&collapse=timestamp:4&from=" + oldest[:8] + "&filter=mimetype:text/dns") + arr = jf(body) + if isinstance(arr, list) and len(arr) > 1: + for row in arr[1:]: + try: + if row[3] == "text/dns": + parts = row[2].split("/") + old_dns["a"].append(row[2]) + except Exception: + continue + changes = [] + for field in ("a", "mx"): + nowv = "; ".join(cur_dns[field]) or "(none)" + thenv = "; ".join(old_dns[field]) or "(not archived)" + changes.append({"record": field.upper(), "oldest": thenv, "current": nowv, + "drift": "no data" if not old_dns[field] else ("same" if thenv == nowv else "CHANGED")}) + if rdap: + changes.append({"record": "REGISTRAR", "oldest": "(unknown)", "current": rdap["registrar"] or "(n/a)", "drift": "current"}) + changes.append({"record": "CREATED", "oldest": "(unknown)", "current": rdap["created"] or "(n/a)", "drift": "current"}) + return {"domain": domain, "oldest_snapshot": oldest, "rdap": rdap, "dns_now": cur_dns, "changes": changes}, 200 + +def _warp_lookup(domain): + res = {} + info, st1 = _warp_snapinfo(domain) + res["archive"] = info + if info: + _warp_put(domain, "archive", info) + dnsh, st2 = _warp_dns_history(domain) + res["dns_history"] = dnsh + if dnsh: + _warp_put(domain, "dns", dnsh) + titles, st3 = _warp_titles(domain) + res["hosted"] = titles + if titles: + _warp_put(domain, "hosted", titles) + return res, (st1 if info is None else 200) + +def _warp_page_body(domain, data, msg): + res_html = "" + if msg: + res_html = '
NOTE ' + esc(msg) + "
" + if data: + arch = data.get("archive") or {} + if arch.get("total"): + rows = "" + for t in arch.get("timeline", []): + ts = t["first"] + shot = "https://web.archive.org/web/" + ts + "if_/" + domain + live = "https://web.archive.org/web/" + ts + "/" + domain + rows += ("" + esc(t["year"]) + "" + str(t["count"]) + "" + + "preview " + ts[:8] + "" + + "open copy") + res_html += ('
SNAPSHOT TIMELINE — ' + esc(domain) + '' + + '
' + str(arch.get("total", 0)) + ' snapshots collapsed to one per year.
' + + '
' + rows + "
YearSnapshotsScreenshot previewArchived copy
") + else: + res_html += '
NO ARCHIVE The Wayback Machine has no snapshots of ' + esc(domain) + ". Either it never hosted a site, or it was never crawled.
" + dns = data.get("dns_history") or {} + if dns: + crows = "" + for c in dns.get("changes", []): + tag = "ok" if c["drift"] in ("same", "current", "no data") else "bad" + crows += ("" + esc(c["record"]) + "" + esc(c["oldest"]) + "" + esc(c["current"]) + "" + + '' + esc(c["drift"]) + "") + ol = dns.get("oldest_snapshot", "") + res_html += ('
DNS / WHOIS DRIFT' + + '
Oldest snapshot: ' + (esc(ol[:8]) if ol else "none") + " vs today.
" + + '
' + crows + "
RecordOldest archivedCurrentDrift
") + hosted = data.get("hosted") or {} + if hosted and hosted.get("entries"): + hrows = "" + for e in hosted["entries"]: + hrows += ('' + esc(e["timestamp"][:8]) + "" + esc(e["path"]) + 'view') + res_html += ('
WHAT DID THIS DOMAIN HOST?' + + '
Distinct archived paths (' + str(hosted.get("scanned", 0)) + " sampled).
" + + '
' + hrows + "
DatePathLink
") + return res_html + +WARP_EXPLAIN = how([ + "The Wayback Machine is a public crawl archive — its CDX index API lists every snapshot it holds for a domain, free, no key.", + "We ask for the index collapsed to one snapshot per year (collapse=timestamp:4) so you get a clean timeline instead of 10,000 rows.", + "Screenshot previews use the 'if_' replay modifier: web.archive.org/web/if_/ strips the Wayback toolbar and serves the page as captured.", + "DNS drift: we pull today's A/MX records over DNS-over-HTTPS and today's RDAP registration, then line them up against the oldest archived year.", + "Everything is cached in a local sqlite table for 6 hours — repeat lookups are instant and do not hammer the archive.", + "A domain with snapshots every year and stable records is usually legit. A 2-year gap plus a registrar change plus new MX is a takeover tell.", + "Agents: GET /api/warp?domain=example.com returns the whole picture as JSON — timeline, drift table, hosted paths, ready for a report.", +]) + +WARP_FLOW = flow("watch a scam site morph over 3 years", [ + "you get a phishing email from secure-login-example.com and want to know if it is a fresh drop or an old hijack.", + "Punch the domain into WARP. The timeline shows snapshots in 2019 and 2020 — then nothing until last month.", + "Click the 2019 screenshot preview: the archived copy shows a quiet small-business bakery homepage.", + "The drift table tells the rest: registrar changed this year, MX moved to a bulk-mail provider, A record now points at bulletproof hosting.", + "The hosted paths list confirms the morph: /menu.pdf in 2019, /wp-login.php and /secure/verify today.", + "you report it as a compromised/repurposed domain with archive receipts — far more credible than 'it looks phishy'.", +]) + +@app.route("/warp", methods=["GET", "POST"]) +def warp_page(): + uid = current_user_id() + domain_raw = param("domain") or "" + data, msg = None, "" + if domain_raw: + domain = _warp_norm(domain_raw) + if not domain: + msg = "that does not look like a domain — try example.com" + else: + cached = _warp_get(domain, "archive") + if cached: + dns_c = _warp_get(domain, "dns") + host_c = _warp_get(domain, "hosted") + data = {"archive": cached, "dns_history": dns_c, "hosted": host_c} + msg = "cached result (fresh within 6h)" + else: + r = rate_limit("warp", 6, 60) + if r: + return r + data, st = _warp_lookup(domain) + if data.get("archive") is None: + data = None + msg = "archive lookup failed (status " + str(st) + ") — the Wayback CDX API may be slow or unreachable" + res_html = _warp_page_body(domain_raw, data, msg) + body = f""" +

WARP ARCHIVE

A domain time machine. Line up the Wayback Machine's snapshots of any domain year by year, preview what it used to look like, and see whether its DNS and registrar story drifted. Passive — one public API, zero packets to the target.

+
Look up a domain +
+ +
+
Snapshots per year · screenshot previews · DNS/whois drift · hosted-path inventory. Results cached 6h.
+{res_html} +{WARP_FLOW} +{WARP_EXPLAIN} +""" + gloss([("Wayback Machine", "web.archive.org — the Internet Archive's crawl of the web since 1996. Public, free, no key."), + ("CDX API", "the index in front of the archive: query it for every snapshot (timestamp, URL, mimetype, hash) it holds for a site."), + ("collapse=timestamp:4", "dedupe the index to one hit per year (first 4 digits of the timestamp) — the clean timeline trick."), + ("if_ modifier", "replay URL flag that serves the raw archived page with the Wayback toolbar injected CSS/JS removed — good for previews."), + ("RDAP", "the modern WHOIS: registration dates, registrar, status over HTTPS/JSON."), + ("DoH", "DNS over HTTPS — lets us resolve A/MX records from the app without a resolver."), + ("takeover", "an aged domain that changed hands: registrar event + new MX + new hosting is the classic signature."), +]) + agent_card("GET /api/warp?domain=example.com", + "curl -s \"" + SITE + "/api/warp?domain=example.com\" -H \"Authorization: Bearer dk_...\"", + "Free. Returns timeline, drift table, hosted paths. Cached 6h per domain.") + return page("intel", body) + +@app.route("/api/warp") +def api_warp(): + r = rate_limit("warp", 10, 60) + if r: + return r + domain = _warp_norm(param("domain") or "") + if not domain: + return jsonify({"ok": False, "error": "domain required, e.g. ?domain=example.com"}), 400 + cached = _warp_get(domain, "archive") + if cached: + ok = bool(cached.get("total")) + out = {"ok": ok, "cached": True, "domain": domain, "archive": cached, + "dns_history": _warp_get(domain, "dns"), "hosted": _warp_get(domain, "hosted")} + if not ok: + out["error"] = "no snapshots found for this domain (cached)" + return jsonify(out), (200 if ok else 404) + data, st = _warp_lookup(domain) + out = {"ok": bool(data.get("archive") and data["archive"].get("total")), "cached": False, "domain": domain} + out.update(data) + if not out["ok"]: + out["error"] = "no snapshots found for this domain (status " + str(st) + ")" + return jsonify(out), (200 if out["ok"] else 404) +# ---------- END TOOL: WARP ARCHIVE ---------- + + # ---------- 9. OPERATOR CONSOLE ---------- @app.route("/admin", methods=["GET", "POST"]) def admin(): @@ -3309,6 +5315,14 @@ def index(): ("shot","SCREENSHOT","Headless-Chromium PNG capture of any page. Agents: poll the status API.","◈","HUNT"), ("score","FRAUD-SCORE","Composite 0-100 risk: IP intel + disposable-email + BIN heuristics, with full breakdown.","◈","HUNT"), ("tools","FREE TOOLS","DNS resolver, HTTP header inspector, JWT decoder, hasher, generators.","◈","UTILITY"), + ("beacon","PORT BEACON","Heartbeat your servers home on a timer; if the box goes dark or the probe URL gets touched, you know in seconds.","◆","Hunt"), + ("bssid","BSSID RADAR","Turn a WiFi router's MAC into an approximate place on Earth, with map links and accuracy radius.","◈","Hunt"), + ("hooks", "HOOK RELAY", "Instant public webhook inspector: capture every callback, auto-detect the sender, replay it anywhere.", "◈", "Operate"), + ("face","FACE TRACE","Hash an avatar, harvest a username's profile pictures across platforms, and get Hamming verdicts — no reverse-image APIs.","◈","Hunt"), + ("rotator","ROTATOR","Spin consistent browser identities from four pools with replayable seeds.","◈","UTILITY"), + ("shelf","IDENTITY SHELF","Every burner you own on one page — mailboxes, numbers, drops, traps — with live countdowns so nothing dies silently.","◈","ACCOUNT"), + ("unfurl", "UNFURL", "Follow every redirect hop in a URL chain and dissect the page at the end.", "◈", "Hunt"), + ("warp","WARP ARCHIVE","A domain time machine: snapshot timeline from the Wayback Machine, archived-page previews, and DNS/whois drift — watch a domain morph over years.","◈","INTEL"), ("passport","AGENT PASSPORT","Machine-readable trust badge for your bots. Agents are first-class here.","◈","ACCOUNT"), ] tcards = "".join(