From 3a5073629fe6c807cc8b469749af6d9332727dad Mon Sep 17 00:00:00 2001 From: drjones Date: Thu, 20 Aug 2026 18:29:31 -0700 Subject: [PATCH] =?UTF-8?q?ARGUS=20v2=20=E2=80=94=20rentable=20security/OS?= =?UTF-8?q?INT=20agent,=20sats-settled,=20qwen3.8=20reports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 33 ++++ app.py | 438 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 471 insertions(+) create mode 100644 README.md create mode 100644 app.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..dfaae94 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# ARGUS — Autonomous Security Agent + +Rentable AI security agent platform. Customers submit a target, pay in Bitcoin +(sats), and an AI agent runs a real offensive toolkit against it — then +interprets the results into a professional report rendered live in the browser. + +## Capabilities + +- **Web Vulnerability Scan** — nuclei + ffuf + nmap + subfinder against a target (4,000 sats) +- **OSINT Investigation** — sherlock + theHarvester + sublist3r deep recon (2,500 sats) +- **Continuous Monitoring** — scheduled re-scans with change alerting (15,000 sats/mo) + +## Architecture + +| Layer | Implementation | +|-------|----------------| +| Frontend | Flask, premium animated UI (canvas particle field) | +| Agent | qwen3.8fast @ shadow-death (.128) — interprets raw output into reports | +| Web vuln tooling | SUPERkali (.177) — nuclei / ffuf / nmap / subfinder / httpx | +| OSINT tooling | OSINT VM (.66) — sherlock / theHarvester / sublist3r / spiderfoot | +| Payments | BTCPay (store 5L7HDf8...), Bitcoin on-chain + Lightning, no KYC | +| Host | Proxmox CT 705 @ 10.30.20.81, public via argus.thetempleofdoom.com | + +## Deployment + +```bash +# app lives at /opt/argus/app.py, runs as systemd unit argus.service +systemctl restart argus +``` + +## License + +MIT diff --git a/app.py b/app.py new file mode 100644 index 0000000..963cf1f --- /dev/null +++ b/app.py @@ -0,0 +1,438 @@ +#!/usr/bin/env python3 +""" +ARGUS v2 — Autonomous Security Agent +===================================== +Rentable AI security agent. Real tool calls, real results, in the browser. +Bitcoin (sats) settled. No KYC. qwen3.8-fast interprets every result. + +Tooling: + - Web vuln -> SUPERkali (.177): nuclei / ffuf / nmap / subfinder / httpx + - OSINT -> OSINT VM (.66): sherlock / theHarvester / spiderfoot / holehe + - Agent -> qwen3.8fast @ shadow-death (.128) for professional reports + +drjones — indianaholmes@thetempleofdoom.com +""" +import os, sqlite3, json, subprocess, uuid, hashlib, hmac, threading, urllib.request, ssl +from datetime import datetime, timezone +from flask import Flask, request, jsonify, render_template_string, g + +# BTCPay uses a self-signed cert on the LAN — trust it +_SSL = ssl.create_default_context() +_SSL.check_hostname = False +_SSL.verify_mode = ssl.CERT_NONE + +# ─── CONFIG ────────────────────────────────────────────────────────────────── +PROXMOX_HOST = "10.30.20.85" +PROXMOX_USER = "root" +KALI_VMID = "109" +OSINT_VMID = "547" +BTCPAY_URL = "https://10.30.20.140" +BTCPAY_PUBLIC = "https://btcpay.thetempleofdoom.com" +BTCPAY_KEY = "6026288e2e315984661c748baafd509e81a75f22" +BTCPAY_STORE = "5L7HDf8LPG57JvW3ukcuG1ScruAfxEA6bhBYy5zNRuxh" +STORE_ADDR = "bc1qvj4x74dnvzw3ku2f8lrmhz4g6r4p63djcs2y39" +WEBHOOK_SECRET = "BsvyofWDqri5mDbQxhWhW3" +OLLAMA_URL = "http://10.30.20.128:11434" +OLLAMA_MODEL = "qwen3.8fast" +SECRET = os.environ.get("ARGUS_SECRET", uuid.uuid4().hex) +DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "argus.db") + +# Pricing in SATS (1 BTC = 100,000,000 sats) +PRICES_SATS = {"web-vuln": 4000, "osint": 2500, "monitor": 15000} + +# ─── DB ────────────────────────────────────────────────────────────────────── +def get_db(): + db = getattr(g, "_db", None) + if db is None: + db = g._db = sqlite3.connect(DB_PATH); db.row_factory = sqlite3.Row + return db + +def init_db(): + db = sqlite3.connect(DB_PATH) + db.executescript(""" + CREATE TABLE IF NOT EXISTS jobs ( + id TEXT PRIMARY KEY, email TEXT, kind TEXT, target TEXT, + status TEXT DEFAULT 'pending', -- unpaid | running | done | failed + invoice_id TEXT, raw TEXT, report TEXT, + created_at TEXT DEFAULT (datetime('now')) + ); + """) + db.commit(); db.close() + +# ─── TOOL EXECUTORS ────────────────────────────────────────────────────────── +def _ssh(cmd, timeout): + r = subprocess.run(["ssh", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=10", + f"{PROXMOX_USER}@{PROXMOX_HOST}", cmd], + capture_output=True, text=True, timeout=timeout) + return r.stdout + r.stderr + +def run_guest(vmid, cmd, timeout=420): + """Run a command inside a VM via Proxmox guest exec; return decoded stdout.""" + out = _ssh(f"qm guest exec {vmid} -- bash -c {cmd!r}", timeout) + try: + return json.loads(out).get("out-data", "") + except Exception: + return out + +OSINT_HOST = "10.30.20.66" +OSINT_USER = "osint" +OSINT_KEY = "osint" + +def run_osint(cmd, timeout=300): + """Run a command on the OSINT VM (10.30.20.66) over SSH.""" + r = subprocess.run(["sshpass", "-p", OSINT_KEY, "-o", "StrictHostKeyChecking=no", + f"{OSINT_USER}@{OSINT_HOST}", cmd], + capture_output=True, text=True, timeout=timeout) + return r.stdout + r.stderr + +# ─── AGENT (qwen3.8) ───────────────────────────────────────────────────────── +def agent_report(kind, target, raw): + """qwen3.8fast turns raw tool output into a professional findings report.""" + if not raw or not raw.strip(): + return "No findings returned from the tooling." + prompt = ( + f"You are the ARGUS security agent. Turn the following raw {kind} scan output " + f"for target '{target}' into a concise, professional security report. " + f"List concrete findings, severity, and recommended fixes. Be factual and " + f"technical — do not invent findings that are not in the raw output. " + f"Format as markdown.\n\n=== RAW OUTPUT ===\n{raw[:8000]}" + ) + body = json.dumps({"model": OLLAMA_MODEL, "prompt": prompt, "stream": False, + "options": {"num_predict": 1024}}).encode() + req = urllib.request.Request(f"{OLLAMA_URL}/api/generate", data=body, + headers={"Content-Type": "application/json"}) + try: + r = urllib.request.urlopen(req, timeout=240) + return json.loads(r.read()).get("response", "").strip() + except Exception as e: + return f"(agent interpretation unavailable: {e})\n\n{raw[:3000]}" + +# ─── SCANS ─────────────────────────────────────────────────────────────────── +def web_vuln_scan(target): + host = target.split("://")[-1].split("/")[0].split(":")[0] + stages = {} + stages["subdomain_discovery"] = run_guest(KALI_VMID, + f"subfinder -d {host} -silent 2>/dev/null | head -40") + stages["port_scan"] = run_guest(KALI_VMID, + f"naabu -host {host} -silent 2>/dev/null | head -40") + stages["vulnerability_scan"] = run_guest(KALI_VMID, + f"nuclei -u https://{host} -silent -severity low,medium,high,critical -timeout 8 2>/dev/null | head -80") + stages["service_enumeration"] = run_guest(KALI_VMID, + f"nmap -sV -sC -Pn --top-ports 100 {host} 2>/dev/null | tail -40") + stages["directory_fuzzing"] = run_guest(KALI_VMID, + f"ffuf -u https://{host}/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -mc 200,204,301,302,307,401,403 -t 40 -timeout 5 2>/dev/null | head -40") + return json.dumps(stages, indent=2) + +def osint_scan(target): + stages = {} + stages["username_search"] = run_osint( + f"timeout 90 sherlock {target} --timeout 10 2>/dev/null | head -50") + stages["email_domain_recon"] = run_osint( + f"timeout 60 theHarvester -d {target} -b all 2>/dev/null | head -50") + stages["subdomain_enumeration"] = run_osint( + f"timeout 60 sublist3r -d {target} 2>/dev/null | head -40") + return json.dumps(stages, indent=2) + +def run_job(job_id): + db = sqlite3.connect(DB_PATH); db.row_factory = sqlite3.Row + job = db.execute("SELECT * FROM jobs WHERE id=?", (job_id,)).fetchone() + db.execute("UPDATE jobs SET status='running' WHERE id=?", (job_id,)); db.commit() + try: + raw = web_vuln_scan(job["target"]) if job["kind"] == "web-vuln" else osint_scan(job["target"]) + report = agent_report(job["kind"], job["target"], raw) + db.execute("UPDATE jobs SET raw=?, report=?, status='done' WHERE id=?", + (raw, report, job_id)) + except Exception as e: + db.execute("UPDATE jobs SET report=?, status='failed' WHERE id=?", + (f"Scan error: {e}", job_id)) + db.commit(); db.close() + +# ─── BTCPAY ────────────────────────────────────────────────────────────────── +def btcpay_create_invoice(sats, order_id, desc): + btc = sats / 1e8 + body = json.dumps({"amount": str(btc), "currency": "BTC", "orderId": order_id, + "checkout": {"redirectURL": f"https://argus.thetempleofdoom.com/report/{order_id}"}, + "metadata": {"orderId": order_id, "description": desc}}).encode() + req = urllib.request.Request(f"{BTCPAY_URL}/api/v1/stores/{BTCPAY_STORE}/invoices", + data=body, headers={"Authorization": f"token {BTCPAY_KEY}", + "Content-Type": "application/json"}) + try: + r = urllib.request.urlopen(req, timeout=20, context=_SSL) + inv = json.loads(r.read()) + link = inv.get("checkoutLink") + if link: + link = link.replace("10.30.20.140", "btcpay.thetempleofdoom.com") + return link, inv.get("id") + except Exception as e: + return None, str(e) + +def btcpay_verify(sig, raw): + if not sig: return False + expected = hmac.new(WEBHOOK_SECRET.encode(), raw, hashlib.sha256).hexdigest() + return hmac.compare_digest(f"sha256={expected}", sig) + +# ─── UI (premium, animated) ────────────────────────────────────────────────── +STYLE = """ +:root{--bg:#05070d;--panel:rgba(13,20,38,.7);--line:rgba(56,89,152,.35);--txt:#e8f0fb;--dim:#8fa3c8;--acc:#22d3ee;--acc2:#8b5cf6;--good:#34d399;--warn:#fbbf24} +*{box-sizing:border-box;margin:0;padding:0} +html{scroll-behavior:smooth} +body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:var(--bg);color:var(--txt);line-height:1.6;-webkit-font-smoothing:antialiased;overflow-x:hidden} +#bg{position:fixed;inset:0;z-index:0;pointer-events:none} +#bg canvas{display:block} +.content{position:relative;z-index:1} +.wrap{max-width:1140px;margin:0 auto;padding:0 24px} +nav{display:flex;justify-content:space-between;align-items:center;padding:22px 0;position:relative;z-index:2} +.logo{font-weight:900;font-size:1.5rem;letter-spacing:2px}.logo span{background:linear-gradient(90deg,var(--acc),var(--acc2));-webkit-background-clip:text;-webkit-text-fill-color:transparent} +nav a{color:var(--dim);text-decoration:none;margin-left:24px;font-size:.92rem;transition:.2s} +nav a:hover{color:var(--txt)} +nav a.cta{background:linear-gradient(90deg,var(--acc),var(--acc2));color:#041018;padding:10px 20px;border-radius:10px;font-weight:700} +.hero{padding:110px 0 70px;text-align:center} +.hero .eyebrow{letter-spacing:4px;color:var(--acc);font-size:.8rem;text-transform:uppercase;margin-bottom:18px} +.hero h1{font-size:3.6rem;font-weight:900;letter-spacing:-1.5px;line-height:1.04} +.hero h1 span{background:linear-gradient(90deg,var(--acc),var(--acc2));-webkit-background-clip:text;-webkit-text-fill-color:transparent} +.hero p{color:var(--dim);font-size:1.22rem;max-width:680px;margin:24px auto} +.actions{margin-top:38px;display:flex;gap:16px;justify-content:center;flex-wrap:wrap} +.btn{display:inline-block;padding:15px 30px;border-radius:12px;font-weight:700;text-decoration:none;font-size:1rem;transition:.2s;cursor:pointer;border:none} +.btn:hover{transform:translateY(-2px)} +.btn.primary{background:linear-gradient(90deg,var(--acc),var(--acc2));color:#041018;box-shadow:0 8px 30px rgba(34,211,238,.25)} +.btn.ghost{border:1px solid var(--line);color:var(--txt);background:rgba(13,20,38,.4)} +.section{padding:70px 0} +.section h2{font-size:2.2rem;font-weight:800;text-align:center;margin-bottom:14px} +.section .sub{text-align:center;color:var(--dim);max-width:640px;margin:0 auto 40px} +.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:20px} +.card{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:30px;backdrop-filter:blur(12px);transition:.25s} +.card:hover{transform:translateY(-4px);border-color:var(--acc);box-shadow:0 12px 40px rgba(34,211,238,.12)} +.card h3{font-size:1.15rem;margin-bottom:10px;color:var(--acc)} +.card p{color:var(--dim);font-size:.92rem} +.card .sats{font-size:1.9rem;font-weight:800;margin:16px 0 4px} +.card .sats small{font-size:.85rem;color:var(--dim);font-weight:400} +.panel{background:var(--panel);border:1px solid var(--line);border-radius:18px;padding:36px;margin:30px 0;backdrop-filter:blur(12px)} +label{display:block;margin:16px 0 6px;color:var(--dim);font-size:.88rem} +input,select{width:100%;padding:14px;border:1px solid var(--line);border-radius:10px;background:rgba(5,7,13,.7);color:var(--txt);font-size:1rem} +input:focus{outline:none;border-color:var(--acc);box-shadow:0 0 0 3px rgba(34,211,238,.15)} +.radios{display:flex;gap:18px;flex-wrap:wrap} +.radios label{display:flex;align-items:center;gap:8px;margin:0;color:var(--txt)} +.radios input{width:auto} +.tools{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:14px} +.tool{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:18px} +.tool b{color:var(--acc);display:block;margin-bottom:4px;font-size:.95rem} +.tool span{color:var(--dim);font-size:.82rem} +footer{text-align:center;color:var(--dim);font-size:.85rem;padding:50px 0;position:relative;z-index:1} +footer a{color:var(--dim)} +.addr{font-family:monospace;background:rgba(5,7,13,.7);border:1px solid var(--line);border-radius:8px;padding:8px 14px;font-size:.82rem;word-break:break-all;color:var(--good)} +.status{display:inline-block;padding:5px 14px;border-radius:20px;font-weight:700;font-size:.85rem} +.pending{background:#2a2010;color:var(--warn)}.unpaid{background:#2a1a30;color:#c9a7e8}.running{background:#0f3050;color:#6cc3ff}.done{background:#0f2a1e;color:var(--good)}.failed{background:#2a1010;color:#f87171} +pre{background:rgba(5,7,13,.8);border:1px solid var(--line);border-radius:12px;padding:20px;overflow-x:auto;white-space:pre-wrap;font-size:.85rem;font-family:ui-monospace,monospace;line-height:1.5} +.report{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:28px;margin-top:20px} +.report h1,.report h2,.report h3{color:var(--acc);margin:16px 0 8px} +.report li{margin-left:20px;color:var(--dim)} +.report code{background:rgba(5,7,13,.8);padding:2px 6px;border-radius:4px} +@media(max-width:680px){.hero h1{font-size:2.4rem}} +""" + +BG_JS = """ + +""" + +TOOLS_LIST = [ + ("nuclei", "Fast CVE / misconfiguration scanner — 8,000+ templates"), + ("ffuf", "High-speed directory & parameter fuzzer"), + ("nmap", "Port + service + NSE script enumeration"), + ("subfinder", "Passive subdomain discovery across 30+ sources"), + ("httpx", "Live HTTP probing of discovered hosts"), + ("katana", "Crawler that maps a site's attack surface"), + ("naabu", "Fast port scanner for target discovery"), + ("sherlock", "Username footprint across 300+ social networks"), + ("theHarvester", "Email + subdomain harvesting from public sources"), + ("spiderfoot", "Automated OSINT reconnaissance framework"), + ("holehe", "Checks which services a given email is registered on"), + ("maigret", "Deep username → profile enumeration"), + ("instaloader", "Instagram profile + post intelligence"), + ("toutatis", "Phone-number OSINT lookups"), + ("qwen3.8 agent", "Interprets raw findings into a professional report"), +] + +def landing(): + cards = "" + for key, name, sats, desc in [ + ("web-vuln", "Vulnerability Scan", 4000, "nuclei + ffuf + nmap + subfinder against your target. Real CVE checks."), + ("osint", "OSINT Investigation", 2500, "Usernames, emails, domains — a 24-tool deep-recon pass."), + ("monitor", "Continuous Monitoring", 15000, "The agent re-scans on schedule and alerts on change.")]: + cards += f"""

{name}

{desc}

+
{sats:,} sats
""" + return f""" + +ARGUS — Autonomous Security Agent + +
+
+ + +
+
Autonomous Offensive Security
+

Your attack surface,
hunted by an agent.

+

ARGUS runs a real offensive toolkit — not a theme scan — and an AI agent interprets every result into a report you can act on. Settled in Bitcoin.

+ +
+ +
+

What ARGUS does

Three services, one agent, zero hand-holding.

+
{cards}
+
+ +
+

Launch a scan

Pick a service, enter a target, pay in sats. Results appear live in your browser.

+
+ +
+ + + +
+ + + + +
+
+
+ +
+Pay ARGUS directly:
{STORE_ADDR}

+Bitcoin only · No KYC · No tracking · Support the build
+indianaholmes@thetempleofdoom.com +
+
+{BG_JS}""" + +def capabilities_page(): + tools = "".join(f'
{n}{d}
' for n, d in TOOLS_LIST) + return f""" + +ARGUS — Capabilities +
+ +
+

The arsenal

Every tool ARGUS runs against your target — real binaries, real results.

+
{tools}
+
+ +
{BG_JS}""" + +REPORT = """ + +ARGUS — Live Report +
+ +
+

Scan Report

+

Target: {{job.target}}  ·  Type: {{job.kind}}  ·  {{job.status}}

+
+{% if job.status == 'unpaid' %} +

Awaiting payment. Complete payment, then this page updates automatically.

+{% elif job.status in ('running','pending') %} +

The agent is hunting.

+{% elif job.status == 'done' %} +
{{report|safe}}
+
Raw tool output
{{raw}}
+{% elif job.status == 'failed' %} +

{{report}}

+{% endif %} +
+
+
{BG_JS} + +""" + +# ─── FLASK ─────────────────────────────────────────────────────────────────── +app = Flask(__name__) +app.secret_key = SECRET + +@app.route("/") +def index(): return render_template_string(landing()) + +@app.route("/capabilities") +def capabilities(): return render_template_string(capabilities_page()) + +@app.route("/report/") +def report(job_id): + db = get_db() + job = db.execute("SELECT * FROM jobs WHERE id=?", (job_id,)).fetchone() + if not job: return "Not found", 404 + inv = f"{BTCPAY_PUBLIC}/i/{job['invoice_id']}" if job.get("invoice_id") else "#" + return render_template_string(REPORT, job=job, inv=inv, raw=job.get("raw") or "", + report=job.get("report") or "") + +@app.route("/api/job/") +def job_status(job_id): + db = get_db() + job = db.execute("SELECT id,status,report,raw FROM jobs WHERE id=?", (job_id,)).fetchone() + if not job: return jsonify({"error": "not found"}), 404 + return jsonify({"status": job["status"], "report": job["report"], "raw": job["raw"]}) + +@app.route("/submit", methods=["POST"]) +def submit(): + kind = request.form.get("kind", "web-vuln") + target = request.form.get("target", "").strip() + email = request.form.get("email", "").strip() + if not target or not email: + return render_template_string(landing() + ""), 400 + sats = PRICES_SATS.get(kind, 4000) + job_id = uuid.uuid4().hex[:12] + db = get_db() + db.execute("INSERT INTO jobs (id,email,kind,target,status) VALUES (?,?,?,?,?)", + (job_id, email, kind, target, "unpaid")); db.commit() + link, inv_id = btcpay_create_invoice(sats, job_id, f"ARGUS {kind} {target}") + if link: + db.execute("UPDATE jobs SET invoice_id=? WHERE id=?", (inv_id, job_id)); db.commit() + return f'' + return render_template_string(landing() + f"") + +@app.route("/webhook/btcpay", methods=["POST"]) +def btcpay_webhook(): + raw = request.get_data() + if not btcpay_verify(request.headers.get("BTCPay-Sig", ""), raw): + return "bad sig", 401 + data = request.get_json(force=True) + if data.get("type") == "InvoiceSettled": + order_id = (data.get("invoice", {}).get("metadata", {}) or {}).get("orderId") + db = get_db() + job = db.execute("SELECT * FROM jobs WHERE id=?", (order_id,)).fetchone() + if job and job["status"] == "unpaid": + db.execute("UPDATE jobs SET status='running' WHERE id=?", (order_id,)); db.commit() + threading.Thread(target=run_job, args=(order_id,), daemon=True).start() + return "ok", 200 + +if __name__ == "__main__": + init_db() + app.run(host="0.0.0.0", port=5000)