#!/opt/threatmarket/venv/bin/python3 """THREATMARKET — threat-intel marketplace. Metadata public, payloads paid.""" import hashlib import hmac import json import os import secrets import sqlite3 import time import requests from flask import Flask, Response, jsonify, redirect, render_template_string, request, send_file BASE = os.environ.get("TM_BASE", "/opt/threatmarket") ARCHIVE = os.path.join(BASE, "archive") MANIFEST = os.path.join(ARCHIVE, "manifest.json") DB = os.path.join(BASE, "threatmarket.db") BTCPAY_URL = os.environ.get("BTCPAY_URL", "https://10.30.20.140").rstrip("/") BTCPAY_STORE = os.environ["BTCPAY_STORE"] BTCPAY_KEY = os.environ["BTCPAY_KEY"] WEBHOOK_SECRET = os.environ["BTCPAY_WEBHOOK_SECRET"] PACK_PRICE_USD = os.environ.get("TM_PRICE_USD", "12.00") app = Flask(__name__) def db(): con = sqlite3.connect(DB, timeout=10) con.row_factory = sqlite3.Row return con def init_db(): with db() as con: con.execute( """CREATE TABLE IF NOT EXISTS purchases ( token TEXT PRIMARY KEY, pack_id TEXT, invoice_id TEXT UNIQUE, status TEXT, created INTEGER, downloaded INTEGER DEFAULT 0)""" ) def packs(): with open(MANIFEST) as f: return json.load(f) def get_pack(pack_id): for p in packs(): if p["pack_id"] == pack_id: return p return None def file_sha256(path): h = hashlib.sha256() with open(path, "rb") as f: for chunk in iter(lambda: f.read(1 << 16), b""): h.update(chunk) return h.hexdigest() def btcpay_invoice(pack_id, token): r = requests.post( f"{BTCPAY_URL}/api/v1/stores/{BTCPAY_STORE}/invoices", headers={"Authorization": f"token {BTCPAY_KEY}"}, json={ "amount": PACK_PRICE_USD, "currency": "USD", "metadata": {"orderId": token, "itemDesc": f"THREATMARKET intel pack {pack_id}"}, "checkout": {"redirectURL": f"http://10.30.20.102/status/{token}", "redirectAutomatically": True}, }, verify=False, timeout=30, ) r.raise_for_status() return r.json() # ---------- pages ---------- BASE_CSS = """ body{background:#0a0e14;color:#c9d1d9;font-family:'SF Mono',ui-monospace,Menlo,monospace;margin:0} a{color:#58a6ff;text-decoration:none}a:hover{text-decoration:underline} .wrap{max-width:960px;margin:0 auto;padding:40px 24px} h1{color:#f0f6fc;letter-spacing:.18em}h1 .tm{color:#3fb950} .tag{color:#8b949e;font-size:14px} .card{background:#11161f;border:1px solid #1f2733;border-radius:10px;padding:18px 22px;margin:14px 0} .card:hover{border-color:#3fb950} .grid{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap} .badge{background:#12261e;color:#3fb950;border:1px solid #1f4d33;border-radius:99px;padding:2px 10px;font-size:12px;margin-left:6px} .count{color:#58a6ff;font-weight:bold} .btn{display:inline-block;background:#238636;color:#fff;border-radius:8px;padding:10px 22px;font-weight:bold} .btn:hover{text-decoration:none;background:#2ea043} .price{color:#e3b341;font-size:20px;font-weight:bold} footer{border-top:1px solid #1f2733;margin-top:48px;padding:22px 24px;text-align:center;color:#8b949e;font-size:13px} .sha{color:#8b949e;font-size:11px;word-break:break-all} """ FOOTER = """ """ def page(title, body): return render_template_string( "
" "Threat-intel packs harvested by a real scanning fleet. Certificate-transparency logs, DNS liveness, exposed-service inventory. Metadata below is public; payloads unlock after BTC payment.
{rows or 'No packs archived yet — the pipeline runs 2x daily.
'}""" return page("THREATMARKET", body) @app.get("/archive") def archive(): out = [ {k: p[k] for k in ("pack_id", "domain", "date", "categories", "finding_counts", "size_bytes", "sha256")} for p in packs() ] return jsonify(out) @app.get("/buy/Price ${PACK_PRICE_USD} · paid in BTC over BTCPay. After settlement you get a one-time download link for the pack zip.
""" return page(f"Buy {pack_id}", body) @app.post("/buy/Your one-time download is ready. The link dies after the first successful download.
Download {row['pack_id']}.zip""" elif row["status"] == "paid": body = "This one-time link has been consumed.
" else: body = f"""Invoice {row['invoice_id']} is
{row['status']}. This page refreshes every 15s.