
Self-hostable AI SOC that fuses security alerts, auto-triages via agentic AI, runs MITRE ATT&CK investigations, and logs every agent decision in a replayable ledger.
<div align="center">
<img src="https://raw.githubusercontent.com/beenuar/aisoc/HEAD/apps/web/public/logo-mark.svg" alt="AiSOC" width="120" />
# AiSOC
**An open-source, self-hostable AI Security Operations Center.** It ingests your security telemetry, detects and correlates threats, investigates them with AI agents whose reasoning is fully auditable, and proposes responses a human approves.
[](https://opensource.org/licenses/MIT)
[](CHANGELOG.md)
[](https://github.com/beenuar/AiSOC/actions/workflows/ci.yml)
[](https://github.com/beenuar/AiSOC/actions/workflows/codeql.yml)
[](https://securityscorecards.dev/viewer/?uri=github.com/beenuar/AiSOC)
[Docs](https://beenuar.github.io/AiSOC/) · [Architecture](https://github.com/beenuar/aisoc/blob/main/docs/architecture/README.md) · [What actually works](https://github.com/beenuar/aisoc/blob/main/docs/audit/REPOSITORY_REALITY.md) · [Discussions](https://github.com/beenuar/AiSOC/discussions)
</div>
---
## What AiSOC does
Telemetry arrives from your security tools. AiSOC normalizes it, runs the 2603 executable
rules of its 6991-rule library, groups what fires into incidents, investigates each one with
an AI agent whose every prompt and tool call is recorded, and proposes an action. New threat
intelligence re-sweeps the history you already collected. A human approves before anything runs.
## What it looks like running
<a href="https://github.com/beenuar/aisoc/blob/main/apps/web/public/demo/demo.mp4"><img src="https://raw.githubusercontent.com/beenuar/aisoc/main/apps/web/public/demo/hero.gif" alt="AiSOC on one host: make up brings the stack up and prints the sign-in address, the console shows real CISA KEV rows, a pushed event becomes an alert, and the cost dashboard reports the tokens triage spent" /></a>
**[Watch the full three minutes](https://github.com/beenuar/aisoc/blob/main/apps/web/public/demo/demo.mp4)** — install to AI verdict on one
server, against the published images. Terminal waits are shortened, which the recording says on
screen. ([step by step](https://github.com/beenuar/aisoc/blob/main/apps/docs/docs/deployment/walkthrough.mdx))
Stills from earlier runs under the same rules — no seeded rows, no demo mode, no mockups. The events
were authored to be representative; everything downstream is the product doing its job.
([what is real](https://github.com/beenuar/aisoc/blob/main/apps/web/public/screenshots/README.md))
| | |
|---|---|
| <img src="https://raw.githubusercontent.com/beenuar/aisoc/main/apps/web/public/screenshots/alerts-queue.png" alt="Alerts queue" /> | <img src="https://raw.githubusercontent.com/beenuar/aisoc/main/apps/web/public/screenshots/ai-triage-verdict.png" alt="AI triage verdict in the Investigation Rail" /> |
| **Alerts** — each attributed to the connector that fed it. | **Automated triage** — the bundled local model's verdict, confidence and rationale, verbatim. |
| <img src="https://raw.githubusercontent.com/beenuar/aisoc/main/apps/web/public/screenshots/threat-intel-kev.png" alt="Threat intelligence page showing CISA KEV entries" /> | <img src="https://raw.githubusercontent.com/beenuar/aisoc/main/apps/web/public/screenshots/soc-operations.png" alt="SOC operations dashboard with honest empty states" /> |
| **Threat intelligence** — the real CISA KEV catalog, minutes after boot, with no API key. | **SOC operations** — with nothing connected yet, and it says so rather than showing a placeholder. |
## Quick start
```bash
git clone https://github.com/beenuar/AiSOC && cd AiSOC
make up
```
Needs Docker Compose v2 with **8 GB memory and 20 GB free disk in the Docker VM**, plus `python3`
(3.9+) and `bash` — `make doctor` checks all of it, and
[Installation](https://beenuar.github.io/AiSOC/docs/installation#requirements) says what each number
was measured against. The first run downloads a ~2 GB language model into a named volume; only
`make clean` fetches it again.
`make up` also creates `.env` and generates the **eleven** secrets in it — the credential vault,
the session signing key, the five service-to-service credentials and the four datastore passwords — then
creates an administrator and prints its password. That password is generated on your machine, shown once, and stored nowhere:
copy it, or mint a new one with `make bootstrap ARGS=--reset-password`.
Then **prove it actually works**. `make smoke` posts one real event to the ingest API, follows it
through Kafka, detection, correlation and Postgres, and reads the alert back out of the public API.
Every stage reports PASS or FAIL:
```
$ make smoke
[PASS] raw telemetry accepted by ingest
[PASS] event traversed the spine and became an alert
[PASS] alert is retrievable by id from the API
```
Open **http://localhost:3000** and sign in with the credentials `make up` printed (API docs at
**http://localhost:8000/api/docs**). On a server, set `AISOC_CONSOLE_URL` in `.env` — `make up` then
prints that address rather than localhost, which is the one people can browse to. Stuck?
`make doctor`.
## Try it without connecting anything
`make demo` loads a **synthetic** dataset — the pipeline shape, not real activity, and never a benchmark, a customer or an incident. Every row is `is_synthetic = true` and labelled in the console.
## Connect real data
Two ways in. Push, with a credential from `make ingest-token` (the tenant comes from it, not from a header):
```bash
curl -X POST http://localhost:8081/v1/ingest/batch \
-H 'Content-Type: application/json' -H "Authorization: Bearer $AISOC_INGEST_TOKEN" \
-d '{"connector_id":"edr-1","connector_type":"crowdstrike","source_format":"json",
"events":[{"severity":"high","title":"Encoded PowerShell from Office",
"host":"WIN-FIN-01","process_name":"powershell.exe"}]}'
```
Or pull, by configuring one of **84 click-and-connect data connectors** in **Settings →
Connectors** (needs the `full` profile). Those with vendor-specific normalization and live
setup docs include Splunk, Microsoft Sentinel, Elastic, CrowdStrike, Okta, AWS (GuardDuty /
CloudTrail / Security Hub), Wiz, and Kubernetes audit logs — full list in the
[connector docs](https://beenuar.github.io/AiSOC/docs/connectors/api-coverage). Without a
vendor profile a connector still ingests through a generic mapping that resolves host, user
and source IP from the usual spellings.
## How it works
Ingest normalizes to a common shape and Kafka carries it. Then
fusion runs 2603 executable detection rules, of 6991 on disk, and decides what becomes an
alert, correlation groups related alerts, an agent investigates and writes its reasoning to
the Investigation Ledger, and a human approves any response. Separately, new threat intelligence
sweeps the lake for sightings you already collected, and a hypothesis becomes a hunt without
anyone writing a query — the model fills a closed schema and every value it supplies is bound
as a parameter, so it cannot express a query at all.
**Executable is earned, not declared.** A rule enters the compiled ruleset only after a
vendor-shaped event has been replayed through the real connector and this engine and that
rule was *watched to fire*, with an empty event of the same shape producing nothing — never
inferred from a directory or an `enabled:` flag. The proof can fail: `--prove-gate` reverts
the Windows connector and requires all 1,687 Windows rules to go silent. It means the rule
is reachable, not that it detects an attack.
([how, and why 1,362 were refused](https://github.com/beenuar/aisoc/blob/main/docs/detections/sigma-compilation.md))
Both **[docs/architecture/README.md](https://github.com/beenuar/aisoc/blob/main/docs/architecture/README.md)** and the
[docs portal](https://beenuar.github.io/AiSOC/docs/architecture) walk that path one step at
a time, and every box in every diagram links to the code that implements it.
## Deployment profiles
| Profile | Command | Services | RAM | What you get |
|---|---|---|---|---|
| **core** | `make up` | 16 | ~8 GB | The full alerting pipeline: ingest → detect → correlate → alert → triage → console, plus the LLM gateway, a local model, the CISA KEV threat feed, and the connector and response services the agent's vendor tools reach |
| **full** | `make up-full` | 22 | ~12 GB | Core plus event lake, entity graph, full-text search, enrichment |
| **demo** | `make up && make demo` | 16 | ~8 GB | Core plus labelled synthetic data |
CORE is the smallest deployment that takes a real event and produces a real
alert, and **it needs no credentials to do either** — for two reasons.
**The model ships with the gateway.** Ollama runs a pinned ~2 GB
`llama3.2:3b-instruct-q4_K_M` sized for CPU-only inference, so `make up` produces real triage
verdicts with real token counts in the Investigation Ledger — not a stub. It is not a frontier
model: over 50 alerts it gave triage usable output 44 times before the reply was constrained to
JSON and 50 after ([method](https://github.com/beenuar/aisoc/blob/main/scripts/measure_triage_reliability.py)); the rail labels which path
answered. To upgrade, set `OPENAI_API_KEY`, `AISOC_LLM_MODEL_FAST`, `AISOC_LLM_MODEL_DEEP` and an
empty `AISOC_LLM_API_BASE`. **No hosted provider has ever been exercised here** — there is no
funded key, so per-model rows read *not measured* rather than zero.
([ADR-0006](https://github.com/beenuar/aisoc/blob/main/docs/decisions/0006-llm-gateway-in-core.md))
**One real external feed ships too.** `services/threatintel` polls the CISA Known Exploited
Vulnerabilities catalog — authoritative, public, no API key — into the console's Threat
Intelligence page: the one thing in a fresh install that is neither synthetic nor yours.
`cisa.gov` answers 403 to whole networks regardless of user agent, so it falls back to CISA's
own GitHub mirror rather than sitting at zero rows and calling that a clean estate.
## Real vs synthetic data
| Kind | Where | How you can tell |
|---|---|---|
| **Real** | Your connectors and the ingest API | `is_synthetic = false` (the default) |
| **Real, and not yours** | The CISA KEV feed on the Threat Intelligence page | Every row carries `source: cisa-kev`; it is the public catalog, unmodified |
| **Demo** | `make demo` | `is_synthetic = true`, labelled in the console |
| **Benchmark** | `services/agents/tests/eval_data/` | Every published row carries `substrate: true` |
| **Test fixtures** | `tests/`, `**/tests/` | Never shipped in an image |
**Production never silently falls back to synthetic data.** When a backend is unreachable
the console names the failure, not an invented investigation — and an unmeasured figure
reads *not measured*, never `0`. That was not always true; see
[the reality audit](https://github.com/beenuar/aisoc/blob/main/docs/audit/REPOSITORY_REALITY.md) for where it was wrong and how it was fixed.
## AI agents
Agents triage alerts and investigate incidents. What they can and cannot do:
- **They read** the alert, its correlated siblings, entity context, and prior verdicts for the same signature.
- **They call typed tools** — lake queries, graph traversals, enrichment lookups. The model chooses a tool and passes arguments; it never writes SQL.
- **Everything is logged** to the Investigation Ledger: prompts, tool calls, citations, the verdict, and token cost.
- **Grounding is checked.** A verdict citing an indicator the evidence never contained is demoted to human review rather than auto-closed.
- **A prompt is validated before it is sent.** Raw logs, OCSF payloads and secret-shaped values are refused, not redacted after the fact.
- **Nothing executes without a human.** An approver must hold the required permission tier and must not be the person who requested the action.
The bundled model means agents reason for real out of the box. When it returns something the schema
rejects, triage falls back to a deterministic path and the rail shows which one answered — it never fabricates a verdict.
## Project maturity
| Capability | Status | Tested | Production ready |
|---|---|---|---|
| Ingest → detect → correlate → alert | Stable | E2E + unit | Yes |
| Detection engine (2603 executable rules) of 6991 | Stable | Replay proof | Yes |
| Alert correlation into incidents | Stable | Unit | Yes |
| REST API + web console | Stable | Unit + integration | Yes |
| AI triage + Investigation Ledger | Beta | Unit + substrate eval + local-model run | Yes, copilot mode |
| Event lake + hunting (ClickHouse) | Beta | Unit | Yes, `full` profile |
| Retro-hunts when new intel arrives | Beta | Unit + live ClickHouse replay | Yes, `full` profile |
| Hunting agent + 68-hunt library | Beta | Unit + boundary gate | Yes, `full` profile |
| SCIM 2.0, white-label, usage metering | Beta | Unit + Okta/Entra sequences | Yes |
| Entity graph (Neo4j) | Beta | Unit | Yes, `full` profile |
| Governed response actions | Beta | Unit | Human-approved only |
| Scheduled connectors | Beta | Contract tests | `full` profile |
| UEBA | Beta | Unit + live migration round-trip | `full` profile |
| Package distribution (npm/PyPI) | Ready, unpublished | `release.yml` builds and packs all eight on every tag | Install from source — the upload is blocked on registry credentials, which is an account action |
## What AiSOC is not
- **Not a drop-in SIEM replacement.** It correlates and investigates; it does
not replace long-term log retention and compliance search.
- **Not able to see telemetry you have not connected.** There is no discovery.
- **Not autonomous by default.** Response requires explicit policy
authorization and a human approver.
- **Demo incidents are not real incidents**, and benchmark corpora are not
customer telemetry.
- **Benchmark numbers are substrate self-consistency measures**, not live
agent accuracy, and are labelled as such wherever published.
## Troubleshooting
`make doctor` checks the host tools, memory and disk in the Docker VM, every port, each
datastore by querying it rather than by asking whether its container is up, and whether `.env`
still holds placeholders — then prints the command to run next. The six failures it is most
often right about are tabulated under [Installation](https://beenuar.github.io/AiSOC/docs/installation#the-six-most-common-failures).
## Security
Secrets are generated per deployment and never committed; connector credentials are encrypted at
rest. Services connect to Postgres as a DML-only role, so the row-level-security policies actually
apply to them, and tenant isolation is enforced at the query layer in every store. RBAC gates every
mutating route, ingest is authenticated, and the default install sends no prompt anywhere — the
model runs beside it.
**A service with no credential refuses to serve rather than serving unauthenticated.** As of v12.0.0
the actions service and the realtime edge fail closed on a missing secret; `make up` generates all
six, including into an `.env` that already exists. Eight reported vulnerabilities were fixed in that
release — the [changelog](https://github.com/beenuar/aisoc/blob/main/CHANGELOG.md) says what each was. Report via [SECURITY.md](https://github.com/beenuar/aisoc/blob/main/SECURITY.md).
## Developing
```bash
make test # unit tests for every service
make smoke # the golden pipeline, against a running stack
make stats # recount every figure this README publishes
```
Guides: [add a connector](https://beenuar.github.io/AiSOC/docs/plugins/hello-plugin) ·
[add a detection](https://beenuar.github.io/AiSOC/docs/detections/hello-hunt) ·
[plugin lifecycle](https://beenuar.github.io/AiSOC/docs/plugins/lifecycle) ·
[contributing](https://github.com/beenuar/aisoc/blob/main/CONTRIBUTING.md). Every count above is recounted from the tree by
`scripts/project_stats.py`, and CI fails if this README disagrees with it.
## Roadmap · Contributing · License
[ROADMAP.md](https://github.com/beenuar/aisoc/blob/main/ROADMAP.md) · [CONTRIBUTING.md](https://github.com/beenuar/aisoc/blob/main/CONTRIBUTING.md) · [SECURITY.md](https://github.com/beenuar/aisoc/blob/main/SECURITY.md) · MIT