diff --git a/app.py b/app.py index 1ce2071..b5118af 100644 --- a/app.py +++ b/app.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 """Dark0rbits v2 — toolbox: IP intel, card validator, SMS rentals, proxy lab, stego lab, trackable files (BTCPay), no-KYC site-only messaging inbox. Single-file Flask + SQLite.""" -import base64, binascii, hashlib, hmac, html, io, json, os, re, secrets, socket, sqlite3, struct, time, uuid +import base64, binascii, hashlib, hmac, html, io, ipaddress, json, os, re, secrets, shutil, socket, sqlite3, struct, subprocess, time, uuid import urllib.request, urllib.parse from flask import Flask, request, jsonify, render_template_string, Response, send_file from flask import redirect +import importlib.util as _ilu +_HAVE_AESGCM = _ilu.find_spec("cryptography") is not None app = Flask(__name__) DB_PATH = os.environ.get("DARK0RBITS_DB", "/opt/dark0rbits/dark0rbits.db") UPLOAD_DIR = os.environ.get("DARK0RBITS_UPLOADS", "/opt/dark0rbits/uploads") @@ -22,6 +24,11 @@ BTCPAY_WHSEC = os.environ.get("BTCPAY_WHSEC", "TgJhmoBcNf9ATK2SFCg1VS") BMAC = "https://buymeacoffee.com/r26xrthzttg" SITE = "https://dark0rbits.thetempleofdoom.com" +def _migrate(con): + cols = [r[1] for r in con.execute("PRAGMA table_info(sms_rentals)")] + if "user_id" not in cols: + con.execute("ALTER TABLE sms_rentals ADD COLUMN user_id INTEGER DEFAULT 0") + def db(): con = sqlite3.connect(DB_PATH); con.row_factory = sqlite3.Row con.executescript("""CREATE TABLE IF NOT EXISTS sms_rentals(id INTEGER PRIMARY KEY, phone TEXT, service TEXT, country TEXT, purchase_id TEXT, cost REAL, status TEXT, created INTEGER, expires INTEGER); @@ -40,7 +47,10 @@ def db(): CREATE TABLE IF NOT EXISTS apikeys(id INTEGER PRIMARY KEY, user_id INTEGER, key TEXT UNIQUE, label TEXT, created INTEGER, revoked INTEGER DEFAULT 0); CREATE TABLE IF NOT EXISTS ledger(id INTEGER PRIMARY KEY, user_id INTEGER, delta_cents INTEGER, reason TEXT, ts INTEGER); CREATE TABLE IF NOT EXISTS wh_processed(invoice_id TEXT PRIMARY KEY, ts INTEGER); - CREATE TABLE IF NOT EXISTS rate_hits(bucket TEXT, ip TEXT, ts INTEGER);""") + CREATE TABLE IF NOT EXISTS rate_hits(bucket TEXT, ip TEXT, ts INTEGER); + CREATE TABLE IF NOT EXISTS deadrops(id INTEGER PRIMARY KEY, user_id INTEGER, token TEXT UNIQUE, body_enc TEXT, reads_left INTEGER, burn_after INTEGER, expires INTEGER, pw_hash TEXT, created INTEGER); + CREATE TABLE IF NOT EXISTS shots(id INTEGER PRIMARY KEY, user_id INTEGER, url TEXT, status TEXT, result TEXT, created INTEGER);""") + _migrate(con) return con # ---------- BILLING CORE (per-call metering for outside users) ---------- @@ -119,6 +129,39 @@ def param(name): def esc(s): return html.escape(str(s)) +# ---------- DEAD-DROP CRYPTO (AES-GCM on CT768, XOR-HMAC stream fallback) ---------- +def _dd_master_key(): + return hashlib.sha256(("dark0rbits-deaddrop-v1:" + (os.environ.get("DARK0RBITS_SECRET", "ct768-fallback-secret"))).encode()).digest() + +def dd_encrypt(plaintext): + """AES-256-GCM when cryptography is present, else HMAC-verified XOR stream. Returns 'mode:vault' string.""" + if _HAVE_AESGCM: + from cryptography.hazmat.primitives.ciphers.aead import AESGCM + nonce = secrets.token_bytes(12) + vault = AESGCM(_dd_master_key()).encrypt(nonce, plaintext.encode(), None) + return "aesgcm:" + base64.urlsafe_b64encode(nonce + vault).decode() + key = secrets.token_bytes(32) + stream = bytes(a ^ b for a, b in zip(plaintext.encode(), hashlib.shake_256(key + str(len(plaintext)).encode()).digest(len(plaintext) + 64))) + mac = hmac.new(_dd_master_key(), stream, hashlib.sha256).hexdigest() + return "xor:" + base64.urlsafe_b64encode(key + stream).decode() + ":" + mac + +def dd_decrypt(vault): + try: + if vault.startswith("aesgcm:"): + from cryptography.hazmat.primitives.ciphers.aead import AESGCM + raw = base64.urlsafe_b64decode(vault[7:].encode()) + return AESGCM(_dd_master_key()).decrypt(raw[:12], raw[12:], None).decode() + if vault.startswith("xor:"): + k64, mac = vault[4:].rsplit(":", 1) + raw = base64.urlsafe_b64decode(k64.encode()) + if not hmac.compare_digest(hmac.new(_dd_master_key(), raw[32:], hashlib.sha256).hexdigest(), mac): return None + n = len(raw) - 32 - 64 + stream = bytes(a ^ b for a, b in zip(raw[32:], hashlib.shake_256(raw[:32] + str(n).encode()).digest(n + 64))) + return stream.decode() + except Exception: + pass + return None + BASE = """ DARK0RBITS — No-KYC Network Toolbox: IP Intel, Stego, Burner Mail, SMS Rentals, Proxy Lab @@ -150,6 +193,13 @@ header{position:sticky;top:0;z-index:40;background:rgba(7,10,19,.86);backdrop-fi .dnav{display:flex;flex-wrap:wrap;gap:.35rem;justify-content:center} .dnav a{color:var(--dim);text-decoration:none;font-size:.72rem;padding:.3rem .55rem;border:1px solid var(--line);border-radius:999px;white-space:nowrap;transition:.15s} .dnav a.on,.dnav a:hover{color:var(--acc);border-color:var(--acc)} +.tchip{color:var(--fg);margin-right:.5rem;white-space:nowrap} +:focus-visible{outline:2px solid var(--acc2);outline-offset:2px;border-radius:4px} +.skip{position:absolute;left:-9999px;top:0;z-index:100;background:var(--acc);color:#0d0722;padding:.5rem 1rem;border-radius:0 0 8px 0;font-weight:800} +.skip:focus{left:0} +table{display:block;overflow-x:auto;max-width:100%;-webkit-overflow-scrolling:touch} +@media(max-width:640px){.dnav a{padding:.45rem .7rem;font-size:.8rem}#dev{display:none}} +@media(prefers-reduced-motion:reduce){ #space{display:none}.gridlines{display:none}#lbar{transition:none}.type,.typed-cursor,.crt{display:none}.logo::before,.logo::after{animation:none}.card{transition:none}} .drawer{position:fixed;inset:0;z-index:60;background:rgba(7,10,19,.96);backdrop-filter:blur(6px);display:none;flex-direction:column;padding:1.2rem;overflow-y:auto} .drawer.open{display:flex} .drawer .dhead{display:flex;justify-content:space-between;align-items:center;margin-bottom:.8rem} @@ -198,48 +248,54 @@ li{text-align:left;margin:.2rem 0} .gridlines{position:fixed;inset:0;z-index:1;pointer-events:none;background:repeating-linear-gradient(0deg,rgba(255,255,255,.012) 0 1px,transparent 1px 3px),linear-gradient(rgba(111,214,255,.03) 1px,transparent 1px),linear-gradient(90deg,rgba(111,214,255,.03) 1px,transparent 1px);background-size:auto,80px 80px,80px 80px;mask-image:linear-gradient(rgba(0,0,0,.7),rgba(0,0,0,.25))}
+
{{acct}}

Intel

-◈ IP INTEL geo, ASN, ISP, VPN flags — any target -◈ CARD CHECK luhn + BIN issuer intelligence -◈ MAIL FORENSICS origin + SPF/DKIM/DMARC + spoof flags -◈ IMAGE FORENSICS EXIF, GPS, ELA, edit detection +◈ IP INTEL geo, ASN, ISP, VPN flags — any target +◈ CARD CHECK luhn + BIN issuer intelligence +◈ MAIL FORENSICS origin + SPF/DKIM/DMARC + spoof flags +◈ IMAGE FORENSICS EXIF, GPS, ELA, edit detection

Operate

-◈ SMS RENTAL 30-min numbers, refundable -◈ PROXY LAB residential egress, geo builder -◈ STEGO LAB hide words in pictures -◈ BURNER MAIL receive-only mailboxes, countdown +◈ SMS RENTAL 30-min numbers, refundable +◈ PROXY LAB residential egress, geo builder +◈ STEGO LAB hide words in pictures +◈ BURNER MAIL receive-only mailboxes, countdown

Hunt

-◈ TRACK FILE opens report back: IP, city, ISP -◈ CANARY TRAPS tripwires with instant alerts -◈ FREE TOOLS DNS, headers, JWT, hasher +◈ TRACK FILE opens report back: IP, city, ISP +◈ CANARY TRAPS tripwires with instant alerts +◈ DEAD-DROP burn-after-read encrypted notes +◈ SCREENSHOT page capture or rendered-text fallback +◈ FRAUD-SCORE composite IP + email + BIN risk 0-100 +◈ FREE TOOLS DNS, headers, JWT, hasher

Account

-◈ INBOX no-KYC messaging -◈ API KEYS metered access, balance -◈ PASS $10/mo all-access -◈ AGENT PASSPORT machine-readable badge +◈ INBOX no-KYC messaging +◈ API KEYS metered access, balance +◈ PASS $10/mo all-access +◈ AGENT PASSPORT machine-readable badge
-
{{body}}
+
{{body}}
✦ REACH THE DEV {result}""" + body += gloss([("BIN","first 6-8 digits of a card — identifies issuer, country, brand"),("Luhn","checksum test every real card number passes"),("prepaid","issued as prepaid — elevated fraud risk")]) return page("card", body) @app.route("/api/card", methods=["POST"]) @@ -535,6 +627,188 @@ def api_card(): "type": bl.get("type"), "prepaid": bl.get("prepaid")}, "flags": (["prepaid-risk"] if (bl.get("type")=="prepaid" or bl.get("prepaid") is True) else []) + (["luhn-invalid"] if not ok else [])}) +# ---------- 7i. SCREENSHOT SERVICE (chromium if present, else rendered-text fallback) ---------- +def shot_url_ok(u): + if not re.match(r"^https?://", u): return None, "url must start with http:// or https://" + try: + host = urllib.parse.urlsplit(u).hostname or "" + except Exception: + return None, "url parse error" + if not host: return None, "url has no host" + try: + candidate = ipaddress.ip_address(host) + except ValueError: + candidate = None + if candidate: + if candidate.is_private or candidate.is_loopback or candidate.is_link_local or candidate.is_reserved: return None, "private/reserved IPs blocked" + return u, None + try: + resolved = ipaddress.ip_address(socket.gethostbyname(host)) + except Exception: + return u, None # cannot resolve here — let the fetcher report the failure + if resolved.is_private or resolved.is_loopback or resolved.is_link_local or resolved.is_reserved: return None, "private/reserved IPs blocked" + return u, None + +def shot_find_browser(): + for b in ("chromium", "chromium-browser", "google-chrome", "google-chrome-stable"): + if shutil.which(b): return b + return None + +def _shot_html_harvest(url): + """HTTP fetch + readability-ish text harvest + page intel. No browser, no fake PNG.""" + status, html_text = http(url, timeout=15) + out = {"http_status": status} + try: + title = re.search(r"]*>(.*?)", html_text, re.I | re.S) + if title: out["title"] = html.unescape(title.group(1)).strip()[:300] + desc = re.search(r']+name=["\']description["\'][^>]+content=["\'](.*?)["\']', html_text, re.I | re.S) + if desc: out["description"] = html.unescape(desc.group(1)).strip()[:400] + except Exception: + pass + intel = [] + for m in re.finditer(r"]*>(.*?)", html_text, re.I | re.S): + t = html.unescape(re.sub(r"<[^>]+>", "", m.group(2))).strip() + if t: intel.append("h" + m.group(1) + ": " + t[:120]) + if len(intel) >= 15: break + t = re.sub(r"(?is)<(script|style|noscript|svg)[^>]*>.*?", " ", html_text) + t = re.sub(r"(?s)", " ", t) + t = re.sub(r"(?i)<(br|/p|/div|/li|/h[1-6]|/tr)[^>]*>", "\n", t) + t = re.sub(r"<[^>]+>", " ", t) + t = html.unescape(t) + t = re.sub(r"[ \t\r]+", " ", t) + t = re.sub(r"\n\s*\n+", "\n", t).strip() + words = t.split() + out["text_preview"] = " ".join(words[:400]) + out["text_chars_total"] = len(words) + out["headings"] = intel + return out + +def shot_run(sid): + con = db() + s = con.execute("SELECT * FROM shots WHERE id=?", (sid,)).fetchone() + if not s: return + url = s["url"] + browser = shot_find_browser() + if browser: + out = os.path.join(UPLOAD_DIR, f"shot_{sid}.png") + try: + cmd = [browser, "--headless=new", "--no-sandbox", "--disable-gpu", "--hide-scrollbars", + "--window-size=1280,1600", f"--screenshot={out}", "--virtual-time-budget=8000", url] + p = subprocess.run(cmd, capture_output=True, timeout=45) + if p.returncode == 0 and os.path.exists(out) and os.path.getsize(out) > 0: + with open(out, "rb") as f: png = f.read() + os.remove(out) + con.execute("UPDATE shots SET status=?, result=? WHERE id=?", ("done", base64.b64encode(png).decode(), sid)) + con.commit(); return + err = (p.stderr or b"").decode(errors="replace")[:200] + result = {"error": "chromium render failed: " + (err or f"exit {p.returncode}")} + except subprocess.TimeoutExpired: + result = {"error": "chromium timed out after 45s"} + except Exception as e: + result = {"error": f"chromium error: {e}"} + else: + try: + result = _shot_html_harvest(url) + result["mode"] = "text_fallback" + except Exception as e: + result = {"error": f"fetch failed: {e}"} + con.execute("UPDATE shots SET status=?, result=? WHERE id=?", ("text_fallback" if "mode" in result else "error", json.dumps(result), sid)) + con.commit() + +SHOT_API = ("
AGENT API
POST " + SITE + """/api/shot/create
+  Content-Type: application/json  (or form fields)
+  {"url":"https://example.com"}
+  -> {"ok":true,"id":42,"status":"queued","poll":"BASE/api/shot/status/42"}
+GET  /api/shot/status/42
+  -> {"ok":true,"id":42,"status":"done","png_b64":"iVBORw..."}   (chromium present)
+  -> {"ok":true,"status":"text_fallback","title":"...","text_preview":"...","headings":[...]}
+auth: session cookie or Authorization: Bearer dk_...
+25c/shot, free with PASS - rate limit 6/min
+NOTE: no headless browser on this host yet - expect text_fallback
""").replace("BASE", SITE) + +SHOT_EXPLAINER = """
HOW CAPTURE WORKS
+• With headless Chromium installed, /shot returns a real browser render as base64 PNG. +• No browser on the host? You get text_fallback: an honest fetch of the page with rendered-text preview + page intel. A status field always tells you which. +• SSRF guard: private/reserved network targets are refused before any fetch. +• 25¢ per shot, free with PASS. Rate limit 6/min.
""" + +@app.route("/shot") +def shot_page(): + body = f""" +

SCREEN SHOT

Point at a URL, get a render. Real headless-Chromium PNG when the host has one — an honest rendered-text + intel fallback when it doesn't. Never a fake image.

+
New capture +
+ +
+
{'Free with your PASS — or 25¢ from balance.' if current_user_id() else 'Login + balance (or PASS): 25¢ per shot.'}
+ +{SHOT_EXPLAINER} +""" + SHOT_API + how(["Paste a URL — the job queues with a 25¢ charge (free with PASS).", + "With a headless browser on the host you get a real PNG back as base64.", + "No browser installed? You get text_fallback: title, description, headings, first 400 words — honestly labeled.", + "Agents: POST /api/shot/create then poll /api/shot/status/ until status != queued.", + "SSRF guard: localhost and private ranges are refused — this is a capture service, not a port scanner."]) + return page("shot", body) + +@app.route("/api/shot/create", methods=["POST"]) +def api_shot_create(): + r = rate_limit("shot", 6, 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 dk_ key"}), 401 + url = str(jp("url") or "").strip() + if not url: return jsonify({"ok": False, "error": "url required"}), 400 + url, err = shot_url_ok(url) + if err: return jsonify({"ok": False, "error": err}), 400 + if not has_pass(uid) and not charge(uid, 25, "shot create"): + return jsonify({"ok": False, "error": "insufficient balance", "balance_cents": get_balance(uid), "topup": SITE + "/keys"}), 402 + con = db() + cur = con.execute("INSERT INTO shots(user_id,url,status,created) VALUES(?,?,?,?)", (uid, url, "queued", int(time.time()))) + con.commit() + shot_run(cur.lastrowid) + st = con.execute("SELECT status FROM shots WHERE id=?", (cur.lastrowid,)).fetchone() + return jsonify({"ok": True, "id": cur.lastrowid, "status": st["status"], "poll": f"{SITE}/api/shot/status/{cur.lastrowid}"}), 200, {"Cache-Control": "no-store"} + +def shot_dict(row): + d = {"ok": True, "id": row["id"], "status": row["status"]} + try: + r = json.loads(row["result"]) if row["result"] else None + except Exception: + r = row["result"] + if row["status"] == "done" and r: + d["png_b64"] = r + try: + d["png_bytes"] = len(base64.b64decode(r)) + except Exception: + pass + elif r: + d.update(r if isinstance(r, dict) else {"detail": str(r)[:400]}) + return d + +@app.route("/api/shot/status/") +def api_shot_status(sid): + con = db() + s = con.execute("SELECT * FROM shots WHERE id=?", (sid,)).fetchone() + if not s: return jsonify({"ok": False, "error": "unknown shot id"}), 404 + if s["status"] == "queued": shot_run(sid) # lazy exec (reload-safe) + s = con.execute("SELECT * FROM shots WHERE id=?", (sid,)).fetchone() + return jsonify(shot_dict(s)) + # ---------- 3. SMS RENTALS ---------- SMSP = "https://api.smspool.net" SERVICES = [("google","Google"),("discord","Discord"),("telegram","Telegram"),("whatsapp","WhatsApp"),("other","Other/Any")] @@ -564,6 +838,7 @@ def sms_guard(): @app.route("/sms", methods=["GET", "POST"]) def sms(): + uid = current_user_id() msg = "" if request.method == "POST": act = request.form.get("act") @@ -576,8 +851,8 @@ def sms(): d = jf(b) or {} if d.get("success") == 1: con = db(); now = int(time.time()) - con.execute("INSERT INTO sms_rentals(phone,service,country,purchase_id,cost,status,created,expires) VALUES(?,?,?,?,?,?,?,?)", - (d.get("number"), request.form["service"], request.form["country"], str(d.get("purchase_id")), d.get("cost",0), "active", now, now+1800)) + con.execute("INSERT INTO sms_rentals(user_id,phone,service,country,purchase_id,cost,status,created,expires) VALUES(?,?,?,?,?,?,?,?,?)", + (uid, d.get("number"), request.form["service"], request.form["country"], str(d.get("purchase_id")), d.get("cost",0), "active", now, now+1800)) con.commit() msg = f'
RENTED Your number: +{d.get("number")} · 30 min · order #{d.get("purchase_id")}
' else: @@ -595,8 +870,8 @@ def sms(): con = db(); con.execute("UPDATE sms_rentals SET status=? WHERE purchase_id=?", ("refunded" if ok else "cancel-failed", request.form["pid"])); con.commit() msg = f'
{"CANCELLED + REFUNDED" if ok else "CANCEL FAILED"}
' con = db() - hist = con.execute("SELECT * FROM sms_rentals ORDER BY id DESC LIMIT 8").fetchall() - hist_rows = "".join(f"+{h['phone']} copy{h['service']}{h['status']}#{h['purchase_id']}…" for h in hist) + hist = con.execute("SELECT * FROM sms_rentals WHERE user_id=? ORDER BY id DESC LIMIT 8", (uid,)).fetchall() + hist_rows = "".join(f"+{h['phone']} copy{h['service']}{h['status']}#{h['purchase_id']}…" for h in hist) body = f"""

SMS RENTAL

Disposable numbers, 30-minute windows. Cancel before a code = full refund.

@@ -618,7 +893,10 @@ var els=document.querySelectorAll('.cdown');var now=Math.floor(Date.now()/1000); els.forEach(function(e){{var s=e.dataset.exp-now;if(s>0)e.textContent=Math.floor(s/60)+'m '+(s%60)+'s left';else e.textContent='expired'}});}},1000); setInterval(function(){{ fetch('/api/sms/history').then(r=>r.json()).then(rows=>{{ -rows.filter(r=>r.status==='active').forEach(r=>{{ +var act=rows.filter(r=>r.status==='active'); +document.dispatchEvent(new CustomEvent('drb-sms',{{detail:{{active:act.length}}}})); +if(!act.length)return; +act.forEach(r=>{{ fetch('/api/sms/check?pid='+r.purchase_id).then(x=>x.json()).then(d=>{{ if((d.sms||d.code)&&d.sms!==lastMsg){{lastMsg=d.sms||d.code; var box=document.getElementById('codesbox');box.style.display='block'; @@ -628,6 +906,7 @@ toast('SMS CODE: '+lastMsg);document.title='✉ '+lastMsg;}} }})}},6000);
API: POST /api/sms/rent (service,country) · GET /api/sms/check?pid= · GET /api/sms/cancel?pid= · GET /api/sms/history
""" + how(["Pick a service and country, rent — the number is live for 30 minutes exactly.","Use it for any signup/verification. The code arrives as a text.","Poll the order (auto or manual) until the code shows.","Cancel before a code arrives and you get every satoshi back.","Each rental is logged in the recent-rentals table with a live countdown."]) + body += gloss([("OTC","one-time code — the PIN a service texts you"),("burn","cancel an unused rental inside the refund window"),("SMSPool","our upstream number provider")]) return page("sms", body) @app.route("/api/sms/rent", methods=["POST"]) @@ -641,8 +920,8 @@ def api_sms_rent(): d = jf(b) or {} if d.get("success") == 1: con = db(); now = int(time.time()) - con.execute("INSERT INTO sms_rentals(phone,service,country,purchase_id,cost,status,created,expires) VALUES(?,?,?,?,?,?,?,?)", - (d.get("number"), param("service"), param("country"), str(d.get("purchase_id")), d.get("cost",0), "active", now, now+1800)) + con.execute("INSERT INTO sms_rentals(user_id,phone,service,country,purchase_id,cost,status,created,expires) VALUES(?,?,?,?,?,?,?,?,?)", + (uid, d.get("number"), param("service"), param("country"), str(d.get("purchase_id")), d.get("cost",0), "active", now, now+1800)) con.commit() cost = int(d.get("cost_in_cents") or 5) if uid and not has_pass(uid): @@ -667,7 +946,10 @@ def api_sms_history(): con = db(); now = int(time.time()) con.execute("UPDATE sms_rentals SET status='expired' WHERE status='active' AND expires < ?", (now,)) con.commit() - return jsonify([dict(r) for r in con.execute("SELECT * FROM sms_rentals ORDER BY id DESC LIMIT 50")]) + uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "login required (POST /inbox act=login)"}), 401 + return jsonify([dict(r) for r in con.execute("SELECT * FROM sms_rentals WHERE user_id=? ORDER BY id DESC LIMIT 50", (uid,))]) # ---------- 4. PROXY LAB ---------- @app.route("/proxy", methods=["GET", "POST"]) @@ -714,6 +996,7 @@ def proxy():
Rent more — storefront: {PLEIADES_APP}
API: POST /api/proxy/test (user, pass) → egress IP + geo JSON.
""" + how(["Enter your Pleiades gateway user:pass — the same credentials work across the fleet.","The lab tunnels a CONNECT request through the gateway and reports the true egress IP, geo and ISP.","Use the geo builder to steer the exit: region, country, city, sticky 30-min sessions.","Need bandwidth? Buy GB plans at the Pleiades storefront."]) + body += gloss([("sticky session","same exit IP kept across requests"),("egress","the exit IP the rest of the internet sees"),("Pleiades","our proxy gateway network")]) return page("proxy", body) @app.route("/api/proxy/test", methods=["POST"]) @@ -893,7 +1176,8 @@ d.addEventListener('change',function(){{}}); document.getElementById('ih').addEventListener('change',function(){{document.querySelector('.fnh').textContent=this.files[0].name}}); document.getElementById('ie').addEventListener('change',function(){{document.querySelector('.fne').textContent=this.files[0].name}}); -
API: POST /api/steg/hide (image, text, password?, bits 1-3, spread) → PNG · POST /api/steg/extract (image, password?, bits?, spread?) → JSON
""" + how(["Drop a PNG — your words are written into the least-significant bits of its pixels.","Depth 1 = invisible and robust; depth 2-3 fits more text but is easier to detect.","Spread=randomized scatters bits across the image instead of top-down.","A password encrypts the payload AND derives the scatter pattern — wrong password = noise.","Extract reads the embedded metadata automatically — just drop the file and the words come back."]) +
API: POST /api/steg/hide (image, text, password?, bits 1-3, spread) → PNG · POST /api/steg/extract (image, password?, bits?, spread?) → JSON
""" + how(["Drop a PNG — your words are written into the least-significant bits of its pixels.","Depth 1 = invisible and robust; depth 2-3 fits more text but is easier to detect.","Spread=randomized scatters bits across the image instead of top-down.","A password encrypts the payload AND derives the scatter pattern — wrong password = noise.","Extract reads the embedded metadata automatically — just drop the file and the words come back."]) + body += gloss([("LSB","least significant bit — pixel bits that carry hidden data"),("depth","how many bit planes carry the payload"),("spread","payload dispersed across the image to survive edits")]) return page("steg", body) @app.route("/api/steg/hide", methods=["POST"]) @@ -959,6 +1243,8 @@ def btc_invoice(amount="1.00"): def api_track_create(): fn = param("filename") or "file" uid = key_user() or current_user_id() + if not uid: + return jsonify({"ok": False, "error": "login required — create a no-KYC account at /inbox (POST /inbox act=register), then retry"}), 401 token = secrets.token_urlsafe(16) con = db() if has_pass(uid): @@ -976,7 +1262,7 @@ def api_track_create(): con.execute("INSERT INTO trackables(user_id,token,filename,kind,invoice_id,paid,created) VALUES(?,?,?,?,?,0,?)", (uid or 0, token, esc(fn[:100]), "file", inv["id"], int(time.time()))) con.commit() - return jsonify({"ok": True, "invoice_id": inv["id"], "checkoutLink": inv.get("checkoutLink"), "token": token, + return _checkout_or_json({"ok": True, "invoice_id": inv["id"], "checkoutLink": inv.get("checkoutLink"), "token": token, "after_payment_upload_url": f"{SITE}/track/pay?token={token}"}) @app.route("/track/pay", methods=["GET"]) @@ -1121,7 +1407,7 @@ def mail():
Type your desired mailbox name, pick a length, pay the invoice — the mailbox activates the moment the payment settles.
{mine}
API: POST /api/mail/create (local, days) → invoice · GET /api/mail/inbox?addr= (needs login) — inbound via Cloudflare Email Routing → worker relay.
""" + how(["Pick a name and a package — 7, 30 or 90 days, BTC priced.","Pay the invoice; the mailbox activates the second it settles.","The address is receive-only: verification codes, confirmations, one-off handouts.","The countdown runs in real time; when it hits zero the mailbox retires itself.","All mail shows on the site inbox — nothing touches any other identity."]) - return page("steg", body) + return page("mail", body) @app.route("/api/mail/create", methods=["POST"]) def api_mail_create(): @@ -1153,7 +1439,7 @@ def api_mail_create(): con.execute("INSERT INTO mailboxes(user_id,address,invoice_id,paid,expires,created,plan_days) VALUES(?,?,?,?,?,?,?)", (uid or 0, addr, inv["id"], 0, 0, int(time.time()), pack[3])) con.commit() - return jsonify({"ok": True, "address": addr, "checkoutLink": inv.get("checkoutLink"), "invoice_id": inv["id"]}) + return _checkout_or_json({"ok": True, "address": addr, "checkoutLink": inv.get("checkoutLink"), "invoice_id": inv["id"]}) @app.route("/api/mail/inbound", methods=["POST"]) def api_mail_inbound(): @@ -1172,15 +1458,15 @@ def api_mail_inbound(): @app.route("/mail/view") def mail_view(): uid = current_user_id() - if not uid: return page("inbox", "
login required
") + if not uid: return page("mail", '
login required — sign in / create account
') addr = param("addr") or "" con = db() m = con.execute("SELECT * FROM mailboxes WHERE address=? AND user_id=?", (addr.lower(), uid)).fetchone() - if not m: return page("inbox", "
not your mailbox
") + if not m: return page("mail", "
not your mailbox
") mails = con.execute("SELECT * FROM mails WHERE mailbox_id=? ORDER BY id DESC LIMIT 100", (m["id"],)).fetchall() rows = "".join(f'
{esc(x["sender"])} · {time.strftime("%b %d %H:%M", time.localtime(x["ts"]))}
{esc(x["subject"])}
{esc(x["body"])}
' for x in mails) or '
empty — waiting for mail…
' left = max(0, m["expires"] - int(time.time())) - return page("steg", f""" + return page("mail", f"""

{esc(m['address'])}

remaining — auto-refreshes every 15s.

{rows}
API: POST /api/forensics (image) → JSON: exif, gps, flags, ELA score.
""" + how(["Drop any image — EXIF and GPS get dumped instantly.","Error-level analysis (ELA) re-compresses and diffs: edited regions glow in the amplified view.","Edit-tool tags (Photoshop/GIMP) are flagged automatically.","EXIF-stripped images get flagged too — usually means scrubbed or generated.","If the image carries a DARK0RBITS stego payload, this tool sees it."]) - return page("steg", body) + body += gloss([("ELA","error level analysis — regions re-saved after editing light up"),("EXIF","camera/software metadata embedded in the file"),("quantization","JPEG compression-table fingerprints")]) + return page("forensics", body) def _ela_score(img_bytes): from PIL import Image, ImageChops, ImageEnhance @@ -1505,7 +1793,7 @@ def api_forensics(): except Exception: pass _, maxdiff = _ela_score(data) return jsonify({"ok": True, "exif": ex, "gps_present": bool(exif.get_ifd(0x8825)) if hasattr(exif, "get_ifd") else False, - "stego_auriga": ("auriga_meta" in im.info or "dark0rbits_meta" in im.info), "ela_max_diff": maxdiff, + "stego_payload": ("auriga_meta" in im.info or "dark0rbits_meta" in im.info), "ela_max_diff": maxdiff, "flags": (["exif-stripped"] if not ex else [])}) # ---------- 6f. CANARY TRAPS ---------- @@ -1520,7 +1808,7 @@ def canary():
You get: a link (paste anywhere), a pixel URL (embed in docs/pages), and a fake credential line to drop in files.
{canary_list()}
API: POST /canary (tag) · GET /api/canary/list (login) · hits log like trackables.
""" + how(["Create a trap and tag it with who/where it belongs.","Plant the link anywhere — or embed the pixel URL, or drop the fake credential line.","The moment ANYONE touches it: IP, geo, ISP, device fire into your inbox.","Each trap shows its hit count and armed/triggered status.","One trap per place — re-plant after it fires."]) - return page("track", body) + return page("canary", body) def canary_list(): uid = current_user_id() @@ -1536,7 +1824,7 @@ def canary_list(): @app.route("/canary", methods=["POST"]) def canary_create(): uid = current_user_id() - if not uid: return page("track", "
login required
") + if not uid: return page("canary", "
login required
") tag = (param("tag") or "untagged")[:80] con = db() token = secrets.token_urlsafe(12) @@ -1571,7 +1859,7 @@ def canary_pixel(token): @app.route("/api/canary/list") def api_canary_list(): uid = current_user_id() - if not uid: return jsonify({"ok": False, "error": "login required"}) + if not uid: return jsonify({"ok": False, "error": "login required — free no-KYC account at /inbox"}) con = db() rows = [dict(r) | {"hits": con.execute("SELECT COUNT(*) c FROM canary_hits WHERE canary_id=?", (r["id"],)).fetchone()["c"]} for r in con.execute("SELECT * FROM canaries WHERE user_id=? ORDER BY id DESC LIMIT 50", (uid,))] return jsonify(rows) @@ -1610,7 +1898,7 @@ def admin_reply(): @app.route("/api/passport") def api_passport(): uid = current_user_id() - if not uid: return jsonify({"ok": False, "error": "login required"}) + if not uid: return jsonify({"ok": False, "error": "login required — free no-KYC account at /inbox"}) con = db() u = con.execute("SELECT * FROM users WHERE id=?", (uid,)).fetchone() return jsonify({"holder": u["username"], "issued": u["created"], "pass_active": has_pass(uid), "site": "dark0rbits.thetempleofdoom.com"}) @@ -1695,6 +1983,266 @@ def api_inbox_msgs(): con = db() return jsonify([dict(r) for r in con.execute("SELECT * FROM messages WHERE user_id=? ORDER BY id DESC LIMIT 100", (uid,))]) +# ---------- 7h. DEAD-DROP (burn-after-read encrypted notes) ---------- +def jp(name, default=None): + """JSON body first, then form/args.""" + if request.is_json: + j = request.get_json(silent=True) + if isinstance(j, dict) and name in j: return j[name] + v = param(name) + return v if v is not None else default + +DD_API = ("
AGENT API
POST " + SITE + """/api/deaddrop/create
+  Content-Type: application/json  (or form fields)
+  {"body":"meet at 03:00","burn_after_reads":3,"ttl_hours":24,"password":"hunter2"}
+  -> {"ok":true,"url":"BASE/drop/TOKEN","reads":3,"expires_epoch":...}
+  auth: session cookie or Authorization: Bearer dk_...
+GET /drop/TOKEN          burns one read; append ?p=password when locked
+free with PASS - otherwise 5c/note from balance (top up at /keys)
+rate limit: 10 creates/min
""").replace("BASE", SITE) + +DD_EXPLAINER = """
OPSEC NOTES
+• Payload is sealed with AES-256-GCM before it touches disk. The server holds ciphertext only — no plaintext column, no log. +• TTL (1-72h) and burn-after-read (1-10) are both armed at creation. +• The link token is ~96 bits of randomness. No listing, no search, no directory. Lose it and it is gone. +• Optional password gate — wrong attempts cost nothing. +• Billing: free with PASS, otherwise 5¢ per note from your metered balance.
""" + +@app.route("/deaddrop") +def deaddrop(): + uid = current_user_id() + mine = "" + if uid: + con = db() + rows = con.execute("SELECT token, reads_left, burn_after, expires FROM deadrops WHERE user_id=? ORDER BY id DESC LIMIT 10", (uid,)).fetchall() + if rows: + trs = "".join(f'/drop/{r["token"][:10]}…{r["reads_left"]}/{r["burn_after"]}' for r in rows) + mine = f'
Your drops{trs}
LinkReadsSelf-destructs
' + body = f""" +

DEAD DROP

Burn-after-read encrypted notes. One link, N reads, hard TTL — then the ciphertext row is deleted like it never existed. No sender, no receiver, no trace.

+
New drop +
+ +
+ + + +
+
{'Free with your PASS — or 5¢ from balance.' if uid else 'Sign in first (no KYC, no email) — free with PASS, else 5¢ from balance.'}
+{mine} +{DD_EXPLAINER} +""" + DD_API + how(["Write the payload, set reads + TTL, add a password if the channel is noisy.", + "Nothing with PASS — or 5 cents from your metered balance. No KYC either way.", + "Share only the /drop/ link — once, over a channel you trust.", + "Every open burns a read; the remaining count shows live on the page.", + "The final read deletes the row server-side. A tombstone is all that remains."]) + return page("deaddrop", body) + +@app.route("/api/deaddrop/create", methods=["POST"]) +def api_deaddrop_create(): + r = rate_limit("ddcreate", 10, 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 dk_ key"}), 401 + body = str(jp("body") or "").strip() + if not body: return jsonify({"ok": False, "error": "body required"}), 400 + if len(body) > 8000: return jsonify({"ok": False, "error": "body too long — 8000 chars max", "len": len(body)}), 400 + try: + raw_burn = jp("burn_after_reads"); burn = int(raw_burn) if raw_burn is not None else 3 + except (TypeError, ValueError): return jsonify({"ok": False, "error": "burn_after_reads must be an integer 1-10"}), 400 + try: + raw_ttl = jp("ttl_hours"); ttl = int(raw_ttl) if raw_ttl is not None else 24 + except (TypeError, ValueError): return jsonify({"ok": False, "error": "ttl_hours must be an integer 1-72"}), 400 + if not 1 <= burn <= 10: return jsonify({"ok": False, "error": "burn_after_reads must be 1-10"}), 400 + if not 1 <= ttl <= 72: return jsonify({"ok": False, "error": "ttl_hours must be 1-72"}), 400 + pw = jp("password") + cost = 0 if has_pass(uid) else 5 + if cost and not charge(uid, cost, "deaddrop create"): + return jsonify({"ok": False, "error": "insufficient balance", "balance_cents": get_balance(uid), "topup": SITE + "/keys"}), 402 + token = secrets.token_urlsafe(12) + con = db() + exp = int(time.time()) + ttl * 3600 + con.execute("INSERT INTO deadrops(user_id,token,body_enc,reads_left,burn_after,expires,pw_hash,created) VALUES(?,?,?,?,?,?,?,?)", + (uid, token, dd_encrypt(body), burn, burn, exp, hash_pw(pw) if pw else "", int(time.time()))) + con.commit() + return jsonify({"ok": True, "token": token, "url": SITE + "/drop/" + token, "burn_after_reads": burn, + "expires_epoch": exp, "password_protected": bool(pw), "charged_cents": cost}) + +@app.route("/drop/", methods=["GET", "POST"]) +def drop_view(token): + pw = param("p") or "" + con = db() + d = con.execute("SELECT * FROM deadrops WHERE token=?", (token,)).fetchone() + head = '

DEAD DROP

burn-after-read viewer

' + if not d: + return page("deaddrop", head + '
GONE burned, expired, or never existed. there is no listing to check — that is the point.
') + if d["expires"] < int(time.time()): + con.execute("DELETE FROM deadrops WHERE id=?", (d["id"],)); con.commit() + return page("deaddrop", head + '
TTL EXPIRED the note aged out and was destroyed server-side.
') + if d["pw_hash"] and hash_pw(pw) != d["pw_hash"]: + return page("deaddrop", head + """
LOCKED
+
+
Wrong attempts burn nothing — a read counts only when the note actually opens.
""") + left = d["reads_left"] - 1 + content = dd_decrypt(d["body_enc"]) or "(payload unreadable)" + prot = " · password-protected" if d["pw_hash"] else "" + if left <= 0: + con.execute("DELETE FROM deadrops WHERE id=?", (d["id"],)); con.commit() + note = 'FINAL READ — NOTE DESTROYED the ciphertext row is gone. this is the last copy anyone will ever see.' + else: + con.execute("UPDATE deadrops SET reads_left=? WHERE id=?", (left, d["id"])); con.commit() + note = f'READ OK {left} of {d["burn_after"]} reads left{prot} — the link dies at zero.' + return page("deaddrop", head + f""" +
{note}
+
PAYLOAD
{esc(content)}
""") + + +# ---------- 8b. FRAUD-SCORE (composite heuristic 0-100) ---------- +DISPOSABLE_DOMAINS = {"mailinator.com","guerrillamail.com","guerrillamail.net","guerrillamail.org","10minutemail.com","10minutemail.net", +"temp-mail.org","tempmail.com","tempmailo.com","yopmail.com","yopmail.net","throwawaymail.com","getnada.com","nada.email", +"dispostable.com","maildrop.cc","mailnesia.com","trashmail.com","trashmail.de","mytrashmail.com","sharklasers.com","grr.la", +"bugmenot.com","mailcatch.com","tempinbox.com","tmpmail.org","tmpmail.net","fakeinbox.com","spamgourmet.com","mailexpire.com", +"moakt.com","mohmal.com","emailondeck.com","burnermail.io","33mail.com","mailsac.com","inboxkitten.com","linshiyouxiang.net", +"tempmail.plus","minuteinbox.com","instantemailaddress.com","discard.email","spam4.me","1secmail.com","1secmail.net","1secmail.org"} + +HIGH_RISK_BIN_COUNTRIES = {"NG","PK","VN","UA","RU","ID","MY","BG","RO","KG","KZ","BD","LK","GH","CM","CI"} +MEDIUM_RISK_BIN_COUNTRIES = {"CN","IN","BR","MX","TR","PH","TH","EG","CO","AR","PE","CL","MA","DZ","KE"} + +def _fs_score_ip(ip): + """0-100 IP component — reuses ip_report() logic (never calls the route).""" + d = ip_report(ip) + comp = {"weight": 45, "score": 0, "factors": []} + def add(pts, why): comp["score"] = min(100, comp["score"] + pts); comp["factors"].append(f"+{pts} {why}") + if d.get("proxy"): add(40, "proxy/VPN flag on IP") + if d.get("hosting"): add(25, "hosting/datacenter ASN (not residential)") + if d.get("mobile"): add(-10, "mobile carrier (typ. consumer device)") + cc = str(d.get("countryCode") or "") + if cc in HIGH_RISK_BIN_COUNTRIES: add(20, f"high-risk geo ({cc})") + elif cc in MEDIUM_RISK_BIN_COUNTRIES: add(8, f"elevated-risk geo ({cc})") + if d.get("status") == "fail" or not d.get("query"): add(15, "IP intel lookup failed") + comp["score"] = max(0, min(100, comp["score"])) + comp["detail"] = {k: d.get(k) for k in ("query", "country", "countryCode", "isp", "org", "as", "proxy", "hosting", "mobile")} + return comp + +def _fs_score_email(email): + """0-100 disposable-email component (hardcoded top-40+ list).""" + comp = {"weight": 25, "score": 0, "factors": []} + if not email: + comp["factors"].append("not provided — component skipped") + return comp + e = email.strip().lower() + if "@" not in e or e.startswith("@") or e.endswith("@"): + comp["score"] = 50; comp["factors"].append("+50 malformed address") + return comp + dom = e.rsplit("@", 1)[1] + if dom in DISPOSABLE_DOMAINS: + comp["score"] = 100; comp["factors"].append(f"+100 disposable domain ({dom})") + else: + comp["score"] = 5; comp["factors"].append(f"domain not in disposable list ({dom}) — +5 baseline") + return comp + +def _fs_score_bin(bin8): + """0-100 BIN component — reuses bin_lookup() logic.""" + comp = {"weight": 30, "score": 0, "factors": []} + if not bin8: + comp["factors"].append("not provided — component skipped") + return comp + bin8 = re.sub(r"\D", "", str(bin8))[:8] + if len(bin8) < 6: + comp["score"] = 50; comp["factors"].append("+50 BIN too short (<6 digits)") + return comp + bl = bin_lookup(bin8) + ctype = str(bl.get("type") or "").lower() + prepaid = bl.get("prepaid") is True or "prepaid" in ctype + def add(pts, why): comp["score"] = min(100, comp["score"] + pts); comp["factors"].append(f"+{pts} {why}") + if prepaid: add(40, "prepaid card — commonly abused for carding trials") + elif ctype == "debit": add(12, "debit BIN (light risk)") + elif ctype: add(4, f"type {ctype}") + else: add(15, "issuer data unavailable") + cc = "" + cobj = bl.get("country") or {} + cc = (cobj.get("alpha2") or cobj.get("countryCode") or cobj.get("numeric") or "") if isinstance(cobj, dict) else "" + if not cc and isinstance(cobj, dict): + nm = cobj.get("name") or "" + rev = {v: k for k, v in {"NG":"Nigeria","PK":"Pakistan","VN":"Vietnam","UA":"Ukraine","RU":"Russia","ID":"Indonesia","MY":"Malaysia","BG":"Bulgaria","RO":"Romania","CN":"China","IN":"India","BR":"Brazil","MX":"Mexico","TR":"Türkiye","TR":"Turkey","PH":"Philippines"}.items()} + cc = rev.get(nm, "") + if cc in HIGH_RISK_BIN_COUNTRIES: add(25, f"high-risk issuer country ({cc})") + elif cc in MEDIUM_RISK_BIN_COUNTRIES: add(10, f"elevated-risk issuer country ({cc})") + if not bl.get("bank") or not (bl.get("bank") or {}).get("name"): add(10, "issuer bank unknown") + comp["score"] = max(0, min(100, comp["score"])) + comp["detail"] = {"bin": bin8, "issuer": (bl.get("bank") or {}).get("name"), "country": (cobj.get("name") if isinstance(cobj, dict) else None) or cc or None, "type": bl.get("type"), "prepaid": bl.get("prepaid"), "scheme": bl.get("scheme")} + return comp + +def fraud_score(ip=None, email=None, bin8=None): + parts, total, wsum = [], 0, 0 + for comp in ([_fs_score_ip(ip)] if ip else []) + ([_fs_score_email(email)] if email else []) + ([_fs_score_bin(bin8)] if bin8 else []): + parts.append(comp); total += comp["score"] * comp["weight"]; wsum += comp["weight"] + if not wsum: return None + composite = round(total / wsum) + if composite >= 70: band = "HIGH" + elif composite >= 40: band = "MEDIUM" + else: band = "LOW" + conf = min(100, 30 + int(20 * (len(parts) - 1) + wsum / 3)) + return {"score": composite, "band": band, "confidence": conf, "components": parts} + +SCORE_EXPLAINER = """
RISK MODEL
+• IP component (weight 45): datacenter or relay origins, high-risk geos. +• Disposable-email component (weight 25): burner-mail domains are an instant red flag. +• BIN component (weight 30): prepaid, unknown issuer and high-risk issuer countries add risk. +• Composite = weighted average, banded LOW <40 ≤ MEDIUM <70 ≤ HIGH. +• Confidence rises with the number of inputs scored. 2¢/call, free with PASS. Rate limit 20/min.
""" + +SCORE_API = ("
AGENT API
GET " + SITE + """/api/score?ip=1.2.3.4&email=victim@mailinator.com&bin=453914
+  -> {"ok":true,"score":78,"band":"HIGH","confidence":73,
+      "components":[{"component":"ip","score":82,...},"email":...,"bin":...]}
+  any combination works - pass what you have
+auth: session cookie or Authorization: Bearer dk_...
+2c/call, free with PASS - rate limit 20/min
""").replace("BASE", SITE) + +@app.route("/score") +def score_page(): + q_ip = (param("ip") or "").strip() + q_email = (param("email") or "").strip() + q_bin = (param("bin") or "").strip() + res = "" + if q_ip or q_email or q_bin: + r = fraud_score(q_ip or None, q_email or None, q_bin or None) + if r: + res = f"""
SCORE: {r['score']}/100 — {r['band']} RISK confidence {r['confidence']}% +{''.join(f"" for c in r['components'])}
ComponentScoreFactors
{esc(c['weight'])}{esc(c['score'])}{esc('; '.join(c['factors']))}
""" + body = f""" +

FRAUD SCORE

Composite 0-100 risk for an identity shard: IP + email + card BIN. Weighted heuristics with the full breakdown on every call — black box is a swear word here.

+
+ + + +
+{res} +{SCORE_EXPLAINER}""" + SCORE_API + how(["Feed any combination of IP, email and BIN — components re-weight around what you provide.", + "IP: proxy/hosting flags + geo risk, via the same intel engine as /ip.", + "Email: matched against a hardcoded list of burner-mail domains.", + "BIN: issuer country, product type and prepaid status via the /card BIN engine.", + "Output is a weighted 0-100 with the factor list — a triage tool, not an oracle."]) + return page("score", body) + +@app.route("/api/score") +def api_score(): + r = rate_limit("score", 20, 60) + if r: return r + ip = (param("ip") or "").strip() or None + email = (param("email") or "").strip() or None + bin8 = (param("bin") or "").strip() or None + if not (ip or email or bin8): return jsonify({"ok": False, "error": "at least one of ip, email, bin required"}), 400 + 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 dk_ key"}), 401 + if not has_pass(uid) and not charge(uid, 2, "fraud score"): + return jsonify({"ok": False, "error": "insufficient balance", "balance_cents": get_balance(uid), "topup": SITE + "/keys"}), 402 + fr = fraud_score(ip, email, bin8) + if not fr: return jsonify({"ok": False, "error": "scoring failed"}), 500 + return jsonify({"ok": True, "ip": ip, "email": email, "bin": bin8, "score": fr["score"], "band": fr["band"], "confidence": fr["confidence"], "components": fr["components"]}) + # ---------- 8. FREE TOOLS ---------- TOOLS_JS = """ function tab(n){document.querySelectorAll('.pane').forEach(p=>p.style.display='none');document.getElementById(n).style.display='block'} @@ -1751,7 +2299,11 @@ def tools(): """ +

+
Heavy tools (full pages, each with a JSON API)
+◈ DEAD-DROP — burn-after-read encrypted notes  ·  +◈ SCREENSHOT — page capture or rendered-text preview  ·  +◈ FRAUD-SCORE — composite IP + email + BIN risk 0-100
""" return page("tools", body) # ---------- 9. OPERATOR CONSOLE ---------- @@ -1762,7 +2314,7 @@ def admin(): resp.set_cookie("dark0rbits_admin", secrets.token_urlsafe(16), max_age=86400, httponly=True) return resp if not request.cookies.get("dark0rbits_admin"): - return page("ip", '

OPERATOR

') + return page("track", '

OPERATOR

') con = db() msgs = con.execute("SELECT m.*, u.username FROM messages m JOIN users u ON u.id=m.user_id ORDER BY m.id DESC LIMIT 100").fetchall() msgs_html = "".join(f'
{esc(m["username"])} · {time.strftime("%b %d %H:%M", time.localtime(m["created"]))}
{m["body"]}
' for m in msgs) or '
empty
' @@ -1800,6 +2352,9 @@ def index(): ("eh","MAIL FORENSICS","Paste raw headers → real origin IP + geo, SPF/DKIM/DMARC verdicts, spoof flags."), ("forensics","IMAGE FORENSICS","EXIF, GPS, edit-tool detection, error-level analysis — expose doctored photos."), ("canary","CANARY TRAPS","Tripwire links and pixels — instant alert the moment anyone touches one."), + ("deaddrop","DEAD-DROP","AES-GCM encrypted notes that burn after N reads or TTL. Optional password. No trace left."), + ("shot","SCREENSHOT","Headless-capture any page when Chromium is up; otherwise a rendered-text + intel preview. Agents: poll the status API."), + ("score","FRAUD-SCORE","Composite 0-100 risk: IP intel + disposable-email + BIN heuristics, with full breakdown."), ("tools","FREE TOOLS","DNS resolver, HTTP header inspector, JWT decoder, hasher, generators."), ("passport","AGENT PASSPORT","Machine-readable trust badge for your bots. Agents are first-class here."), ] @@ -1873,5 +2428,20 @@ def _dr_legacy_redirect(): return None # REDACT-REDIRECT +@app.errorhandler(404) +def not_found(e): + if request.path.startswith("/api/"): + return jsonify({"ok": False, "error": "no such endpoint", "path": request.path}), 404 + body = """ +

404 — LOST SIGNAL

+
This page drifted off the map. The tools are all still here:
+""" + return page("home", body), 404 + + if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, threaded=True)