Spaces:
Running
Running
| title: TinySOC | |
| emoji: 🛰️ | |
| colorFrom: red | |
| colorTo: gray | |
| sdk: gradio | |
| sdk_version: 4.44.1 | |
| app_file: app_tinysoc.py | |
| pinned: false | |
| license: mit | |
| tags: | |
| - build-small | |
| - backyard-ai | |
| - off-the-grid | |
| - well-tuned | |
| - off-brand | |
| - llama-champion | |
| - field-notes | |
| - tiny-titan | |
| - best-demo | |
| short_description: A fully-local AI SOC analyst on a small model | |
| # 🛰️ TinySOC: a local SOC analyst for people with no SOC | |
| **TinySOC** triages security logs for a **solo operator or a small MSP with no SOC | |
| team**. It reads a log stream (Linux syslog, a Wazuh alert feed, or raw Windows | |
| events), flags what actually looks wrong, highlights *why*, and explains each | |
| finding in plain English: severity, summary, risk, and the next concrete action. | |
| Submission to the **Build Small Hackathon** (Hugging Face × Gradio), *Backyard AI* | |
| track. The "someone I know" is the solo security operator drowning in alert noise. | |
| > **Runs 100% locally on CPU via `llama.cpp`: no cloud API, no data leaves the box.** | |
| ## Submission (Build Small Hackathon) | |
| - **Demo video:** https://huggingface.co/spaces/build-small-hackathon/tinysoc/resolve/main/assets/demo.mp4 | |
| - **Social post:** https://x.com/Mroqui/status/2066654531258118382 | |
| - **Team (Hugging Face):** [@Mroqui](https://huggingface.co/Mroqui) | |
| - **Field notes (write-up):** [FIELD_NOTES.md](./FIELD_NOTES.md) | |
| - **Considered for:** `build-small`, `backyard-ai`, `off-the-grid`, `well-tuned`, `off-brand`, `llama-champion`, `field-notes`, `tiny-titan`, `best-demo` | |
| <video controls src="https://huggingface.co/spaces/build-small-hackathon/tinysoc/resolve/main/assets/demo.mp4" width="100%"></video> | |
| ## Why a *small, local* model is the point (not an accessory) | |
| Most "explain this alert" demos could call a cloud API and be done. TinySOC can't, | |
| **by design**, and that is exactly what makes the small local model load-bearing: | |
| - The **highlighter** scores every token of a log line by its **perplexity** | |
| (how surprising it is given normal behavior). That needs **token-level logprobs | |
| from the model itself**, something cloud chat APIs do not expose. So the model | |
| has to run **locally**, and a *small* one keeps it real-time on CPU. | |
| - Perplexity only **paints** novel tokens (a random C2 domain, a base64 blob). It | |
| does **not** decide maliciousness, a code model finds a reverse shell mundane. | |
| The deterministic baseline decides; perplexity highlights; the model explains. | |
| ## How it works: three honest stages | |
| 1. **Baseline (deterministic detector).** Learns a host profile from normal logs | |
| (users, processes, IPs, active hours) and scores each line with a noisy-OR per | |
| axis, emitting plain-English reasons. This **carries detection**, no LLM guesswork. | |
| 2. **Perplexity (highlighter).** A small **base** code model scores token surprise | |
| in a single forward pass, underlining the spans that are genuinely novel. | |
| 3. **Explanation.** A small (≤4B) model turns a flagged line + its reasons into a | |
| compact JSON triage: `severity`, `summary`, `why`, `next_action`, | |
| `likely_false_positive`. | |
| Timestamps and hostnames are masked before perplexity scoring, so it measures | |
| *message content*, not the clock. | |
| ## Inputs it understands | |
| - **Linux without Wazuh**: paste `/var/log/auth.log` or `journalctl -o short`. | |
| - **Windows with Wazuh**: Wazuh JSON alerts, including `data.win` events. | |
| - **Windows without Wazuh**: `Get-WinEvent | ConvertTo-Json` output. | |
| - **Live Wazuh** (dev): tails a local Wazuh manager's `alerts.json`. | |
| The UI is a **dark SOC console**: a live feed, one-shot injection, log upload, | |
| five built-in scenarios, a per-axis risk radar, and **provenance badges** | |
| (SAMPLE / INJECTED·DEMO / YOUR LOG / LIVE WAZUH) so a real alert is never confused | |
| with demo data. | |
| ## Tracks and badges | |
| Tagged in the YAML above to be considered for: | |
| - **`build-small`** (base) and **`backyard-ai`** (track): a local-first tool for the | |
| solo operator working in their own backyard, no cloud, no data leaving the box. | |
| - **`off-the-grid`**: no cloud APIs, the whole pipeline runs on CPU on the box in | |
| front of you. | |
| - **`well-tuned`**: ships a fine-tuned explainer published on the Hub | |
| (`Mroqui/TinySOC-Qwen2.5-3B`). | |
| - **`off-brand`**: a hand-built dark SOC-console theme with custom panels, radar, | |
| and heatmaps, not a default Gradio look. | |
| - **`llama-champion`**: the model runs through the `llama.cpp` runtime | |
| (`llama-cpp-python`). | |
| - **`field-notes`**: a full write-up of the design bets and the things that bit me, | |
| see [FIELD_NOTES.md](./FIELD_NOTES.md). | |
| - **`tiny-titan`**: a small (≤4B) model does load-bearing work, token-level | |
| perplexity highlighting plus plain-English triage, not a giant model in disguise. | |
| - **`best-demo`**: app, narrated demo video, and social post, a live dashboard that | |
| learns a baseline, flags an injected attack, and explains it in seconds. | |
| ## Run locally | |
| ```bash | |
| pip install -r requirements.txt | |
| python app_tinysoc.py | |
| ``` | |
| On first launch the models download automatically: a small **base** model for the | |
| perplexity highlighter and a small **instruct** model for the explanation. | |
| ## Configuration (environment variables) | |
| | Variable | Default | Role | | |
| |----------|---------|------| | |
| | `WEC_BACKEND` | `llamacpp` | Inference backend: `llamacpp` (self-contained) or `ollama` (dev) | | |
| | `WEC_PPL_REPO` / `WEC_PPL_FILE` | `mradermacher/Qwen2.5-Coder-1.5B-GGUF` / `Qwen2.5-Coder-1.5B.Q4_K_M.gguf` | Highlighter **base** GGUF (perplexity) | | |
| | `WEC_PPL_MODEL_PATH` | - | Explicit local path for the highlighter GGUF | | |
| | `WEC_MODEL_REPO` / `WEC_MODEL_FILE` | `Mroqui/TinySOC-Qwen2.5-3B` / `TinySOC-Qwen2.5-3B-Q5_K_M.gguf` | Explainer GGUF | | |
| | `WEC_MODEL_PATH` | - | Explicit local path for the explainer GGUF | | |
| | `WEC_N_CTX` / `WEC_PPL_N_CTX` | `4096` / `1024` | Context size (explainer / highlighter) | | |
| | `WEC_N_GPU_LAYERS` | `0` | GPU layers (set `-1` on a GPU grant) | | |
| | `WEC_FLAG_THRESHOLD` | `0.6` | Baseline score above which a line is enriched | | |
| | `WEC_OLLAMA_URL` | `http://localhost:11434` | Ollama server (dev backend only) | | |
| | `WEC_WAZUH_CONTAINER` | `wazuh-wazuh.manager-1` | Wazuh manager Docker container to tail (Docker live source) | | |
| | `WEC_ALERTS_PATH` | `/var/ossec/logs/alerts/alerts.json` | Path to `alerts.json` inside that container | | |
| | `WEC_ALERTS_FILE` | - | Tail this local `alerts.json` directly, no Docker (file live source) | | |
| | `WEC_ALLOW_UI_SOURCE` | `false` | Allow choosing the live source from the UI. Keep off on a shared/public deployment (see security note) | | |
| | `WEC_TZ` | `America/Toronto` | Local time zone for displayed timestamps | | |
| **Dev:** point at a remote Ollama server (`WEC_BACKEND=ollama` + | |
| `WEC_OLLAMA_URL=http://<host>:11434`) to skip CPU model loads, see `.env.example`. | |
| **Public Space:** keep the `llamacpp` default; a Space cannot reach a private IP. | |
| ### Live feed sources | |
| The live feed reads new Wazuh alerts as they happen. Two interchangeable | |
| transports read the **same Wazuh `alerts.json` schema**, so pick whichever fits | |
| your deployment: | |
| - **Docker (default):** tails the Wazuh manager container. If your container has | |
| a different name, set `WEC_WAZUH_CONTAINER` (and `WEC_ALERTS_PATH` if the alert | |
| file lives elsewhere). | |
| - **Plain file (no Docker):** set `WEC_ALERTS_FILE` to a local `alerts.json`. This | |
| fits a native (non Docker) Wazuh install, or a setup that mounts or ships its | |
| `alerts.json` onto the host running TinySOC. | |
| The app auto-detects the source: if neither a reachable container nor a readable | |
| file is found (for example on a public Space), the live tab is hidden and the | |
| other inputs stay fully usable. The on-demand "Generate live event" injection | |
| works in Docker mode only; in file mode the upstream pipeline is not ours to | |
| write to, so that button is inert. | |
| **Format scope.** The live tab is Wazuh specific: it expects Wazuh alert JSON | |
| and reads severity from `rule.level`. Other sources (Zeek, Suricata `eve.json`, | |
| Security Onion, an Elasticsearch or OpenSearch index) use different schemas and | |
| are **not** wired into the live tab. Feed those through **paste or upload** | |
| instead: the baseline plus perplexity engines run on raw log text and are format | |
| agnostic, they just score from the learned baseline rather than a vendor | |
| severity field. Native live support for those sources would mean adding a parser | |
| per format, which is out of scope here. | |
| **Security note.** Choosing the live source from the UI is gated by | |
| `WEC_ALLOW_UI_SOURCE` and is **off by default**. On a shared or public deployment | |
| this prevents a visitor from pointing the file tail at arbitrary local files and | |
| reading them through the feed. With the gate off, the app only reads the source | |
| the operator configured via env vars; the UI fields are shown read-only. Enable | |
| the gate only when you self-host and trust who can reach the UI. | |
| > **Why we read logits directly, not `echo` logprobs.** `llama-cpp-python`'s | |
| > echoed prompt logprobs are misaligned in the 0.3.x line, so the highlighter loads | |
| > the model with `logits_all=True` and reads `Llama.eval()` / `.scores` to compute | |
| > each token's true conditional log-probability in one pass. | |
| ### Fine-tuned explainer | |
| TinySOC defaults to a distilled fine-tune of Qwen2.5-3B specialized for security | |
| log triage, auto-downloaded from the Hub (`Mroqui/TinySOC-Qwen2.5-3B`). To override | |
| it, point `WEC_MODEL_PATH` (a local GGUF) or `WEC_MODEL_REPO`/`WEC_MODEL_FILE` at | |
| another model. | |
| ## Limits we own | |
| - The deterministic baseline carries detection; the LLM only explains and is a | |
| suggestion engine, not a verdict. Pure novelty detection produces false | |
| positives on real Windows fleets (benign-but-rare events), so live detection is | |
| shown on a controlled feed, with **badged injection** to demonstrate a catch. | |
| - Bundled examples are **anonymized** (TEST-NET IPs, fake hostnames). Never paste | |
| real infrastructure data or PII into a public Space. | |