Spaces:
Running
A newer version of the Gradio SDK is available: 6.20.0
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
- Field notes (write-up): FIELD_NOTES.md
- Considered for:
build-small,backyard-ai,off-the-grid,well-tuned,off-brand,llama-champion,field-notes,tiny-titan,best-demo
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
- 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.
- Perplexity (highlighter). A small base code model scores token surprise in a single forward pass, underlining the spans that are genuinely novel.
- 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.logorjournalctl -o short. - Windows with Wazuh: Wazuh JSON alerts, including
data.winevents. - Windows without Wazuh:
Get-WinEvent | ConvertTo-Jsonoutput. - 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) andbackyard-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 thellama.cppruntime (llama-cpp-python).field-notes: a full write-up of the design bets and the things that bit me, see 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
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(andWEC_ALERTS_PATHif the alert file lives elsewhere). - Plain file (no Docker): set
WEC_ALERTS_FILEto a localalerts.json. This fits a native (non Docker) Wazuh install, or a setup that mounts or ships itsalerts.jsononto 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
echologprobs.llama-cpp-python's echoed prompt logprobs are misaligned in the 0.3.x line, so the highlighter loads the model withlogits_all=Trueand readsLlama.eval()/.scoresto 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.