v0.2.0: WebUI dashboard, remove stealth, action button

- Full WebUI dashboard (localhost:8088) with status, module toggles,
  IOC stats, alerts, settings editor, log viewer, quick actions
- Works in both KernelSU embedded mode (ksu.exec) and standalone (HTTP API)
- action.sh opens WebUI when tapping module card in KernelSU manager
- Removed stealth.sh (overengineered, not needed yet)
- Added WEBUI_ENABLED and WEBUI_PORT config options
- Bumped version to v0.2.0
This commit is contained in:
sssnake
2026-03-31 18:55:57 -07:00
parent 6f3cd3f0f8
commit 65966eb952
8 changed files with 818 additions and 3 deletions

28
action.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/system/bin/sh
# Vigil — Module Action Button
# This runs when the user taps the module card in KernelSU/Magisk manager
# Opens the WebUI in the default browser
VIGIL_DATA="/data/adb/vigil"
WEBUI_PORT=8088
[ -f "$VIGIL_DATA/vigil.conf" ] && . "$VIGIL_DATA/vigil.conf"
# Check if WebUI is running
WEBUI_RUNNING=0
if [ -f "$VIGIL_DATA/vigild.pid" ]; then
VIGILD_PID=$(cat "$VIGIL_DATA/vigild.pid")
kill -0 "$VIGILD_PID" 2>/dev/null && WEBUI_RUNNING=1
fi
if [ "$WEBUI_RUNNING" = "0" ]; then
echo "Starting Vigil WebUI..."
MODDIR="${0%/*}"
nohup "$MODDIR/vigil/lib/webui.sh" serve >> "$VIGIL_DATA/vigil.log" 2>&1 &
sleep 2
fi
# Open WebUI in browser
am start -a android.intent.action.VIEW -d "http://localhost:${WEBUI_PORT}" 2>/dev/null
echo "Vigil WebUI: http://localhost:${WEBUI_PORT}"

View File

@@ -1,7 +1,7 @@
id=vigil id=vigil
name=Vigil — Anti-Surveillance Shield name=Vigil — Anti-Surveillance Shield
version=v0.1.0 version=v0.2.0
versionCode=1 versionCode=2
author=Setec Labs author=Setec Labs
description=Anti-surveillance, anti-stalkerware, and anti-forensic protection for journalists, activists, and at-risk users. Detects Pegasus, stalkerware, IMSI catchers, silent SMS, forensic extraction tools, and more. description=Anti-surveillance, anti-stalkerware, and anti-forensic protection for journalists, activists, and at-risk users. Detects Pegasus, stalkerware, IMSI catchers, silent SMS, forensic extraction tools, and more.
updateJson= updateJson=

View File

@@ -227,6 +227,12 @@ cmd_deep_scan() {
"$VIGIL_LIB/deep_scan.sh" deep "$VIGIL_LIB/deep_scan.sh" deep
} }
cmd_webui() {
check_module
echo "Starting Vigil WebUI on http://localhost:${WEBUI_PORT:-8088}"
"$VIGIL_LIB/webui.sh" serve
}
cmd_harden() { cmd_harden() {
check_module check_module
local subcmd="${1:-harden}" local subcmd="${1:-harden}"
@@ -298,6 +304,9 @@ cmd_help() {
echo " duress [setup|status]" echo " duress [setup|status]"
echo " Duress/panic trigger configuration" echo " Duress/panic trigger configuration"
echo "" echo ""
echo "${BOLD}Dashboard${NC}"
echo " webui Start WebUI dashboard (localhost:8088)"
echo ""
echo "${BOLD}Maintenance${NC}" echo "${BOLD}Maintenance${NC}"
echo " update-ioc Update threat indicator database" echo " update-ioc Update threat indicator database"
echo " version Show version" echo " version Show version"
@@ -328,6 +337,7 @@ case "$1" in
app) shift; cmd_app_honeypot "$@" ;; app) shift; cmd_app_honeypot "$@" ;;
network) shift; cmd_network "$@" ;; network) shift; cmd_network "$@" ;;
duress) shift; cmd_duress "$@" ;; duress) shift; cmd_duress "$@" ;;
webui) cmd_webui ;;
log) shift; cmd_log "$@" ;; log) shift; cmd_log "$@" ;;
version) echo "Vigil v${VERSION}" ;; version) echo "Vigil v${VERSION}" ;;
help|--help|-h|"") cmd_help ;; help|--help|-h|"") cmd_help ;;

View File

@@ -167,7 +167,14 @@ main() {
"$VIGIL_LIB/antiforensics.sh" harden >> "$VIGIL_LOG" 2>&1 "$VIGIL_LIB/antiforensics.sh" harden >> "$VIGIL_LOG" 2>&1
fi fi
# 9. Install network blocklists # 9. WebUI dashboard
if [ "${WEBUI_ENABLED:-1}" = "1" ]; then
log INFO "Starting WebUI on port ${WEBUI_PORT:-8088}..."
"$VIGIL_LIB/webui.sh" serve >> "$VIGIL_LOG" 2>&1 &
log INFO "WebUI PID: $!"
fi
# 10. Install network blocklists
if [ "${NETWORK_BLOCK_C2:-1}" = "1" ] || [ "${NETWORK_BLOCK_TRACKERS:-1}" = "1" ]; then if [ "${NETWORK_BLOCK_C2:-1}" = "1" ] || [ "${NETWORK_BLOCK_TRACKERS:-1}" = "1" ]; then
log INFO "Installing network blocklists..." log INFO "Installing network blocklists..."
"$VIGIL_LIB/network_monitor.sh" install >> "$VIGIL_LOG" 2>&1 "$VIGIL_LIB/network_monitor.sh" install >> "$VIGIL_LOG" 2>&1

View File

@@ -86,3 +86,7 @@ APP_HONEYPOT_AUTO=0 # Auto-honeypot detected threats (0=manual)
IOC_UPDATE_INTERVAL=86400 # Seconds between auto-updates (86400=24hr) IOC_UPDATE_INTERVAL=86400 # Seconds between auto-updates (86400=24hr)
VIGIL_API_KEY="" # Autarch API key for backend updates VIGIL_API_KEY="" # Autarch API key for backend updates
VIGIL_BACKEND_URL="" # Autarch backend URL VIGIL_BACKEND_URL="" # Autarch backend URL
# ── WebUI Dashboard ─────────────────────────────────
WEBUI_ENABLED=1 # Start WebUI on boot
WEBUI_PORT=8088 # Port for local dashboard

284
vigil/lib/webui.sh Executable file
View File

@@ -0,0 +1,284 @@
#!/system/bin/sh
# Vigil — WebUI Server
# Serves a local web dashboard for settings, status, alerts, and scan control
# (c) Setec Labs
#
# Runs on localhost:8088 (configurable)
# Uses busybox httpd with CGI, or falls back to nc-based server
VIGIL_DATA="/data/adb/vigil"
VIGIL_LOG="$VIGIL_DATA/vigil.log"
WEBUI_PORT="${WEBUI_PORT:-8088}"
WEBUI_DIR=""
VIGIL_LIB="$(dirname "$0")"
# Find the webroot
for d in /data/adb/modules/vigil/vigil/webroot "$VIGIL_LIB/../webroot"; do
[ -d "$d" ] && WEBUI_DIR="$d" && break
done
[ -f "$VIGIL_DATA/vigil.conf" ] && . "$VIGIL_DATA/vigil.conf"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [webui] $1" >> "$VIGIL_LOG"
}
# ── CGI API HANDLER ──
# Processes API requests and returns JSON
handle_api() {
local endpoint="$1"
local method="$2"
echo "HTTP/1.1 200 OK"
echo "Content-Type: application/json"
echo "Access-Control-Allow-Origin: *"
echo "Connection: close"
echo ""
case "$endpoint" in
/api/status)
local daemon_status="stopped"
local daemon_pid=""
if [ -f "$VIGIL_DATA/vigild.pid" ]; then
daemon_pid=$(cat "$VIGIL_DATA/vigild.pid")
kill -0 "$daemon_pid" 2>/dev/null && daemon_status="running"
fi
local lockdown=$([ -f "$VIGIL_DATA/.lockdown" ] && echo "true" || echo "false")
cat <<ENDJSON
{
"daemon": "$daemon_status",
"pid": "$daemon_pid",
"lockdown": $lockdown,
"version": "0.2.0",
"modules": {
"scanner": "${SCANNER_ENABLED:-1}",
"frostguard": "${FROSTGUARD_ENABLED:-1}",
"forensic_shield": "${FORENSIC_SHIELD_ENABLED:-1}",
"sms_shield": "${SMS_SHIELD_ENABLED:-1}",
"network_monitor": "${NETWORK_MONITOR_ENABLED:-1}",
"key_wiper": "${KEYWIPER_ENABLED:-1}",
"deep_scan": "${DEEP_SCAN_BACKGROUND:-1}",
"antiforensics": "${ANTIFORENSICS_ENABLED:-1}",
"duress": "${DURESS_ENABLED:-0}",
"sms_honeypot": "${SMS_FAKE_RESPONSE:-0}",
"app_honeypot": "${APP_HONEYPOT_AUTO:-0}",
"quarantine": "${QUARANTINE_ENABLED:-0}"
}
}
ENDJSON
;;
/api/alerts)
echo "["
if [ -f "$VIGIL_DATA/alerts/history" ]; then
local first=1
tail -50 "$VIGIL_DATA/alerts/history" | while IFS='|' read -r sev ts mod msg; do
[ $first -eq 0 ] && echo ","
first=0
# Escape quotes in message
msg=$(echo "$msg" | sed 's/"/\\"/g')
echo " {\"severity\":\"$sev\",\"timestamp\":$ts,\"module\":\"$mod\",\"message\":\"$msg\"}"
done
fi
echo "]"
;;
/api/ioc-stats)
echo "{"
local first=1
for f in packages.txt certificates.txt domains.txt ips.txt hashes.txt cellebrite_hashes.txt hosts.txt; do
[ $first -eq 0 ] && echo ","
first=0
local name=$(echo "$f" | sed 's/\.txt//')
local count=0
[ -f "$VIGIL_DATA/$f" ] && count=$(wc -l < "$VIGIL_DATA/$f")
echo " \"$name\": $count"
done
echo "}"
;;
/api/config)
if [ "$method" = "POST" ]; then
# Read POST body from stdin
read -r body
# Parse key=value pairs and update config
echo "$body" | tr '&' '\n' | while IFS='=' read -r key val; do
key=$(echo "$key" | tr -d ' ')
val=$(echo "$val" | tr -d ' ')
if grep -q "^${key}=" "$VIGIL_DATA/vigil.conf" 2>/dev/null; then
sed -i "s|^${key}=.*|${key}=${val}|" "$VIGIL_DATA/vigil.conf"
fi
done
echo "{\"status\":\"ok\"}"
else
# Return current config as JSON
echo "{"
local first=1
grep -v '^#' "$VIGIL_DATA/vigil.conf" 2>/dev/null | grep '=' | while IFS='=' read -r key val; do
key=$(echo "$key" | tr -d ' ')
val=$(echo "$val" | sed 's/^"//' | sed 's/"$//' | sed 's/#.*//' | tr -d ' ')
[ -z "$key" ] && continue
[ $first -eq 0 ] && echo ","
first=0
echo " \"$key\": \"$val\""
done
echo "}"
fi
;;
/api/scan)
echo "{\"status\":\"started\"}"
# Run scan in background
"$VIGIL_LIB/scanner.sh" quick >> "$VIGIL_LOG" 2>&1 &
;;
/api/deep-scan)
echo "{\"status\":\"started\"}"
"$VIGIL_LIB/deep_scan.sh" deep >> "$VIGIL_LOG" 2>&1 &
;;
/api/lockdown)
"$VIGIL_LIB/key_wiper.sh" lockdown >> "$VIGIL_LOG" 2>&1 &
echo "{\"status\":\"lockdown_initiated\"}"
;;
/api/harden)
"$VIGIL_LIB/antiforensics.sh" harden >> "$VIGIL_LOG" 2>&1 &
echo "{\"status\":\"hardening\"}"
;;
/api/sanitize)
"$VIGIL_LIB/antiforensics.sh" sanitize >> "$VIGIL_LOG" 2>&1 &
echo "{\"status\":\"sanitizing\"}"
;;
/api/update-ioc)
"$VIGIL_LIB/ioc_updater.sh" update >> "$VIGIL_LOG" 2>&1 &
echo "{\"status\":\"updating\"}"
;;
/api/log)
echo "["
if [ -f "$VIGIL_LOG" ]; then
local first=1
tail -100 "$VIGIL_LOG" | while read -r line; do
line=$(echo "$line" | sed 's/"/\\"/g')
[ $first -eq 0 ] && echo ","
first=0
echo " \"$line\""
done
fi
echo "]"
;;
/api/exec)
# Standalone mode: execute shell command (replaces ksu.exec)
echo "HTTP/1.1 200 OK"
echo "Content-Type: text/plain"
echo "Connection: close"
echo ""
if [ "$method" = "POST" ] && [ -n "$POST_BODY" ]; then
eval "$POST_BODY" 2>&1
fi
return
;;
*)
echo "{\"error\":\"unknown endpoint\"}"
;;
esac
}
# ── NC-BASED HTTP SERVER ──
# Simple HTTP server using netcat — no dependencies
cmd_serve() {
log "WebUI starting on port $WEBUI_PORT..."
echo "Vigil WebUI: http://localhost:$WEBUI_PORT"
while true; do
# Listen for a connection and handle it
{
# Read the HTTP request
local request=""
local method=""
local path=""
local content_length=0
while read -r line; do
line=$(echo "$line" | tr -d '\r')
[ -z "$line" ] && break
if [ -z "$request" ]; then
request="$line"
method=$(echo "$line" | awk '{print $1}')
path=$(echo "$line" | awk '{print $2}')
fi
if echo "$line" | grep -qi "Content-Length:"; then
content_length=$(echo "$line" | grep -oE '[0-9]+')
fi
done
# Read POST body if present
local POST_BODY=""
if [ "$method" = "POST" ] && [ "$content_length" -gt 0 ] 2>/dev/null; then
POST_BODY=$(dd bs=1 count="$content_length" 2>/dev/null)
fi
export POST_BODY
# Route the request
case "$path" in
/api/*)
handle_api "$path" "$method"
;;
/|/index.html)
echo "HTTP/1.1 200 OK"
echo "Content-Type: text/html"
echo "Connection: close"
echo ""
cat "$WEBUI_DIR/index.html" 2>/dev/null || echo "<h1>WebUI files not found</h1>"
;;
*)
local file="$WEBUI_DIR${path}"
if [ -f "$file" ]; then
local mime="text/plain"
case "$path" in
*.html) mime="text/html" ;;
*.css) mime="text/css" ;;
*.js) mime="application/javascript" ;;
*.json) mime="application/json" ;;
*.png) mime="image/png" ;;
*.svg) mime="image/svg+xml" ;;
esac
echo "HTTP/1.1 200 OK"
echo "Content-Type: $mime"
echo "Connection: close"
echo ""
cat "$file"
else
echo "HTTP/1.1 404 Not Found"
echo "Connection: close"
echo ""
echo "404"
fi
;;
esac
} | busybox nc -l -p "$WEBUI_PORT" 2>/dev/null || {
# Fallback: use toybox nc or /system/bin/nc
log "busybox nc not available, trying alternatives..."
break
}
done
}
# ── DISPATCH ──
case "$1" in
serve) cmd_serve ;;
status) echo "WebUI port: $WEBUI_PORT" ;;
*)
echo "Vigil WebUI Server"
echo "Usage: webui.sh serve"
echo " Starts web dashboard on http://localhost:$WEBUI_PORT"
;;
esac

241
vigil/webroot/index.html Normal file
View File

@@ -0,0 +1,241 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vigil — Anti-Surveillance Shield</title>
<style>
:root{--bg:#0a0a0f;--s:#12121a;--s2:#1a1a25;--b:#2a2a3a;--t:#e0e0e8;--t2:#8888a0;--a:#4a9eff;--g:#22c55e;--y:#eab308;--r:#ef4444;--o:#f97316}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'SF Mono','Cascadia Code','Fira Code',monospace;background:var(--bg);color:var(--t);min-height:100vh;font-size:14px}
.c{max-width:900px;margin:0 auto;padding:16px}
.hdr{text-align:center;padding:24px 0 16px;border-bottom:1px solid var(--b);margin-bottom:20px}
.hdr h1{font-size:22px;font-weight:600;letter-spacing:2px}
.hdr .sub{color:var(--t2);font-size:12px;margin-top:4px}
.hdr .ver{color:var(--a);font-size:11px}
.sb{display:flex;gap:12px;margin-bottom:20px;flex-wrap:wrap}
.sc{display:flex;align-items:center;gap:6px;background:var(--s);border:1px solid var(--b);border-radius:6px;padding:8px 14px;font-size:12px;flex:1;min-width:140px}
.sd{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.sd.on{background:var(--g);box-shadow:0 0 6px var(--g)}.sd.off{background:var(--r)}
.cd{background:var(--s);border:1px solid var(--b);border-radius:8px;margin-bottom:16px;overflow:hidden}
.ch{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--b);background:var(--s2);cursor:pointer;user-select:none}
.ch h2{font-size:14px;font-weight:600}
.cb{padding:16px}.cb.h{display:none}
.mg{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px}
.mi{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;background:var(--s2);border-radius:6px;border:1px solid var(--b)}
.mn{font-size:13px}
.tg{position:relative;width:40px;height:22px;background:#333;border-radius:11px;cursor:pointer;transition:background .2s;border:none;outline:none}
.tg.on{background:var(--g)}
.tg::after{content:'';position:absolute;top:3px;left:3px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s}
.tg.on::after{transform:translateX(18px)}
.ig{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:8px}
.is{text-align:center;padding:12px 8px;background:var(--s2);border-radius:6px}
.is .n{font-size:20px;font-weight:700;color:var(--a)}.is .l{font-size:10px;color:var(--t2);margin-top:4px;text-transform:uppercase}
.al{max-height:300px;overflow-y:auto}
.ai{display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--b);font-size:12px;align-items:flex-start}
.ai:last-child{border:none}
.sv{font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;flex-shrink:0;white-space:nowrap}
.sv.CRITICAL{background:var(--r);color:#fff}.sv.HIGH{background:var(--o);color:#fff}.sv.MEDIUM{background:var(--y);color:#000}.sv.LOW{background:var(--t2);color:#fff}.sv.INFO{background:var(--a);color:#fff}
.at{color:var(--t2);white-space:nowrap;font-size:11px}.am{color:var(--t);word-break:break-word}
.br{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}
.bt{padding:8px 16px;border:1px solid var(--b);border-radius:6px;background:var(--s2);color:var(--t);font-family:inherit;font-size:12px;cursor:pointer;transition:all .15s;outline:none}
.bt:hover{background:var(--b)}.bt:active{transform:scale(.97)}
.bt.d{border-color:var(--r);color:var(--r)}.bt.d:hover{background:var(--r);color:#fff}
.bt.p{border-color:var(--a);color:var(--a)}.bt.p:hover{background:var(--a);color:#fff}
.bt.g{border-color:var(--g);color:var(--g)}.bt.g:hover{background:var(--g);color:#fff}
.bt.ld{opacity:.5;pointer-events:none}
.lv{background:#000;border-radius:6px;padding:12px;max-height:250px;overflow-y:auto;font-size:11px;line-height:1.5;color:var(--t2);white-space:pre-wrap;word-break:break-all}
.sr{display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid var(--b)}
.sr:last-child{border:none}
.sl{font-size:13px}.sd2{font-size:11px;color:var(--t2)}
.si{background:var(--s2);border:1px solid var(--b);border-radius:4px;color:var(--t);padding:4px 8px;font-family:inherit;font-size:12px;width:140px;text-align:right;outline:none}
.si:focus{border-color:var(--a)}
.toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:var(--s2);border:1px solid var(--a);color:var(--t);padding:10px 20px;border-radius:8px;font-size:13px;z-index:999;opacity:0;transition:opacity .3s;pointer-events:none}
.toast.show{opacity:1}
@media(max-width:500px){.mg{grid-template-columns:1fr}.ig{grid-template-columns:repeat(3,1fr)}.sb{flex-direction:column}}
</style>
</head>
<body>
<div class="c">
<div class="hdr">
<h1>VIGIL</h1>
<div class="sub">Anti-Surveillance Shield by Setec Labs</div>
<div class="ver">v0.2.0</div>
</div>
<div class="sb">
<div class="sc"><span class="sd" id="dd"></span><span id="dt">Checking...</span></div>
<div class="sc"><span class="sd" id="ld"></span><span id="lt">Checking...</span></div>
</div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>Quick Actions</h2><span>&#9660;</span></div>
<div class="cb"><div class="br">
<button class="bt p" onclick="run('scanner.sh quick',this)">Quick Scan</button>
<button class="bt p" onclick="run('deep_scan.sh deep',this)">Deep Scan</button>
<button class="bt g" onclick="run('antiforensics.sh harden',this)">Harden</button>
<button class="bt" onclick="run('antiforensics.sh sanitize',this)">Sanitize</button>
<button class="bt" onclick="run('ioc_updater.sh update',this)">Update IOCs</button>
<button class="bt" onclick="run('integrity.sh verify',this)">Check Integrity</button>
<button class="bt d" onclick="if(confirm('Enter BFU lockdown? Reboot to restore.'))run('key_wiper.sh lockdown',this)">LOCKDOWN</button>
</div></div></div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>Protection Modules</h2><span>&#9660;</span></div>
<div class="cb" id="mb"></div></div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>Threat Database</h2><span>&#9660;</span></div>
<div class="cb" id="ib"></div></div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>Recent Alerts</h2><span>&#9660;</span></div>
<div class="cb"><div class="al" id="al"></div></div></div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>Settings</h2><span>&#9660;</span></div>
<div class="cb h" id="stb"><div id="stl"></div><div class="br"><button class="bt p" onclick="saveCfg(this)">Save Settings</button></div></div></div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>System Log</h2><span>&#9660;</span></div>
<div class="cb h" id="lb"><div class="lv" id="lv">Loading...</div><div class="br"><button class="bt" onclick="loadLog()">Refresh</button></div></div></div>
</div>
<div class="toast" id="toast"></div>
<script>
const D='/data/adb/vigil';
const L='/data/adb/modules/vigil/vigil/lib';
const isKSU=typeof ksu!=='undefined';
async function ex(cmd){
if(isKSU){
try{const r=await ksu.exec(cmd);return (r&&(r.output||r.stdout))||''}catch(e){return ''}
}else{
try{const r=await fetch('/api/exec',{method:'POST',body:cmd});return await r.text()}catch(e){return ''}
}
}
function toast(m){const t=document.getElementById('toast');t.textContent=m;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2500)}
function tc(h){h.nextElementSibling.classList.toggle('h')}
async function run(s,b){
const lbl=b?b.textContent:'';
if(b){b.classList.add('ld');b.textContent='Running...';}
toast('Running '+s.split('.')[0]+'...');
await ex(L+'/'+s+' 2>&1');
if(b){b.classList.remove('ld');b.textContent=lbl;}
toast('Done');loadStatus();loadAlerts();
}
const MODS={
SCANNER_ENABLED:{l:'Threat Scanner',d:'1'},
FROSTGUARD_ENABLED:{l:'FrostGuard (Integrity)',d:'1'},
FORENSIC_SHIELD_ENABLED:{l:'Forensic Shield',d:'1'},
SMS_SHIELD_ENABLED:{l:'SMS Shield',d:'1'},
NETWORK_MONITOR_ENABLED:{l:'Network Monitor',d:'1'},
KEYWIPER_ENABLED:{l:'Key Wiper',d:'1'},
DEEP_SCAN_BACKGROUND:{l:'Deep Forensic Scan',d:'1'},
ANTIFORENSICS_ENABLED:{l:'Anti-Forensics',d:'1'},
DURESS_ENABLED:{l:'Duress / Panic',d:'0'},
SMS_FAKE_RESPONSE:{l:'SMS Honeypot',d:'0'},
APP_HONEYPOT_AUTO:{l:'App Honeypot',d:'0'},
QUARANTINE_ENABLED:{l:'Quarantine',d:'0'},
SMS_BLOCK_SILENT_INSTALL:{l:'Silent Install Block',d:'1'},
WEBUI_ENABLED:{l:'WebUI Dashboard',d:'1'},
};
const SETS={
SCANNER_INTERVAL:{l:'Scan Interval (sec)',d:'Time between auto scans'},
FROSTGUARD_INTERVAL:{l:'Integrity Check (sec)',d:'Time between checks'},
IOC_UPDATE_INTERVAL:{l:'IOC Update (sec)',d:'Time between updates'},
WEBUI_PORT:{l:'WebUI Port',d:'Dashboard port'},
DURESS_ACTION:{l:'Duress Action',d:'lockdown / wipe-session / wipe'},
DURESS_PIN:{l:'Duress PIN',d:'PIN that triggers panic'},
VIGIL_BACKEND_URL:{l:'Backend URL',d:'Autarch server'},
VIGIL_API_KEY:{l:'API Key',d:'Autarch API key'},
};
let cfg={};
async function loadCfg(){
const raw=await ex('cat '+D+'/vigil.conf 2>/dev/null');
cfg={};
raw.split('\n').forEach(line=>{
line=line.trim();if(!line||line[0]==='#')return;
const i=line.indexOf('=');if(i<0)return;
const k=line.substring(0,i).trim();
let v=line.substring(i+1).trim().replace(/^["']|["']$/g,'').replace(/\s*#.*$/,'');
cfg[k]=v;
});
}
function gc(k,d){return cfg[k]!==undefined?cfg[k]:d}
async function sc(k,v){cfg[k]=v;await ex("sed -i 's|^"+k+"=.*|"+k+"="+v+"|' "+D+"/vigil.conf")}
async function loadStatus(){
await loadCfg();
const pid=(await ex('cat '+D+'/vigild.pid 2>/dev/null')).trim();
const running=pid&&(await ex('kill -0 '+pid+' 2>/dev/null&&echo y')).trim()==='y';
document.getElementById('dd').className='sd '+(running?'on':'off');
document.getElementById('dt').textContent=running?'Daemon: PID '+pid:'Daemon: stopped';
const lk=(await ex('[ -f '+D+'/.lockdown ]&&echo y')).trim()==='y';
document.getElementById('ld').className='sd '+(lk?'off':'on');
document.getElementById('lt').textContent=lk?'LOCKDOWN ACTIVE':'Normal';
let h='<div class="mg">';
for(const[k,m]of Object.entries(MODS)){
const on=gc(k,m.d)==='1';
h+='<div class="mi"><span class="mn">'+m.l+'</span><button class="tg '+(on?'on':'')+'" onclick="tmod(this,\''+k+'\','+on+')"></button></div>';
}
document.getElementById('mb').innerHTML=h+'</div>';
}
async function tmod(el,k,cur){
const v=cur?'0':'1';await sc(k,v);el.classList.toggle('on');
toast(MODS[k].l+': '+(v==='1'?'ON':'OFF'));
}
async function loadIOC(){
const files=['packages','certificates','domains','ips','hashes','cellebrite_hashes','hosts'];
let h='<div class="ig">',tot=0;
for(const f of files){
const n=parseInt((await ex('wc -l<'+D+'/'+f+'.txt 2>/dev/null||echo 0')).trim())||0;
tot+=n;h+='<div class="is"><div class="n">'+n.toLocaleString()+'</div><div class="l">'+f.replace(/_/g,' ')+'</div></div>';
}
document.getElementById('ib').innerHTML='<div class="ig"><div class="is"><div class="n">'+tot.toLocaleString()+'</div><div class="l">Total IOCs</div></div>'+h.replace('<div class="ig">','')+'</div>';
}
async function loadAlerts(){
const raw=await ex('tail -50 '+D+'/alerts/history 2>/dev/null');
const el=document.getElementById('al');
if(!raw.trim()){el.innerHTML='<div style="color:var(--g);padding:8px">No alerts</div>';return}
el.innerHTML=raw.trim().split('\n').reverse().map(line=>{
const p=line.split('|');if(p.length<4)return'';
const[sev,ts,mod,...mp]=p;const msg=mp.join('|');
const d=new Date(parseInt(ts)*1000);
const time=d.toLocaleDateString('en',{month:'short',day:'numeric'})+' '+d.toLocaleTimeString('en',{hour:'2-digit',minute:'2-digit'});
return'<div class="ai"><span class="sv '+sev+'">'+sev+'</span><span class="at">'+time+'</span><span class="am">'+msg+'</span></div>';
}).join('');
}
async function loadSettings(){
await loadCfg();
let h='';
for(const[k,m]of Object.entries(SETS)){
h+='<div class="sr"><div><div class="sl">'+m.l+'</div><div class="sd2">'+m.d+'</div></div><input class="si" data-key="'+k+'" value="'+(gc(k,'')||'')+'"></div>';
}
document.getElementById('stl').innerHTML=h;
}
async function saveCfg(b){
if(b)b.classList.add('ld');
for(const inp of document.querySelectorAll('.si')){
const k=inp.dataset.key,v=inp.value;
if(v!==gc(k,''))await sc(k,v);
}
if(b)b.classList.remove('ld');
toast('Settings saved');
}
async function loadLog(){
const raw=await ex('tail -100 '+D+'/vigil.log 2>/dev/null');
const el=document.getElementById('lv');
el.textContent=raw||'No log data';
el.scrollTop=el.scrollHeight;
}
(async()=>{await loadStatus();await loadIOC();await loadAlerts();await loadSettings();setInterval(()=>{loadStatus();loadAlerts()},30000)})();
</script>
</body>
</html>

241
webroot/index.html Normal file
View File

@@ -0,0 +1,241 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vigil — Anti-Surveillance Shield</title>
<style>
:root{--bg:#0a0a0f;--s:#12121a;--s2:#1a1a25;--b:#2a2a3a;--t:#e0e0e8;--t2:#8888a0;--a:#4a9eff;--g:#22c55e;--y:#eab308;--r:#ef4444;--o:#f97316}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'SF Mono','Cascadia Code','Fira Code',monospace;background:var(--bg);color:var(--t);min-height:100vh;font-size:14px}
.c{max-width:900px;margin:0 auto;padding:16px}
.hdr{text-align:center;padding:24px 0 16px;border-bottom:1px solid var(--b);margin-bottom:20px}
.hdr h1{font-size:22px;font-weight:600;letter-spacing:2px}
.hdr .sub{color:var(--t2);font-size:12px;margin-top:4px}
.hdr .ver{color:var(--a);font-size:11px}
.sb{display:flex;gap:12px;margin-bottom:20px;flex-wrap:wrap}
.sc{display:flex;align-items:center;gap:6px;background:var(--s);border:1px solid var(--b);border-radius:6px;padding:8px 14px;font-size:12px;flex:1;min-width:140px}
.sd{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.sd.on{background:var(--g);box-shadow:0 0 6px var(--g)}.sd.off{background:var(--r)}
.cd{background:var(--s);border:1px solid var(--b);border-radius:8px;margin-bottom:16px;overflow:hidden}
.ch{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--b);background:var(--s2);cursor:pointer;user-select:none}
.ch h2{font-size:14px;font-weight:600}
.cb{padding:16px}.cb.h{display:none}
.mg{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px}
.mi{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;background:var(--s2);border-radius:6px;border:1px solid var(--b)}
.mn{font-size:13px}
.tg{position:relative;width:40px;height:22px;background:#333;border-radius:11px;cursor:pointer;transition:background .2s;border:none;outline:none}
.tg.on{background:var(--g)}
.tg::after{content:'';position:absolute;top:3px;left:3px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s}
.tg.on::after{transform:translateX(18px)}
.ig{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:8px}
.is{text-align:center;padding:12px 8px;background:var(--s2);border-radius:6px}
.is .n{font-size:20px;font-weight:700;color:var(--a)}.is .l{font-size:10px;color:var(--t2);margin-top:4px;text-transform:uppercase}
.al{max-height:300px;overflow-y:auto}
.ai{display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--b);font-size:12px;align-items:flex-start}
.ai:last-child{border:none}
.sv{font-size:10px;font-weight:700;padding:2px 6px;border-radius:3px;flex-shrink:0;white-space:nowrap}
.sv.CRITICAL{background:var(--r);color:#fff}.sv.HIGH{background:var(--o);color:#fff}.sv.MEDIUM{background:var(--y);color:#000}.sv.LOW{background:var(--t2);color:#fff}.sv.INFO{background:var(--a);color:#fff}
.at{color:var(--t2);white-space:nowrap;font-size:11px}.am{color:var(--t);word-break:break-word}
.br{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}
.bt{padding:8px 16px;border:1px solid var(--b);border-radius:6px;background:var(--s2);color:var(--t);font-family:inherit;font-size:12px;cursor:pointer;transition:all .15s;outline:none}
.bt:hover{background:var(--b)}.bt:active{transform:scale(.97)}
.bt.d{border-color:var(--r);color:var(--r)}.bt.d:hover{background:var(--r);color:#fff}
.bt.p{border-color:var(--a);color:var(--a)}.bt.p:hover{background:var(--a);color:#fff}
.bt.g{border-color:var(--g);color:var(--g)}.bt.g:hover{background:var(--g);color:#fff}
.bt.ld{opacity:.5;pointer-events:none}
.lv{background:#000;border-radius:6px;padding:12px;max-height:250px;overflow-y:auto;font-size:11px;line-height:1.5;color:var(--t2);white-space:pre-wrap;word-break:break-all}
.sr{display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid var(--b)}
.sr:last-child{border:none}
.sl{font-size:13px}.sd2{font-size:11px;color:var(--t2)}
.si{background:var(--s2);border:1px solid var(--b);border-radius:4px;color:var(--t);padding:4px 8px;font-family:inherit;font-size:12px;width:140px;text-align:right;outline:none}
.si:focus{border-color:var(--a)}
.toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:var(--s2);border:1px solid var(--a);color:var(--t);padding:10px 20px;border-radius:8px;font-size:13px;z-index:999;opacity:0;transition:opacity .3s;pointer-events:none}
.toast.show{opacity:1}
@media(max-width:500px){.mg{grid-template-columns:1fr}.ig{grid-template-columns:repeat(3,1fr)}.sb{flex-direction:column}}
</style>
</head>
<body>
<div class="c">
<div class="hdr">
<h1>VIGIL</h1>
<div class="sub">Anti-Surveillance Shield by Setec Labs</div>
<div class="ver">v0.2.0</div>
</div>
<div class="sb">
<div class="sc"><span class="sd" id="dd"></span><span id="dt">Checking...</span></div>
<div class="sc"><span class="sd" id="ld"></span><span id="lt">Checking...</span></div>
</div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>Quick Actions</h2><span>&#9660;</span></div>
<div class="cb"><div class="br">
<button class="bt p" onclick="run('scanner.sh quick',this)">Quick Scan</button>
<button class="bt p" onclick="run('deep_scan.sh deep',this)">Deep Scan</button>
<button class="bt g" onclick="run('antiforensics.sh harden',this)">Harden</button>
<button class="bt" onclick="run('antiforensics.sh sanitize',this)">Sanitize</button>
<button class="bt" onclick="run('ioc_updater.sh update',this)">Update IOCs</button>
<button class="bt" onclick="run('integrity.sh verify',this)">Check Integrity</button>
<button class="bt d" onclick="if(confirm('Enter BFU lockdown? Reboot to restore.'))run('key_wiper.sh lockdown',this)">LOCKDOWN</button>
</div></div></div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>Protection Modules</h2><span>&#9660;</span></div>
<div class="cb" id="mb"></div></div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>Threat Database</h2><span>&#9660;</span></div>
<div class="cb" id="ib"></div></div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>Recent Alerts</h2><span>&#9660;</span></div>
<div class="cb"><div class="al" id="al"></div></div></div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>Settings</h2><span>&#9660;</span></div>
<div class="cb h" id="stb"><div id="stl"></div><div class="br"><button class="bt p" onclick="saveCfg(this)">Save Settings</button></div></div></div>
<div class="cd"><div class="ch" onclick="tc(this)"><h2>System Log</h2><span>&#9660;</span></div>
<div class="cb h" id="lb"><div class="lv" id="lv">Loading...</div><div class="br"><button class="bt" onclick="loadLog()">Refresh</button></div></div></div>
</div>
<div class="toast" id="toast"></div>
<script>
const D='/data/adb/vigil';
const L='/data/adb/modules/vigil/vigil/lib';
const isKSU=typeof ksu!=='undefined';
async function ex(cmd){
if(isKSU){
try{const r=await ksu.exec(cmd);return (r&&(r.output||r.stdout))||''}catch(e){return ''}
}else{
try{const r=await fetch('/api/exec',{method:'POST',body:cmd});return await r.text()}catch(e){return ''}
}
}
function toast(m){const t=document.getElementById('toast');t.textContent=m;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2500)}
function tc(h){h.nextElementSibling.classList.toggle('h')}
async function run(s,b){
const lbl=b?b.textContent:'';
if(b){b.classList.add('ld');b.textContent='Running...';}
toast('Running '+s.split('.')[0]+'...');
await ex(L+'/'+s+' 2>&1');
if(b){b.classList.remove('ld');b.textContent=lbl;}
toast('Done');loadStatus();loadAlerts();
}
const MODS={
SCANNER_ENABLED:{l:'Threat Scanner',d:'1'},
FROSTGUARD_ENABLED:{l:'FrostGuard (Integrity)',d:'1'},
FORENSIC_SHIELD_ENABLED:{l:'Forensic Shield',d:'1'},
SMS_SHIELD_ENABLED:{l:'SMS Shield',d:'1'},
NETWORK_MONITOR_ENABLED:{l:'Network Monitor',d:'1'},
KEYWIPER_ENABLED:{l:'Key Wiper',d:'1'},
DEEP_SCAN_BACKGROUND:{l:'Deep Forensic Scan',d:'1'},
ANTIFORENSICS_ENABLED:{l:'Anti-Forensics',d:'1'},
DURESS_ENABLED:{l:'Duress / Panic',d:'0'},
SMS_FAKE_RESPONSE:{l:'SMS Honeypot',d:'0'},
APP_HONEYPOT_AUTO:{l:'App Honeypot',d:'0'},
QUARANTINE_ENABLED:{l:'Quarantine',d:'0'},
SMS_BLOCK_SILENT_INSTALL:{l:'Silent Install Block',d:'1'},
WEBUI_ENABLED:{l:'WebUI Dashboard',d:'1'},
};
const SETS={
SCANNER_INTERVAL:{l:'Scan Interval (sec)',d:'Time between auto scans'},
FROSTGUARD_INTERVAL:{l:'Integrity Check (sec)',d:'Time between checks'},
IOC_UPDATE_INTERVAL:{l:'IOC Update (sec)',d:'Time between updates'},
WEBUI_PORT:{l:'WebUI Port',d:'Dashboard port'},
DURESS_ACTION:{l:'Duress Action',d:'lockdown / wipe-session / wipe'},
DURESS_PIN:{l:'Duress PIN',d:'PIN that triggers panic'},
VIGIL_BACKEND_URL:{l:'Backend URL',d:'Autarch server'},
VIGIL_API_KEY:{l:'API Key',d:'Autarch API key'},
};
let cfg={};
async function loadCfg(){
const raw=await ex('cat '+D+'/vigil.conf 2>/dev/null');
cfg={};
raw.split('\n').forEach(line=>{
line=line.trim();if(!line||line[0]==='#')return;
const i=line.indexOf('=');if(i<0)return;
const k=line.substring(0,i).trim();
let v=line.substring(i+1).trim().replace(/^["']|["']$/g,'').replace(/\s*#.*$/,'');
cfg[k]=v;
});
}
function gc(k,d){return cfg[k]!==undefined?cfg[k]:d}
async function sc(k,v){cfg[k]=v;await ex("sed -i 's|^"+k+"=.*|"+k+"="+v+"|' "+D+"/vigil.conf")}
async function loadStatus(){
await loadCfg();
const pid=(await ex('cat '+D+'/vigild.pid 2>/dev/null')).trim();
const running=pid&&(await ex('kill -0 '+pid+' 2>/dev/null&&echo y')).trim()==='y';
document.getElementById('dd').className='sd '+(running?'on':'off');
document.getElementById('dt').textContent=running?'Daemon: PID '+pid:'Daemon: stopped';
const lk=(await ex('[ -f '+D+'/.lockdown ]&&echo y')).trim()==='y';
document.getElementById('ld').className='sd '+(lk?'off':'on');
document.getElementById('lt').textContent=lk?'LOCKDOWN ACTIVE':'Normal';
let h='<div class="mg">';
for(const[k,m]of Object.entries(MODS)){
const on=gc(k,m.d)==='1';
h+='<div class="mi"><span class="mn">'+m.l+'</span><button class="tg '+(on?'on':'')+'" onclick="tmod(this,\''+k+'\','+on+')"></button></div>';
}
document.getElementById('mb').innerHTML=h+'</div>';
}
async function tmod(el,k,cur){
const v=cur?'0':'1';await sc(k,v);el.classList.toggle('on');
toast(MODS[k].l+': '+(v==='1'?'ON':'OFF'));
}
async function loadIOC(){
const files=['packages','certificates','domains','ips','hashes','cellebrite_hashes','hosts'];
let h='<div class="ig">',tot=0;
for(const f of files){
const n=parseInt((await ex('wc -l<'+D+'/'+f+'.txt 2>/dev/null||echo 0')).trim())||0;
tot+=n;h+='<div class="is"><div class="n">'+n.toLocaleString()+'</div><div class="l">'+f.replace(/_/g,' ')+'</div></div>';
}
document.getElementById('ib').innerHTML='<div class="ig"><div class="is"><div class="n">'+tot.toLocaleString()+'</div><div class="l">Total IOCs</div></div>'+h.replace('<div class="ig">','')+'</div>';
}
async function loadAlerts(){
const raw=await ex('tail -50 '+D+'/alerts/history 2>/dev/null');
const el=document.getElementById('al');
if(!raw.trim()){el.innerHTML='<div style="color:var(--g);padding:8px">No alerts</div>';return}
el.innerHTML=raw.trim().split('\n').reverse().map(line=>{
const p=line.split('|');if(p.length<4)return'';
const[sev,ts,mod,...mp]=p;const msg=mp.join('|');
const d=new Date(parseInt(ts)*1000);
const time=d.toLocaleDateString('en',{month:'short',day:'numeric'})+' '+d.toLocaleTimeString('en',{hour:'2-digit',minute:'2-digit'});
return'<div class="ai"><span class="sv '+sev+'">'+sev+'</span><span class="at">'+time+'</span><span class="am">'+msg+'</span></div>';
}).join('');
}
async function loadSettings(){
await loadCfg();
let h='';
for(const[k,m]of Object.entries(SETS)){
h+='<div class="sr"><div><div class="sl">'+m.l+'</div><div class="sd2">'+m.d+'</div></div><input class="si" data-key="'+k+'" value="'+(gc(k,'')||'')+'"></div>';
}
document.getElementById('stl').innerHTML=h;
}
async function saveCfg(b){
if(b)b.classList.add('ld');
for(const inp of document.querySelectorAll('.si')){
const k=inp.dataset.key,v=inp.value;
if(v!==gc(k,''))await sc(k,v);
}
if(b)b.classList.remove('ld');
toast('Settings saved');
}
async function loadLog(){
const raw=await ex('tail -100 '+D+'/vigil.log 2>/dev/null');
const el=document.getElementById('lv');
el.textContent=raw||'No log data';
el.scrollTop=el.scrollHeight;
}
(async()=>{await loadStatus();await loadIOC();await loadAlerts();await loadSettings();setInterval(()=>{loadStatus();loadAlerts()},30000)})();
</script>
</body>
</html>