From 7b1d12a07c0faf351ef7de2240165dbb4a6e7be4 Mon Sep 17 00:00:00 2001 From: SiNaPsEr0x <5438823+SiNaPsEr0x@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:54:29 +0200 Subject: [PATCH] feat: SOCKS5 egress proxy + harden local-model path (#79) Integrate SOCKS5 attack-traffic proxying, egress leak visibility, and local-model hardening. Preserve direct local/LAN model traffic while the proxy is active and reconcile the branch with current upstream. --- docs/index.html | 1021 +++++++++++++++++++--- package-lock.json | 35 + package.json | 2 + src/__tests__/proxy-local-bypass.test.ts | 33 + src/agent/local-agents.ts | 7 +- src/config/index.ts | 33 +- src/llm/index.ts | 123 ++- src/net/proxy.ts | 345 ++++++++ src/server.ts | 247 +++++- 9 files changed, 1708 insertions(+), 138 deletions(-) create mode 100644 src/__tests__/proxy-local-bypass.test.ts create mode 100644 src/net/proxy.ts diff --git a/docs/index.html b/docs/index.html index 7322aed..481dc21 100755 --- a/docs/index.html +++ b/docs/index.html @@ -345,7 +345,31 @@ .header-title { font-size: 18px; font-weight: 600; } - .header-actions { display: flex; gap: 10px; } + .header-actions { display: flex; gap: 10px; align-items: center; } + + /* Egress IP badge — shows the IP the world sees for test traffic. Colour encodes + state: green = proxied (safe), red = real IP exposed (leak), grey = unknown. */ + .egress-badge { + display: inline-flex; align-items: center; gap: 6px; + padding: 5px 10px; border-radius: 6px; cursor: pointer; user-select: none; + font-size: 11px; font-family: 'JetBrains Mono', ui-monospace, monospace; + border: 1px solid rgba(255,255,255,0.14); background: rgba(255,255,255,0.04); + transition: background .15s, border-color .15s; + } + .egress-badge:hover { background: rgba(255,255,255,0.08); } + .egress-badge .egress-dot { width: 8px; height: 8px; border-radius: 50%; background: #888; flex: 0 0 auto; } + .egress-badge .egress-label { color: #9aa; font-weight: 600; letter-spacing: .5px; } + .egress-badge .egress-ip { color: #cfd; font-weight: 700; } + .egress-loading .egress-dot { background: #888; animation: egressPulse 1s ease-in-out infinite; } + .egress-proxied { border-color: rgba(0,255,136,0.4); background: rgba(0,255,136,0.08); } + .egress-proxied .egress-dot { background: #00ff88; box-shadow: 0 0 6px rgba(0,255,136,0.7); } + .egress-proxied .egress-ip { color: #7CFFC4; } + .egress-leak { border-color: rgba(255,64,64,0.55); background: rgba(255,64,64,0.12); } + .egress-leak .egress-dot { background: #ff4040; box-shadow: 0 0 8px rgba(255,64,64,0.8); animation: egressPulse .8s ease-in-out infinite; } + .egress-leak .egress-label, .egress-leak .egress-ip { color: #ff8080; } + .egress-error { border-color: rgba(255,176,32,0.4); } + .egress-error .egress-dot { background: #ffb020; } + @keyframes egressPulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } } .btn { padding: 8px 16px; @@ -868,7 +892,7 @@ .modal-body { padding: 16px; } /* Toast */ - .toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 2000; } + .toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 2000; transition: bottom 0.2s ease; } .toast { background: var(--bg-secondary); @@ -893,6 +917,72 @@ .toast.error { border-left: 3px solid var(--danger); } .toast.warning { border-left: 3px solid var(--warning); } + /* LLM Call Queue popup — live feed of queued/running LLM calls, bottom-right, above toasts */ + .llm-queue-popup { + position: fixed; bottom: 20px; right: 20px; z-index: 2100; + width: 960px; max-width: calc(100vw - 40px); + background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 8px; + box-shadow: 0 8px 24px rgba(0,0,0,0.4); font-size: 12px; overflow: hidden; + animation: slideIn 0.25s ease; + } + .llm-queue-popup.minimized .llm-queue-body { display: none; } + .llm-queue-header { + display: flex; align-items: center; justify-content: space-between; + padding: 8px 10px; cursor: pointer; user-select: none; + background: var(--bg-tertiary); border-bottom: 1px solid var(--border-color); + } + .llm-queue-popup.minimized .llm-queue-header { border-bottom: none; } + .llm-queue-title { color: var(--accent); font-weight: 700; font-size: 11px; letter-spacing: 0.3px; } + .llm-queue-title span { color: var(--text-muted); font-weight: 400; } + .llm-queue-min-btn { + background: none; border: 1px solid var(--border-color); color: var(--text-secondary); + width: 20px; height: 18px; line-height: 14px; border-radius: 3px; cursor: pointer; font-size: 12px; + } + .llm-queue-min-btn:hover { color: var(--accent); border-color: var(--accent); } + .llm-queue-body { max-height: 260px; overflow-y: auto; } + .llm-queue-item { + display: flex; align-items: center; gap: 8px; padding: 8px 10px; + border-bottom: 1px solid var(--border-color); + } + .llm-queue-item:last-child { border-bottom: none; } + .llm-queue-item-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; } + .llm-queue-item.running .llm-queue-item-dot { background: var(--cyan); box-shadow: 0 0 6px var(--cyan); animation: llmQueuePulse 1.2s infinite; } + .llm-queue-item.queued .llm-queue-item-dot { background: var(--text-muted); } + .llm-queue-item.done .llm-queue-item-dot { background: var(--accent); } + .llm-queue-item.error .llm-queue-item-dot { background: var(--danger); } + .llm-queue-item.cancelled .llm-queue-item-dot { background: var(--warning); } + @keyframes llmQueuePulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } } + .llm-queue-item-info { flex: 1; min-width: 0; } + .llm-queue-item-label { color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .llm-queue-item-status { color: var(--text-muted); font-size: 10px; } + .llm-queue-item-stop { + background: none; border: 1px solid var(--border-color); color: var(--text-muted); + width: 20px; height: 20px; border-radius: 3px; cursor: pointer; font-size: 11px; flex-shrink: 0; line-height: 1; + } + .llm-queue-item-stop:hover { color: var(--danger); border-color: var(--danger); } + /* Live log panel inside the queue popup — a real-time feed of what the harness + is launching (benchmarks, API calls, fallbacks, pass/fail). */ + .llm-queue-header-btns { display: flex; gap: 4px; align-items: center; } + .llm-queue-log { + max-height: 190px; overflow-y: auto; border-top: 1px solid var(--border-color); + background: var(--bg-primary); font-size: 10px; padding: 3px 0; display: none; + } + .llm-queue-log.show { display: block; } + .llm-queue-popup.minimized .llm-queue-log { display: none !important; } + .llm-queue-log-entry { + display: flex; gap: 6px; padding: 2px 10px; line-height: 1.35; + border-left: 2px solid transparent; word-break: break-word; + } + .llm-queue-log-time { color: var(--text-muted); flex-shrink: 0; font-size: 9px; } + .llm-queue-log-src { color: var(--text-secondary); font-weight: 700; flex-shrink: 0; font-size: 9px; letter-spacing: 0.3px; } + .llm-queue-log-msg { color: var(--text-primary); flex: 1; min-width: 0; } + .llm-queue-log-entry.info { border-left-color: var(--cyan, #0088ff); } + .llm-queue-log-entry.success { border-left-color: var(--accent, #00ff88); } + .llm-queue-log-entry.warning { border-left-color: var(--warning, #ffaa00); } + .llm-queue-log-entry.error, .llm-queue-log-entry.danger { border-left-color: var(--danger, #ff4444); } + .llm-queue-log-entry.error .llm-queue-log-msg, .llm-queue-log-entry.danger .llm-queue-log-msg { color: var(--danger, #ff4444); } + .llm-queue-log-empty { color: var(--text-muted); padding: 8px 10px; font-style: italic; font-size: 10px; } + /* Scrollbar */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: var(--bg-primary); } @@ -939,6 +1029,7 @@ .bench-result.pass { color: var(--accent); } .bench-result.fail { color: var(--danger); } .bench-result.partial { color: var(--warning); } + .bench-result.cancelled { color: var(--warning); opacity: 0.8; } .perf-bar { width: 100%; @@ -3457,6 +3548,11 @@

War Room

+ + + IP: + … + @@ -5609,6 +5705,78 @@
+
+

🖥️ Local Model (llama.cpp / Ollama / OpenAI-compatible)

+

+ Point the War Room at a model running on your own host — no cloud, no OpenRouter. + Requires npm run server. When enabled, BOTH server-dispatched missions/General AND the + browser "AI" features route through your local model. +

+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ llama.cpp (OpenAI-compatible server): path /v1 · Ollama native: path /api (port 11434) · LM Studio: /v1 (port 1234) +
+
+ + +
+
+ +
+ + +
+
+
+ + +
+
+
+
+ +
+

🧅 Egress Proxy (SOCKS5)

+

+ Route all probe/attack fetch() traffic through a SOCKS5 proxy so tests + don't leave from your own IP (Tor, an SSH -D tunnel, a VPS, etc.). Loopback (the local model & this + server) is always bypassed. Requires npm run server; also settable via TEMPEST_PROXY_URL. +

+
+
+ + +
+
+ Tor: socks5://127.0.0.1:9050 · SSH tunnel: ssh -D 1080 host → socks5://127.0.0.1:1080. Use socks5h:// to resolve DNS at the proxy. +
+
+ + + +
+
+
+
+

🔌 Local Agents

Enlist agents you've already authed on this machine — no API keys needed. T3MP3ST detects each CLI and can drive it as an operator. Tick several and connect them all at once.

@@ -5888,6 +6056,20 @@ tempest.operators.spawn('scan
+ + +