# Threat Sphere: Plan A Three.js cyber dashboard that visualizes SSH auth.log data from acequia nodes as a 3D threat sphere. The server appears as a defended node at the center; attackers orbit in a ring; legitimate access flows through a file tree. Data is queried in place using HTTP Range headers (federated query), with no full download required. ## Data Source Any syslog-format `auth.log` accessible via HTTPS with Range header support. The app accepts URLs via query string, text input, select menu with presets, drag-and-drop, or local file browse. Current presets: | Source | URL | Auth | Live | |---|---|---|---| | leila.dev (snapshot) | `https://guerin.acequia.io/apps/log-visualizer/auth.log` | None (public) | No | | leila.dev (live) | `https://files.leila.dev/stephen/auth.log` | Bearer token (read-only, expires 2026-05-29) | Polls every 30s | Log format: ISO 8601 timestamps with microseconds and timezone offset. ``` 2026-02-22T18:10:51.793140-07:00 leila-Macmini7-1 sshd[1234]: Failed password for root from 103.88.76.27 port 37134 ssh2 ``` ## Federated Query Strategy Data stays on the origin server. The app reads it in place using HTTP Range requests. 1. **File size discovery.** HEAD request for Content-Length. If absent (nephele), fall back to `Range: bytes=0-0` and parse total from `Content-Range: bytes 0-0/TOTAL`. 2. **Sparse index.** Sample 64 evenly-spaced 4 KB chunks across the file. Parse the first complete line in each chunk to extract a timestamp. Produces a sorted array of `{ byteOffset, timestampMs }` entries. 3. **Binary search.** To locate a time window, binary search the sparse index for bounding entries, then iteratively bisect the byte range until precision < 8 KB. 4. **Index caching.** Sparse index is cached in localStorage keyed by source ID. On revisit, only the new tail of the file is re-indexed. 5. **Polling.** For live sources, poll every 30s. Read from the last known file size to current. Parse new lines and extend the index. For the current ~2.3 MB file, the app loads all events after indexing. The Range-based index is ready for when files grow larger and full-load becomes impractical. ## Authentication Two token types in `.credentials/`: - **User device token** (`leila-dev-token.txt`): read/write, no expiry. Used for deployment. - **App token** (`leila-dev-app-token.txt`): read-only delegated child (depth 1/3), expires 2026-05-29. Embedded in config for runtime fetch. Safe for client-side use since `writePaths: []`. Public URLs (like the snapshot on guerin.acequia.io) require no token. The RangeReader omits the Authorization header when token is null. Future: prompt for token when a source returns 401/403. Per-source token storage in localStorage. ## 3D Scene Built with Three.js r149 (ES modules via importmap). Post-processing with UnrealBloomPass for glow effects. ### Visual hierarchy (center outward) 1. **Server core** (r=3): wireframe icosahedron with inner glow sphere, "leila-Macmini7-1" label sprite. Pulses on events, color-shifts under attack. 2. **Torus rings** (r=8, r=9): orbital reference rings around the core. 200 floating particles. 3. **File tree** (r=10-25): hyperbolic tree layout of server directory structure (`/home/leila`, `/root`, `/var/log`, `/etc/ssh`, `/etc/caddy`). Nodes as small spheres with connecting lines. Green particle trail on legitimate file access. 4. **Shield dome** (r=45): wireframe sphere with hex grid overlay. Impact ripple rings on attack hits. Opacity and color shift from blue (calm) to red (high attack rate). 5. **Attack ring** (r=60): outer ring of attacker IP nodes positioned by IP hash. Nodes scale with attack count. Red beams (failed password) and orange beams (invalid user) fire inward to shield surface. Green beams (accepted login) pass through to center. ### Connection beams Animated line + traveling particle from attacker to shield/center. Configurable lifetime via fade time slider. Pool capped at 200 active beams (oldest recycled). ## UI Components ### Source bar (standard URL pattern) - `?url=` query string for shareable links (updated via `history.replaceState`) - URL text input + Load button (Enter key triggers load) - Select with Presets and Recent optgroups (localStorage history, 20 entries max) - Drop zone for dragged URLs or local `.log`/`.txt` files - Auto-load priority: query string > last recent > first preset ### Timeline bar Dual-mode timeline at bottom of screen: - **Playback mode**: play/pause button, scrubber, speed controls (1x/10x/100x/1000x). Events fire into the 3D scene as the playhead advances. - **Analysis mode**: snapshot view showing all events at once. Toggle via PLAYBACK/ANALYSIS button. - **Fade time slider**: controls how long attack beams persist (visual density control). ### Stats panel Right panel (240px) showing: - Total events, failed attempts, successful logins, unique attackers - Top 5 attacking IPs with counts - Top 5 targeted usernames with counts ### HUD overlay Canvas overlay with: - Current timestamp (top center) - Event label - Alert flash for authorized logins (green) and attack surges (red) - Color legend (bottom left) ## Event Types The parser classifies each log line into one of 10 types: | Type | Visual effect | |---|---| | FAILED_PASSWORD | Red beam from attacker to shield, shield impact ripple, server pulse | | INVALID_USER | Orange beam from attacker to shield, shield impact ripple | | ACCEPTED_PASSWORD | Green beam through shield to center, file tree access animation, HUD alert | | ACCEPTED_PUBLICKEY | Green beam through shield to center, file tree access animation, HUD alert | | CRON_SESSION | Small server pulse | | PAM_AUTH_FAILURE | Attacker node added to ring | | BANNER_EXCHANGE | Attacker node added to ring | | DISCONNECT | (no visual) | | CONNECTION_CLOSED | (no visual) | | UNKNOWN | (no visual) | ## File Structure ``` apps/log-visualizer/ threat-sphere.html Entry point threat-sphere-plan.md This file auth.log Snapshot data (public) css/ styles.css Dark NOC theme js/ config.js PRESETS array, tuning constants main.js CyberDashboard class, URL pattern, init data/ RangeReader.js HTTP Range fetch wrapper, optional bearer auth LogParser.js Regex syslog parser, 10 event types LogIndex.js Sparse index builder, binary search, localStorage cache EventStore.js Sorted event array with binary search and stats scene/ SceneManager.js Three.js setup, OrbitControls, bloom post-processing ServerNode.js Central server visualization ShieldDome.js Defensive shield with impact effects AttackRing.js Outer ring of attacker nodes ConnectionBeam.js Animated beam + particle FileTree.js Hyperbolic directory tree ui/ TimelineController.js Playback/analysis modes, scrubber, speed, fade slider StatsPanel.js Right panel statistics HUD.js Overlay timestamp, alerts, legend ``` ## Hosting The app is hosted on `guerin.acequia.io` (HostGo cPanel, LiteSpeed). Deployed via WebDAV PUT on port 2078 with netrc auth. The app queries log data cross-origin from `files.leila.dev` (nephele, port 443, CORS enabled with `Access-Control-Allow-Origin: *`). ## Roadmap ### Phase 1 (done) - 3D scene with all visual elements - Federated query with Range headers and sparse index - Dual-mode timeline with playback and analysis - Stats panel, HUD, color legend - Standard URL input pattern with presets, history, drop zone, query string ### Phase 2 (next) - GeoIP lookup for attacker locations (client-side DB or API) - Attacker clustering by geography on the ring - Per-source token prompt on 401/403 - Shield dome strength tied to actual fail2ban or firewall state ### Phase 3 (future) - Multi-node view: multiple server cores in scene, each querying its own log - Agent perception layer: the mayordomo evaluates threat patterns locally - Defensive actions: agent can trigger firewall rules, displayed as shield reinforcement - Aggregate statistics mode: full-file scan for histogram, heatmap, network graph - Live WebSocket feed from nodes running real-time log tailing