#!/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.8fast 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} --timeout 300 -- 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,
"think": False,
"options": {"num_predict": 1500, "enable_thinking": False, "reasoning_effort": "low"}}).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' %}
{% 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)