#!/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.

{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)