All notable changes to AiSOC will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
-
UEBA can no longer read an unscoreable baseline as normal behaviour. A feature that had never been observed, had too few samples, or had zero variance produced a
0.0z-score — the same value an observation sitting exactly on its own mean produces. Since the composite is a root-sum-of- squares, that zero contributed nothing and the entity read as all-clear; service accounts, batch jobs and automation users converge on a constant stream, so they could not raise an anomaly at all.compute_z_scorenow returnsNonefor these cases and callers exclude it. Per-feature composites are unchanged (0.0 ** 2already contributed nothing), but a peer group whose features are all degenerate now yields no peer signal instead of a0.0that halved the caller's personal composite — acriticalscore was being reported asmedium. Affected feature names are logged atinfo. A newMIN_BASELINE_SAMPLESsetting (default 30) gates the sample count. -
Agent investigations are now persisted to the ledger (issue #601). The demo seed renames the canonical seed tenant's slug from
defaulttodemo, so the agents service'stenant_ref="default"fallback matched no tenant and every investigation was silently skipped (ledger.skip_run reason=unknown_tenantatdebug) after already spending compute. Two fixes: the API now forwards the authenticatedtenant_idto the agents service on/cases/{id}/investigate(the run is attributed to the real tenant), and the ledger resolver maps the"default"placeholder to the canonical seed tenant (stable UUID, regardless of its current slug) or the sole tenant in a single-tenant install. A genuinely unresolvable tenant now logs atwarningwith the run/case id, notdebug.
- Self-service data lifecycle (Wave 5). (1) Configurable retention
(
/data-lifecycle/retention): per-tenant windows (days) forraw_events(lake) /alerts/audit, clamped to[1, 3650], with a bounded tenant-scoped ClickHouse purge and a parameterised Postgres purge (W5.1). (2) A field-extraction / transform DSL (pipeline_transforms): a safe, whitelisted, no-evalpipeline (rename/copy/set/set_default/drop/lowercase/uppercase/coalesce/ grok-styleextractwith dotted paths + named captures) that reshapes events onto OCSF; theextractop compiles a%{TOKEN:name}template fromre.escaped literals + a fixed linear-time token map, so it is ReDoS-proof by construction (no raw user regex); validation rejects unknown ops/tokens and oversized pipelines; runtime is fail-open per op and never mutates the input (W5.3). (3) Runtime custom parsers (/data-lifecycle/parsers+/parsers/test): a parser is a named, tenant-scoped, validated transform pipeline you can register and dry-run against a sample event (W5.2). Gated bytest_retention.py+test_pipeline_transforms.py. - Customizable dashboard / report builder (Wave 7). A declarative report is
a list of widgets (whitelisted
type× datasource), validated for unknown types/sources, duplicate ids, and bounded size, then rendered by resolving each widget's data server-side — resilient to a failing/missing resolver (one bad widget renders anerror, never breaks the report).POST /report-builder/validate+/render(the latter wires the real tenant-scopedalerts_by_severityresolver). The same definition can drive the live dashboard and an exported report. Gated bytest_report_builder.py(8 cases). - Compliance mapping + agentless CSPM + destinations (Wave 6). (1) An
agentless CSPM scan engine (
cspm.scan_resources+POST /posture/scan): evaluates a read-only cloud-resource snapshot against misconfiguration checks (public/unencrypted S3, world-open security groups on sensitive ports, IAM users without MFA / stale keys, public/unencrypted RDS, unencrypted EBS), emitting findings with severity + control refs (W6.2). (2) Compliance control mapping with auto-evidence (compliance_mapping): CSPM findings and fired detections (via MITRE) map to CIS / SOC 2 controls and mint dated evidence records automatically (W6.1). (3) Notification/SOAR destinations (destinations+POST /posture/destinations/preview): Opsgenie, email, and a genericaisoc.handoff.v1external-SOAR webhook, with an SSRF guard on outbound targets (W6.3). Gated bytest_compliance_cspm.py(14 cases). - Invoking-identity scoping for response actions (Wave 4). An
ActionPrincipal(user id + tenant + roles + permissions) now rides on everyActionRequest(W4.1). The actions service enforces least-privilege: an action only runs if its principal holds the permission its blast radius demands (actions:execute:{low,medium,high}, higher tiers granting lower;actions:*granting all), with tenant binding (W4.2). The mutating action routes require a service bearer token and fail closed in production when unconfigured (W4.3). Approvals are bound: an approver must hold the action's permission and cannot approve their own request (separation of duties, W4.4). Gated bytest_authz.py(10 cases). - Three detection-authoring modes (Wave 3). (1) A Python detection
framework (
packages/aisoc-detections): write detections asdef rule(event) -> bool+ metadata + inline positive/negativeTESTS, complementing the YAML/Sigma corpus; a fixture harness fails a blind rule (misses a positive) or a noisy one (fires on a negative),evaluate()is fail-closed, and a CI gate (python-detections.yml) runs it. Ships a CLI (aisoc-detections) + example detections. (2) An AI Detection Builder (POST /nl-detection/propose): a plain-English threat description becomes a Sigma rule with auto-generated positive/negative fixtures derived from the rule's own selection (not invented by the LLM), run through the non-circular eval-gate, and opened as a governed DRAFT proposal. (3) A no-code / simple detection builder (SimpleRuleBuilder): a form (field/operator/value rows) compiles to a valid Sigma rule dropped into the governed editor. Also fixes a real bug:detection_loop/ the Wave-1 tuner + hunt bridge inserted into a non-existentaisoc_detection_rule_proposalstable (the real table isdetection_rule_proposals). - Detection backtesting over the event lake (Wave 2). New
POST /api/v1/rules/{rule_id}/backtestruns a candidate detection rule against REAL historical events in the tenant-scoped ClickHouse lake (aisoc.raw_events) over a bounded window and reports exactly how many events would have fired (would_fire), thehit_rate, and sample matches — so an engineer can see a rule's noise on real history before promoting it, instead of only testing against hand-crafted fixtures. The source filter is injection-sanitised and the SELECT is tenant-rewritten vialake_sql.rewrite_for_tenant. Read-only (rules:read). Gate:test_backtest.py. AlsoPOST /detection-proposals/{id}/backtestattaches the result to the proposal'seval_result["backtest"]as a third promotion signal, with an opt-inAISOC_BACKTEST_MAX_HIT_RATEgate that blocks/decideapproval for rules too noisy over history (W2.2,test_backtest_gate.py), and the rule editor gains a "Backtest over history" panel (W2.3). - Closed loop: outcomes compound, repeat alerts auto-suppress (Wave 1). Every
durable auto-triage outcome is now written back as a per-signature institutional
prior (
services/agents/app/memory/outcomes.py), and a later alert matching a trusted prior benign/false-positive disposition is auto-resolved WITHOUT re-triage — human priors are trusted immediately, AI priors require corroboration (repeat count + high confidence), and a prior true-positive never auto-closes a future alert. Suppressions are recorded toaisoc_outcome_suppressions(migration 048) and surfaced as a measuredrepeat_alerts_suppressed/repeat_suppression_rateonGET /metrics/funnel. Fusion now applies a bounded (±0.10), per-tenant institutional-memory nudge to fuse-time confidence (MemoryPriorProviderdistils disposition history; refreshed on a cadence), scheduled hunt findings open governed DRAFTDetectionRuleProposals (hunt-findingsource), and the previously-orphaned disposition-history tuner is wired viaPOST /detection/tuning/auto-suggest. Gates:test_outcome_memory.py,test_memory_nudge.py,test_wave1_loop_edges.py; claim-to-gate matrix +4. - LLM gateway (LiteLLM) — task-based model routing + observability
(#478, PR1). New
litellmservice indocker-compose.ymlas the single entry point for live LLM calls. AiSOC requests a logical task alias (aisoc-triage,aisoc-recon,aisoc-investigation,aisoc-copilot,aisoc-summary,aisoc-report,aisoc-nl); the alias → real-model mapping lives entirely ininfra/litellm/config.yaml, so operators assign different local or hosted models per task — and swap them — without any AiSOC code change (commented Ollama/vLLM/Anthropic examples ship in the config). Per-task latency, tokens, cost, errors, retries, and fallbacks are exported on/metricsand scraped by the bundled Prometheus (newaisoc-litellmjob; the third-party image is allowlisted inscripts/audit_prometheus_targets.py). Opt-in and non-breaking: unsetOPENAI_BASE_URLkeeps calls going direct to the provider, and the deterministic offline path is unaffected. Docs:apps/docs/docs/operations/llm-gateway.md; config testservices/agents/tests/test_litellm_config.py. (A follow-up PR wires the ~10 in-code callsites to request these aliases viamodel_pinsand removes the hardcodedgpt-4o-minidefault, closing #478.) - LLM task-alias routing — no more shipped default model
(#478, PR2). Every live LLM
call now asks for a logical task alias instead of a hardcoded model. New
services/agents/app/llm/factory.py(make_chat_model/resolve_model_alias) resolves a task role to itsaisoc-<role>alias + the gateway base URL;model_pins.pynow pins all seven roles (triage, recon, investigation, copilot, summary, report, nl) to aliases with adeterministicfloor. The ~10 scatteredos.getenv("AISOC_LLM_MODEL"/"OPENAI_MODEL","gpt-4o-mini")+ChatOpenAI(...)/ raw-HTTP callsites across the agents (auto-triage, cloud/identity/insider/ phishing, recon/forensic/responder/report-writer, copilot, contextual, NL translator) and the API endpoints (translation, hunts, knowledge base, phishing; via newservices/api/app/services/model_aliases.py) now request aliases; the hardcodedgpt-4o-minidefault is gone. Behaviour change: live LLM now runs through the gateway (OPENAI_BASE_URL), or pin a concrete model per role viaAISOC_MODEL_PIN_<ROLE>(escape hatch; the slim demo does this so keyed demos keep working); with neither, the deterministic offline path is used. Tests:services/agents/tests/test_llm_factory.py+ tightenedtest_litellm_config.py. Closes #478. - v8 P4 — Compounding Memory (verdicts that measurably improve). New
services/fusion/app/memory/: a nightly-distillable institutional memory that makes verdicts more accurate the longer an instance runs. Distillation (distill.py) compresses analyst overrides + verdict history into two versioned (content-hashed), ledger-referenceable outputs — per-signature priors (FP rate + prior) and a top-N few-shot exemplar bank per category. Memory verdict stage (stage.py) turns a signature's prior into a bounded verdict delta capped at ±0.10 (nudge, never dominate; cap + direction unit-tested). Improvement telemetry (improvement.py) computes verdict precision over time + the lift from install to latest ("N% more accurate than at install") — measured 0.60→0.90 on a simulated (clearly labelled synthetic) 90-day override history. Portable signed memory packs (pack.py) —aisoc memory export(pnpm aisoc:memory:export -- --demo) distills + Ed25519-signs a pack so an MSSP can bootstrap a child tenant from a curated baseline; import verifies the signature and rejects a tampered pack + can pin the publisher key (round-trip + tamper-rejection tests). Theaisoc-memory-packformat is the marketplace memory-pack artifact type. 9 tests (auto-run in the fusion CI job); docsapps/docs/docs/concepts/compounding-memory.md. (Nightly distill scheduling, the dashboard improvement chart, and live-path consumption of the memory stage are the documented remaining integration steps.) - v8 P3 — Investigation Swarm (parallel hypothesis agents). New
services/agents/app/swarm/: for hard cases, fan out 3–5 competing hypothesis agents in parallel, then run a structured debate node that ranks them. Complexity gate (complexity.py) fires the swarm only above an entity/technique-spread threshold (defaults ≥3/≥3); simple alerts stay on the cheaper single-agent path. Hypotheses (hypotheses.py) — ransomware staging, insider exfil, lateral movement, C2 beacon, and a benign backup/maintenance FP — each with supporting/contradicting signal + corroborating techniques. Swarm (swarm.py) runs the agents concurrently (asyncio.gather) each under a per-agent token budget, so total spend is bounded. Debate (debate.py) scores hypotheses on explicit criteria (evidence coverage, contradiction count, institutional-memory prior) and emits a ranked list with margin-based confidence, recorded as a first-class newdebateledger step type (the public replay UI colors + renders it). Eval gatetests/test_swarm_vs_single.pypublishes both numbers and asserts the swarm beats single-agent on the investigation-completeness macro by ≥10% under a cost ceiling (measured lift +0.556 on the synthetic set) — added to the agents CI job. Completeness is a substrate self-consistency macro (breadth of hypotheses considered), explicitly not a live-LLM accuracy claim; the incident set is labelled synthetic. Docs:apps/docs/docs/concepts/investigation-swarm.md. 9 tests. - v8 P2 — Self-Play Purple Team (the SOC that attacks itself). New
services/purple-team/app/adversary/: turns the purple-team service from a test runner into a continuous adversary. Hard scope guard (scope_guard.py) — a SOC that attacks itself must never touch production, so this is enforced in code (raisesScopeViolationbefore any step) not as a prompt: every target must carry an allowlisted lab tag AND no forbidden production tag; no force flag. Adversarial tests cover production assets, untagged assets, empty target sets, and alab+crown-jewellaundering attempt (all hard-fail). Planner (planner.py) composes an ordered kill-chain (initial-access → execution → persistence → privesc → exfil), selecting only techniques whose platform exists among the lab targets ("attack what exists"). Closed loop (campaign.py) emits telemetry per step (pluggable: in-memory for tests/canned, Kafka on the live path), a detection oracle scores detected/missed, and computes detection rate + mean-time-to- verdict. DAC auto-file (dac.py) files one eval-gated Sigma-scaffold proposal per miss (statusproposed, low confidence — self-play can only propose, never silently merge). Scoreboard (scoreboard.py+apps/docs/static/data/selfplay-scoreboard.json) with a per-rowsyntheticflag so a canned campaign is never mistaken for a measured live run. Canned 5-stage campaign viapnpm aisoc:selfplay(offline, deterministic, ~seconds) runs in CI throughtest_canned_campaign_runs_end_to_end. 14 tests total; docs atapps/docs/docs/concepts/self-play.md. (Nightly live wiring — Kafka emitter- alert-store oracle + HTTP DAC filer into the scheduler — is the documented remaining integration step.)
- v8 P1 — Federated Threat Intel Mesh (the network effect). New
services/mesh/(Python/FastAPI, port 8010): opt-in gossip of two privacy-preserving artifact types between self-hosted instances via a lightweight, open-source hub. (1) IOC sightings —SHA-256of the normalized indicator (never the raw value) + coarse type + severity + first/last-seen; private-set-intersection style, so a peer learns a value only if it already has it. (2) Verdict signatures — the institutional-memory signature key (category + connector + technique) + verdict distribution + mean confidence; no entities, tenant data, or free text. Privacy gates: k-anonymity (consensus revealed only at>= kdistinct instances, default 5,AISOC_MESH_K), per-instance Ed25519 signing (verified hub-side, so one actor can't inflate consensus with sock-puppets — tested), tenant/rule-level opt-out, a per-instance outbound-audit receipts log, and amesh_previewthat shows the exact outbound payload before sharing is enabled. Consumption: a deterministicconsensus.py:mesh_contributionverdict stage bounded to ±0.10 (the mesh nudges, never dominates; cap unit-tested). Public network stats page at/mesh(fetches the hub's/v1/stats, graceful when the hub is offline). 11 tests cover the full privacy contract (k-anonymity threshold, sock-puppet resistance, Ed25519 verify, PSI hashing, opt-out, bounded contribution, preview redaction, two-instance exchange, per-instance audit); added to the wave-2 service CI matrix. Threat model:docs/architecture/mesh.md;SECURITY.mdgains a mesh disclosure policy. The measured FP-suppression lift (mesh on vs. off) is explicitly deferred and labelled simulated-until-measured on the benchmark//meshpages — never presented as measured production performance. - v8 G1 — launch kit (ships in-repo with the code). New
marketing/launch/: a Show HN draft centered onnpx aisoc triage --demo, a 90-second demo-video shot list (CLI wow → replay permalink → self-play → mesh stats), Product Hunt assets, six technical blog outlines (one per phase, each ending in a reproducible command), and a category-level comparison dossier vs. closed-source AI SOC products — deliberately without naming any competitor (per project policy), with every AiSOC-side claim linked to code or a CI gate. Plus adocs/press/kit (boilerplate, fast facts, logo kit, naming). All materials are written to two rules — no superlatives, and synthetic-vs-measured always labelled — and are linked fromCONTRIBUTING.mdfor community amplification. The launch-kit README points every claim back to the benchmark page + claim-to-gate matrix so nothing ungated gets published. - v8 W4 — GitHub-native distribution (
aisoc-action). Newpackages/aisoc-action/(Node20 JS action, dependency-free — a hand-rolled Actions runtime + afetch-based GitHub REST client, deliberately no@actions/*/octokit so the shipped bundle carries no vulnerableundici; the committed bundle is 18 KB): triages the repo's own security signals — Dependabot alerts, CodeQL/code-scanning findings, and secret-scanning alerts — with the deterministic AiSOC verdict engine (no LLM, nothing leaves the runner) and posts verdicts + suppression rationale + prioritization as a PR comment (idempotent update-in-place), a job summary, or a weeklyaisoc-digestposture issue with an A–F grade and week-over-week delta. Runtime-scope Dependabot vulns are prioritized as exploitable-in-your-dependency-graph ("3 of 41 findings are act-now"); sources the token can't read degrade gracefully. Inputs:mode,min-severity,fail-on(gate mode),sources. The verdict engine is a byte-for-byte vendored copy ofpackages/aisoc-lite/src/verdict/kept in sync byscripts/sync_vendored_verdict.py(CI--checkgate), bundled into a committeddist/index.js. Dogfooded on this repo via.github/workflows/aisoc-selfscan.yml. CI (aisoc-action.yml): sync-check + typecheck + 6 fixture tests + a dist-freshness gate (committed bundle must match a fresh build). Docs:apps/docs/docs/integrations/github-action.mdwith copy-paste PR + digest workflows. Fixes a latent workspace defect: the monorepo root package was also namedaisoc(colliding with the CLI package), so it was renamed toaisoc-monorepo(installer repo-detection sentinels now prefix-match, staying compatible with existing clones). - v8 W2 — standalone free web tools (search-indexed acquisition). Four
login-free, open-source tools under
apps/web/src/app/(tools)/tools/, each with its own landing page, JSON-LD, OG metadata, and an "open source, part of AiSOC" backlink; everything runs in the browser — user rules never touch the server (the deterministic path is pure client-side). (1) Detection Translator (/tools/translate): paste any rule, get Sigma / SPL / KQL / ES|QL / YARA-L2 / UDM at once, with per-dialect copy buttons and a stable?s=permalink. (2) NL → Detection (/tools/nl2sigma): plain English → a Sigma scaffold plus the three SIEM dialects, via a deterministic artifact-extraction generator (honest about being a starting point). (3) ATT&CK Coverage Grader (/tools/coverage): paste Sigma rules / technique IDs → an A–F grade, a per-tactic heatmap, the top-10 highest-prevalence uncovered techniques, and a downloadable shareable grade card (via@aisoc/report-card). (4) Alert Noise Calculator (/tools/noise): project FP suppression + analyst hours/cost saved from the published deterministic-tier suppression rate (methodology linked; labelled as a substrate figure, not a live-LLM claim). SEO plumbing: 30 programmatic format-pair landing pages (/tools/translate/spl-to-kql, …) generated from a matrix viagenerateStaticParams, plus sitemap entries for all tool routes. Logic (apps/web/src/lib/tools/) is pure and unit-tested (13 tests: translate field-map + permalink round-trip, coverage extraction/grading/top-uncovered, noise projection/clamping, NL→Sigma scaffolding). Production build verified (tools static, pair pages SSG'd). - v8 W3 — shareable investigation artifacts (the screenshot loop). Public,
immutable, redacted investigation-replay permalinks. New
services/api/app/api/v1/endpoints/replay.py:POST /ledger/{run_id}/publish/previewbuilds a redacted snapshot and returns the alias map so the publisher reviews exactly what will be hidden (the pre-publish diff) before confirming;POST /ledger/{run_id}/publish(needsconfirm=true) re-builds server-side and stores an immutablepublished_replaysrow (migration045, with an UPDATE-blocking trigger that only allows the view counter to change); publicGET /r/{slug}serves the snapshot without auth from a non-RLS session (the data is post-redaction and non-identifying by design). Redaction reuses the reversiblePseudonymizer(vendored into the API viascripts/sync_vendored_redactor.py): internal IPs / emails / paths / secrets / internal hostnames / usernames become aliases, while public IOCs + ATT&CK techniques are preserved as the shareable value; only the redacted snapshot is persisted, never the alias map (services/api/app/services/replay_redaction.py). Web: a public/r/[slug]page renders an animated playback (timeline scrubber, evidence cards, growing attack graph, verdict stamp with elapsed time) with a dynamicnext/ogOpen Graph image for X/LinkedIn/Slack unfurls; shields.io-compatible badge endpoints at/api/badge/<kind>. New sharedpackages/@aisoc/report-cardrenders triage / coverage-grade / replay share cards (SVG + Markdown) and is now the canonical renderer behind the CLI--shareflag (bundled intoaisocat build time). The seeded LockBit caseINC-RT-001is published as the canonical demo replay at/r/demo-lockbit. Tests: redaction (no raw PII survives, public IOCs preserved), report-card renderers, badge endpoint, and the replay fetch client.docs/openapi.yamlregenerated (+284 lines, additions only). - v8 W1 —
npx aisocwedge CLI (the 60-second wow). Newpackages/aisoc-lite/(TypeScript, published to npm asaisoc, one runtime dependency): a zero-install front door that triages a batch of alerts to verdicts in under a minute with no credentials and no LLM key.npx aisoc triage --demoruns a bundled, fully-deterministic 200-alert fixture in ~50 ms and prints a terminal verdict table plus the copy-pasteable headline "AiSOC triaged 200 alerts: 12 TP, 171 FP suppressed (85.5% noise), 17 need review". The verdict engine (src/verdict/stages.ts) is a faithful port of the production triage scorerservices/agents/app/confidence/scoring.py— the weight stack and band thresholds (≥.80 TP, ≥.60 likely-TP, ≥.40 review, else benign; clamp [0.05, 0.95]) are pinned by a parity test so a CLI verdict lands where the full stack would.--file alerts.jsonltriages a local export (Splunk / Sentinel / Elastic ECS / CrowdStrike field spellings auto-detected);--llmrefines only the ambiguousneeds_reviewmiddle using the user's ownANTHROPIC_API_KEY/OPENAI_API_KEYcalled directly (never proxied);--sharewrites a redacted, aggregate-only report card (Markdown + 1200×630 SVG, no alert content);translateis a CLI front for the deterministic detection-rule field-map translator (Sigma/SPL/KQL/ES|QL/YARA-L2/UDM);upboots the full demo stack from a pinned Compose bundle. Telemetry is strictly opt-in (--telemetry/AISOC_TELEMETRY=1, default off), aggregate counts only, documented inpackages/aisoc-lite/TELEMETRY.mdand asserted content-free by a unit test. 22 vitest tests. New CI:aisoc-cli.yml(build + typecheck + tests + a cross-platform coldtriage --demoe2e asserting the headline and a <60 s bound + a fixture-staleness diff gate) andpublish-cli.yml(npm publish with build provenance on acli-v*tag; no-ops safely untilNPM_TOKENis configured — we never fake a publish). README top fold rewritten around the one-liner (guarded "lands on npm with the v8.0 launch; today it builds frompackages/aisoc-lite/").
Fully-Operational AI-SOC release. Completes the A1–E1 roadmap that wired the three end-to-end paths the reality audit found unwired — the event lake is now populated, the executable detection corpus fires on the live stream, every fused alert is auto-triaged (copilot default), and approved SOAR actions execute against real connector credentials under an autonomy policy — and adds the competitive-parity differentiators (unified Data Explorer, live effective-permissions, fuse-time attack chains, autopilot/copilot scorecard) plus nine new connectors and AI/LLM-usage governance. The claim-to-gate matrix reaches 33 GATED / 7 PARTIAL / 0 NO GATE: every product claim is backed by a failing test, and the ratchet (MAX_NO_GATE=0) forbids regression.
- Phase E1 — the last
NO GATEis closed: every product claim is now backed by a failing test. The public benchmark scoreboard was hand-maintained and the only automation (wet-eval.yml) no-ops without a funded LLM key, so nothing in per-PR CI proved the published headline number matched what the agent actually scores. Newscripts/check_scoreboard.pymakes the scoreboard backed by a failing test: on every PR (agents CI job) it runs the deterministic live-agent MITRE-accuracy eval over the 200-incident corpus and fails if the newestsubstraterow inapps/docs/static/data/scoreboard.jsondrifts more than 0.02 from the fresh run, the JSON breaks its schema, or a substrate row is mislabelled (honesty invariant: a deterministic number can never be quoted as live-LLM). The funded weeklywet-eval.ymlstill appends the LLM-tier (substrate:false) rows. A freshv7.5.0substrate row (0.97 MITRE accuracy, tokens/USD = 0) is published. Claim-to-gate matrix: the last NO GATE → GATED andMAX_NO_GATEratcheted 1 → 0 (thesecurity.ymlgate now fails if any NO GATE ever reappears); the L0–L4 row also moved PARTIAL → GATED (Phase B2decide()-in-dispatch). Matrix is now 33 GATED / 7 PARTIAL / 0 NO GATE — the Fully-Operational roadmap (Phases A1–E1) is complete. - Phase D3 — live-vendor connector smoke (mock-server conformance). The contract test proved each connector declares the async runtime methods; this goes further. New
services/connectors/tests/connectors/test_live_vendor_smoke.pystands up a mock HTTP server (respx) returning realistic vendor payloads and drives each connector's realtest_connection()+ paginatedfetch_alerts()HTTP path end-to-end, asserting a successful probe and that pulled events normalize to a valid five-tier severity — catching the wrong-endpoint-path / normalize-KeyErrors-on-real-shape failures a bare contract test misses. Covers the Phase D1/D2 connectors (QRadar, Exabeam, Securonix, Devo, Netskope, Windows/Sysmon, Zeek/Suricata, syslog/CEF, LLM-usage). Moves two claim-to-gate rows PARTIAL → GATED ("Connectors: schema-driven config + vault-encrypted secrets" and "Connectors: live Test connection"), retiring the Phase 10b deferral (31 GATED / 8 PARTIAL / 1 NO GATE). - Phase D2 — AI/LLM-usage governance + tiered lake storage. Three pieces. (1) AI/LLM-usage audit connector (
services/connectors/app/connectors/llm_usage.py) pulls OpenAI + Anthropic organization audit logs — API key creation, role grants, logging/MFA changes, project deletes — and emits the dottedevent_type(openai.api_key.created,anthropic.member.added) that the detections match. (2) Eight nativellm-*detection rules (scripts/detection_specs_part3_application.py): LLM API/admin-key created, owner granted, audit logging disabled (critical), MFA disabled, service-account created, project archived — regenerated into the corpus (825 executable rules) and re-exported to the fusion live-detection ruleset; verified firing end-to-end. (3) Hot/warm/cold lake tiering (services/api/clickhouse/tiering/): an opt-in ClickHouse storage policy +002_tiering.sqlthat rebindsaisoc.raw_eventsto atieredpolicy and moves data to a cold (object/NAS) volume at 30 days, deleting at 90 — the Phase 6 tiering wired now that the lake is populated. Verified end-to-end on ClickHouse 23.8 (policy loads, table rebinds, TTL applied); a static config gate (test_storage_tiering.py) catches drift. Registry 77→78; connector-count + conformance-matrix + marketplace regenerated. Claim-to-gate matrix +1 GATED (29 GATED / 10 PARTIAL / 1 NO GATE). - Phase D1 — eight new connectors close the biggest coverage gaps (SIEM / NDR / edge / endpoint). Following the connector convention (schema + registry +
plugins/<id>/plugin.yaml+ docs +marketplace:sync), adds: IBM QRadar (offenses; magnitude→severity), Exabeam (notable risk-scored sessions), Securonix (incidents; priority→severity), Devo (triggered alerts) — the four SIEMs the reality audit flagged as missing; Netskope (SASE/SWG DLP/malware/anomaly alerts, malware/DLP floored athigh); Windows Event / Sysmon (WEF collector spool; severity from channel + Event ID — log clears, service installs, process-injection surfaces floored); Zeek / Suricata NDR (Suricataeve.jsonpriority + Zeeknoticetypes); and a first-class generic syslog / CEF listener (parses the ArcSight CEF header + extension, CEF severity 0–10 → five-tier, non-CEF lines ingested atinfo). Every connector maps onto the exact five-tier ladder and passes the schema + runtime conformance gates. Registry now 69→77 connectors; connector-count + conformance-matrix + marketplace index regenerated. 40 new unit tests (severity mapping across the ladder, CEF parser, mocked pulls); full connectors suite 791 passed at 67.37% coverage. Claim-to-gate matrix +1 GATED (28 GATED / 10 PARTIAL / 1 NO GATE). - Phase C3 — autopilot/copilot posture with a visible autonomy scorecard (defaults to copilot). The per-action guardrail editor already let operators scope autonomy by action; C3 adds the whole-SOC posture view a CISO asks for. New
apps/web/src/components/settings/AutonomyScorecard.tsxcomputes an honest posture from the configured policy (not fabricated runtime stats): Copilot (the safe default — high/critical-blast actions always require a human) vs Autopilot (flips only when a high/critical-blast action is configured to auto-execute), plus the distribution of actions by blast radius and auto-exec/override counts. Rendered atop the existingAutonomyPolicyPanel. The compute is a pure, unit-tested function; 6 vitest tests (AutonomyScorecard.test.tsx). Combined with the Phase B2AISOC_MATURITY_TIERgate (which enforces copilot at the dispatch layer), the platform is copilot-by-default end-to-end. Claim-to-gate matrix +1 GATED (27 GATED / 10 PARTIAL / 1 NO GATE). - Phase C1 — Advanced Data Explorer: one investigation surface, no SIEM context-switch. New
/explore(apps/web/src/components/explore/ExploreView.tsx) unifies the surfaces shipped earlier in the roadmap into a single workbench: ask a question in plain English → it translates to SQL via/api/v1/nl-query/translate→ runs against the now-populated ClickHouse event lake (/api/v1/lake/sql, Phase A1) → renders a BI-like table (row count, latency, referenced tables), with a raw-SQL escape hatch always available. Source tabs pivot to identity (effective permissions), config/graph, and threat intel so the analyst answers "who touched this, with what access, and is the IP known-bad?" without leaving the page. Adds a typedlakeApiclient, sidebar + command-palette + sitemap entries. 5 vitest smoke tests (ExploreView.test.tsx); web type-check + full coverage gate green (395 tests). Claim-to-gate matrix +1 GATED (26 GATED / 10 PARTIAL / 1 NO GATE). - Phase C2 — Effective Permissions now resolves against a live posture snapshot. The resolver is pure (snapshot → effective access), but the only production snapshot source was
_default_snapshot_loaderreturning{}— so every live "what can this principal do?" call 412'd with "no policy snapshot ingested yet". Newservices/api/app/services/effective_permissions/posture_loader.pycollects a real snapshot via the connector'sget_resource_configread path: a newPOST /connectors/{id}/resource_configendpoint (same vault-decrypt trust model as/testand federated/query) exposes it, andHttpResourceConfigFetcher+collect_snapshotassemble the resolver's snapshot. Coverage is explicit and honest — Okta is fully assembled here (user → groups → assigned apps → admin roles, then resolved), while aws/azure/gcp/gws consume a connector-provided reconciled snapshot (sentinel resource id__posture_snapshot__); a provider whose connector hasn't implemented that still 412s (we never fabricate a cloud snapshot). Wired into the endpoint behindAISOC_EFFECTIVE_PERMISSIONS_LIVE(default off → prior behaviour preserved), fail-soft to the 412 path on any collection error. 7 API unit tests + the connectors suite (751 passed). Claim-to-gate matrix +1 GATED (25 GATED / 10 PARTIAL / 1 NO GATE). - Phase C4 — related alerts now auto-collapse into one ordered attack chain at fuse time. Correlation grouped alerts into incidents by shared entity and the API could compute a chain per case on demand, but nothing formed/extended a chain as alerts arrived — so an analyst saw N separate alerts instead of "step 3 of an intrusion on host X that began 20m ago". New
services/fusion/app/services/attack_chain_grouper.py: for each entity an alert touches (host/user/ip) it looks up (or mints) a stablechain_idin Redis with a rolling window, so a follow-on alert on the same entity — or a different entity sharing an IP — joins the same chain. Members are ordered by MITRE kill-chain stage (initial-access → … → impact), so the assignment'sposition/stagereflect where in the intrusion this alert sits, not its arrival order. The fusion engine attaches the assignment toFusedAlert.enrichments["attack_chain"](chain_id, position, stage, prior_alert_ids, member_count) for the UI + triage agent. Fail-soft (Redis miss/outage ⇒ no assignment). 8 unit tests; full fusion suite 130 passed at 65% coverage. Claim-to-gate matrix +1 GATED (24 GATED / 10 PARTIAL / 1 NO GATE). - Phase B4 — Business Context Rules now run on the live path (environment-specific noise reduction). A leading AI-SOC differentiator — suppress alerts during a maintenance window, bump severity for production assets, route cloud alerts to the cloud team — existed as an engine + authoring UI in
services/apibut only ran in a dry-run preview; it never touched a live alert. Newservices/agents/app/workers/business_context.pyapplies the samewhen/thensemantics (dotted-path fields +eq/ne/lt/gt/contains/in/exists/...comparators +all/any/not; effectsset_severity/route_to/tag/suppress) in the auto-triage worker's post-fusion → pre-triage seam. A suppress rule drops the alert before any triage spend; severity/route/tag mutations flow into the alert the agent reasons over. Rules load fromAISOC_BUSINESS_CONTEXT_RULES_FILE(mtime-reloaded), gated byAISOC_BUSINESS_CONTEXT_ENABLED(default on), fail-soft (bad/missing file ⇒ no rules applied, never an error into triage). The agents image can't importservices/api, so the evaluator is a faithful, independently-tested re-implementation of the same documented semantics (unifying both call sites is a follow-up). 15 unit tests (test_business_context_hotpath.py, added to the CI agents gate). Claim-to-gate matrix +1 GATED (23 GATED / 10 PARTIAL / 1 NO GATE). - Phase B3 — rollback is real, actions are post-verified, and approval SLA timers survive restarts. Three "honest response" gaps closed. (1) Real rollback: every executor's
rollback()previously returned a bareTrue— logging "rolling back" without calling the vendor. Newservices/actions/app/services/rollback.pyperforms the real reverse via the same clients (isolate→lift_containment/unisolate_machine, block_ip→unblock_ip/unblock_ip_zone, disable_user→enable_user/unsuspend_user, suspend_session→unsuspend_user) and returns an honestRollbackResult(reversed_/simulated/supported) — never a fake success; a failed reverse is reported, not hidden.autonomy_safety.REVERSIBLE_ACTIONSnow imports from this module (single source of truth) andtest_rollback.pygates the two sets so an action can't be declared reversible without a real reverse. (2) Post-action verification: newservices/actions/app/services/verification.pyre-queries the vendor to confirm the effect is actually present (VERIFIED/FAILED/ honestUNVERIFIEDwhen no probe or no creds) — a probe error is never a falseVERIFIED. (3) Durable approvals:services/slack-bot/app/services/timer_store.pyadds a Postgres-backedTimerStore; theApprovalTimeoutSchedulerpersists pending SLA timers andrecover()re-arms them on startup (firing overdue ones promptly), so a bot restart can no longer strand a forgotten approval forever. Fail-soft to in-memory when no DB. 25 new tests across actions + slack-bot. Claim-to-gate matrix +1 GATED (22 GATED / 10 PARTIAL / 1 NO GATE). - Phase B2 — connector credentials now reach the SOAR executors, and the autonomy policy governs every real execution. Two wiring gaps closed. (1) Executors read vendor-prefixed parameter keys (
cs_client_id,okta_domain,splunk_url…) but connectors store schema field names (client_id,domain,base_url…) — nothing translated, so even fully-configured credentials never reached an executor and every action fell back to simulation. Newservices/actions/app/services/credential_resolver.pypins the per-vendor translation (15 vendors, connector-id aliases likeaws_security_hub→aws_security_groups,azure_defender→defender; unknown fields dropped, never blindly forwarded), andLiveActionRequest.auth_configlets callers pass connector-style creds that the dispatcher resolves at the boundary. (2) The Phase 9aautonomy_safety.decide()policy existed but was never called on the live path (the 9b gap) — now every dispatch whose capability maps to anActionTypeis governed before the executor is invoked: above-tier ⇒ downgraded to a dry-run preview; with dry-run disabled ⇒PENDING_APPROVAL(executor never invoked); tier L0 ⇒BLOCKED; explicit dry-run honoured unchanged; governance verdict attached to the result for the audit trail. Deployment tier viaAISOC_MATURITY_TIER(default L1 — copilot). Also registers ten previously-missing vendor adapters (SentinelOne isolate, Entra/GWS disable-user, PAN-OS/FortiGate/Cloudflare block-ip, Jira/ServiceNow/PagerDuty create-ticket, Slack notify) so the agent can plan against them (19→29 builtins). 29 new/updated tests; full actions suite 224 passed at 64% coverage. Claim-to-gate matrix +1 GATED (21 GATED / 10 PARTIAL / 1 NO GATE). - Phase B1 — the agent now auto-triages every alert off the stream (copilot default). The core autonomy gap: investigations were manual/API-only — nothing consumed
aisoc.alerts.fused, so "an agent that triages every alert" wasn't true out of the box. Newservices/agents/app/workers/fused_alert_consumer.py(FusedAlertTriageWorker) subscribes to the fused-alert topic and auto-triages each alert. Copilot / dry-run is the default: triage is read-only — it classifies (verdict + calibrated confidence), records the reasoning to the Investigation Ledger (best-effort), and never dispatches a response (proposed actions carryrequires_approval=True;response_dispatchedis alwaysFalse). Tier selection is cost- and determinism-aware: cost-governorDEDUPLICATEDreuses the cached verdict (a flood of identical alerts costs one triage),CIRCUIT_OPEN/AISOC_DETERMINISTIC/no-LLM-key falls to deterministic heuristic triage (run_triage— the air-gapped/CI default), otherwise LLM auto-triage with a deterministic fallback on failure. Wired into the agents lifespan (off unlessKAFKA_BOOTSTRAP_SERVERSis set) + compose (depends_on: kafka). 9 unit tests (test_fused_alert_worker.py, added to the CI agents gate; the job now also installslanggraph). Claim-to-gate matrix adds a GATED row "Agent auto-triages every alert" (20 GATED / 10 PARTIAL / 1 NO GATE). - Phase A4 — the behavioral (UEBA) model now feeds alert scoring in production (the three-model story is real).
services/uebacontinuously scored every entity and emittedueba.anomalies, but fusion never consumed them — so the Semantic-graph / Behavioral-UEBA / Knowledge-LLM story was only two models live. Newservices/fusion/app/services/ueba_signal.py: the fusion consumer subscribes toueba.anomaliesand caches the latest per-(tenant, entity_type, entity_id)anomaly in Redis with a TTL (behavioral signal is time-decaying); duringFusionEngine.process, an alert looks up the highest anomaly across its own entities (username→user, hostname→device, src/dst IP→ip) andapply_ueba_boostraises the alert's confidence (risk-scaled, label recomputed) and anomaly score, recording an explainableueba_anomalyfactor. Fail-soft throughout (Redis miss/outage or malformed message ⇒ no boost, no raise). 10 fusion unit tests; full fusion suite 122 passed at 64% coverage. Claim-to-gate matrix adds a GATED row "Three-model AI: behavioral (UEBA) model feeds alert scoring" (19 GATED / 10 PARTIAL / 1 NO GATE). - Phase A3 — the default cold-boot stack is complete ("just works"). A plain
docker compose uppreviously started the core services but left the connector runtime behind aconnectorsprofile and graph-at-ingest OFF, so a fresh install wasn't the full spine. Nowdocker-compose.ymlships the connector runtime in the default profile (it idles harmlessly with no configured instances) and enables graph-at-ingest by default oningest-worker(AISOC_GRAPH_ENABLED=true+ Neo4j env +depends_on; the writer soft-fails if Neo4j is unreachable, so it never blocks ingest).integration.ymlgains a Phase A3 gate asserting the default boot ships the connectors service and ingest enabled the graph writer and the Neo4j graph has nodes after the spine flowed — so combined with A1 (lake) and A2 (detection), a coldupis proven end-to-end: ingest → OCSF normalize → Kafka → ClickHouse lake + entity graph + detection engine → fused alert row. Claim-to-gate matrix: "pnpm aisoc:demoboots the real stack" PARTIAL → GATED (18 GATED / 10 PARTIAL / 1 NO GATE). - Phase A2 — the executable detection corpus now fires on the live event stream. The reality audit's second SIEM gap: ~939 executable rules existed but only ran in CI fixture-replay — nothing evaluated them against ingested events, so any telemetry that wasn't a vendor-asserted finding (the promoter's job) never became an alert. New
services/fusion/app/services/detection_engine.pyloads the native corpus exported toapp/data/detection_ruleset.json(817 rules, byscripts/export_detection_ruleset.py) and evaluates each ingested event's connector-normalized fields — recovered fromocsf_event["raw_data"], the shape the specs were authored against — via a vendored, parity-gated copy of the canonicalmatch_whenmatcher (app/services/detection_matcher.py;test_detection_matcher_parity.pyasserts byte-for-behaviour agreement withscripts/generate_detections.pyover every committed fixture). Each firing rule becomes aRawAlertrouted through the normal fusion dedup/correlate/persist pipeline. Product routing is fuzzy (correctness-first: unknown product ⇒ evaluate all). Gates:integration.ymlposts an event matchingaws-root-account-loginand asserts the alert appears;validate-detections.ymldrift-checks the exported ruleset; 16 fusion unit tests. Claim-to-gate matrix adds a GATED row "Detection rules fire on the live event stream" (17 GATED / 11 PARTIAL / 1 NO GATE). - Phase A1 — the ClickHouse event lake is now populated from the live stream. The reality audit's central SIEM gap: the
aisoc.raw_eventslake table and its/api/v1/lake/sqlread API existed with no writer, so every hunt/query ran against an empty warehouse. Newservices/fusion/app/services/lake_writer.py(LakeWriter) archives every normalized OCSF event from theaisoc.raw_eventsKafka topic into ClickHouse — mapping the OCSF envelope to the lake columns (IPv4→IPv4-mapped-IPv6 coercion,DateTime64binding, MITRE/IOC extraction), batched by size or age, and fail-soft (a ClickHouse outage drops the batch and logs, never crashes the consumer). Archival is independent of promotion (a non-promoted Medium event is still queryable), and a background periodic-flush ticker guarantees a low-traffic batch is never stranded. Wired into the fusion consumer + lifespan;docker-compose.ymlfusion service gets ClickHouse env +depends_on.integration.ymlgains a Phase A1 gate assertingSELECT count() FROM aisoc.raw_events > 0after the spine ingests, against a live ClickHouse container (verified locally: a real INSERT round-trips). Claim-to-gate matrix adds a GATED row "Ingested events land in the queryable ClickHouse lake" (16 GATED / 11 PARTIAL / 1 NO GATE). At-least-once with a deterministicevent_id(MergeTree does not dedup; the gate asserts queryability, not exact-once).
- CodeQL (
security-and-quality) alert cleanup. Resolved the open code-scanning alerts surfaced by the freshsecurity-and-qualityanalysis. Real code fixes: (1)go/clear-text-logging+go/log-injectioninservices/ingest/internal/enrichment/shodan.go— the failure path logged the raw transporterr(which can carry the request URL with the Shodan API key or response-derived data) alongside an unsanitized, attacker-influenceableip; now it logs only a control-char-stripped IP (sanitizeLogValue) and never the error object. (2)py/stack-trace-exposureinservices/actions/app/api/router.py— the action record echoedstr(exc)back to the API caller; it now returns only the exception type and logs full detail server-side. (3)py/clear-text-storage-sensitive-datainscripts/connector_conformance.py— a false positive where the integer count ofsecret-type fields tripped CodeQL's sensitive-name heuristic; renamed the count tovaulted(accurate — those fields are vault-encrypted) so the matrix write is no longer misread as clear-text secret storage. (4)py/ineffectual-statement(×7) — Protocol/abstract method bodies written as bare...now use docstring bodies. (5)py/empty-except(×3) — silentexcept: passblocks now carry an explanatory comment. The 20py/request-without-cert-validationfindings are the connector/appliance clients' TLS controls, which default toverify=Trueand only disable verification when an operator explicitly opts in (required for on-prem SIEM/firewall appliances with self-signed/internal-CA certs); these are triaged as accepted-risk (secure-by-default, explicit opt-in), with CA-bundle certificate pinning tracked as the recommended future alternative.
- Phase 12 — observability + governance (completes the World-Class Hardening Program's 0–12 phase checklist). Two halves of "run this next to your crown jewels": (1) Observability — every service under
services/declares its reliability posture indocs/operations/slos.yaml(availability + p95 latency + golden signals; all 18 services covered — 16 SLOs + 2 exempt), gated byscripts/check_slos.pyso a new service can't ship without an SLO.docs/operations/observability.mddocuments the four golden signals and the single OpenTelemetry trace that spansingest → fusion → realtime → api → agents → actions. (2) Governance — newGOVERNANCE.md(roles, lazy-consensus decision-making, the maintainer path, and an explicit vendor-neutral-home intent),MAINTAINERS.md, and a Developer Certificate of Origin sign-off requirement added toCONTRIBUTING.md.scripts/check_governance.py+governance.ymlgate that the governance surface (governance/maintainers/security/CoC/trademark/DCO/SLOs/observability) exists and is non-trivial, so it can't silently rot. With this, all 13 phases (0–12) have landed; the claim-to-gate matrix stands at 15 GATED / 11 PARTIAL / 1 NO GATE (the last NO GATE, wet-eval live-agent tables, needs a budgeted live run — Phase 4c). - Phase 11 — OpenAPI breaking-change gate.
check-openapi.ymlproved the spec matches the code (drift) but had no breaking-change semantics — a PR could delete an endpoint, remove a response field, tighten a request body, or drop an enum value with every check green while every generated SDK client silently broke (the reality-auditNO GATErow). Newscripts/openapi_diff.py(pyyaml-only) classifies the changes between two specs as breaking vs non-breaking from an existing client's perspective: removed path/operation/schema/property, changed property type signature, optional→required, a new required field on a request-shaped schema, a removed enum value, or a new required parameter. Newopenapi-breaking.ymldiffs the PR'sdocs/openapi.yamlagainst the base branch and fails on any breaking change; a deliberate breaking release ships with a version bump + a CHANGELOG BREAKING note and--allow-breaking. 15 detector tests (tests/test_openapi_diff.py) prove every breaking class is caught and that safe additive changes (new path, new optional field, new enum value, new response field) are not flagged — a breaking-change gate that cries wolf gets disabled. Claim-to-gate matrix: "OpenAPI stability for 3 SDKs + MCP" NO GATE → GATED, and the ratchet ceiling lowered 2 → 1 (15 GATED / 11 PARTIAL / 1 NO GATE — only the wet-eval live-agent scoreboard tables remain, closing in Phase 4c with a budgeted live run). Per-language SDK generated-client contract-drift is tracked as 11b. - Phase 10 — connector runtime-contract conformance suite + published matrix. The reality audit left the "live Test connection" click-and-connect claim with NO gate at all, and the schema/vault claim only partially gated. New
scripts/connector_conformance.py+services/connectors/tests/test_conformance.pygate the runtime contract across all 69 connectors: every connector must implementtest_connectionas an async coroutine (the contract behind the "live Test connection" button), implementfetch_alertsas an async coroutine, declare only validCapabilityverbs, and mark every secret-shaped fieldtype="secret"— a field namedapi_key/token/passwordrendered as a plainstringwould be stored outside the vault, the exact leak this check prevents. The publisheddocs/connectors/conformance-matrix.md(69/69 conform) is drift-gated by--check, so a new connector cannot land without conforming and the matrix can't diverge from the registry. Claim-to-gate matrix: "Connectors: live Test connection" NO GATE → PARTIAL, and the ratchet ceiling lowered 3 → 2 (14 GATED / 11 PARTIAL / 2 NO GATE). Detection-content lifecycle is already gated by Phase 4a (DAC candidate-rule) + 4b (truth table). Live-vendor sandbox smoke + rate-limit/checkpoint durability are tracked as 10b indocs/audit/PROGRESS.md. - Phase 9a — autonomy-safety policy + honest rollback contract + scorecard. The reality audit found three holes behind "L0-L4 automation maturity gates every action": dry-run was opt-in (a mis-configured caller executes for real), ~15 executor
rollback()s silentlyreturn Truewith no reverse vendor call (the platform claimed to reverse containment it never did), and there was no post-action verification. Newservices/actions/app/services/autonomy_safety.pycloses them at the policy layer:decide()makes dry-run the safe default — anything not explicitly permitted to auto-execute is previewed (DRY_RUN), never silently executed; CRITICAL blast never auto-executes; HIGH only at L4 with a whitelist entry AND theAISOC_ALLOW_HIGH_BLAST_AUTObreak-glass flag.rollback_capability()with a pinnedREVERSIBLE_ACTIONS={block_ip}set makes the rollback claim honest and bounded — a caller learns "this cannot be auto-reversed" instead of a silentTrue, and the set can't grow without a conscious edit + real reverse implementation. Every unattended containment (AUTO, blast ≥ MEDIUM) setsrequires_verification, andAutonomyScorecardcounts executions that were never verified — and executions of irreversible actions — as visible gaps rather than assumed-away. 16 tests (services/actions/tests/test_autonomy_safety.py, in the actions coverage matrix). Enforcement-wiring ofdecide()into the live/dispatch+submit_actionrouter, rewriting the silent-return Trueexecutor rollbacks, real vendor verifiers, and a durable approval-SLA timer table (replacing the in-memoryapproval_timeout.py) are tracked as 9b indocs/audit/PROGRESS.md. - Phase 8 — LLMOps: prompt registry, model pins, response cache, structured-output validation. A coherent
services/agents/app/llm/LLMOps layer, all dependency-light and gated. (1) Prompt registry (prompt_registry.py) — every production prompt is a named, versioned, sha256-hashed artifact; the committedprompts.lock.jsonpins version→hash, andscripts/check_prompt_lock.py --check(wired into the CI lint job) fails when a prompt's text changes without a version bump + lock regeneration. This makes theAGENTS.md"prompt change ⇒ re-grade the eval harness" rule enforceable — you can no longer edit a prompt silently. (2) Model pins + provider fallback (model_pins.py) — logical roles pinned to concrete models with ordered fallback chains that ALWAYS terminate in the deterministic tier (env-overridable primaries, but the deterministic floor is gate-enforced), replacing the scatteredos.getenv("...", "gpt-4o-mini")defaults. (3) Content-addressed response cache (response_cache.py) — keyed on sha256(model+prompt+input) with field separation and LRU eviction; safe under the determinism contract (a hit is byte-identical). (4) Fail-closed structured-output validation (structured_output.py) — strips code fences/prose, parses JSON, validates against a caller schema, and on ANY failure returns a deterministic fallback rather than propagating a half-parsed object into an autonomy decision. 15 tests (services/agents/tests/test_llm_ops.py, appended to the CI agents gate). Migration of the inline agent prompts toregistry.get()is tracked as 8b. Doc:apps/docs/docs/concepts/llmops.md. - Phase 7a — unified multi-model router with tier attribution + a determinism contract. The reality audit confirmed the knowledge graph is already built at ingest (v8 T1.1,
services/ingest/internal/graph/); the genuine Phase 7 delta was the model router. Before it, the deterministic→LLM fallback was reimplemented independently in NL query, playbook drafting, explain, copilot, and each sub-agent — no single audit point for which model answered or why. Newservices/agents/app/routing/model_router.pyis that place: aModelRouterthat escalates deterministic → ML → LLM only when the cheaper tier is under-confident, and attributes every decision (tier,model_used,attributiontrail,tiers_considered,escalation_blocked_reason). It never silently uses the LLM — a skipped or blocked LLM tier (no key, air-gap, deterministic mode, governor circuit open, or a tier error) is always recorded. Introduces the canonicalAISOC_DETERMINISTICflag and composes with the existingCostGovernorcircuit breaker: either one forces deterministic-only, in which case the router is reproducible (same input → identical decision). 12 tests (services/agents/tests/test_model_router.py, appended to the CI agents gate) prove tier selection, attribution, the no-silent-LLM property, graceful LLM-failure degradation, and the determinism contract. Doc:apps/docs/docs/concepts/model-router.md. Remaining Phase 7 enrichments (posture collection, effective-permissions snapshot loader, bi-temporal validity, fusion-time ContextBundle) are tracked as 7b+ indocs/audit/PROGRESS.md. - Phase 6 — performance + cost, both gated. Two cheap, non-flaky gates in a new
.github/workflows/perf.yml. (1) Throughput —scripts/perf/throughput_harness.pyruns the real fusion hot path (promote_normalized_event) over a deterministic 20k-event synthetic corpus and reports events/sec + p50/p95/p99 per-event latency (measured ~250k eps on commodity hardware). The gate asserts a generous regression floor (1,000 eps — a >200× margin) so it fires only on a catastrophic regression such as an I/O call slipping onto the hot path, never on shared-runner jitter; it is explicitly a regression floor, not a production SLO. (2) Cost —scripts/storage_cost_model.pyis a deterministic tiered-storage $/TB model (hot ClickHouse block 30d / warm object 60d / cold archive 275d, 8× ZSTD); the committed worked example (docs/decisions/storage-cost-model.json: ≈ $902/mo and ≈ $30/raw-TB at 1 TB/day) is drift-gated by--check, so the number can never silently diverge from the rate card. Rate-card values are labelled reference list prices to verify per provider/region — the value is the methodology, not the exact dollars.docs/decisions/0005-storage-consolidation.md(ADR) records the three-tier decision (keep ClickHouse-only hot; do not add a second hot engine) and cites the gated model. - Phase 5 — data spine correctness: schema registry + dead-letter queue + lineage. The reality audit's load-bearing gap: the fusion consumer logged a warning and silently dropped any malformed message — silent data loss. Now every message is schema-validated against a versioned envelope registry (
services/fusion/app/services/event_schema.py:aisoc.raw_eventsandaisoc.alerts.raw, each pinned tov1, with a drift-guard test so the wire contract can't move unnoticed) before the promoter sees it. Anything that fails — non-object payload, unknownschema_version, missingocsf_event, non-UUID tenant, or a RawAlert that fails deep Pydantic validation — is routed to a fail-soft dead-letter queue (services/fusion/app/services/dlq.py:LoggingDLQdefault,InMemoryDLQfor tests,KafkaDLQrepublishing toaisoc.alerts.dlq) with its reason, schema version, source-event lineage, and a truncated payload, instead of vanishing.safe_recordguarantees a DLQ that itself throws never crashes the consumer. Each processed alert logsfusion.lineage(source event id + schema version). 21 new tests (test_event_schema.py,test_dlq.py,test_consumer_dlq.py) prove a valid event is processed with an empty DLQ, every poison shape is captured (not dropped), and a failing DLQ sink doesn't crash the consumer; full fusion suite 92/92 at 59% coverage (floor 48). Idempotency (AlertSink dedup fingerprint, Phase 3.1) and event-time watermarking are already present; backfill/replay-from-offset is tracked as 5b indocs/audit/PROGRESS.md. - Phase 4a — the Detection-as-Code gate is no longer circular. The reality audit's #1 circular gate: the detection-proposal promote path shelled out to
run_evals.pywithout ever passing the proposed rule body, so "passed" meant the repo-wide substrate MITRE accuracy didn't move — a value independent of the rule under review. A blind rule (matches nothing) or a noisy rule (matches everything) sailed through its own exam. Newservices/api/app/services/detection_eval.py::evaluate_candidate_ruleruns the candidaterule_bodyitself through the real runtime engine (rule_engine.execute_rule) against caller-supplied positive/negative fixtures: it must fire on every positive and stay silent on every negative. NewPOST /detection-proposals/{id}/evaluate-rulestores the verdict undereval_result["candidate_rule"], andPOST /decide(approve) now requires it — a benchmark-only pass is no longer sufficient.services/api/tests/test_detection_eval.py(6 tests) is the mutation test that proves the gate rejects a blind rule, rejects a noisy rule, rejects a no-positive-fixture rule, and that the verdict genuinely depends on the rule body. Claim-to-gate matrix: DAC row PARTIAL(circular) → GATED. - Phase 4b — detection content truth table (honest coverage). The README advertised "6000+ imported detection rules", but ~97% live under
_quarantine/(enabled: false) because their upstream query language (SPL / YARA-L / CAR pseudocode) does not execute on the engine. Newscripts/detection_truth_table.pywalksdetections/and classifies every rule executable (fires today) vs non-executable (provenance/coverage only), renderingdocs/detections/truth-table.md. The honest headline: 939 executable rules (861 native + 77 sigma-imported + 1 community) of 6975 on disk; 5921 quarantined, 115 disabled.--checkgates the doc invalidate-detections.ymlso the number can never quietly drift from reality, and the README now cites the executable figure for coverage (the on-disk figure only describes the imported library). Claim-to-gate matrix: 6000+-imported row PARTIAL → GATED (14 GATED / 10 PARTIAL / 3 NO GATE). The LLM-dependent half of Phase 4 (live-agent Tier-1 eval, hallucination/calibration/abstention, model matrix, 150-payload prompt-injection adversarial) is tracked as 4c+ indocs/audit/PROGRESS.md. - Phase 3.4 — cross-store tenant isolation, now proven against live containers. The offline
isolation.ymlgate proved each read path constructs a tenant scope; it could not prove the scope actually isolates. New.github/workflows/isolation-live.yml+tests/isolation/test_live_stores.pyseed tenant A and tenant B in real containers (Neo4j, Redis, ClickHouse, Redpanda/Kafka) and assert a read as A returns zero B data. ClickHouse runs the productionlake_sql.rewrite_for_tenantrewriter against a live warehouse (verified:SELECT … FROM aisoc.raw_eventsbecomes… WHERE tenant_id = '<A>', so B's rows never return); Neo4j uses thetenant_idproperty filter; Redis uses theaisoc:t:<tenant>:*keyspace namespacing; Kafka replays thegraph_wsper-tenant envelope filter. Every test also asserts the unscoped read sees both tenants, so a scoped pass can never be vacuous on an empty store. The isolation registry (tests/isolation/stores.py) flips Neo4j/Redis/ClickHouse/Kafka fromcontainer_pending→container_gated, and the claim-to-gate matrix moves "Cross-tenant isolation (Qdrant/Neo4j/Redis/ClickHouse/Kafka)" PARTIAL → GATED (12 GATED / 12 PARTIAL / 3 NO GATE). The heavy-demo-stack items (Playwright real-stack E2E, demo time-to-first-investigation budget) are tracked as non-blocking 3.5+ indocs/audit/PROGRESS.md. - Phase 3.3 — upgrade-safety gate ("people upgrade; nothing tested it"). New
upgradejob inintegration.yml(matrixv7.5.0 → HEADandv7.3.1 → HEADagainst a real Postgres 16): install a prior release's migration set, seed 250 probe rows on that released schema, then apply HEAD's migration set — the actual self-host upgrade path — and assert the seeded rows survive and every HEAD migration applied. The signal this adds beyond the existing fresh-apply job is destructive-migration detection: a migration that drops or rewrites existing data fails here even though it applies cleanly on an empty database. Thev7.3.1 → HEADleg lands 8 incremental migrations on a pre-existing populated schema (verified locally: 250/250 rows survived, 55/55 migrations tracked). Usesgit checkout <ref> -- services/api/migrationswith anrm -rffirst so the released set is exact (acheckout … -- pathalone leaks HEAD-only files and silently degrades to a fresh apply). - Phase 3.2 — Postgres-outage chaos gate (fail-soft + self-heal). Extends the
integration.ymlspine job with a second chaos scenario beyond the consumer-kill test: kill Postgres mid-stream, assert the fusion consumer keeps running (theAlertSinkdegrades to "fused alerts still stream over Kafka/WS, persistence dropped for the outage window" instead of crashing the worker), then restart Postgres and assert a fresh event persists — proving the sink's asyncpg pool and the API's SQLAlchemy pool both self-heal with no service restart. Distinct event titles side-step fusion's in-memory dedup so the post-restart event is a genuine fresh persist. Verified locally against a real Postgres 16 before gating (persist during outage →Nonewith no raise; first persist after restart → row written).scripts/integration/spine_test.pypolling now swallows transient HTTP errors during the recovery window so a 5xx blip while the API reconnects extends the poll rather than failing the run. - Phase 3.1 — the event spine is now continuous, and CI proves it with real containers. The reality audit found two silent gaps in the product's central claim: ingest published normalized OCSF events to
aisoc.raw_eventsthat nothing consumed, and fusion published toaisoc.alerts.fusedthat nothing persisted — a raw event could never become an alert row without a human calling the API. Both bridges now exist inservices/fusion: a deterministic promotion policy (app/services/promoter.py— OCSF Findings-category events andseverity_id >= 4telemetry becomeRawAlerts; everything else is left to the detection engine by design) and a fail-soft, idempotent PostgresAlertSink(app/services/alert_sink.py— dedup-fingerprint-guarded insert, duplicates never persisted, DB outage degrades instead of crashing the consumer). New.github/workflows/integration.ymlgates three claims against real containers, no mocks: (1) spine —POST /v1/ingest/batch→ OCSF normalize → Kafka → fusion → WebSocketalert.fusedframe + Postgres row viaGET /api/v1/alerts, plus duplicate suppression and a chaos step (kill the fusion consumer mid-stream, produce, restart, assert zero event loss) — driver:scripts/integration/spine_test.py; (2) migrations — the full SQL chain applies on fresh Postgres 16, re-run is a no-op (newAISOC_MIGRATIONS_STRICT=1mode fails CI on any failed migration), and the UEBA alembic chain round-trips upgrade → downgrade → upgrade; (3) backup-restore —scripts/backup.sh/restore.shagainst Postgres + MinIO: seed → backup → destroy → restore → assert full integrity, with measured RTO published to the job summary. 20 new fusion unit tests cover the mapping/skip logic (test_promoter.py,test_alert_sink.py); full fusion suite 71/71.
-
Hosted demo API 500s from stale Postgres pool + broken waitlist funnel (QA 2026-07-19). Live
/healthshoweddemo_bootstrap.last_error_type=create_seed:ConnectionDoesNotExistErrorafter 22 attempts — Fly Postgres autostop closed pooled sockets and every subsequent checkout 500ed (/api/v1/auth/login,/metrics/*,/alerts/*,/cases→ 503). Fixes: (1)pool_pre_ping=True+pool_recycle=300on the SQLAlchemy engine; (2) demo self-heal bootstrap now disposes the pool after every disconnect, splits create_all / SQL migrations / seed into separate steps, and surfaces stage-tagged errors on/health; (3) demo-mode middleware allowlistsPOST /api/v1/waitlist/signupso the managed-instance conversion funnel on tryaisoc.com is no longer 403ed for every visitor. -
Demo bootstrap
create_allAttributeError (QA follow-up). After Fly Postgres was restarted,/healthstill reportedcreate_all:AttributeErrorbecauseAsyncConnection.execution_options(...)is a coroutine and was chained into.run_syncwithoutawait. Await the options object first; pin intest_database_pool.py. Unblockspublished_replayscreate +/r/demo-lockbit. -
Canonical
/r/demo-lockbitmissing after re-seed short-circuit. When INC-RT-* cases already exist,_seed_in_flight_investigationreturned early and never createdpublished_replays. Now that path still ensures the canonical replay; bootstrap only marksdoneafter verifying the slug. -
Out-of-the-box 500 from schema drift on migration-bootstrapped installs (#492).
docker-compose.ymlmountsservices/api/migrationsinto/docker-entrypoint-initdb.d, so a fresh compose stack builds Postgres from the001_init.sqllineage — which createddetection_rulesin its pre-refactor shape (rule_type/rule_content/hit_count/last_hit_at) andcaseswithoutresolution/lessons_learned. The currentDetectionRuleandCasemodels query the refactored columns, so every default install servedUndefinedColumnError500s (e.g.GET /api/v1/detection/tuning) andseed_demofailed oncases.resolution. Newservices/api/migrations/046_detection_rules_cases_schema_drift_fix.sqlreconciles both tables with the models — additive, fully idempotent (ADD COLUMN IF NOT EXISTS), and dual-lineage safe (thecreate_allpath is a no-op; the legacy path backfillsrule_body/rule_languagefrom the old columns and drops the stalerule_content NOT NULLunder aninformation_schemaguard so ORM inserts succeed). Newservices/api/tests/test_schema_drift_046.pypins the fix and adds a forward guard asserting the migration lineage covers every column both models declare. -
Phase 3.1 gates caught two latent bugs before merge (exactly what the real-container tier is for — both would have shipped invisibly under the previous mock-only CI). (1)
scripts/restore.shnever restored anything.resolve_timestamp()ended in a[[ -z "$TIMESTAMP" ]] && { … }guard that evaluates false once a timestamp is resolved; as the function's last command that non-zero status propagated out and, underset -e, aborted the script before the restore began — for both--latestand--timestamp. An untested backup script had been broken the whole time. Converted the guard to an explicitif+return 0; the backup → destroy → restore gate now restores 500/500 rows with a measured RTO. (2)services/fusionAlertSinksilently failed to persist every alert. The dedup fingerprint ($10) was used untyped in both theINSERT … SELECTtarget (thededup_hash VARCHAR(64)column) andWHERE dedup_hash = $10(varchar comparisons resolve throughtextoperators), so asyncpg's prepare raisedinconsistent types deduced for parameter $10: text versus character varyingand the insert threw — the fused alert streamed over Kafka/WebSocket but never reached the alert store, so the spine'sGET /api/v1/alertsassertion timed out. Pinned both uses to::text; the real-container spine gate now observes the alert row end to end.
-
Phase 2 continuation — signed, attested container images + SHA-pinned CI. Every
ghcr.io/beenuar/*service image pushed bypublish-images.yml(push-to-main) andrelease.yml(tags) is now: (1) signed withcosignkeyless/OIDC, (2) attested with a CycloneDX SBOM generated bysyftand attached viacosign attest --type cyclonedx, and (3) built with BuildKitprovenance: mode=max+sbom: trueso SLSA provenance and an SPDX SBOM ride the image manifest. Every third-party GitHub Action across all 32 workflows is pinned to a full commit SHA (tag retained as a comment) so a tag-hijack of an upstream action cannot change what CI executes.docs/operations/verifying-releases.mdrewritten with copy-pasteable verification commands for all four artifact types. Claim-to-gate matrix: "Signed / attested release artifacts" moved PARTIAL → GATED (11 GATED / 13 PARTIAL / 3 NO GATE). -
Phase 2 — supply chain + truth gates. New
.github/workflows/security.yml: a claim-to-gate matrix ratchet (scripts/check_claim_gate_matrix.py— the NO GATE count may only decrease; enforces the Phase 0 promise) as the HARD gate, plus gitleaks (secret), Semgrep, Trivy (fs), and checkov/tfsec in report-and-ratchet ("observe") mode with a triage allowlist at.security/allowlist.yml/.gitleaksignore(GitHub push-protection remains the always-on hard secret gate). Insecure defaults now hard-fail the boot in production:enforce_secure_defaults()(services/api/app/core/config.py) raisesInsecureProductionDefaultsErrorwhenENVIRONMENT=productionand any placeholder secret remains, wired intoapp/main.pystartup and gated byservices/api/tests/test_security_defaults.py::test_enforce_*. AddedTRADEMARK.md(the MIT code is free; the name is not),docs/operations/verifying-releases.md, a READMEMaturitynote, and fixed the.github/LICENSES.mdlicense inconsistency (AiSOC ships under MIT, matchingLICENSE/README, not Apache-2.0). Claim-to-gate matrix now 10 GATED / 14 PARTIAL / 3 NO GATE. Per-image CycloneDX SBOM + cosign signing + SLSA provenance, SHA-pinning all actions, and flipping the code scanners to blocking are the tracked Phase 2 continuation. -
Phase 1.6 — platform/vault hardening (KMS envelope encryption). New
services/api/app/security/envelope_cipher.py: optional envelope encryption for the credential vault. Each secret is encrypted with a fresh per-secret data key (DEK); the DEK is wrapped by a key-encryption key (KEK) that never leaves KMS/HSM (vault:v2:<kek_id>:<wrapped_dek>:<ciphertext>), so a DB dump or a leaked env var yields only wrapped DEKs. PluggableKeyManagerprotocol withLocalKeyManager(default, backward-compatible),AwsKmsKeyManager(boto3; GCP KMS / Vault Transit implement the same protocol), and an in-memoryFakeKmsKeyManagerfor tests. Key rotation is a cheap re-wrap (EnvelopeCipher.rewrap) that never re-encrypts the secret body. Gated byservices/api/tests/test_envelope_cipher.py(round-trip, rotation + re-wrap, plaintext-never-in-token, fail-closed on tamper/wrong-KEK). Addeddocs/security/platform-threat-model.md(STRIDE, vault as top asset) anddocs/security/connector-least-privilege.md. Completes Phase 1. -
Phase 1.5 — cost-DoS enforcement. New
services/agents/app/core/cost_governor.py: a per-tenantCostGovernorthat turns the existingaisoc_run_coststelemetry into enforcement — rolling-window soft/hard USD budgets, a circuit breaker that drops investigations to deterministic-only mode once the hard cap is hit (instead of billing unboundedly), a per-alert token ceiling (cap_tokens), and an evidence-hash dedup cache so a flood of identical alerts costs one investigation, not N. Gated byservices/agents/tests/test_cost_governor.py(10 tests incl. the headline 10 000-identical-alert flood asserting spend stays at exactly one run, and a distinct-alert flood asserting the circuit breaker bounds spend near the hard cap). Live-orchestrator wiring ofget_governor().check(...)before the LLM call is the tracked continuation. -
Phase 1.4 — evidence redaction pipeline (honest no-exfiltration). New
services/agents/app/privacy/redactor.py: a per-run, per-tenant, in-memory reversiblePseudonymizerthat replaces the customer's identifying data (internal IPs, emails, file paths, secrets, internal hostnames, usernames) with opaque tokens (USER_1,HOST_2,IP_3) before evidence leaves the process, while preserving public threat indicators so the agent can still reason.RedactionConfigdefaults every category on. Gated byservices/agents/tests/test_privacy_redactor.py(golden-corpus assertion: zero raw customer PII survives; round-trip re-hydration; public IOCs preserved). Rewrote the README "no data exfiltration" differentiator to be precise per mode and addeddocs/trust/data-flows.mddocumenting exactly what leaves the perimeter (local air-gapped / hosted-with-redaction / hosted-raw). Contract-egress enforcement + air-gapped CI job + Helm egress NetworkPolicy are the tracked 1.4 continuation. -
Phase 1.3 — cross-store tenant isolation (Qdrant + harness). Closed the Qdrant leak the reality audit flagged:
services/threatintel/app/storage/qdrant.pyhad no tenant scoping at all (global collections, no filter, notenant_idin payloads). Addedtenant_scope_filter+ tenant-stamped payloads + tenant-scoped point ids so a search as tenant A can never surface tenant B's private vectors, while global feed intel stays shared under aSHARED_TENANTsentinel (backward-compatible with the feed pipeline). Stood up a table-driventests/isolation/suite (registry instores.pyso a new store cannot ship without an isolation entry) and a new.github/workflows/isolation.ymlgate running the offline layer on every PR. Neo4j/Redis/ClickHouse/Kafka live-container replay is registered ascontainer_pendingfor Phase 3's integration tier. -
Phase 1.2 — memory-poisoning defenses for the override-learning loop. New pure
services/api/app/services/memory_poisoning.py: provenance on every memory write (MemoryProvenance— no anonymous memory), trust weighting (verified human outranks autonomous closure) with age decay so lessons must be re-confirmed, aPoisoningDetectorthat flags a burst of same-signature false-positive dispositions from low-trust authors, and blast-radius controls for retroactive re-disposition (plan_redisposition+compute_confirmation_token: capped batches, explicit confirmation token over the exact alert set, quarantine on flagged signatures). Wired intooverride_learning.py(poisoning-resistant signature key now includes the entity-independent severity band; provenance stamped on writes;apply_redispositionrequires the token and enforces the cap) and the/feedbackendpoints (preview returns the token + quarantine state; apply rejects stale/tampered/over-cap batches with 409). The farming-attack eval (services/api/tests/test_memory_poisoning.py::test_farming_attack_then_real_attack_is_not_auto_closed) gates the api job: a poisoned signature is flagged and its retroactive apply quarantined, so the real intrusion is not auto-closed. -
Phase 1.1 — prompt-injection structural containment + detection. New
services/agents/app/prompting/envelope.py: per-run cryptographic-nonce evidence fence (EvidenceEnvelope,make_nonce,system_rule) so injected text cannot forge the closing delimiter to break out of the data block, and aPromptInjectionGuardthat scans untrusted evidence for instruction-shaped content (role markers, "ignore previous", secret/prompt exfiltration, SOAR tool-name mentions, base64- and zero-width-obfuscated directives) and, on a high-severity hit, signals demotion of the case autonomy tier to L0. Addedservices/agents/tests/test_prompt_envelope.py(25 tests across every ingest path) and gated it plus the previously-ungatedtest_prompt_sanitizer.pyin the CI agents job (fixed its stale agent-wiring expectations — the investigator agents sanitise viasanitize_text/sanitize_iterable_of_strings/format_bundle_prompt_append). Threat model:docs/security/agent-threat-model.md.
- World-class program Phase 0 — reality audit (no product code).
docs/audit/REALITY_REPORT.mdclassifies every headlineREADME.mdclaim against the code (production/functional-untested/template-fallback/demo-only/stub) and ranks Overclaims, Load-bearing untested paths, and Circular gates.docs/audit/CLAIM_TO_GATE_MATRIX.mdmaps 27 claims to their CI gate orNO GATE(9 GATED / 11 PARTIAL / 7 NO GATE), each with a binding "Closes in" phase. The committed 12-phase status checklist lives inROADMAP.md(per-session working detail is in the gitignoreddocs/audit/PROGRESS.md). Tracking doc:AISOC_CURSOR_PROMPT_V2.md.
v8.0-milestone and trust-readiness release. Folds in the AiSOC missing
pieces — Phases 1–5 rollup (PR #337;
25 commits, 188 files, +23 743 / -907), four named v8.0 milestones (T3.7
NL→playbook, T3.8 design system v2, T4 wave-3 marketplace + 6 hardened
connectors, T5.3 fidelity loaders), the marketing-shell unification on
tryaisoc.com, the threat-actor attribution RBAC + port fix, and a large
Dependabot + security sweep that landed on main since v7.4.0.
- AiSOC missing pieces — Phases 1–5 rollup
(PR #337). Closes every
item in
plans/aisoc-missing-pieces/in a single landing: trust-critical honesty fixes on/sovereign+ Features + README, CI matrix expanded to 7 previously-untested Python services (~971 new test signals), coverage gates, real SOAR executors for SentinelOne EDR / PAN-OS / FortiGate / Cloudflare WAF + DNS / Splunk ES / Elastic / MDE / Entra ID / Google Workspace, realCreateTicketExecutorwired to Jira / ServiceNow / PagerDuty, Azure/GCP/Okta/GWS effective-permissions resolvers, the managed-mode auto-provision pipeline (infra/fly/managed/), CI-built white-paper PDFs + 90 s Playwright screencast, the deterministic NL → ES|QL / KQL / SPL translator (81-case eval at 100 % syntactic + 100 % semantic), real-browser visual regression, a buyer-journey E2E, and four immutable ADRs (docs/decisions/0001-0004). - v8.0 milestones — design system, playbook generator, wave-3
connectors, fidelity loaders.
T3.7 NL → playbook generator
(PR #330);
T3.8 design system v2 + Storybook
(PR #331,
DraftFromPromptDialogstory restored in PR #335, Storybook publicDir conflict fixed in PR #336); T4 wave-3 marketplace scaffolding + six hardened connectors (PR #333, wave-1 parity hardening in PR #328); T5.3 AIT-LDS + MITRE Engenuity fidelity loaders (PR #332). - Threat-actor attribution — port fix + optional RBAC. The
investigation agent defaulted
AISOC_THREATINTEL_URLtohttp://threatintel:8083, but the service binds 8005 — everyPOST /api/v1/actors/attributefromservices/agents/app/agents/investigation_agent.pytherefore hit a port nothing listens on and silently degraded. Default corrected, docs +AISOC_ATTRIBUTION_TIMEOUT_SECONDSaligned, regression test added (PR #327). Same release ships an opt-in shared-secret gate (PR #329): whenAISOC_THREATINTEL_SERVICE_TOKENis set, every/api/v1/actors/*call must presentAuthorization: Bearer <token>(constant-time compared,401on mismatch); unset keeps the legacy unauthenticated behaviour and logs a warning. Resolves the[#TODO-attribution-rbac]caveat indocs/threat-actor-attribution.md. - Marketing-shell unification on
tryaisoc.com(QA wave, 2026-06-29). Every/(marketing)page, plus the standalone/not-found,/why-open-source, and/benchmarkroutes, now renders the sameStickyNav+sections/Footershell. The old simplerLandingNav.tsxandlanding/Footer.tsxwere deleted; eleven marketing pages had their per-page nav/footer JSX + imports removed;(marketing)/layout.tsxcentrally injects the shell;StickyNav's anchors were absolutised (/#solution,/benchmark,/pricing) so they resolve identically from the landing page and from any subpage. Folded together with the smaller fixes from the same QA pass: branded/not-foundpage (ISSUE-004),308 /signup → /dashboardfor the anonymous demo (ISSUE-003),Testimonials"Become a reference partner" CTA repointed from the 404'ing/partnersto/contact(ISSUE-002), deadstatus.tryaisoc.comfooter link removed (ISSUE-005), and an SSR-whitespace bug on/aboutthat rendered "the 69connectors" fixed by forcing an explicit{' '}token (ISSUE-007). - Knowledge-base ingest — boundary-aware chunking with overlap (PR #321, closes #277). KB ingestion no longer splits mid-sentence or mid-code-fence; the new chunker prefers paragraph / sentence / code-block boundaries, applies a configurable overlap so retrieval doesn't lose context across chunks, and keeps the produced chunks within the embedding model's hard token budget.
- Realtime — WS/SSE authenticated via short-lived tickets
(PR #246, closes
#239). The realtime
service's WebSocket and SSE endpoints previously accepted any
connection. They now require a short-lived signed ticket that the API
mints for the authenticated session, closing the unauthenticated
fan-out surface that lived between
services/realtimeandapps/web. apps/web— Create Case button wired on/alerts/{id}(PR #294, closes #293). The button on alert detail rendered but did nothing; it now POSTs through the cases endpoint and navigates to the new case workspace.- Infrastructure — Terraform CI + missing core modules. Terraform
workflow on every
infra/terraform/**change (PR #251) runsterraform init -backend=false,terraform validate, andterraform fmt -check -recursiveagainst the AWS, GCP, Azure, and BYOC configurations; the three reusable modules the AWS and BYOC references were already importing —rds,elasticache,kafka— are now actually present ininfra/terraform/modules/(PR #252) so a freshterraform initagainst the multi-cloud skeletons no longer errors on missing sources. GCP sensitive-var taint cleared onfor_each(PR #243); Azure Terraform skeleton documented (PR #247). - Dependency & CI maintenance. ~15 Dependabot upgrades across the
Python, JS, and Go services (FastAPI in
services/{api,actions,agents}via #317, #319, #320;next16.2.7 → 16.2.9 in #323;framer-motion11.18.2 → 12.40.0 in #307;cryptographyin #301 / #302; Goredis/go-redisin #297 / #298;strawberry-graphqlin #318;actions/checkoutv6 → v7 in #316; plus@xyflow/react,@types/node,tsx,@tailwindcss/postcss); pnpm audit high/critical findings cleared (PR #322) so the dep-bump PR queue could actually merge; a duplicate@mdx-js/reactkey that was breakingpnpm installremoved (PR #296);aiohttpbumped to 3.14.1 to clear CVE-2026-34993 + CVE-2026-47265 (PR #295).
AiSOC missing pieces — Phases 1–5 (PR #337)
The largest single landing in this release. Twenty-five commits implement
the entire plans/aisoc-missing-pieces/ roadmap; nothing in the plan is
deferred.
Phase 1 — Trust-critical fixes (1.1–1.6): one build-time
generator + CI gate is now the only place the marquee connector count
lives; the hard-coded ★ 2.3k GitHub-stars badge was replaced with a
live shields.io endpoint; every SOC 2 / ISO 27001 / GDPR / DPDP claim
across /sovereign, Features.tsx, and README.md is now qualified
with the honest "controls aligned to" framing pending a Type I audit
(ADR-0002 below); seven 404'ing footer links and two pricing CTAs were
either stubbed, repointed to mailto:, or redirected to GitHub; the
real services/connectors/app/connectors/gitlab.py connector that the
marquee pill had been claiming was real now exists; and the /sovereign
Terraform deep-links route to the correct subdirectories per cloud, with
Azure added and the unsupported clouds struck.
Phase 2 — Operational readiness (2.1–2.6): the seven Python
services that were silently outside the CI matrix
(services/{ueba,honeytokens,purple-team,osquery-tls,connectors,actions, threatintel} in practice) are now included; the pytest and Vitest
configurations enforce a coverage floor via --cov-fail-under and the
Vitest coverage.thresholds; prometheus.yml no longer lists scrape
targets that don't exist (CI now gates against drift); Prometheus
alerting rules + Alertmanager container are wired in docker-compose.yml;
seven incident runbooks land under docs/runbooks/; and every FastAPI
service now exposes /livez (the process is up) and /readyz
(dependencies are reachable) separate from the existing /health.
Phase 3 — Real SOAR executors (3.1–3.5): the executor surface
stops being a façade. SentinelOne EDR has a real client
(services/actions/app/integrations/sentinelone.py) wired to
ContainHostExecutor; PAN-OS, FortiGate, Cloudflare WAF, and Cloudflare
DNS firewall each have a real client wired to the appropriate Block…
executor; AckAlertExecutor and SuppressAlertExecutor now talk to
Splunk Enterprise Security, Elastic Security, and Microsoft Defender for
Endpoint directly; Entra ID and Google Workspace are wired as real IdP
clients for DisableUserExecutor; and CreateTicketExecutor no longer
returns SIMULATED — it delegates to the existing Jira, ServiceNow, and
PagerDuty connectors.
Phase 4 — Larger build-out (4.1–4.8):
- 4.1 — Azure RBAC, GCP IAM, Okta, and Google Workspace effective-permissions resolvers (closes T3.2). The investigation agent can now answer "what can this principal actually do?" across all four IdPs, not just AWS.
- 4.2 — Managed-mode auto-provision pipeline (closes T6.1):
infra/fly/managed/+ a workflow that creates a fresh Fly tenant from a push tomain, dry-run-safe (won't act withoutFLY_API_TOKEN). - 4.3 —
make papersbuilds the white-paper PDFs in CI, and a Playwright project records a 90-second product screencast on demand. - 4.4 — Connector wave finished: Sysdig, Vault, Snowflake, and Cloudflare Zero Trust manifests + docs.
- 4.5 — Pluggable event-warehouse provider
(
services/api/app/services/event_warehouse/) with Elasticsearch, Splunk, and Chronicle implementations;croniter-backed hunt scheduler (closes Milestone 1F). - 4.6 — Deterministic NL → ES|QL / KQL / SPL translator. 81-case eval, 100 % syntactic, 100 % semantic — every output is parsed through a grammar validator before return.
- 4.7 — Real-browser visual regression: Playwright + Storybook,
pinned to
mcr.microsoft.com/playwright:v1.49.0-jammy. First CI run needs--update-snapshots. - 4.8 — Buyer-journey E2E covering
/alerts → Investigation Rail → /playbooksruns onpnpm e2e.
Phase 5 — Strategic decisions (5.1–5.4): four immutable ADRs.
0001-cyble-cti-moat.md retires
the Cyble-only CTI moat in favour of a pluggable MIT-compatible CTI
fusion layer; 0002-compliance-claims.md
fixes the "controls aligned to" framing until a Type I audit lands and
gates it on a concrete enterprise design partner;
0003-mssp-pricing-shape.md
keeps three public tiers, with MSSP getting its own narrative at
/mssp; 0004-live-demo-strategy.md
retires the Cloudflare Tunnel demo and provisions a dedicated managed-mode
tenant on Fly.io.
The Playwright projects (screencast, visual, journey) are gated by
PLAYWRIGHT_PROJECT so no project's webServer boots when another
runs. The four ADRs are immutable: future changes write a new ADR that
supersedes the old one.
T3.7 — NL → playbook generator
(PR #330). Operators can
type a runbook in English and the agent emits a structured playbook YAML
that fits the existing services/actions schema: graph of executors,
inputs, and conditionals, with the same JSON-schema validation the
console editor enforces. Backed by the same deterministic translator
substrate as Phase 4.6 so the output stays parsable when the LLM goes
sideways.
T3.8 — Design system v2 + Storybook
(PR #331). The console
finally has a single source of truth for tokens, primitives, and
composites. apps/web/src/components/ui/ is now organized as
tokens / primitives / patterns, every component renders in Storybook,
and the visual-regression CI gate from Phase 4.7 watches it.
DraftFromPromptDialog was momentarily lost during the migration and
restored in PR #335. The
Vite publicDir copy that broke the Storybook build under the new
config was disabled in PR
#336 so main CI stays
green.
T4 — Wave-3 marketplace scaffolding + six hardened connectors (PR #333). The marketplace registry gains the schema + tooling for the third connector wave; six wave-2 connectors had their tests and fixtures hardened to wave-1 parity in PR #328 so every first-party connector ships with the same shape of negative-path coverage.
T5.3 — AIT-LDS + MITRE Engenuity fidelity loaders (PR #332). Detection-fidelity scoring now ingests two canonical labelled datasets: the AI-Threats Labelled Dataset and the MITRE Engenuity ATT&CK evaluation set, both fronted by deterministic loaders so the fidelity-score outputs are reproducible across CI runs.
Two narrowly-scoped fixes that together close the only path by which the investigation agent could silently degrade.
services/agents/app/agents/investigation_agent.py defaulted
AISOC_THREATINTEL_URL to http://threatintel:8083. The service binds
8005 in its Dockerfile, in docker-compose.yml, and in the README
service table — every POST /api/v1/actors/attribute call therefore hit
a port nothing listens on. The error path was soft-handled, so
attribution wasn't 500-ing; it was returning empty
attribution silently. PR #327
corrects the default to http://threatintel:8005, fixes the matching
docs/threat-actor-attribution.md references, raises the stale
AISOC_ATTRIBUTION_TIMEOUT_SECONDS default from 10 to 30, and adds
a regression test (services/agents/tests/test_attribution_service_url.py)
that pins the URL and timeout so this can't drift again.
PR #329 layers an opt-in
shared-secret gate on the actor-attribution router. When
AISOC_THREATINTEL_SERVICE_TOKEN is set, every /api/v1/actors/* call
must present Authorization: Bearer <token>; the comparison is
constant-time, 401 on mismatch. When the env var is unset, the
endpoints stay unauthenticated for backward compatibility and emit a
single startup warning so the operator knows the gate isn't on. The
investigation agent forwards the token via its own
AISOC_THREATINTEL_SERVICE_TOKEN. Resolves the
[#TODO-attribution-rbac] caveat in docs/threat-actor-attribution.md.
Pre-7.5 the marketing surface was rendering two different navigation
components — the richer StickyNav on the landing page and the older
LandingNav everywhere else — and likewise two footers. Subpage visitors
saw a degraded nav with hash-only anchors that misbehaved (e.g. #pricing
on /about was a no-op rather than navigating to /pricing).
The unification (commit
77039a41):
apps/web/src/app/(marketing)/layout.tsxnow importsStickyNavandsections/Footerand renders them around{children}. Every page in the(marketing)route group is content-only.- Eleven marketing pages had their per-page nav/footer JSX + imports removed — they now inherit from the layout.
- The standalone routes (
not-found.tsx,why-open-source/page.tsx,benchmark/page.tsx) — which live outside(marketing)and so can't pick up its layout — importStickyNavandsections/Footerdirectly. StickyNav'sNAV_LINKSwere absolutised so they work from any URL:/#solution,/#pillars,/#connectors,/benchmark,/pricing,docs/intro. The "Self-host" CTA points at/pricingfor the same reason.apps/web/src/components/landing/LandingNav.tsxandapps/web/src/components/landing/Footer.tsxwere deleted.
Bundled in the same QA wave:
ISSUE-002—Testimonials"Become a reference partner" CTA was pointing at/partners, which 404s. Now goes to/contact.ISSUE-003—/signup308-redirects to/dashboard. The anonymous demo dashboard is the signup flow; the old form-fronted signup is gone.ISSUE-004—/not-foundis now a branded dark-theme page with the unified shell and a "back to home" CTA, replacing Next's default.ISSUE-005— Removed the deadstatus.tryaisoc.comlink from the footer.ISSUE-007—/aboutrendered "the 69connectors" because React's JSX text-children whitespace rules drop the leading space of a text segment that wraps right after a{expression}. Forced an explicit{' '}token so the layout-quirk is immune to reflow.
PR #321 (closes
#277). The previous
chunker split on a flat character budget, which routinely produced
mid-sentence chunks and severed code fences. The new chunker walks the
document with paragraph → sentence → token precedence, applies an
overlap (default 64 tokens, configurable) so retrieval doesn't lose
context across chunks, and keeps every produced chunk under the
embedding model's hard token budget. Retrieval quality on the existing
KB ingestion fixtures improved without any model change.
PR #246 (closes #239). The realtime service previously accepted any WebSocket or SSE connection — there was no way to assert which tenant a stream belonged to except via the client's word for it. Connections now require a short-lived signed ticket that the API issues to the authenticated session; the ticket encodes the tenant and the subscription scope and expires after a small window so a stolen ticket can't long-tail. Closes a multi-tenant fan-out surface that had been live since the realtime service shipped.
PR #251 — every push that
touches infra/terraform/** now runs terraform init -backend=false,
terraform validate, and terraform fmt -check -recursive against the
AWS, GCP, Azure, and BYOC configurations. The same gates ran locally in
the v7.4.0 deploys; they're now actually enforced.
PR #252 — the AWS and BYOC
references in v7.4.0 imported infra/terraform/modules/rds,
modules/elasticache, and modules/kafka from sources that did not
exist in the repo. The three modules are now actually present, so a
fresh terraform init against the multi-cloud skeletons no longer
errors on a missing source. PR
#243 drops the
sensitive-var taint from for_each in the GCP module so the plan stays
clean. PR #247 documents
the Azure Terraform skeleton end-to-end in apps/docs/.
Around fifteen Dependabot landings since v7.4.0; the headline ones:
- FastAPI updated in
services/api,services/actions, andservices/agents(PRs #317, #319, #320). next16.2.7 → 16.2.9 (PR #323).framer-motion11.18.2 → 12.40.0 (PR #307).cryptographyupdated inservices/apiandservices/actions(PRs #301, #302).redis/go-redis/v9updated inservices/enrichmentandservices/ingest(PRs #297, #298).strawberry-graphqlupdated inservices/api(PR #318).actions/checkoutv6 → v7 across every workflow (PR #316).aiohttp3.14.1 to clear CVE-2026-34993 + CVE-2026-47265 (PR #295).- pnpm audit cleared of all high/critical findings (PR #322) so the dep-bump queue could merge without the global gate failing on unrelated noise.
pnpm-lock.yamlduplicate@mdx-js/reactkey fixed (PR #296) — was breakingpnpm installon fresh clones.- Other dev/test bumps:
@xyflow/react12.10.2 → 12.11.0 (PR #283),@types/node20.19.39 → 25.9.2 (PR #285),tsx4.22.1 → 4.22.4 (PR #306),@tailwindcss/postcss4.3.0 → 4.3.1 (PR #305).
AISOC_V8_PROGRESS.mdtracker re-introduced (PR #334) so the v8.0 milestone burn-down lives at the repo root again.AGENTS.mdupdated to record AiSOC (github.com/beenuar/AiSOC) as the single source of truth — the olderAISOC-Cyblemirror is now archived (PR #326; archive-notice sync in PR #325;plans/cyble-aisoc/subtree merged for posterity in PR #324).- Marketing-page docs links repointed at the Docusaurus site (PR #245).
- Connector pages — Vault → Auth0/Okta cross-links unbroken
(post-merge fix on
main). README.mdsynced to v7.4.0 ahead of this release (PR #246).
VERSIONbumped 7.4.0 → 7.5.0.apps/web/package.jsonbumped 7.3.1 → 7.5.0. The web app'spackage.jsonhad drifted fromVERSIONsince the v7.3.1 hotfix; this release reconciles them.README.mdversion badge + headline updated to v7.5.0.
None required for users on v7.4.0 — every change in this release is either additive (new endpoints, new env vars defaulting to safe unauthenticated behaviour, new connectors and executors) or a pure bug fix to existing behaviour. Specifically:
- The threat-actor attribution port fix changes a default — if you
had explicitly set
AISOC_THREATINTEL_URLin your environment, it is honoured unchanged. - The optional
AISOC_THREATINTEL_SERVICE_TOKENgate is off until you set it. Set it on both theagentsandthreatintelservices to turn the gate on. - The Realtime short-lived-ticket auth is enforced server-side; the
apps/webclient mints + refreshes tickets automatically against the authenticated API session. No client work is required for in-tree consumers; external SSE consumers must adopt the ticket flow. - The marketing-shell unification is a
tryaisoc.com-only change; it doesn't touch the console attryaisoc.com/dashboardor any product surface.
Security-hardening and platform release. Folds in the May 27–29 hardening wave,
multi-agent routing, and multi-cloud infrastructure skeletons that landed on
main since v7.3.1.
- Security hardening. Prompt-injection sanitizer wired into the classification agents (PR #219); cross-tenant isolation enforced on the detection-loop suggestion lookups (PR #221) and on the compliance, phishing, and knowledge-base endpoints (PR #236); nightly cross-tenant RBAC regression gate (PR #197); cryptography CVEs cleared and unfixable advisories time-boxed (PR #229); CodeQL quality notes resolved (PR #224).
- Multi-agent routing.
DetectAgent.processwired to theFusionEngineover cross-service HTTP (PR #198);/investigateswapped to theRouterOrchestratorbehind theROUTER_INVESTIGATEflag (PR #196); Redis-backed scheduler singleton guard for in-process workers (PR #218). - Multi-cloud infrastructure. Serverless-container Terraform skeletons for GCP (Cloud Run + Cloud SQL + Memorystore) and Azure (Container Apps + PostgreSQL Flexible Server + Cache for Redis), mirroring the AWS/EKS reference file-for-file (PR #240).
- Live dashboard & landing. Real
/metricsdata restored ontryaisoc.com/dashboard(PR #192); API/agents machines kept warm so the dashboard no longer 500s (PR #234); seed timestamps re-anchored so the live dashboard never goes empty (PR #235); landing CTAs pointed at the live dashboard (PR #233). - Dependency & CI maintenance. ~40 Dependabot upgrades across the Python, JS, and Go services plus CI stabilization (Ruff cleanup, OpenAPI export permissions, pnpm-lock dedupe).
Dev-only dependency upgrade (PR #178).
@vitejs/plugin-react@6 is built against vite@8, while vitest@4 (landed in
PR #179) still ships its own internal vite@7. pnpm resolves both side-by-side
without conflict: vitest@4 uses vite@7 for the test runtime, and react() is
loaded from the vite@8-flavoured build of the plugin. Vitest is tolerant of
the plugin API surface across vite 5/6/7/8, so apps/web/vitest.config.ts
needed no further changes after the cast we already removed in #179.
No production code touched. Locally verified: web 349/349 tests pass, lint
remains at 0 errors / 76 warnings (unchanged baseline), tsc --noEmit clean,
production build succeeds.
Dev-only dependency upgrade (PR #179)
across apps/web, packages/sdk-ts, and services/mcp. Vitest v3 and v4
introduced two breaking changes that surfaced in our suite:
vitest/configno longer exportsUserConfig.apps/web/vitest.config.tsusedimport('vitest/config').UserConfig['plugins']to bridge the vitest@2 (vite@5 types) ↔@vitejs/plugin-react@4(vite@7 types) version mismatch. In vitest@4 both packages target vite@7, so the bridging cast is gone andreact()is consumed directly.globalis no longer in the default DOM lib in@vitest/runner's typing.packages/sdk-ts/src/client.test.tsreferenced the Node global namespace via(global.fetch as ...); it now usesglobalThis.fetch, which is the cross-runtime idiom and was already what every other test in the SDK suite used. No runtime behaviour change —global === globalThisin Node.
Verified locally: SDK 9/9 tests pass, web 349/349 tests pass, web lint stays at
0 errors (warning count unchanged from PR #193's baseline). No production code
touched, no behavioural change to the published @aisoc/sdk package or to the
shipped web bundle.
Closes #190.
Closes the missing edge in the four-agent façade: DetectAgent previously
self-described as the public detection surface but had no synchronous entry
point into the fusion pipeline — callers either had to enqueue onto Kafka and
wait, or reach into services/fusion internals directly. This change adds the
last mile so a raw alert from any caller (LLM tool calls, ad-hoc CLI, the API
gateway) runs through the same FusionEngine instance that backs the Kafka
consumer path — dedup, correlation, ML scoring, confidence labelling, and RBA
all apply identically regardless of how the alert arrived.
Three additive pieces, no behavioural changes to existing paths:
POST /processon the fusion service (services/fusion/app/api/router.py). Accepts aRawAlert, returns aFusedAlert, and is wired to the already-runningFusionWorker's engine instance via the module-level_worker_refthe worker registers on startup. Returns503when the worker hasn't finished booting (Kafka consumer not yet attached) so callers fail loudly instead of getting a half-initialised pipeline. Lives at the root path — the router is mounted with no prefix inservices/fusion/app/main.py.services/agents/app/tools/fusion.py— thin async HTTP client used by the agents service. Posts to{FUSION_SERVICE_URL}/process(defaults tohttp://fusion:8003/processinside the docker-compose network), forwards an optional bearer token, and raises on any non-2xx or transport error. This is a deliberate contrast withapp.tools.graph, which degrades gracefully for investigation queries: fusion is the primary detection plane, so a silent fallback here would lose alerts.DetectAgent.process(raw_alert, *, api_token=None)(services/agents/app/agents/__init__.py). Classmethod delegate over the HTTP client — keepsDetectAgentimport-light (no engine instantiation in the agents process) and preserves the existing back-compat aliases.
Tests lock the contract on both sides. services/fusion/tests/test_process_endpoint.py
exercises the endpoint against an ASGITransport + AsyncClient: novel
alerts return a NEW_INCIDENT envelope, replays return DUPLICATE, an
unwired worker yields 503, a worker without an engine yields 503,
malformed and bad-severity payloads return 422, and a regression guard
asserts the endpoint and worker share the same FusionEngine instance.
services/agents/tests/test_fusion_client.py uses respx to lock the
client wiring: it must post to /process (not /api/fusion/process — that
mismatch was caught and fixed during initial wiring), the Authorization
header is set if and only if a token is supplied, httpx.HTTPStatusError
propagates on 503/422, and httpx.HTTPError propagates on transport
failures. A final trio of tests pins DetectAgent.process as a faithful
delegate to the client (args pass through unchanged, errors propagate, no
swallowed exceptions).
No feature flag and no env gate: the wiring is purely additive — no existing caller of the fusion service or the agents service changes shape, and the new endpoint/method only fire when something explicitly invokes them.
Closes #159.
Pure-unit isolation suites that exercise the tenant boundary at the endpoint-function level (no live DB, no FastAPI request cycle) so the contract is testable in milliseconds and survives ORM churn:
services/api/tests/test_threat_intel_tenant_isolation.py— IOC, actor, and feed list/get/create/delete are scoped bytenant_id, cross-tenant lookups resolve to 404, and writes attachcurrent_user.tenant_ideven when the payload smuggles a different one.services/api/tests/test_alerts_tenant_isolation.py— every read/write/queue/claim path on/alertsbindstenant_idinto the compiled SQL or forwards it to the service layer (build_queue/claim_alert).services/api/tests/test_llm_credentials_tenant_isolation.py— BYOK credential GET/PUT/DELETE scope bytenant_id, new rows bind the caller's tenant, andemit_auditis invoked with the caller's tenant + actor (CredentialVaultis stubbed so the assertions are on the persistence boundary, not crypto).
Assertions read the compiled SQL bind parameters rather than the
shape of any single query so they don't break on benign rewrites. All
three suites were mutation-tested by temporarily dropping the
tenant_id predicate in the corresponding endpoint — every dropped
predicate produced at least one failing test, confirming the suites
are wired to the right surface.
.github/workflows/cross-tenant-rbac.yml runs the three suites
nightly on main (06:30 UTC, ahead of compose-smoke-nightly so a
tenant boundary regression shows up as the first nightly signal) and
on-demand via workflow_dispatch. On failure it uploads a JUnit
report and opens a security-labelled tracking issue.
/cases/{id} now ships an Attack Chain tab that visualises the ranked
timeline returned by /v1/cases/{id}/attack-chain (shipped earlier under
8df637b9). The new AttackChainPanel in
apps/web/src/components/cases/CaseWorkspace.tsx:
- Window selector with the same vocabulary as the backend
WindowLiteral(1h,6h,24h,72h,7d,30d) — selection is deep-linkable via?window=…and survives reload. - One card per
ChainLinkwith the alert title, severity chip (driven by the canonical 5-tier ladderinfo | low | medium | high | critical), confidence percent, MITRE technique IDs, and the deterministic narrative reason emitted byservices/api/app/services/attack_chain.py. - Entity-graph summary panel — node count grouped by
kind(user,asset,process,ip,domain,alert), top edges, and a per-node severity chip when present in_entity_graph_payload. - SWR-keyed on
(case_id, window)with skeleton, error, and empty states that match the rest of the case workspace. - New
casesApi.getAttackChainmethod +AttackChainTimeline,AttackChainWindow,AttackChainLink,AttackChainEntityNode,AttackChainEntityEdge,BackendAttackChainResponsetypes inapps/web/src/lib/api.ts. The wire format matches the backendto_dictshape exactly (nodekindrather thantype; optionalseverityandevent_timefrom_entity_graph_payload). - Coverage in
apps/web/src/components/cases/CaseWorkspace.test.tsx: empty-state, error-state, and three data-rendering assertions (alert titles, confidence percent, MITRE techniques). The SWR mock is now key-aware so attack-chain and attack-path fetches stay isolated, anduseSearchParamsis stateful so window-selection deep-links round-trip cleanly under test. TheWindowSelectoris a labelledrole="group"of buttons witharia-pressed, so deep-link assertions resolve the active option via the single pressed button inside the group rather than a non-existent<select>value.
Closes the UI side of T3.3 in AISOC_V8_PROGRESS.md. Pre-existing
non-blocking lint warnings in CaseWorkspace.tsx are unchanged by this
diff.
Closes T2.3 by adding the missing bypass-prevention layer on top of the
existing fail-closed validator (services/agents/app/llm/contract.py). Two
new test files in services/agents/tests/:
test_llm_contract_extra.py(10 cases) — fills the coverage gaps in the shipped contract:safe_astreamvalidates messages exactly once and refuses to yield any chunk on violation;make_safe_chat_modelproxies non-LLM attributes through but routesainvoke/astreamthrough validation;classify_messagerejectsapi_key = '...'assignments and PEM private-key headers;set_contract_enforcement(False)lets raw OCSF through in soft mode and re-arms cleanly when flipped back toTrue.test_llm_contract_no_bypass.py(3 cases) — AST-based static gate that walks every*.pyfile underservices/agents/app/and fails CI on any direct.ainvoke(...)/.astream(...)call whose receiver is not on an explicit allowlist (_graph,investigation_graph,graph— all LangGraph control-flow handles, not LLMs) or whose file is not the contract module itself. Ships with self-tests proving (a) a syntheticllm.ainvoke(...)bypass trips the detector and (b) allowlisted receivers do not. Adding a new agent that calls a chat model directly now fails the build until it routes throughsafe_ainvoke/safe_astream/make_safe_chat_model.
The survey behind this gate confirmed every existing direct chat-model call
under services/agents/app/ already goes through the safe wrapper — the
remaining .ainvoke / .astream call sites are LangGraph control-flow on
compiled graphs, which is why those receivers are explicitly allowlisted
rather than silently ignored.
services/agents/tests/test_llm_contract.py exercises classify_message /
LLMInputContract.validate / validate_messages: raw OCSF-shaped JSON in a
user message fails closed when AISOC_AGENTS_LLM_CONTRACT_ENFORCED=1
(default), and prose plus summarize_structure_for_llm output passes. Tests
use {"role", "content"} dict messages so they run without importing
langchain_core (the contract already coerces LangChain BaseMessage and
dicts the same way).
Closes the v8.0 loop between the ingest-side graph writer (T1.1) and the
operator console. services/realtime now exposes a graph WebSocket
channel reachable at /ws/graph (or piggy-backed on /ws/all) and runs a
dedicated aisoc-realtime-graph Kafka consumer group against the
security.graph_updates topic that the Go ingest writer publishes to
(services/ingest/internal/graph/writer.go). Each GraphUpdate envelope
(entity_id, change_type, ts, label, rel_type, from, to,
properties, schema_version) is fanned out to clients scoped by
tenant_id, with default as the single-tenant fallback so self-hosted
deploys without explicit tenant tagging still light up live. The new
consumer is wired alongside the existing fused-alerts consumer in
non-blocking mode: a missing or unreachable graph topic logs at warn and
never blocks the higher-priority alerts/cases/agents/insights fan-out. The
topic name honours both AISOC_GRAPH_UPDATES_TOPIC and
KAFKA_TOPIC_GRAPH_UPDATES envs (defaults to security.graph_updates so
it matches the Go writer's default in
services/ingest/internal/config/config.go without manual plumbing), and
setting it to the empty string disables the consumer entirely for tests
that don't spin up Kafka graph traffic. The Investigation Rail and Attack
Chain views (T3.3 UI, in flight) can subscribe today and pick up node /
edge mutations within ~1s of the upstream event reaching ingest.
Public, append-only weekly scoreboard now lives at
/docs/benchmark-scoreboard.
One row per published eval run — date, agent version, commit SHA, MITRE
accuracy, MTC p50/p95, total USD, total tokens — sourced from a
checked-in JSON file at apps/docs/static/data/scoreboard.json and
validated against scoreboard.schema.json on every docs build via the new
pnpm --filter @aisoc/docs scoreboard:check script. Substrate rows
(deterministic CI gate, no LLM) are visually separated from wet-eval rows
(real LangGraph agent, real LLM, real cost), so substrate numbers can
never be quoted as live agent performance. Includes an inline SSR-rendered
SVG sparkline of MITRE accuracy over time, no Recharts/client JS bundle
hit. The marketing /benchmark page now cross-links to the scoreboard for
the full weekly history. Wet-eval rows arrive automatically once the T5.5
weekly CI workflow lands.
New first-class endpoint connector for Wazuh deployments. AiSOC now polls the
Wazuh Indexer API directly (no agent rewrite required) and normalizes alerts
into the platform's OCSF-aligned schema, collapsing Wazuh's native severity
ladder into the four-tier info | low | medium | high set used everywhere
else.
services/connectors/app/connectors/wazuh.py—WazuhConnectorsubclassesBaseConnector, pollswazuh-alerts-*indices over HTTPX with basic-auth, paginates time-windowed queries, retries on 5xx with capped backoff, and emits one normalized event per alert hit. Cursor is the highest@timestampseen so reruns are idempotent.services/connectors/app/connectors/__init__.py— registered in_CONNECTOR_CLASSES; the registry now declares 52 first-party connectors.plugins/wazuh/plugin.yaml+pnpm marketplace:sync— connector ships as a marketplace entry under categorysiem, mirrored intoapps/web/public/marketplace/index.json.apps/docs/docs/connectors/wazuh.md+ sidebar entry — operator setup walkthrough (API user + role, time-window semantics, severity collapse table, troubleshooting matrix).services/connectors/tests/test_wazuh_connector.py— 24 unit tests cover schema, auth headers, time-window query shape, retry policy, every documented severity bucket, and the empty/error paths.
Replaces the old hard-coded plugin scaffold with a real templated generator
keyed on plugin kind (enricher | connector | responder | detection | widget).
Templates ship inside the aisoc-cli wheel via importlib.resources so the
CLI works unchanged after pip install aisoc-cli.
packages/aisoc-cli/src/aisoc_cli/main.py—aisoc plugin new <NAME> --type <kind>loads the template tree fromsrc/aisoc_cli/templates/<kind>/, runsstring.Templatesubstitution for${slug},${name},${author}, and writes a project that already validates against the manifest schema.aisoc plugin scaffoldis preserved as an alias for backwards compatibility.pyproject.toml—force-includeships the templates tree in the wheel.- Tests parameterize across all five plugin types and assert the manifest
validates and no
${...}placeholders leak through. plugins/templates/README.mdis now a pointer to the canonical templates inside the CLI package.apps/docs/docs/plugins/cli.md— documents the new CLI surface and is added to the Plugin SDK sidebar.
Adds a serverless-first BYOC equivalent of the existing AWS module so AiSOC
can be stood up on Google Cloud with one terraform apply. Stage 2 #15.
infra/terraform/gcp/— Cloud Run forapi/web/ingest, Cloud SQL Postgres 16 + Memorystore Redis 7.2 on private IPs through a dedicated VPC and Serverless VPC Access connector, Secret Manager for every credential (auto-generatedpostgres_password,secret_key,credential_key,redis_auth, optionalopenai_api_key), and Artifact Registry for images. One service account per Cloud Run service with least-privilegesecretAccessorbindings. The skeleton points at the public GHCR demo images so a freshapplyworks zero-config; operators override viaapi_image/web_image/ingest_image.apps/docs/docs/deployment/gcp.md+ sidebar entry (betweenkubernetesandenv-vars) — quickstart, state-backend guidance, Cloud SQL Auth Proxy notes, cost envelope, and the long-running-services follow-up plan (GKE Autopilot foragents,realtime,connectors,alert-fusion,threatintel,fusion).infra/terraform/gcp/README.mdmirrors the deploy doc for module-local consumption.
Adds a vendor-pluggable response-action surface so plugins can register
executors against the existing capability taxonomy without forking the
in-tree executor list. The dispatcher always returns a typed
LiveActionResult; unknown (vendor_id, capability) pairs return FAILED
with error="executor_not_found" so the agent degrades gracefully instead
of seeing a 500.
services/actions/app/live_actions/models.py—LiveActionRequest/Result/DescriptorPydantic models (UTC-aware).services/actions/app/live_actions/registry.py—LiveActionExecutorABC + module-levelLiveActionRegistry.services/actions/app/live_actions/dispatcher.py— structured logging, error translation, dry-run + missing-credential semantics (SIMULATED, neverPARTIAL).- Adapters wrap every existing in-tree executor (CrowdStrike, Okta, AWS SG,
Splunk) so they now show up as
builtindescriptors. services/api/app/api/v1/endpoints/live_actions.py—discover,dispatch,dry-runREST routes; built-ins are registered at app startup.- 45 new tests across models / registry / dispatcher / router / builtins (full actions suite: 99 passed).
apps/docs/docs/concepts/live-actions.md+ sidebar slot.- Drive-by: fixed two pre-existing broken doc links flagged by the
Docusaurus build (osctrl → aisoc-direct stub,
air-gapped→env-vars).
Replaces the template fallback in
services/api/app/api/v1/endpoints/nl_query.py with a real, offline-friendly,
deterministic IR + renderer that emits ES|QL, KQL, and SPL and runs every
output through a lightweight grammar validator before returning. An optional
LLM enhancement path (gpt-4o-mini) is exposed via enhance_with_llm for
callers with credentials; failures fall back to the deterministic path so the
air-gapped story keeps working and the eval harness stays reproducible.
services/agents/app/nl_query/— IR, grammar, translator, renderers.- All
# TODO: translatecomments removed fromnl_query.py. services/agents/tests/eval_data/nl_query_eval.json— 50-pair gold NL→ES|QL eval set.services/agents/tests/test_nl_query_eval.py— 100% syntactic validity, 100% semantic match (50/50 perfect) against gold intents.- Pre-existing services/agents tests still green (162 passed) when ignoring the asyncpg-dependent suites that fail on a fresh checkout.
Replaces the host-agent dependency for Linux endpoint visibility with a
file-tail connector that consumes audit.log directly, plus an opinionated
auditctl ruleset whose -k keys map 1:1 to detection rules.
services/connectors/app/connectors/auditd.py—AuditdConnectortails/var/log/audit/audit.log, reassembles multi-record events by msg id, decodes hexproctitle/argvblobs, and normalizes via_severity_from_eventusingaisoc_*keys baked into the audit rules profile. Cursor is(inode, byte_offset)so log rotation is handled.profiles/auditd/aisoc.rules+profiles/auditd/README.md— ships an opinionated auditctl ruleset and documents install + reload.detections/— 4 new detection rules pivot offauditd_keyfor sudoers / SSH config tampering, kernel module load, and systemd persistence. No host-agent dependency.plugins/auditd/plugin.yaml+pnpm marketplace:sync— registers the connector in the public marketplace.apps/docs/docs/connectors/auditd.md+ sidebar entry — setup doc.services/connectors/tests/test_auditd_connector.py— covers schema, hex decode, argv reassembly, multi-record merge, severity heuristic, and file tailing (full connectors suite: 444 passed, excluding theapschedulerdev-deptest_scheduler.py).
Two new operator-facing docs pages, both registered in the Docusaurus sidebar:
apps/docs/docs/operations/notifications.md— complete inventory of every notification surface in AiSOC: Web Push to the responder PWA (VAPID, Redis, topic routing), Slack ChatOps via/aisoc, Slack/Teams ChatOps verification, one-shotnotify_slackfrom playbooks,create_ticketsimulation + recommended plugin path, honeytoken first-touch webhooks, connector freshness alerts, on-call gating, suppression / quiet-hours, and a per-mechanism testing recipe.apps/docs/docs/plugins/lifecycle.md— operator's view of plugin states (Discovered → Loaded → Enabled/Disabled, plussignature_status), trust modes (strict | warn | disabled), filesystem + OCI discovery, the full operator REST API with required permissions, configuration reference, upgrade and rollback semantics, and the structlog events worth alerting on.
Both pages cross-link the existing concepts/live-actions, plugins/overview,
plugins/publishing, and plugins/cli pages so they sit in the right place
in the information architecture.
Mirrors the existing case auto-summary pipeline to produce a deterministic, blameless retrospective for any case.
services/api/app/services/case_postmortem.py— pure builder + async DB orchestrator (build_case_postmortem). ReusesSummaryCaseRow/SummaryCommentRow/SummaryTaskRowfetchers fromcase_summaryso the post-mortem and the live summary draw from the same source of truth. Output is a PydanticCasePostmortemcovering incident overview, contributing factors, detection timing/gaps, response phases (detect → contain → eradicate → recover), blast radius, what went well / what fell short, and concrete action items.services/api/app/services/case_postmortem_html.py— pure HTML renderer matching the summary renderer (inline CSS, print-friendly, defensive escaping, no external assets).services/api/app/api/v1/endpoints/cases.py—GET /api/v1/cases/{case_id}/postmortemwith?format=json|html.services/api/tests/test_case_postmortem.py— pure-builder + HTML tests including XSS escaping, deterministic ordering, and explicit blamelessness assertions (analyst handles must not surface in the narrative; the assignee header line is explicitly allow-listed).apps/docs/docs/operations/case-reports.md+ sidebar — operator page covering both/summaryand/postmortemwith audience, output, automation, and runbook archive guidance. Cases summary breadcrumb now points operators at both endpoints.
The threat-intel pipeline already pulled events from MISP (read-only). This
closes the loop with a write path: every STIX 2.1 indicator or bundle
published through /api/v1/threatintel/stix/... can be mirrored into the
configured MISP instance as a native event with one or more attributes.
services/api/app/services/misp_push.py- Pure mappers:
parse_stix_pattern,stix_indicator_to_misp_attribute,stix_bundle_to_misp_event,confidence_to_threat_level. Coversipv4/ipv6,domain-name,url,email-addr,file:hashes(MD5/SHA-1/SHA-256/SHA-512) andfile:name. Untranslatable patterns are counted inskipped_attributes, never silently dropped. MispPushClient— async httpx wrapper for/users/view/me(health),/events/add(push),/events/view/{id}(read-back). Every call runs through the air-gap gate (enforce_airgap_for_url) first.
- Pure mappers:
services/api/app/api/v1/endpoints/stix_taxii.pyPOST /stix/indicators?push_to_misp=true— response now includes amispblock (pushed,misp_event_id,misp_event_uuid,url,pushed_attributes,skipped_attributes,error).POST /stix/bundles?push_to_misp=true— same, but the whole bundle becomes one MISP event.GET /stix/misp/health— calls MISP/users/view/me, never echoes the API key back.POST /stix/misp/dry-run— returns the exact MISP event payload AiSOC would send, plus anairgap_blockedflag for air-gapped audits.- Push failures are intentionally non-fatal: the AiSOC store is the source of truth, the MISP mirror is best-effort and surfaces the structured error on the same response.
services/api/app/core/config.py— new MISP push settings:MISP_VERIFY_SSL,MISP_PUSH_AUTO,MISP_PUSH_DEFAULT_DISTRIBUTION,MISP_PUSH_DEFAULT_THREAT_LEVEL,MISP_PUSH_DEFAULT_ANALYSIS,MISP_PUSH_TIMEOUT_SECONDS. ExistingMISP_URL/MISP_API_KEYare reused from the read path.services/api/tests/test_misp_push.py— 76 tests covering pure mappers, air-gap gating, MISP HTTP failures (401 / 5xx / timeout), the publish endpoints with and without push, the health probe, and the dry-run endpoint.apps/docs/docs/integrations/misp-push.md+ sidebar entry — operator doc with config, endpoints, the STIX→MISP type table, failure modes, and the dry-run-as-air-gap-proof workflow.apps/docs/docs/operations/airgap.md— clarifies that the existingMISP_URL/MISP_API_KEYenvs cover both pull and push, with a pointer to the new integration page.
The /v1/threat-intel/* endpoints (IOCs, threat actors, intel feeds) were
previously gated only by get_current_user, meaning any authenticated
role, including viewer and soc_analyst, could POST an IOC, DELETE
a feed, or create a new ThreatActor profile. In a managed-SOC / MSSP
deployment that is a privilege-escalation vector: a compromised analyst
seat can poison detections across the whole tenant by injecting false IOCs
or deleting the feed that hydrates them.
services/api/app/api/v1/endpoints/threat_intel.py— every route now declares the explicit permission it needs viaDepends(require_permission("threat_intel:read" | "threat_intel:write")). Read routes (GET /iocs,/iocs/{id},/actors,/feeds) requirethreat_intel:read; write routes (POST /iocs,DELETE /iocs/{id},POST /actors,POST /feeds,DELETE /feeds/{id}) requirethreat_intel:write. The legacyUser-typed dependency was replaced with the platform-standardAuthUserso JWT and API-key callers are gated by the same code path.services/api/app/core/security.py—ROLE_PERMISSIONSnow grantsthreat_intel:writetotenant_adminandsoc_leadin addition to the existingadmin/platform_admin/threat_hunterset. Without this the endpoint hardening would have locked out the two roles that legitimately need to manage tenant intel during an investigation.services/api/tests/test_threat_intel_rbac.py— 38 new regression tests pin the role/permission map (write-roles must hold:write, read-only roles must not), assert thatCurrentUser.require_permissionraises HTTP 403 for under-privileged roles and 200 for privileged ones, cover the API-key code path including scope wildcards, and grep the endpoint module to ensure every route still usesrequire_permission(...)(so a refactor that silently downgrades a route fails CI).
Tracked as F013 in docs/community-feedback/2026-05-12/.
scripts/validate_detections.py already replays each native rule against
its own positive + negative fixture (TP / TN gates), but that test cannot
catch the failure mode operators feel hardest in production: rule R
firing on an event that was meant for rule O. A single overly-broad
rule that matches every ConsoleLogin or every rundll32.exe execution
silently drives alert volume up and precision down across the whole pack
without tripping the per-rule TP/TN replay.
services/agents/tests/test_detection_fp_rate.py— new pytest suite that replays every native rule'smatch_whenagainst every other rule's positive fixture and grades the per-rule cross-fire FPR. Fails CI if any rule exceedsMAX_PER_RULE_FPR(default 5%) or regresses on its own positive/negative fixture. Failure output groups the worst 10 offenders with their cross-fire targets so the operator can narrow the rule (or allowlist a deliberate broad-vs-narrow overlap viaEXPECTED_CROSS_FIRES) without re-running a full eval sweep. Current corpus: 816 native rules evaluated, mean FPR 0.0, worst FPR 0.49% — well under the 5% ceiling.scripts/run_evals.py— wires the new gate into the unified eval runner assuites.detection_fp_rate, reportingworst_per_rule_fp_rate(lower-is-better) alongside the existing alert-reduction / investigation-completeness / response-quality gates so dashboards and CI consume it through the same JSON shape.
Tracked as F005 in docs/community-feedback/2026-05-12/.
Documentation-only refresh that aligns every install / architecture page with the actual shipped state of the repo. No service code, schema, or API surface changed.
- One-click install pipeline is now a first-class doc surface.
- New Docusaurus page
apps/docs/docs/installation.md(sidebar position 2) walks throughinstall.sh/install.ps1end-to-end — supported package managers, what gets installed, idempotency, theuninstall.sh/uninstall.ps1graduated cleanup flags, and the security model. apps/docs/docs/quickstart.mdadds it as Path 0 ("zero-prerequisite bootstrap") and renumbers the demo / dev paths.apps/docs/docs/deployment/docker.mdopens with a callout to the installer, refreshes every host/container port mapping againstdocker-compose.yml, splits profile-gated services (connectors,osquery-tls,slack-bot) out of the default stack, and updates the GHCR image list to the full 16-image set.apps/docs/docs/intro.mdadds the installer to Get started and corrects the connector-count copy.- Root
README.mdalready had Path 0 — verified and synced with the architecture refresh below.
- New Docusaurus page
- v2.2 architecture surfaces are now reflected everywhere.
apps/docs/docs/architecture.mddata-flow diagram, monorepo layout, and Service Responsibilities table now includeservices/osquery-tls,services/osquery-extensions, andservices/slack-bot. Connector count corrected to 50 (was 26 / 42 in stale paragraphs).docs/architecture/SYSTEM_DESIGN.mdconnector count corrected to 50, Service Responsibilities table extended with the v2.2 services, and a new §13 — v2.2 Additions appended that documents endpoint telemetry (osquery TLS server + extensions), ChatOps (slack-bot), Responder PWA, MCP server, Investigation Ledger / Ambient Copilot, and the one-click install pipeline. v2 / v2.1 narrative preserved.- Root
README.mdmermaid diagram + service-map table extended withosquery-tls,slack-bot,mcpand the correctedRealtime/Web Consoledescriptions.
- Connector count corrected to 50 across the repo.
apps/docs/docs/connectors/index.md: catalog count updated and the 23 missing connectors added across the existing categories (cloud / CNAPP / vuln-mgmt, SIEM, EDR/XDR, SaaS, ITSM, network, endpoint fleet, container orchestration).apps/docs/docs/connectors/api-coverage.md: coverage-table heading updated.apps/web/src/components/onboarding/StartHero.tsx: in-product copy on the onboarding tile updated.apps/docs/docs/intro.md: two stale paragraphs updated.- Source of truth:
services/connectors/app/connectors/__init__.py(_CONNECTOR_CLASSES).
Old historical entries in AI_STACK_PLAN_PROGRESS.md reference 42
connectors and are intentionally left as a snapshot of the v2.1 increment
they describe.
Track 1 + Track 2 of the docker-compose hardening work that began in
7.1.1. 7.1.1 fixed the boot-path bugs that surfaced on
a clean clone; this release attacks the time dimension. The previous
behaviour — docker compose up -d on a fresh checkout building all 15
services from source — took 10–20 minutes on a typical laptop and was the
single largest source of "I tried AiSOC and gave up" reports. With this
release, the same command pulls 12 prebuilt images from GHCR and is
healthy in roughly 90 seconds.
No service code, no API surface, no database schema changed. Every change in this release is in the boot path, the image-publish path, or the CI gate that proves both still work.
docker-compose.yml: Every service that previously had abuild:directive now also has animage:andpull_policy: missing. Compose will pull the prebuilt image fromghcr.io/aisoc-platform/aisoc-<svc>if it exists locally or in the registry; only if the pull fails does it fall back to building from source. The 12 backend services that publish images (api, agents, realtime, web, ingest, enrichment, fusion, actions, connectors, threatintel, ueba, slack-bot) are tagged via the${AISOC_VERSION:-latest}interpolation so the same compose file works forlatest,main, a release tag (v7.2.0), or a local override. The three deferred services (osquery-tls, honeytokens, purple-team) are marked with a# TODO(publish)comment and continue to build locally..env.example: Added a new top-of-fileAISOC_VERSION=latestblock that documents how to pin the entire backend to a release tag for reproducible deploys (AISOC_VERSION=v7.2.0), or track the bleeding edge (AISOC_VERSION=main)..github/workflows/publish-images.yml: Extended the build matrix from 4 services to 12 by adding ingest, enrichment, fusion, actions, connectors, threatintel, ueba, and slack-bot. These are the backend services that every full-stackdocker compose up -dboots; without them in the publish matrix,pull_policy: missingwould resolve to "build from source" for two-thirds of the stack and the change would be cosmetic..github/workflows/release.yml: Mirrored the same 12-service matrix on tagged-release builds so thatAISOC_VERSION=v7.2.0resolves to a real published image for every service in the compose file, not just the demo subset.
The pull-by-default path only matters if the underlying images actually build. Track 2 attacks the two largest historical sources of build-path flakes — Poetry resolution failures during image build, and Dockerfile regressions that nobody catches until release day.
- All seven Python service Dockerfiles
(
services/{api,fusion,threatintel,slack-bot,actions,connectors,osquery-tls}/Dockerfile): Added apoetry install→pip installfallback. The previous pattern failed the build on any transient PyPI hiccup, lock-file drift, or proxy timeout duringpoetry install. The new pattern wraps the install inset -eux; if poetry install ...; then ...; else pip install <pinned list>; fi, logs which path was taken, and pins every runtime dependency explicitly in the fallback list. The pinned list is documented as needing to trackpyproject.tomland is exercised by the new nightly cold-cache CI run. .github/workflows/compose-smoke.yml(new): On every PR that touchesdocker-compose.yml,docker-compose.demo.yml, any service Dockerfile,.env.example, or the workflow itself, GitHub Actions now boots the full stack from a clean checkout and assertsaisoc-postgresis healthy,apireturns 200 on/health, andwebreturns 200 on/— all within a 10-minute budget. Pull-by-default by design (so the CI run mirrors what the user sees), with automatic detection of Dockerfile changes that flips the workflow into rebuild-from-source mode so we don't smoke-test against a stale published image. Capturesdocker compose ps,docker compose logs, disk, and memory on failure..github/workflows/compose-smoke-nightly.yml(new): At 09:00 UTC every day, GitHub Actions does a full cold-cache rebuild of every service (docker compose build --no-cache --pull) and re-runs the same smoke gates with a wider 20-minute budget. This is the gate that catches the regressions PR smoke physically cannot — upstreampython:3.11-slimbreakage, transitive dependency drift,pyproject.toml↔ pip-fallback drift in the seven Python services. Failures upload a forensics artifact and open aci-labelled tracking issue automatically so a nightly break is visible by standup.
apps/web/package.json: Bumped to7.2.0.
None for users on 7.1.1. The compose file is backwards-compatible —
pull_policy: missing only changes behaviour the first time you boot
(it tries the registry before building); existing local images are
honoured. If you want the new fast path explicitly, run docker compose pull once after upgrading. To pin a deploy to this release rather than
tracking latest, set AISOC_VERSION=v7.2.0 in .env.
If you skipped 7.1.1, also read its migration note
about the osquery-tls host-port change (8007 → 8091).
Hotfix in response to user-reported docker compose up -d failures on a clean
clone. None of these are functional changes to the running services — every
fix is in the boot path, the boot documentation, or the pre-flight check.
-
docker-compose.yml: Removed the obsoleteversion: '3.8'declaration, which Docker Compose v2 ignores and warns about on every invocation (level=warning msg="...the attribute version is obsolete..."). The warning is harmless but is the very first line of output a new user sees, which signals "this project is broken" before the build even starts. -
docker-compose.yml: Addedmem_limit+mem_reservationto the four data-tier containers most likely to OOM-kill on an under-provisioned Docker Desktop:kafka: 1.5 GB limit / 1 GB reservationclickhouse: 1 GB limit / 768 MB reservationopensearch: 1 GB limit / 768 MB reservationneo4j: 1 GB limit / 768 MB reservation
Without these caps, a 4 GB Docker Desktop allocation (the default on macOS) would silently OOM-kill OpenSearch or Neo4j during JVM warmup, leaving the rest of the stack running but the alert/case feeds permanently empty.
-
docker-compose.yml(osquery-tlsservice): FixedAISOC_INGEST_BASE_URLpointing at the non-existentingest:8080(the actual service is namedingest-worker). Also remapped the host port from8007to8091to resolve a host-port collision with theuebaservice. Both bugs only surfaced if the user actually queried the osquery TLS server, which is why they survived the previous release; runningdocker compose up -dwould succeed butosquery-tlswould log connection-refused errors on every agent check-in.
README.md— Quick start: Restructured sopnpm aisoc:demois the canonical first-touch path (4 prebuilt images, ~90s to a working SOC console) anddocker compose up -dis explicitly labelled the "developer-build path" (22 services, 10–20 min cold build, requires Docker with at least 6 GB RAM allocated). The previous structure presented both paths as equally valid, which led users with stock Docker Desktop settings straight into a stack that physically cannot fit in the daemon's memory.README.md— Service map: Updatedosquery-tlsfrom:8090to:8091and added aKafka UIrow at:8090, matching the compose hygiene fix above.README.md— Boot section: Added explicit timing expectations ("~5 GB of base image pulls + 10–20 min of build on a typical laptop"), a recommendation to runpnpm aisoc:doctorbefore kicking off the build, and a troubleshooting note pointing under-provisioned Docker Desktop installs at Settings → Resources.
The pre-flight check that the user is now told to run before
docker compose up -d was previously useless to first-time users — its
container check used docker compose ps (which is project-scoped and
therefore couldn't see containers launched by a sibling compose file), and
it had no opinion on whether Docker itself was provisioned to actually run
the stack. This release fixes both:
- Docker Compose plugin enforcement: New check that fails with an
actionable error if the user only has Compose v1 (
docker-composePython binary) on PATH, which is now end-of-life and lacks healthcheck semantics the stack depends on. - Docker daemon RAM check: Reads
docker info --format jsonand asserts at least 6 GB allocated for the full stack (4 GB for the demo stack). Anything less hard-fails with a pointer to Docker Desktop → Settings → Resources. This single check would have prevented every variant of "the build succeeds butdocker compose psshows half my containers in a restart loop" reported to date. - Cross-compose-project container discovery: Replaced
docker compose pswithdocker ps -a --format json --filter name=aisoc-. The doctor now detects whether the user is on the demo stack (aisoc-demo-*containers) or full stack (aisoc-*containers) and accepts either as a valid boot, so demo users no longer see falseFAILrows for services the demo intentionally omits (kafka-ui, neo4j, etc.). - Exit-code aware container reporting: When a container exists but is
not running, the doctor now emits the exact
Exited (255)status fromdocker psand tells the userrun \docker logs ``. The previous output ("not running") gave the user no signal about whether the container had crashed, never started, or been manually stopped. - Stack flavor summary: A new
stack flavorrow reportsdemo,full, ormixed, plus a running/total container count ((4/8 container(s) running)) so the user can see at a glance whether they're looking at a half-broken stack or a fully-broken stack.
apps/web/package.json: Bumped to7.1.1.
None. This is a docker-compose hygiene release — no service code,
no database schema, no API surface area changed. Pull, re-run
pnpm aisoc:doctor, and re-run docker compose up -d (the
osquery-tls port change means existing deployments need to update any
osquery-agent tls_hostname:tls_port config from localhost:8007 to
localhost:8091, but no one was using that interface yet).
Six new connectors, three documentation backfills, and one new ingest template. Closes the biggest cloud-security gap in the connector catalogue: every Tier-1 cloud workload protection platform (Wiz, Prisma Cloud, Orca, Lacework, AWS Security Hub) now has a first-class integration, AWS gets three native data sources (GuardDuty, CloudTrail, VPC Flow Logs), and Kubernetes audit logs land through a dual-mode connector that works on both managed and air-gapped clusters.
apps/docs/docs/connectors/wiz.md: Documented the Wiz GraphQL connector end-to-end — service-account creation, scope (read:issues,read:vulnerabilities), token rotation, normalised severity mapping, and a worked example of a WizIssuecollapsing tocategory=cloud_alertin the inbox.apps/docs/docs/connectors/aws-security-hub.md: Documented IAM role vs. static-key auth, thesecurityhub:GetFindingspermission model, and theBLOCK_IP/ALLOW_IPcapabilities backed byservices/actions/app/clients/aws_security_groups.py(i.e. how a SOC analyst can quarantine an attacker IP from the Security Hub finding without leaving the case workspace).apps/docs/docs/connectors/lacework.md: Documented the Lacework API token flow,api_urlregional variants, and the alert→event severity map.apps/docs/sidebars.ts: Registered all three new docs pages under theConnectorscategory, plus the four new connector pages from Tracks B–D (prisma-cloud,orca,aws-guardduty,aws-cloudtrail,aws-vpc-flow,kubernetes-audit).
services/connectors/app/connectors/prisma_cloud.py—PrismaCloudConnectorwith full Prisma Cloud (CSPM/CWPP) coverage. JWT auth viaPOST /login, paginatedGET /alert/v1/alertwithtime.from/time.towindowing, severity collapse (critical/high → high,medium → medium,low/informational → low), and acompute_urloverride for self-hosted Compute Edition. Capability:PULL_ALERTS. Manifest:plugins/prisma-cloud/plugin.yaml, docs atapps/docs/docs/connectors/prisma-cloud.md, tests inservices/connectors/tests/test_prisma_cloud.py.services/connectors/app/connectors/orca.py—OrcaConnectorhittinghttps://api.orcasecurity.io/api/alertswith anapi_tokenfield, severity collapse (critical/high/hazardous → high,medium → medium,informational/low → low). Manifest, docs, and tests follow the same pattern. Capability:PULL_ALERTS.
services/connectors/app/connectors/aws_guardduty.py—AWSGuardDutyConnectormirroringAWSSecurityHubConnector's shape: boto3-based, supports IAM-role or static-key auth, callsguardduty.list_findings+get_findingsper detector. Normalises GuardDuty's continuous numeric severity scale (0.1–10.0) into AiSOC's four-tierinfo|low|medium|highladder (>= 7.0 → high,>= 4.0 → medium,>= 1.0 → low, elseinfo). Capability:PULL_ALERTS.services/connectors/app/connectors/aws_cloudtrail.py—AWSCloudTrailConnectorusingcloudtrail.lookup_events. Ships with a curated default allow-list of 21 high-signal event names covering identity abuse (ConsoleLogin,AssumeRoleWithSAML,GetSessionToken,GetFederationToken,CreateAccessKey,CreateLoginProfile,CreateUser), persistence (AttachUserPolicy,PutUserPolicy,CreateRole,AttachRolePolicy), data-plane abuse (PutBucketPolicy,PutBucketAcl,DeleteBucketPolicy,PutObjectAcl), network exposure (AuthorizeSecurityGroupIngress,RevokeSecurityGroupIngress,ModifyDBInstance), and trail tampering (DeleteTrail,StopLogging,UpdateTrail). Allow-list is overridable via theevent_namesconfig field. Pagination handled viaNextTokenwith a hard cap to keep poll latency bounded. Capability:PULL_LOGS.services/connectors/app/connectors/aws_vpc_flow.py—AWSVPCFlowLogsConnectorusingcloudwatch_logs.filter_log_events. Parses both v2 (default 14-field) and v5 (header-defined) flow-log formats. Defaultfilter_patternis?REJECTto surface dropped traffic only — keeps volume manageable while flagging external-facing security groups that are getting scanned. Public-IP heuristic (_is_public_ip) is RFC-5735-aware, treating RFC1918/loopback/link-local/multicast/CGNAT/TEST-NET as private. Severity heuristic: public-IP REJECTs →medium, internal REJECTs →low, ACCEPT-only flows →info. Capability:PULL_LOGS.
services/connectors/app/connectors/kubernetes_audit.py—KubernetesAuditConnectorshipping with two delivery modes selected via themodeconfig field:webhook(recommended) — Kubernetes API server pushes audit events to AiSOC's new dedicatedPOST /v1/ingest/k8s-audit/{tenant_id}route, authenticated with a shared secret in theX-AiSOC-K8s-Tokenheader (compared in constant time so partial-prefix matches still fail). The legacy/v1/inbox/{token}path with thek8s-audittemplate is kept around as a fallback for control planes that cannot inject custom headers into the audit-webhook kubeconfig.file_tail— AiSOC's connector pod tails a localaudit.logfile using a byte-position cursor (atomically written to a.aisoc-cursorsidecar), with rotation/truncation detection and a hard per-poll byte cap so a backlog can't blow up a single poll cycle.
services/ingest/internal/handler/k8s_audit.go— New Go handler for the dedicated webhook route. Caps body size viaK8S_AUDIT_MAX_BODY_BYTES(default 16 MiB), rejects oversized batches with413so the apiserver shrinks--audit-webhook-batch-max-sizeand retries, and publishes eachEventList.items[]entry through the existing normalizer + Kafka publisher usingconnector_type: kubernetes_audit. The route is disabled (returns503) until an operator setsK8S_AUDIT_SHARED_SECRET, so a fresh install never accidentally accepts unauthenticated audit traffic.services/ingest/internal/normalizer/normalizer.go— Added thekubernetes_auditconnector profile. MapsauditIDtoexternal_id,verbtoactivity_name,user.usernametoactor.user.name,objectRef.{namespace,resource,name}to a compositetarget.resource.name, and translates the connector's string severity (critical|high|medium|low| info) into OCSF integer severities (5/4/3/2/1).services/ingest/internal/normalizer/templates/k8s-audit.yaml— New inbox template (legacy path) that maps Kubernetes apiserverEventpayloads (apiVersion: audit.k8s.io/v1) onto AiSOC's normalised event shape:external_id ← auditIDvendor ← "Kubernetes",product ← "apiserver-audit",category ← "k8s_audit"actor ← user.username(plususer.groupscarried through metadata)target ← objectRef.namespace + "/" + objectRef.resource + "/" + objectRef.nameseverityis derived in the connector's_classify_severityheuristic, not in the template, so the same logic applies to both delivery modes.
- Severity heuristic (
_classify_severityinkubernetes_audit.py):high—exec/attach/portforwardon a Pod,createonClusterRoleBinding,impersonateverb,updateonserviceaccounts/token, anyRequestResponseevent whereresponseStatus.code >= 500on a sensitive verb.medium—create/patch/deleteonSecret/ConfigMap/ClusterRole/Role,escalateverb, failed authentication (responseStatus.code == 401|403) on a write verb.low— successful reads on sensitive resources (getonSecret), successful writes on routine resources.info— everything else (health probes, list/watch on benign resources, successful low-impact reads).
plugins/kubernetes-audit/plugin.yaml— Manifest with a 4-field config schema (mode,cluster_name,inbox_token,audit_log_path,cursor_path),category: cloud, capabilitiespull_audit+pull_alerts.apps/docs/docs/connectors/kubernetes-audit.md— Includes a complete sampleAuditPolicy(omitStages on RequestReceived for verbosity control; Metadata level for routine reads, RequestResponse for writes on Secret / ConfigMap / ClusterRoleBinding) and a sampleAuditSinkpointing at AiSOC's inbox URL.
marketplace/index.json+apps/web/public/marketplace/index.json— Rebuilt viapnpm marketplace:sync. Plugin count rose from 43 → 49 (+6 cloud connectors). Total marketplace entries:total=7104 detections=6993 playbooks=62 plugins=49 mitre_techniques=493.apps/web/package.json— Version bumped from7.0.3to7.1.0; the sidebar and landing-page footer both surface the new version automatically.
- 43 unit tests for
KubernetesAuditConnectorcovering both delivery modes, cursor persistence, rotation/truncation, byte-cap drain semantics, and the full severity-heuristic decision table. - 27 unit tests for
AWSVPCFlowLogsConnectorcovering v2/v5 parsing, public-IP classification edge cases (RFC1918, CGNAT, TEST-NET-1/2/3), and the default REJECT filter pattern. - Mirroring tests for
PrismaCloudConnector,OrcaConnector,AWSGuardDutyConnector,AWSCloudTrailConnectorcovering schema, normalise, pagination, and auth-error paths. - Full
services/connectorssuite passes at 364 tests; schema-introspection tests inservices/apialso pass with the six new connectors added to_CONNECTOR_CLASSES.
src/components/layout/AppShell.tsx: Wrapped<DemoBanner />in a new<ClientOnly>boundary so the banner (which readsNEXT_PUBLIC_DEMO_MODE) is never server-rendered. This eliminates React hydration error #418 caused by stale env-var inlining producing a structural tree mismatch (server saw<button>from Sidebar, client expected<div>from DemoBanner).src/app/layout.tsx: Addedpreload: falseto theJetBrains_Mononext/font/googleconfig. The monospace font is only used in code blocks and is not needed on the initial paint of most pages, causing Chrome to log "preloaded but not used within a few seconds" warnings. Lazy-loading the font eliminates these warnings without any visible FOUT.
apps/web/package.json: Bumpedversionto7.0.2; sidebar now showsv7.0.2dynamically.apps/web/src/components/landing/Footer.tsx: Replaced hard-codedv6.1.0string with a dynamic import ofpackage.jsonso the landing page footer always reflects the current package version.README.md: Updated version badge to7.0.1; addedosquery-tls(port 8090) andosquery-extensionsentries to the services table, the Swagger-UI URL table, and the directory tree; added osquery TLS server URL to the dev surface table.
- Python: Resolved
py/unused-global-variableincredential_vault.py,pack_loader.py,executive_digest.py,case_summary.py,cost_dashboard.py, andactions/executors/base.pyby refactoring mutable state into dictionaries and exposing identifiers via__all__. - Python: Resolved
py/cyclic-importbetweenosquery-tlsmodules by extractinggenerate_node_keyinto a newapp/core/crypto.pymodule. - Python: Resolved
py/empty-exceptinapi/main.pyandapi/services/github.pyby replacing barepassblocks withlogger.debugcalls. - Python: Resolved
py/log-injectioningithub.py,detection_proposals.py, andllm_credentials.pyby switching log format specifiers to%r. - Python: Resolved
py/clear-text-logging-sensitive-datainworkers/oauth_refresh.pyby redactingtenant_idand sanitising reason strings. - Python: Resolved
py/incomplete-url-substring-sanitizationinllm_resolver.pyby usingurllib.parse.urlparsefor hostname extraction. - Python: Resolved
py/stack-trace-exposureinagents/api/explain.pyby returning a generic error string from the exception handler. - Python: Resolved
py/call/wrong-argumentsinagents/tests/smoke_explain.pyby importing and passing aLlmConfiginstance to_stream_explanation. - Python: Resolved
py/unused-importinosquery-tls/db/env.py; fixedE402(import ordering) in the same file. - JavaScript: Resolved
js/unused-local-variableinAlertsView.tsx(removed unusedtoastimport) andSettingsView.byok.test.tsx(removed unusedwithinimport).
next.config.js: Removed deprecatedeslint.ignoreDuringBuildskey that Next.js 16 no longer accepts in the config file; addedturbopack.rootso Turbopack resolves workspace packages correctly.src/app/layout.tsx: AddedsuppressHydrationWarningto the<html>element so that the render-blockingthemeBootstrapScriptcan freely writedata-theme,data-theme-preference, andstyle.colorSchemeon the client without React reporting a hydration mismatch on every page load.
⚠️ Reconciliation notice (2026-05-12): The work described in this section was developed on branchfeat/pr6-osquery-extensions(commitse0d70fa1→3ab5aa81) but the branch was not merged intomainbefore this changelog entry was written. The files referenced below — includingservices/osquery-tls/,services/connectors/app/connectors/aisoc_direct.py,services/agents/app/playbook/steps/osquery_live_query.py, and the osquery-extensions Go module — exist on that branch and can be reviewed there, but are not present onmainas of v7.1.0 planning. Treat this section as a record of in-flight work pending PR merge, not as shipped functionality. The community-feedback-driven roadmap (docs/community-feedback/2026-05-12/) builds the genericlive_actioninterface (Issue #8) onmaindirectly rather than assuming this section's primitives are in place.
Added — osctrl, FleetDM, aisoc-osquery-tls, aisoc-direct, native osquery detections, live-query playbook step, FIM, custom virtual tables
Six-PR wave that closes #44 ("osctrl connector for fleet-wide osquery telemetry") and significantly extends osquery coverage end to end. Shipped in the v7.0 release window between the v7.0.0 baseline and the v7.0.1 hardening patch.
services/connectors/app/connectors/osctrl.py,fleetdm.py— Two newBaseConnectorsubclasses with fullschema(),validate(),fetch_events(), andnormalize()implementations. Schema-driven setup runs a liveTest connectionround-trip before save; secrets encrypted with the application-layerCredentialVault(Fernet AES-128-CBC + HMAC-SHA256); polling on per-instance schedule viaConnectorScheduler.plugins/osctrl/plugin.yaml,plugins/fleetdm/plugin.yaml— Marketplace manifests mirroring the connector schemas.marketplace/index.jsonregenerated viapnpm marketplace:sync.services/connectors/tests/test_osquery_connectors.py— Schema contract + severity heuristics tests.
detections/endpoint/osquery-*.yaml— 16 osquery detection rules migrated from_quarantine/to the native schema, IDsdet-endpoint-281throughdet-endpoint-296. Coverage spans credential access, persistence, lateral movement, defense evasion, and discovery on macOS, Linux (auditd), and Windows.detections/fixtures/osquery_*.json— Positive / negative test fixtures for every migrated rule, gated by the Detection Validation workflow in CI.
-
services/actions/app/clients/osctrl_client.py,fleetdm_client.py,aisoc_direct_client.py— Production-grade HTTP clients with per-vendor auth, retries, and structured error handling. -
services/actions/app/clients/osquery_allowlist.py— Strict allowlist enforcing only safe SELECT-only queries against approved tables (noATTACH, noINSERT, nopragma_*introspection of secrets). -
services/agents/app/playbook/engine.py::_handle_osquery_live_query— Newosquery_live_querystep type, registered inservices/agents/app/playbook/models.pyasStepType.OSQUERY_LIVE_QUERYand dispatched from theSTEP_HANDLERStable at the bottom ofengine.py. Pushes allowlisted distributed queries to a single host or fleet-wide via osctrl / FleetDM / aisoc-direct with HMAC-signed ChatOps approval before execution. Tests live inservices/agents/tests/test_osquery_live_query_step.py.v7.0.x reconciliation: Earlier drafts of this CHANGELOG referenced a separate module at
services/agents/app/playbook/steps/osquery_live_query.py. That module never landed onmain— the handler is inlined inengine.pyto keep the playbook engine's dispatch table in one place. The behaviour, tests, and CLI surface are identical to the originally documented design.
-
services/osquery-tls/— New first-party FastAPI service exposing/api/v1/enroll,/api/v1/config,/api/v1/log,/api/v1/distributed/read,/api/v1/distributed/write, plus/api/v1/fimfor file-integrity events. Self-hosted osquery TLS plugin endpoints are FleetDM-compatible so any off-the-shelf osquery agent can enroll without a third-party SaaS hop. Uses dedicated SQLite + Alembic migrations underservices/osquery-tls/db/. -
services/osquery-tls/app/api/v1/endpoints/log.py+ matchingplugins/aisoc-direct/plugin.yamlandservices/actions/app/clients/aisoc_direct_client.py— Direct-from-agent ingest path that consumes the osquery-tls log stream and normalises into the standard alert schema; bypasses third-party SaaS entirely. Theaisoc-directconnector is implemented as a virtual connector: agents push events directly into/api/v1/logon the osquery-tls service, which fans them out to the same ingest pipeline the polled connectors use. The marketplace manifest lives atplugins/aisoc-direct/plugin.yaml; the outbound client (used by playbooks to drive distributed queries) lives atservices/actions/app/clients/aisoc_direct_client.py.v7.0.x reconciliation: Earlier drafts of this CHANGELOG referenced a polled connector module at
services/connectors/app/connectors/aisoc_direct.py. That module never landed onmain. The connector is implemented as a push-based virtual connector (theosquery-tlsservice is itself the ingest endpoint), so there is nothing to register inservices/connectors/app/connectors/__init__.py. Functionally the data path is identical to the originally documented design.
services/osquery-tls/app/osquery_packs/— Bundled IR / OSquery-ATT&CK / FIM packs distributed to every enrolled agent on enrollment. Pack loader preserves hand-crafted playbooks underpack root(do notrmtree).services/osquery-tls/app/api/v1/endpoints/fim.py— File-integrity monitoring endpoint. Ingestsfile_eventsand synthesises alerts on writes to/etc/passwd,/etc/shadow, sshd configs, sudoers, and Windows registry hives. FIM-specific detection IDsdet-endpoint-297..300(renumbered from 281–284 to avoid collision with osquery-macos rules).apps/web/src/components/dashboard/FimDashboard.tsx— New dashboard panel grouping FIM events by host, file, and severity.
services/osquery-extensions/tables/— 5 custom Go-based virtual tables shipping with the agent for richer endpoint visibility plus a bidirectional response channel:aisoc_browser_extensions— installed browser extensions across Chrome, Firefox, Edge, Safari profiles.aisoc_kernel_modules— currently loaded kernel modules with signing / tainting state.aisoc_attck_persistence— MITRE ATT&CK persistence locations (LaunchAgents, scheduled tasks, systemd units, Run keys).aisoc_pending_actions— pending response actions queued for the agent; enables host → server → host bidirectional flow.aisoc_alert_cache— local cache of alerts the agent has emitted, for deduplication and replay.
services/osquery-extensions/tables/pending_actions_test.go— Unit tests for the bidirectional action queue.docs/openapi.yamlregenerated to include the extensions API endpoints.
- CI: Detection Validation workflow now covers the 16 migrated osquery rules; Python Tests, Web Build, and the osquery-tls service build are all green.
- Lint:
ruff formatandruff check --fixapplied across the newosquery-tlsservice; F401 / UP017 / UP037 / I001 / W291 cleared. - Marketplace:
apps/web/public/marketplace/curated.jsonre-synced frommarketplace/after the new connector / plugin manifests landed.
This release ships the complete v1.0 buyer-value plan across 16 workstreams. All items were designed, implemented, tested, and reviewed by Beenu Arora beenu@cyble.com.
services/slack-bot/— New standalone FastAPI service usingslack-boltasync adapter. Ships/aisoc triage <case_id>,/aisoc approve <action_id>,/aisoc status <case_id>, and/aisoc summary <case_id>slash commands. Interactive approval buttons route back through the API approval endpoint so human-in-the-loop gates work from Slack without opening the console.- 61 pytest cases cover the slash-command handlers, interactive payloads, API client calls, and error paths (bad token, non-200 API response, missing case).
services/api/app/services/digest_pdf.py— Generates a branded A4 PDF forExecutiveDigestobjects using ReportLab. Includes cover page, KPI tiles, alert-volume chart, top-rule table, top-actor table, and remediation summary.services/api/app/workers/weekly_digest_task.py— APScheduler task that runs every Monday at 06:00 UTC, builds a digest for every active tenant, and delivers it viaPOST /api/v1/reports/digest/emailor writes it to blob storage. Controlled byDIGEST_SCHEDULE_ENABLEDenv flag.services/api/app/services/digest_html.py— HTML mirror of the PDF for in-browser preview.services/api/tests/test_digest_pdf.py— 12 pytest cases covering PDF generation, chart rendering, and weekly scheduler triggering.
apps/web/src/components/playbooks/PlaybooksGallery.tsx— Tabbed gallery with 12 curated packs (Phishing, Ransomware, BEC, IAM Key Compromise, …). Each card shows TTP coverage badges, author, version, and a one-click Import button that callsPOST /api/v1/playbooks/import.services/api/migrations/039_detection_proposal_github_pr.sql— Addsgithub_pr_url TEXTandgithub_pr_number INTtodetection_proposals.services/api/app/services/github.py—GitHubServicecreates draft PRs against the tenant's detection repo when a detection proposal is promoted. Supports GHES and github.com viaGITHUB_API_URLenv var.- 25 playbook YAML templates added under
detections/playbooks/and 12 pre-built playbook packs underplaybooks/packs/v1/.
apps/web/src/components/settings/SettingsView.tsx— New "AI / LLM" settings panel: provider picker (OpenAI, Azure OpenAI, Anthropic, Ollama), API-key input, model selector, temperature slider, and connection test button.apps/web/src/components/settings/SettingsView.byok.test.tsx— 12 Vitest tests covering form rendering, provider switching, key masking, connection test success/error paths, and save confirmation.
apps/web/src/components/copilot/InvestigationTimeline.tsx— 684-line React component that renders the investigation ledger as a playable timeline. Each step shows the agent name, tool call, rationale, duration, and status badge. A scrubber lets analysts replay from any step.
services/api/app/services/case_summary.py— LLM-powered case summariser (structured output via function-calling). ProducesCaseSummaryResultwithheadline,severity_rationale,recommended_action, andevidence_links.services/api/app/services/case_summary_html.py— HTML renderer for the summary, used by the PDF exporter and the in-browser case card.
apps/web/src/components/theme/ThemeProvider.tsx— Theme preference (light|dark|system) stored inlocalStorageand synced toPATCH /api/v1/users/me/preferences. Survives logout and device switch.
apps/web/src/test/a11y.test.tsx— 55-line axe-core test suite. RendersAlertsView,CasesView,PlaybooksView,DashboardView, and 3 modal components; fails the build if any WCAG 2.1 AA violation is found.- Sidebar landmark roles, ARIA labels, focus trapping in modals, skip-navigation link, and colour-contrast fixes applied across the entire component tree.
apps/web/src/components/dashboard/DashboardView.tsx— Dashboard is now fully composable: widgets can be dragged, dropped, resized, pinned, and removed. Layout serialised toPOST /api/v1/saved-views.services/api/app/api/v1/endpoints/saved_views.py— CRUD for per-user saved views (dashboard layout, column configs, active filters).
services/threatintel/app/actors/attribution.py— NewThreatActorAttributionEnginescores observed IOCs, MITRE ATT&CK techniques, tools, and target sectors against an in-memory catalog of three seed actor profiles (APT28, APT29, Lazarus). Scoring is the weighted sum of TTP (0.4) / Tool (0.3) / Target (0.2) / IOC (0.1) components, multiplied by the actor profile's baseline confidence, then thresholded.services/threatintel/app/api/actor_attribution.py— New router mounted at/api/v1/actorswithPOST /attribute,GET /profiles, andGET /profiles/{actor_id}. Constructs the engine once via FastAPI lifespan and passes it throughDepends(get_attribution_engine).services/agents/app/agents/investigation_agent.py— Investigation agent now callsPOST /actors/attributeand surfaces attribution results in the investigation ledger.docker-compose.airgap.yml— Compose override for fully disconnected deployments: disables all external feed pullers, enables Ollama sidecar, and setsAIRGAP_MODE=trueso the API switches to local-only LLM routing.apps/docs/docs/operations/air-gapped.md— Step-by-step air-gap deployment guide: image pre-pulling, Ollama model loading, threat-feed pre-seeding, and smoke-test checklist.
services/api/app/api/v1/endpoints/mssp.py— NewGET /mssp/tenantsaggregation endpoint: per-child tenant alert counts, open case counts, SLA breach rate, and last-seen connector heartbeat.services/api/app/models/tenant.py— Addedparent_tenant_idandmssp_rolecolumns supporting the parent-child tenant hierarchy.
services/api/app/api/v1/endpoints/llm_credentials.py— CRUD for per-tenant LLM credential records. Secrets encrypted at rest viaCredentialVault.- LLM routing layer (
services/api/app/core/config.py) reads per-tenant credentials before falling back to the platform-wide key.
apps/web/src/components/analytics/TeamAnalyticsView.tsx— Analyst leaderboard with MTTR per analyst, alert disposition accuracy, cases closed per shift, and false-positive rate trend over the selected window.
services/api/app/api/v1/endpoints/llm_status.py— Reports whether the deployment is running in air-gap mode and which local models are available via the Ollama sidecar. Used by the settings UI to auto-populate the model picker.
- Ruff
E501/W291/W293/B007/B017/F821/I001violations inservices/api. mypyerrors across all 16 plan-modified files:RowMappingimport,Optionallistlen(),current_user.user_idrename,fetchone()None checks,sort_keyreturn type,PYTHONPATHsubprocess handling.- Converted structlog-style
logger.info(key=value)calls to stdlib formatting inrule_engine.py,neo4j.py, anddigest_pdf.py. - SQLAlchemy relationship
name-definedmypy errors suppressed with# type: ignore[name-defined]intenant.pyandconnector.py.
The /api/v1/actors/* endpoints are reachable on the threatintel
service without RBAC enforcement in v0 — they assume cluster-internal
network reachability only. Do not expose them through public
ingress until a Depends(require_permission(...)) guard is added.
Tracked as a known limitation in the docs.
services/threatintel/app/actors/attribution.py— NewThreatActorAttributionEnginescores observed IOCs, MITRE ATT&CK techniques, tools, and target sectors against an in-memory catalog of three seed actor profiles (APT28, APT29, Lazarus). Scoring is the weighted sum of TTP (0.4) / Tool (0.3) / Target (0.2) / IOC (0.1) components, multiplied by the actor profile's baseline confidence, then thresholded.services/threatintel/app/api/actor_attribution.py— New router mounted at/api/v1/actorswithPOST /attribute,GET /profiles, andGET /profiles/{actor_id}. Constructs the engine once via FastAPI lifespan and passes it throughDepends(get_attribution_engine).services/agents/app/agents/investigation_agent.py— Investigation agent now calls the attribution API after triage/enrichment and records the result onstate.threat_intel["attribution"]. Failure is soft and surfaces a[medium]finding rather than aborting the investigation.docs/threat-actor-attribution.md— Full operator-facing docs, including scoring model, API surface, observability, env vars, v0 caveats, and instructions for adding custom profiles.
AISOC_ATTRIBUTION_THRESHOLD— Override the default confidence threshold (0.30). Clamped to[0.0, 1.0]; invalid values fall back to the default and emit a warning.AISOC_THREATINTEL_URL— Base URL the agent uses to reach thethreatintelservice. Default:http://threatintel:8083.AISOC_ATTRIBUTION_TIMEOUT_SECONDS— HTTP timeout the agent uses for attribution calls. Default:10.
- New Prometheus series exported by
threatintel:threatintel_attribution_requests_total{result="matched|unknown|error"}threatintel_attribution_score{actor_id}(histogram)
- Tool matching uses an alphanumeric-only boundary regex
(
(?<![a-zA-Z0-9])tool(?![a-zA-Z0-9])) instead of Python's\b. Python's\btreats_as a word character, which broke common malware-filename patterns likeminiduke_v3.dll. The new boundary treats_,-,., and/as delimiters while still rejecting alphanumeric neighbours (sox-agentdoes not matchx-agentic). - Tool matching now also scans the IOC's
descriptionandtagsfields, not justvalue. - IOC lookups go through a new public method
OpenSearchStore.match_ioc_values()rather than reaching intoos_store._os.search()directly. - The attribution engine accepts a
catalogconstructor argument so tests and downstream services can inject custom profiles without monkey-patching module-level state. - An empty catalog now resolves to
actor_id="unknown"with explicit reasoning ("Actor catalog is empty"), instead of confusingly falling through to the no-match-above-threshold branch.
The /api/v1/actors/* endpoints are reachable on the threatintel
service without RBAC enforcement in v0 — they assume cluster-internal
network reachability only. Do not expose them through public
ingress until a Depends(require_permission(...)) guard is added.
Tracked as a known limitation in the docs.
A review of G2, Gartner Peer Insights, and customer feedback on AI SOC / SIEM / SOAR platforms drove this release. Five new agents, eight new console pages, four new API surfaces, and ten new connectors landed at once. Connector catalog goes from 16 → 26.
auto_triage_agent.py— Master triage agent classifies each incoming alert astrue_positive/false_positive/benignwith a confidence score. Low-confidence noise auto-closes; everything else escalates with rationale.phishing_agent.py— Specialised phishing triage: header analysis, URL reputation, attachment sandboxing summary, sender-domain trust.identity_agent.py— Identity-centric reasoning: impossible travel, privilege escalation, MFA bypass, and session-token anomaly classification.cloud_agent.py— Cloud posture / threat reasoning across AWS, Azure, GCP, and Kubernetes signals.insider_threat_agent.py— Behavioural deviation, peer-group scoring, exfiltration intent classification.- All five are exposed via
POST /api/v1/agents/triage.
/investigate— Conversational, multi-turn copilot anchored on a case; reads its evidence, ledger, and entity graph for grounded follow-up Q&A. Component:copilot/InvestigationChat.tsx./coverage-advisor— Ranks MITRE ATT&CK technique gaps by adversary prevalence and recommends rules to close them. Component:coverage/CoverageAdvisorView.tsx./shifts— Outgoing/incoming analyst handoff dashboard: active cases, in-flight investigations, queued approvals on one screen. Component:shifts/ShiftsView.tsx./easm— External Attack Surface Management: discovers public assets, exposed services, and certificate-expiry risks. Component:easm/EASMView.tsx./mssp— MSSP executive dashboard: KPIs, cross-tenant alert volume, and per-customer SLA posture. Component:mssp/MSSPDashboardView.tsx./noise-tuning— Per-rule false-positive rate, suppression candidates, one-click tuning. Component:noise/NoiseTuningView.tsx./analytics/team— Analyst leaderboard, MTTR per analyst, dispositions accuracy, and shift workload balance. Component:analytics/TeamAnalyticsView.tsx.
shifts.py— Shift-handoff CRUD: list active shifts, post handoff notes, view queued approvals scoped to a shift window.stix_taxii.py— STIX 2.1 / TAXII 2.1 publishing; pushes the tenant's IOCs and threat-actor profiles to upstream / community feeds.compliance.py— Automated compliance evidence collection for SOC 2, ISO 27001, NIST CSF, PCI-DSS, HIPAA, and DORA. One-click evidence pull.deployment.py— Deployment / air-gap toggles; tenants that disallow external feeds can flip air-gap mode here.
EDR / XDR: sentinelone.py, cortex_xdr.py. Cloud security: wiz.py,
snyk.py. Network: zscaler.py. SaaS / email: proofpoint.py,
servicenow.py, jira.py. Identity: 1password.py, duo_security.py.
All ten registered in services/connectors/app/connectors/__init__.py,
all ship a marketplace manifest under plugins/<id>/plugin.yaml, all
collapse vendor severity to the standard four-tier ladder.
- AI-generated incident reports — Every case now has a one-click "Export Report" button that generates a PDF incident report from the Investigation Ledger.
- Air-gap deployment configuration — Per-tenant toggles disable external feeds (threat intel, marketplace sync, push notifications) for fully air-gapped deployments.
- Connector catalog count 16 → 26. Landing page hero stat, layout SEO
metadata, and
apps/docs/docs/connectors/index.mdupdated to reflect. apps/docs/docs/architecture.mdadds a v1.5 section and updates the service-responsibilities table to include the new API surfaces and autonomous agents.apps/docs/docs/intro.mdupdated to mention the new connector count and v1.5 features.- Footer release link now points at
v6.1.0.
-
Log-injection mitigation (
services/api/app/api/v1/endpoints/connectors.py) —connector_typeoriginates from user-supplied query parameters and was previously logged verbatim, leaving an injection path for newlines/control characters into structured log records. A character-allowlist reconstructor (_safe_connector_type) now strips every character outside[a-zA-Z0-9_\-]before the value reaches any log call, breaking CodeQL's taint trace (alertpy/log-injection). -
Remove dead rate-limiter code (
services/realtime/src/index.ts) — The hand-rolledmakeRateLimiterfunction was superseded byexpress-rate-limitin the previous release but not removed, leaving dead code that masked the effective rate-limiting path. The function is now deleted;express-rate-limitis the sole limiter in production (resolves CodeQL alertjs/unused-local-variable).
-
MSSP / parent-tenant console (
services/api/migrations/012_mssp_console.sql,services/api/app/models/mssp.py,services/api/app/api/v1/endpoints/mssp.py) — Parent tenants can onboard child tenants, manage cross-tenant delegations, add per-tenant notes, and view an aggregated metrics rollup in a single pane. -
Asset inventory + vuln-to-alert correlation (
services/api/migrations/013_asset_inventory.sql,services/api/app/models/asset.py,services/api/app/api/v1/endpoints/assets.py) — CRUD for discovered assets with vulnerability findings auto-correlated to alerts. Surfaces asset blast radius and enables asset-context enrichment during triage. -
Insider threat module (
services/api/migrations/014_insider_threat.sql,services/api/app/models/insider_threat.py,services/api/app/api/v1/endpoints/insider_threat.py) — User risk profiles, behavioural indicators, peer-group deviation scoring, and watchlist management. Risk scores update incrementally as new indicators arrive. -
L0–L4 auto-remediation maturity tiers (
services/api/migrations/015_remediation_maturity.sql,services/api/app/models/remediation.py,services/api/app/api/v1/endpoints/remediation.py,services/actions/app/services/maturity.py) — Per-tenant configuration of remediation autonomy from L0 (manual only) through L4 (fully autonomous). Gate log records every approve/block decision. Per-action whitelist pre-approves low-risk actions regardless of tier.
-
Internal threat intelligence (
services/api/migrations/016_threat_intel.sql,services/api/app/models/threat_intel.py,services/api/app/api/v1/endpoints/threat_intel.py) — IOC harvesting from alert history, threat actor and campaign profiles, and STIX/TAXII feed subscription management, all queryable via the REST API. -
Cloud security posture management (CSPM/KSPM) (
services/api/migrations/017_cspm.sql,services/api/app/models/posture.py,services/api/app/api/v1/endpoints/posture.py) — Ingests posture findings from cloud providers, tracks drift between scan runs, and surfaces a per-provider posture summary with suppress/resolve workflows. -
Identity-centric correlation graph (
services/api/migrations/018_identity_graph.sql,services/api/app/models/identity_graph.py,services/api/app/api/v1/endpoints/identity_graph.py) — Graph of users, devices, service accounts, and roles with typed relationship edges. Alerts link to identity nodes, enabling blast-radius queries and attack-path reconstruction. -
Auto-generated board reports (
services/api/migrations/019_board_reports.sql,services/api/app/models/report.py,services/api/app/api/v1/endpoints/reports.py) — Report templates and scheduled generation of PDF/HTML executive summaries. Artefacts are stored, versioned, and deliverable via email or webhook.
-
Dashboard metrics API (
services/api/app/api/v1/endpoints/metrics.py) —/api/v1/metrics/dashboardaggregates alert KPIs, case counts, connector source stats, top MITRE tactics, 24-hour alert trend, and threats-by-source for the frontend dashboard tiles./api/v1/metrics/alerts/trendsupports1h / 24h / 7d / 30dperiod buckets. -
Tailscale connector (
services/connectors/app/connectors/tailscale.py) — Pulls audit logs and policy-file change events from the Tailscale API with OAuth client-credential and API-key auth, cursor-based pagination, and four-tier severity mapping. -
AWS GuardDuty credential-exfiltration detection (
detections/cloud/aws-guardduty-instance-credential-exfiltration.yaml) — Sigma rule covering EC2 instance credential exfiltration viaUnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.
This pass turns connectors from a hardcoded, code-edit-only feature into a runtime, schema-driven, click-and-connect surface — and lights up nine new cloud / SaaS / VCS sources (Microsoft Entra, Azure Activity, Defender XDR, GCP Cloud Audit, GCP SCC, Microsoft 365 audit, Google Workspace, Cloudflare, GitHub) on top of the original CrowdStrike / Splunk / AWS Security Hub / Okta / Microsoft Sentinel set.
CredentialVault(services/api/app/security/credential_vault.py,services/connectors/app/security/credential_vault.py) — Fernet (AES-128-CBC + HMAC-SHA256) wrapper forauth_configJSON, keyed off the newAISOC_CREDENTIAL_KEYenv var. SupportsMultiFernetrotation viaAISOC_CREDENTIAL_KEY_ROTATION_FROM. Theservices/connectorsread-path mirror decrypts only; writes always go through the API service. Documented in docs/operations/credentials.- Self-describing connector schemas (
services/connectors/app/connectors/base.py) —BaseConnectorgained aField/OAuthHints/ConnectorSchematrio and an abstractschema()classmethod. Each connector class is now the source of truth for its ownname,connector_category, fields (text / secret / select / textarea / oauth), default poll interval, and hosted-OAuth roadmap hints. The hardcoded dict inservices/connectors/app/api/router.pyis gone — schema responses come from the registry built inservices/connectors/app/connectors/__init__.py. /api/v1/connectorsCRUD endpoints (services/api/app/api/v1/endpoints/connectors.py,services/api/app/schemas/connector.py) —GET /catalog,POST /test,GET / POST / PATCH / DELETE /instances,POST /instances/{id}/test. Tenant-scoped via the existing auth dependency, secrets encrypted on write through the vault, and proxied to the connectors microservice for schema lookups and liveTest connectioncalls.ConnectorScheduler(services/connectors/app/scheduler.py) — APScheduler in-process insideservices/connectors, started in the FastAPI lifespan. One job per enabled instance, pollsfetch_alerts(since_seconds=300)every 5 min by default (connector_config.poll_interval_secondsoverrides per instance), decrypts via the read-path vault, normalizes events through the connector'snormalize()method, and pushes the batch toservices/ingest/v1/ingest/batchvia the newIngestClient. SetAISOC_CONNECTORS_DISABLE_SCHEDULER=1to skip wiring the scheduler in tests.- Nine new connectors in
services/connectors/app/connectors/:azure_entra(Microsoft Graph audit logs),azure_activity(ARM Activity Log via Resource Graph + blast-radius_HIGH_BLAST_RADIUS_VERBSlist),azure_defender(Microsoft Graph Security alerts),gcp_cloud_audit(Cloud Logging API with hand-rolled RS256 JWT signing for service-account auth),gcp_scc(Security Command Center findings, same JWT signer),m365_audit(Office 365 Management Activity API, sharing the Azure AD app fromazure_entra),google_workspace(Reports API with domain-wide delegation),cloudflare(Audit Logs), andgithub(Org Audit Log + Code Scanning alerts). Every connector ships unit tests covering schema contract, normalization, andtest_connection()happy/sad paths (services/connectors/tests/test_*_connectors.py,test_schemas.py,test_scheduler.py). - Frontend click-and-connect wizard
(
apps/web/src/components/connectors/AddConnectorModal.tsx,ConnectorInstanceList.tsx, rewiredConnectorsView.tsx, typed client inapps/web/src/lib/api.ts) — two-step modal: (1) catalog grid grouped by category, (2) schema-driven form withtext/secret/select/textareafields, an inlineTest connectionbutton, and aSave & enableaction.framer-motionfor transitions,react-hot-toastfor feedback. Existing connector cards now render from the live API via SWR. - Marketplace + plugin manifests —
plugins/{azure-entra, azure-activity, azure-defender, gcp-cloud-audit, gcp-scc, m365-audit, google-workspace, cloudflare, github}/plugin.yamlcarry the newschema()shape soscripts/build_marketplace.pycan surface them in the in-app Marketplace, andapps/web/public/marketplace/index.jsonis regenerated viapnpm marketplace:sync. - Documentation —
apps/docs/docs/connectors/index.md(catalog landing with a connector walkthrough and category taxonomy), nine per-connector setup walkthroughs (prereqs, scopes, screenshots),apps/docs/docs/operations/credentials.md(vault threat model, key rotation procedure, hosted-OAuth roadmap), and a newConnectorssection inapps/docs/sidebars.ts.
services/api/app/core/config.py— addedAISOC_CREDENTIAL_KEY,AISOC_CREDENTIAL_KEY_ROTATION_FROM,CONNECTORS_SERVICE_URL,CONNECTORS_SERVICE_TIMEOUT_SECONDS. Documented in.env.example.services/api/app/main.py— the new/api/v1/connectorsrouter is mounted alongside the existing v1 router set.services/connectors/app/api/router.py— schema responses lookup the registry instead of returning a hardcoded dict; newPOST /connectors/{connector_id}/testendpoint runs an unauthenticated dry-runtest_connection()for the wizard's pre-save Test step.services/connectors/app/main.py— the FastAPI lifespan now wires the scheduler, withAISOC_CONNECTORS_DISABLE_SCHEDULERhonored for tests and CI.
Before this pass: adding a connector meant editing Python in three places,
shipping a release, and reading docs to discover the auth fields. Secrets
sat in plain JSON in Postgres. After this pass: connectors are runtime
data; secrets are encrypted with a key the operator controls; rotation
is a documented procedure; the wizard's Test connection round-trip
catches bad credentials before they're saved; and the per-connector docs
each give an analyst a 5-minute path from "I have a tenant" to "alerts
are flowing into the console."
This pass addresses two questions raised on the public launch thread about the v5.2 eval harness:
- "Any interest in shipping synthetic telemetry (M365 audit, CloudTrail,
Sysmon) backing each incident?" — Yes. A companion
synthetic_telemetry.jsonlcorpus is now generated alongsidesynthetic_incidents.jsonand gives connector and Sigma PRs a concrete contract to wire against without provisioning a real tenant. - "INC-EVAL-044, 099, and 154 are the same template with
{user}/{host}swapped — what does the multiplier buy vs. the dilution in regression signal?" — The multiplier still buys breadth for connector regressions, but the eval suites now report a per-template macro alongside the per-case mean so a single broken template (~4 cases) moves the regression signal by ~1.8% rather than ~0.5%, and the failing template IDs are surfaced inline.
- Synthetic telemetry corpus
(
services/agents/tests/eval_data/synthetic_telemetry.jsonl,scripts/generate_eval_incidents.py) — 361 backing events spanning 14 log sources (Sysmon, Windows Security, M365 audit, Azure sign-in, CloudTrail, Linux auditd, journald, EDR, DNS, web access, Kubernetes audit, GitHub audit, VPN, DB audit), wired to all 200 incidents. Each event is a templated dictionary with{user}/{host}/{ip}/{campaign}placeholders resolved against the incident it backs, and carries the fields a real connector pivots on (process tree, principal, source IP, log source, event ID). - Telemetry event factories + recursive resolver
(
scripts/generate_eval_incidents.py) —_sysmon,_winsec,_m365,_azure_signin,_cloudtrail,_auditd,_journald,_edr,_dns,_web,_k8s,_github,_vpn,_dbproduce base event shapes; a recursive resolver walks nested dicts and substitutes incident context. The 55 templates in_TEMPLATESeach now carry atemplate_id, atemplate_index, and a tuple of telemetry events. - Schema + coverage gate (
services/agents/tests/test_synthetic_telemetry.py) — five new assertions: every incident has ≥ 1 backing event, every expected source is present, every event carries the source-specific pivot fields a real connector needs, all placeholders resolve, and no single template dominates the source distribution. - Per-template macros on every scoring suite
(
services/agents/tests/test_mitre_accuracy.py,test_investigation_completeness.py,test_response_quality.py,scripts/run_evals.py) — each result now carries aper_template_summary()(mean, median, min, max, count, failing IDs) alongside the per-case mean, plus a new test gating macro accuracy ≥ 0.80 for MITRE / completeness and ≥ 0.75 for response-plan quality. A template-distribution-balance test asserts no single template accounts for > 5% of incidents (currently 0.5–2.0% each). run_evals.pyoutput expansion — each suite headline now prints the per-case mean and the per-template macro with the failing template IDs inline; the human-readable summary appends a synthetic- telemetry footer (event count, source count, incident coverage, file path);--jsonoutput addsper_templateandtelemetryblocks.
- Incident schema —
synthetic_incidents.jsonentries now includetemplate_id(e.g.m365_admin_impersonation) andtemplate_indexfields. Existing fields are unchanged. Regenerated deterministically from the seeded RNG. apps/docs/docs/benchmark.md— added a "What's new (v1.4)" section, a "Per-case vs. per-template metrics" section explaining the ~0.5% vs ~1.8% sensitivity argument with worked examples, and a new "Synthetic telemetry corpus" section documenting the 14 sources, the pivot fields, the placeholder resolver, and the five schema/coverage checks. The "Help us harden the harness" call-outs now include adding a connector + Sigma rule against the corpus and adding a new template with backing telemetry. The "What this is not" section is updated to call out that the corpus is hand-shaped (not captured from a live tenant) and that the per-template macro is the non-tautological signal on top of the otherwise self-consistent gates.README.md— capability bullet rewritten to call out five suites (was four), 55 distinct templates, per-case + per-template macros, and the synthetic-telemetry coverage gate. The comparison table flags the eval harness as having a synthetic-telemetry corpus + per-template macros. Step 5b (Run the public eval harness) documents the newpython scripts/generate_eval_incidents.pyworkflow for regenerating the dataset and the corpus together.- Eval signature on completeness + response-quality runs — calls
from
run_evals.pynow usekeep_per_incident=Trueso the per- template summary is computable. Default behaviour unchanged for existing direct callers.
The v5.2 harness gave deterministic numbers but two real concerns existed: duplicates could mask a broken template behind 199 working duplicates, and there was no concrete telemetry shape for connector contributors to wire against. v1.4 closes both: the per-template macro is the dilution-resistant regression signal that surfaces template-class breaks, and the synthetic telemetry corpus is the connector-development contract.
This is a "fix the foundations" pass: tighten security defaults, drop
overclaims, harden CI, fix DX rough edges, scale detection content from
~200 to 6,913 rules with explicit tiering, and ship a public demo
hosted on tryaisoc.com via Cloudflare Tunnel.
- GraphQL tenant scoping (
services/api/app/graphql/) — every resolver is wrapped with atenant_scopehelper, GraphiQL is forced off in production, and a tenant-isolation regression test asserts cross-tenant reads return 0 rows. - Plugin signature gate (
services/api/app/services/plugin_manager.py,packages/plugin-sdk-py/src/aisoc_plugin_sdk/loader.py,packages/plugin-sdk-go/aisoc/loader.go) — Ed25519 signature verification is required before loading any plugin.PLUGIN_TRUST_MODEcontrols policy:strict(default, signed only),permissive(warn- load),
dev(skip). Publisher signing flow is documented inpackages/plugin-sdk-py/README.mdandpackages/plugin-sdk-go/README.md.
- load),
/metricsand compose hardening (docker-compose.yml,docker-compose.demo.yml,services/api/app/main.py,services/api/app/core/security.py) — service ports bind to127.0.0.1by default, the API logs a loud warning ifSECRET_KEYis unset or default, theadminrole permissions are corrected to match the documented matrix, and/metricsis gated behindMETRICS_TOKEN.
- Fusion pipeline framing (
services/agents/app/fusion/,apps/docs/docs/architecture.md) — replaced "real fusion pipeline" with the actual scope (rule-based + ML scoring fan-in, no reinforcement learning). - CI cadence wording (
README.md,CONTRIBUTING.md) — "every commit" → "every push and PR tomain". - Eval harness honesty (
scripts/eval/,apps/docs/docs/) — removed "Macro F1" references, reframed the 200-incident synthetic dataset as substrate self-consistency, dropped the hardcodedSUITESconstant, fixed the broken--reportflag, and aligned Prophet usage in code and docs.
- No more
|| true(.github/workflows/ci.yml) — removed every silent failure suppression. - Web Vitest smoke —
apps/webships a Vitest suite covering marketplace filters, detection coverage view, and core layouts. - SDK + service jobs — added Python pytest + Vitest jobs for
packages/sdk-{py,ts,go}andpackages/plugin-sdk-{py,go}, plus pytest jobs forservices/{api,agents,actions,connectors}. - Detection + playbook validation in CI
(
.github/workflows/validate-detections.yml,.github/workflows/check-openapi.yml) —validate_detections.pyruns against all 6,913 rules and the OpenAPI spec is regenerated and compared on every PR.
aisoc-doctorprobes fixed (tools/aisoc-doctor/) — checks match the actual ports, env var names, and service URLs.- CLI consistency (
packages/cli/,README.md,apps/docs/docs/) —npx aisocandaisocresolve identically; package names, missing pnpm scripts, and themcpservice reference are corrected; branching/tooling and env var names match across docs. - Infra READMEs —
infra/k8s/,infra/helm/,infra/terraform/,infra/render/,infra/fly/,infra/railway/,infra/coolify/each have aREADME.mddocumenting prerequisites, secrets, and invocation.
- 800 native rules — added 600 new Sigma-shaped detections across
five new spec modules (
scripts/detection_specs_part3_cloud.py,_identity.py,_endpoint.py,_network.py,_application.py), each withmatch_when, MITRE tagging, and auto-generated positive/negative fixtures viascripts/detection_specs_part3_helpers.py. Native total: 200 → 800. - 6,113 imported rules with provenance — wired importers under
tools/detection_import/{sigma,splunk,chronicle,car}_importer.pyfor SigmaHQ, Splunk Security Content, Chronicle, and MITRE CAR. Each imported rule is tagged with its source, license, and original ID; rules whose mappings cannot be replayed against AiSOC fixtures are quarantined underdetections/<source>-imports/quarantine/(~5,937 quarantined, ~6,113 active). - Title → name migration — imported YAMLs now use the canonical
name:field instead oftitle:, matchingvalidate_detections.py's required schema.tools/detection_import/common.pywas updated and 6,113 existing files were migrated in place. - Marketplace tier UX (
apps/web/src/components/marketplace/MarketplaceView.tsx,MarketplaceView.test.tsx,marketplace/index.json,apps/web/public/marketplace/index.json,scripts/build_marketplace.py) — items now expose atierfield (stable/beta/imported/community), the marketplace UI defaults tostableand shows per-tier counts on filter chips, andbuild_marketplace.pyinfers tiers fromplugin.yamland source paths. - MITRE ATT&CK coverage view (
apps/web/src/app/(app)/detection/coverage/,apps/web/src/lib/mitreTactics.ts) — new in-app dashboard rendering the coverage matrix from the marketplace index. - Documentation refresh — updated
README.md,apps/docs/docs/intro.md,apps/docs/docs/quickstart.md,apps/docs/docs/concepts/detections.md,apps/docs/docs/contributing/dev-setup.md,detections/README.md, and.github/workflows/validate-detections.ymlto reflect 800 native + ~6,000 imported (filterable by tier) and drop stale "200+ rules" claims.
- Cloudflare Tunnel infra (
infra/cloudflare/) —config.yml.example,tunnel.sh, and a README explaining how to run the demo profile behindtryaisoc.comviacloudflared. Tunnel script readsDOMAIN,TUNNEL_NAME,SUBDOMAINS,SKIP_DNS,SKIP_RUNenv vars; defaults publish apex +api.,ws.,docs.subdomains. pnpm demo:publicscript (scripts/demo-public.sh) — bootsdocker-compose.demo.yml(read-only demo profile with seeded incidents) viapnpm aisoc:demo --no-open, then brings up the Cloudflare Tunnel that mapstryaisoc.com→ web (:3000),api.tryaisoc.com→ api (:8000),ws.tryaisoc.com→ realtime (:4000), anddocs.tryaisoc.com→ Docusaurus (:3001). Companion scripts:pnpm demo:public:tunnel-only(skip stack bring-up, just run the tunnel) andpnpm demo:public:setup(provision tunnel + DNS without running cloudflared, forcloudflared service installflows).- Public-host-agnostic web bundle (
apps/web/next.config.js,apps/web/src/lib/api.ts) — the Next.js client now emits same-origin relative paths (/api/v1/...,/ws/...) instead oflocalhost:8000-baked URLs, with server-side rewrites proxying to api/agents/realtime by Docker DNS name. The same image works onlocalhost:3000, behind Cloudflare Tunnel ontryaisoc.com, or behind any reverse proxy without a rebuild. - README "Try it live" — top-of-README link to the public demo with a one-liner for hosting your own on a Cloudflare-managed domain.
5.2.0 — 2026-05-04
This release groups four areas of work: an append-only investigation ledger, a public eval harness, a mobile responder PWA, and a hosted demo profile. Details below.
- Investigation Ledger (
services/api/migrations/008_investigation_ledger.sql,services/api/app/models/investigation.py,services/agents/app/investigator/ledger.py) — every prompt the agent emits, every tool call, every retrieved evidence shard, and every rationale is persisted as an append-onlyinvestigation_steprow, scoped to a tenant + case. - Investigation Ledger UI (
apps/web/src/components/cases/InvestigationLedger.tsx) — replayable step-by-step view in the case workspace with prompt, response, and tool-call diffs. GET /api/v1/investigations/*endpoints (services/api/app/api/v1/endpoints/investigations.py) for listing, retrieving, and replaying ledger entries by case.- Investigator graph upgrades
(
services/agents/app/investigator/{orchestrator,recon_agent,forensic_agent,responder_agent,report_writer_agent,state}.py) — every node now writes a ledger entry on entry and exit, including the structured input it received and the structured output it produced.
- 200-incident synthetic dataset
(
services/agents/tests/eval_data/synthetic_incidents.json) — 200 deterministic, regenerable cases covering all 14 MITRE ATT&CK enterprise tactics across roughly the top 50 techniques. Generated byscripts/generate_eval_incidents.py. - Four eval gates under
services/agents/tests/:test_alert_reduction.py— real measurement: 1 000 noisy alerts → ~250 incidents via 3-tier fusion, with explicit storm and near-duplicate handlingtest_mitre_accuracy.py— substrate self-consistency gate: tactic-level accuracy / precision / recall / F1 between the hand-curated extractor and the dataset that was written to feed ittest_investigation_completeness.py— substrate self-consistency gate: evidence-keyword coverage on a templated reporttest_response_quality.py— substrate self-consistency gate: 5-criterion offline rubric on a templated response plan (action class, severity awareness, MITRE alignment, evidence grounding, actionability)
scripts/run_evals.py— one-shot harness with--jsonand--cioutput modes. Total runtime ~25 ms on a laptop. CI-gated on every commit via.github/workflows/ci.yml. Runs deterministic substrate code against synthetic incidents — does not call the live LLM agent.- Public eval harness page (
apps/docs/docs/benchmark.md,apps/web/src/app/benchmark/page.tsx,apps/web/src/components/benchmark/) — published numbers, full method, comparison to other AI SOC offerings, and explicit framing of which suites measure substrate self-consistency vs real behaviour. Linked from the README and the docs landing page.
- Responder PWA (
apps/web/src/app/(responder)/,apps/web/src/components/responder/,apps/web/src/components/pwa/) — installable, offline-aware, push- enabled responder console for on-call analysts. Service worker atapps/web/public/sw.js, manifest atapps/web/public/manifest.json, offline shell atapps/web/public/offline.html. - Passkey authentication (
services/api/app/models/responder.py,services/api/app/api/v1/endpoints/passkeys.py,apps/web/src/lib/responder/) — WebAuthn registration and login for the Responder surface; FIDO2 platform authenticators only, no SMS fallback. - On-call schedule + handoff (
services/api/app/models/responder.py,services/api/app/api/v1/endpoints/oncall.py) — current responder per tenant, surfaced in the Responder home page and in alert pages on the desktop console. - Approvals workflow (
services/api/app/api/v1/endpoints/approvals.py) — long-lived approval requests for blast-radius-gated SOAR actions, approvable from the Responder PWA with hardware-attested passkey. - Web Push delivery (
services/realtime/src/push.ts,services/api/app/api/v1/endpoints/push.py) — VAPID-signed push notifications wired into the realtime gateway. Subscriptions persist per-device and follow the on-call rotation. - Migration —
services/api/migrations/009_responder_pwa.sql.
- Contextual actions (
services/agents/app/api/contextual.py,apps/web/src/components/alerts/AlertDetailView.tsx,apps/web/src/components/cases/CaseWorkspace.tsx,apps/web/src/components/detections/RuleEditor.tsx,apps/web/src/components/playbooks/PlaybookEditor.tsx) — the AI Copilot now reads the surface the analyst is standing on (alert / case / rule / playbook) and proposes the next two or three concrete actions with the correct payloads pre-filled. One click invokes the agent with the right tool. - Investigator graph awareness — every contextual action is grounded in the same Investigation Ledger so the analyst sees, before clicking, which prompts and tool calls will be issued.
@aisoc/mcp(services/mcp/) — Model Context Protocol server exposing 11 AiSOC tools to Claude Desktop, Cursor, Cody, and Continue.- Discovery tools —
aisoc_list_alerts,aisoc_list_cases,aisoc_query_detections. - Deep-dive tools —
aisoc_get_case,aisoc_get_investigation,aisoc_get_alert. - Action / replay tools —
aisoc_run_investigation,aisoc_replay_decision,aisoc_explain_step,aisoc_create_case,aisoc_assign_alert. The replay set walks the Investigation Ledger step-by-step inside the IDE / chat. - Install command —
npx -y @aisoc/mcp install --host claude --aisoc-url … --api-key …. - Documentation —
apps/docs/docs/integrations/mcp.md,services/mcp/README.md.
- Slim demo profile (
docker-compose.demo.yml) — postgres + redis + kafka + api + agents + realtime + web. ClickHouse, OpenSearch, Neo4j, and Qdrant are gated behind compose profiles for production. - Prebuilt images —
ghcr.io/beenuar/aisoc-{api,agents,realtime,web,…}built and published by.github/workflows/publish-images.ymlon every release tag. - One-shot orchestrator (
scripts/aisoc-demo.ts) — pulls images, brings up the stack, waits on healthchecks, seeds canonical demo data, kicks off an agent investigation against a seeded case, and opens the browser at/cases/<uuid>with the live ledger view selected. - Demo mode middleware (
services/api/app/middleware/demo_mode.py) — gates write operations, resets state every UTC midnight, and watermarks the UI as read-only. Tests atservices/api/tests/test_demo_mode.py. - Target time-to-first-investigation: roughly 3–5 minutes on a warm Docker daemon, depending on image cache state.
- Cleanup —
pnpm aisoc:demo:downremoves the volumes; logs atpnpm aisoc:demo:logs.
- Fly.io (
infra/fly/) — first-class config forapi,agents,realtime,web. Deploys viainfra/fly/fly-demo-deploy.sh, ~$14/mo for the whole stack. - Render (
render.yaml) — managed, sleep-on-idle config suitable for hobbyists and design partners. - Railway (
infra/railway/railway.toml) — pay-as-you-go PaaS. - Coolify (
infra/coolify/README.md) — self-hosted on your own VPS, reuses the existingdocker-compose.yml.
- ~200 detection rules in
detections/covering MITRE ATT&CK Enterprise (cloud, identity, endpoint, network, application). Sigma format, with MITRE technique IDs intags, fixtures underdetections/fixtures/, anddetections/README.mddocumenting the schema. - 50+ response playbooks in
playbooks/packs/v1/— IAM, EDR, network, application, generic. JSON DSL with explicit decision trees, human-approval gates, and rollback steps. Schema inplaybooks/README.md. - 15 plugins in
plugins/— both Go and Python implementations for CrowdStrike, Splunk, Sentinel, AWS Security Hub, Okta, Cloudflare WAF, Defender, GuardDuty, Pagerduty, Slack, Teams, Jira, ServiceNow, VirusTotal, AbuseIPDB. Each ships with manifests, tests, and SDK helpers. - Marketplace index (
marketplace/index.json,apps/web/public/marketplace/index.json) — auto-generated byscripts/build_marketplace.pyfrom the on-disk content tree. - Validation tooling —
scripts/validate_detections.py(Sigma + MITRE ID schema)scripts/validate_playbooks.pyandscripts/lint_playbooks.py(DSL well-formedness + safety).github/workflows/{validate-detections,validate-playbooks,sync-marketplace}.ymlenforce the gates on every PR.
- In-app marketplace (
apps/web/src/app/(app)/marketplace/page.tsx,apps/web/src/components/marketplace/MarketplaceView.tsx) — filterable by category, ratings, verified vs community badge.
packages/plugin-sdk-go— Go plugin SDK (module github.com/beenuar/aisoc/plugin-sdk-go) with action, connector, enricher, registry, widget, and loader primitives. Examples underpackages/plugin-sdk-go/examples/.packages/plugin-sdk-py— Python plugin SDK with the matching primitives, decorators, and a registry. Tests underpackages/plugin-sdk-py/tests/.packages/sdk-py(PyPI:aisoc-sdk) — async Python client SDK for the AiSOC API.packages/sdk-ts(npm:@aisoc/sdk) — TypeScript client SDK with auto-generated types.packages/sdk-go— Go client SDK with OpenAPI-generated models.
/why-open-sourcepage (apps/web/src/app/why-open-source/page.tsx) — long-form description of the project's open-source posture and trade-offs.- Updated landing (
apps/web/src/components/landing/{Hero,LandingNav,Footer,OpenSource}.tsx) — the "live demo" button lands directly on a seeded investigation; comparison rows reference specific behaviours rather than generic claims. - Docusaurus refresh — new MCP integration page, benchmark page, Investigation Ledger references, Responder PWA mentions in concepts and quickstart.
- Repository home — all
cyble-inc/AiSOCandaisoc-os/aisocURLs updated tobeenuar/AiSOCacross docs, README, SDKs, and benchmark badges. packages/sdk-gomodule path is nowgithub.com/beenuar/aisoc/sdk-gofor the API client SDK; the plugin SDK is atgithub.com/beenuar/aisoc/plugin-sdk-go.alertsAPI (services/api/app/api/v1/endpoints/alerts.py,services/api/app/models/alert.py) — surfaces copilot context (suggested next actions) inline on the alert detail response.- API router (
services/api/app/api/v1/router.py) — wires upapprovals,investigations,marketplace,oncall,passkeys,push.
- CI Docker build contexts —
.github/workflows/{ci,release,publish-images}.ymlnow set explicitcontextandfileparameters per service; multi- service builds no longer race on a stale build root. - Docker Compose obsolete
versionwarning — removedversion: '3.8'fromdocker-compose.demo.yml. - Repository hygiene — added
.gocache/,*.tsbuildinfo,apps/docs/.docusaurus/,apps/docs/build/,plugins/**/*-build-test,plugins/**/*-build,eval_report.json, andeval_mitre_accuracy_report.jsonto.gitignore. Removed previously tracked Docusaurus cache and local IDE hook state files from the index.
5.1.0 — 2026-05-03
- UEBA service (
services/ueba) — User & Entity Behavior Analytics- Welford online algorithm for incremental baseline computation
- Z-score anomaly scoring with configurable sensitivity
- Peer-group analysis (same role / department / location clustering)
- Kafka consumer (
security.events) → producer (security.anomalies) integration withfusionservice - Alembic migrations, Dockerfile, Helm deployment template
- Honeytokens service (
services/honeytokens) — deceptive credential & file traps- HMAC-SHA256 signed token generator (URL, file, AWS key, email flavors)
- Webhook handler for first-touch alerting (HTTP signed callbacks)
- Token lifecycle management: active / triggered / expired states
- React UI: create tokens, view trigger log, copy lure URLs
- Alembic migrations, Dockerfile, Helm deployment template
- Purple Team service (
services/purple-team) — adversary emulation & tabletop- Atomic Red Team YAML parser (any
atomics/directory) - Caldera REST integration for remote execution
- ATT&CK coverage heatmap (tactic × technique matrix)
- Test execution tracking with detection reporting (true positive / false negative)
- Tabletop exercise session manager with finding capture
- React UI: Coverage tab, Executions tab, Tabletop tab
- Alembic migrations, Dockerfile, Helm deployment template
- Atomic Red Team YAML parser (any
5.0.0 — 2026-05-03
- SAML 2.0 + OIDC authentication (
services/api/app/auth/)- IdP-initiated and SP-initiated SAML 2.0 flows (python3-saml)
- OIDC authorization-code + PKCE flow with
authlib - JWT issuance on successful SSO login
- Multi-tenant Row-Level Security (Postgres RLS)
tenant_idcolumn on all data tables- RLS policies enforced at the database level
- SQLAlchemy
set_tenant()middleware in FastAPI deps
- Granular RBAC (
services/api/app/api/v1/endpoints/rbac.py)roles,role_permissions,user_rolestablesrequire_permission("resource:action")FastAPI dependency- Admin UI at
/settings/rbac
- Immutable Audit Log
- Append-only
audit_logtable with a before-UPDATE trigger - FastAPI middleware auto-logs every mutating request
GET /api/v1/auditpaginated endpoint with tenant filter- Audit log viewer UI at
/audit
- Append-only
- Compliance dashboards
- SOC 2 evidence auto-collection + PDF export (
/compliance/soc2) - ISO 27001, NIST CSF, PCI-DSS, HIPAA, DORA framework heatmaps
GET /api/v1/compliance/{framework}endpoint with control mapping
- SOC 2 evidence auto-collection + PDF export (
- SLA tracking — MTTD / MTTR / MTTC
tenant_sla_config+alert_sla_eventstablesGET /api/v1/sla/metrics+GET /api/v1/sla/breaches- SLA dashboard widget at
/sla
- HA Helm chart — HPA, PDB, Ingress per service
- Backup & restore scripts (
scripts/backup.sh,scripts/restore.sh) for Postgres + ClickHouse + plugins → S3/R2 - Operational runbook generator (
scripts/generate_runbook.py) from live OTel trace data - Multi-region deployment guide (
docs/operations/multi-region.md) - OpenTelemetry instrumentation across API, UEBA, Honeytokens, and Purple Team services
4.1.0 — 2026-05-03
- AiSOC CLI (
packages/aisoc-cli) —scaffold,validate,publishcommands for plugins and detectionsaisoc scaffold plugin <name>— generate plugin skeletonaisoc validate detection <file>— Sigma/YAML schema validationaisoc publish plugin <path>— submit to community registry with Ed25519 signing
- Plugin publishing flow
community_pluginstable with signature, author, review statePOST /api/v1/plugins/publish— signed submissionPOST /api/v1/plugins/{id}/approve/reject— curator review endpoints- Ed25519 signature verification on every submission
- Marketplace v2 — ratings, install counts, verified badges, category filter, sort options
plugin_ratingstable +POST /api/v1/plugins/{id}/rateGET /api/v1/marketplace?category=&sort=with pagination
- Detection catalog (
/detection/catalog) — paginated Sigma rule browser- Install-to-tenant action from catalog
GET /api/v1/detections/catalogendpoint
- Playbook community submissions
community_playbookstable + submit / curate API- Community tab in PlaybooksView UI
- Docusaurus documentation site (
apps/docs) — full API, architecture, deployment, plugin SDK, quickstart
3.0.0 — 2026-05-02
- Threat Intelligence Enrichment (13 providers)
- Open-source/freemium: VirusTotal, AbuseIPDB, GreyNoise, Shodan, URLScan.io, IPinfo
- Commercial: Cyble Vision, Recorded Future, Mandiant, Crowdstrike Intel, Anomali, IBM X-Force, Flashpoint, Intel 471, DomainTools, RiskIQ
- New enrichment types:
DarkWebContext,VulnerabilityRef,BrandRisk - Concurrent fan-out enrichment engine in Go
- Go module path migration — all services updated from
github.com/cyble/aisoctogithub.com/beenuar/aisoc - SECURITY.md — vulnerability disclosure policy and security contacts
services/enrichment/README.md— full enrichment service documentation
- All GitHub repository references updated to
https://github.com/beenuar/AiSOC - Helm chart container images updated from
ghcr.io/cyble/aisoc-*toghcr.io/beenuar/aisoc-* .env.exampleexpanded with API keys for all commercial TI providers
2.0.0 — 2026-05-01
- Knowledge Graph — Neo4j-backed entity relationship visualization (
services/api/app/services/graph_service.py) - ML Fusion Engine — multi-model alert scoring and deduplication (
services/fusion/app/services/) - Rule Engine — YAML-based detection rules with MITRE ATT&CK mapping (
services/api/app/services/rule_engine.py) - Attack Graph viz with D3.js force layout (
apps/web/src/components/graph/) - MITRE ATT&CK Heatmap on dashboard
- AI Copilot dock — streaming LLM assistant integrated into case and alert views
- Threat Hunt page — query builder with saved hunts and timeline scrubbing
- Case Workspace — full case lifecycle: evidence, timeline, collaborators, MITRE tagging
- Detection Rule Builder — visual rule editor with backtesting
- Settings page — RBAC, notifications, API key management, threat intel feed config
- Live Dashboard — WebSocket-powered real-time alert/event feed
- Command Palette (cmd-K) — fuzzy search for navigation and actions
- Marketing Landing Page — hero, feature highlights, open-source section, footer
- Design Token System — Tailwind + CSS vars, Framer Motion animations, responsive layouts
- Demo Producer — synthetic event generator for local development
scripts/seed_demo.py— database seeding for demos
- Web app migrated to Next.js App Router
- All API routes versioned under
/api/v1
1.0.0 — 2026-04-30
- Initial release of AiSOC — AI Security Operations Center
- FastAPI backend (
services/api) with alert ingestion, case management, detection rules - Next.js 14 frontend (
apps/web) with dashboard, alerts, cases, connectors, threat-intel pages - Real-time service (
services/realtime) using WebSockets - Ingest service (
services/ingest) in Go for high-throughput event ingestion - Enrichment service (
services/enrichment) in Go - Docker Compose stack for local development
- Helm chart for Kubernetes deployment (
infra/helm/aisoc/) - MIT License