Skip to content

Repository files navigation

SANN

SANN is a research and forensics platform for cybersecurity exercises. You give it a red-team / blue-team session — screen recording, terminal casts, syslog, auth logs, Suricata alerts, Zeek flows, sensor packets, keylogger output, behavior-tracker events — and it does two things with it:

  1. Mission Control — an analysis-first dashboard that reads the whole session and shows you the attack story: kill-chain progression, detection efficacy (what the blue team caught vs missed), MITRE/TTP depth, per-subject profiles, alerts and network — all in one surface, with drill-down, live slicing, and a downloadable analysis bundle.
  2. Threat View — a forensic HUD that puts a video, a terminal cast, and six event panels on one synchronized cursor. Scrub and everything moves together.

It runs on a laptop. SQLite for storage, FastAPI for the backend, plain HTML/JS for the UI (no build step, no CDN dependencies in Mission Control — air-gap friendly).

SANN Interface


Contents


What it does

A red-team or CTF session produces a pile of artifacts: a screen recording of the attacker's workstation, asciinema casts of every terminal, kernel-level syslog from each victim host, auth logs, Suricata IDS alerts, Zeek connection logs, raw packet captures, a keylogger TSV, behavior-tracker events, and so on. Reviewing that by hand means jumping between a video player, a SIEM, and a stack of text files while lining up timestamps in your head.

SANN parses every artifact into one SQLite schema, classifies it under MITRE ATT&CK, and then analyses it — not just displays it. A single analysis engine turns the raw events into a structured picture (data quality, kill-chain progression, attack narrative, detection efficacy, technique depth, host roles, per-subject profiles, sessions, alerts, network) that feeds both the dashboard and the downloadable export. What you see on screen is exactly what you download.


Data model — one dataset = one participant

Understanding the data is the point, so the model matters:

  • A dataset (e.g. P032, P003, P017) is one participant's study — one red-teamer, captured across several exercise runs. Each dataset becomes one project with its own isolated database. The cohort code (P032) is derived from the dataset path; it's the real subject identity (the project name is just a label you pick).
  • A scenario (training, ckc1, ckc2, ckc2c, …) is one exercise run. Each run has a subject identity — the userNNNN who holds the attacker box and the keystrokes for that run. So within P032 the ids user0032 (training), user2016 (ckc1), user3017 (ckc2c) are the subject's per-scenario identities, not three unrelated people. Mission Control resolves and presents this for you (the "Subject & Scenario Runs" panel).
  • Within a run, hosts have roles — attacker (the kali box), pivot (compromised jumpboxes), sensor (zeek/suricata/pcap), defense (blue-team infra), target. The engine classifies these so the red-vs-blue picture (what the attacker did vs where they were detected) is legible.

The analysis engine

analysis/ is a pure Python package (no web imports) that is the single source of truth for every derived metric. It takes a scoped SQLite connection plus an optional filter and returns a digest document. The dashboard renders it; the export downloads it; they never disagree.

Sections of the digest:

Section What it answers
data_quality Completeness score, missing timestamps, unknown-phase %, sources present/absent — how much to trust this dataset
identities Scenario → subject identity resolution (+ other actors)
scenarios Per-scenario event/participant/phase breakdown
kill_chain 14 MITRE tactics in canonical order, volume + first/last seen + coverage
narrative Ordered attack milestones per scenario (the story)
detection_efficacy Per-phase attacker activity vs alerts, time-to-detect, blind spots
ttp Technique frequency + dwell, coverage matrix, attack tempo/transitions
host_roles attacker / pivot / sensor / defense / target + per-subject attacker vs detected-on hosts
participants Per-identity profile: events, sessions, commands, tools, alerts, off-hours %, anomaly flags
sessions Activity sessions reconstructed from idle gaps
alerts, network, commands, alert_correlation, activity_timeline Rollups, top talkers/ports, command intelligence (attacker vs system), ±60s alert correlation, time histogram

Heuristic outputs (suspicious commands, anomaly flags, host roles) are labelled advisory — a research aid, not ground truth.


How it works

There are three moving pieces.

1. Ingest — ingest_v2.py

A single Python script that walks a dataset directory and turns raw files into rows in SQLite. Each supported format has its own parser:

File Parser What it gives you
*.cast parse_cast_file Terminal output + extracted commands + cwd from prompts
recording.{ogv,webm} parse_video_file (metadata only) Start time, duration
auth.log parse_auth_log SSH / sudo / PAM events
syslog parse_syslog Kernel and systemd events
eve.json parse_suricata_eve IDS alerts, HTTP, DNS, flows
conn.log parse_zeek_conn TCP/UDP connection records
bt.jsonl parse_bt_jsonl Honeytrap behavior events
sensor*.log parse_sensor_log Raw sensor_packet records
UAT-*.tsv parse_uat_log Keylogger / typed text
hacktools.log parse_hacktools Tool invocations
apt.log parse_apt_log Repo activity

All events land in the same events table — 35 columns covering timestamp, source, host, user, command, src/dst IP and port, action category, MITRE tactic/technique, alert info, HTTP fields, etc. Anything the parser can't fit into a column gets dumped into raw_data as JSON so you can recover it later.

A few details that took some iteration to get right:

  • Suricata timezones. Suricata writes ISO timestamps with an explicit offset (2025-08-15T11:25:23.826914-0400). The parser normalizes the compact offset, parses with the offset preserved, converts to UTC, then stores naive UTC. Earlier versions stripped the offset and stored local time, which made suricata events appear several hours before the video window for any EDT host.
  • sensor_packet lines. These look like JSON but use single quotes ({'time': ..., 'data': {...}}). They are Python dict literals, not JSON. The parser tries ast.literal_eval first and falls back to a JSON-with-quote-swap heuristic. Using only the swap heuristic corrupts payloads containing apostrophes.
  • Syslog years. The classic syslog format (Aug 15 19:07:17 host kernel: ...) has no year. The ingester guesses from file mtime, and a post-processing pass cross-checks against the cast header epoch (which carries the real year) and shifts the rows if the guess was wrong.
  • C2 attribution. sensor_packet events whose src or dst IP matches the project's configured attacker IPs get reclassified as command_and_control. The attacker IP set is project-scoped.

MITRE classification uses two stages. First, a dict lookup on action_name handles the cases the source itself labels (e.g. sudo_executionprivilege_escalation). Then a list of (regex, phase, tactic, technique) rules runs against the command text — nmapreconnaissance, crontabpersistence, sudoprivilege_escalation, and so on. First match wins. Anything that doesn't match falls to unknown.

When the ingester is invoked with --main-db and --project-id, it dual-writes every event: once into the project's own database, once into the combined corpus with project_id stamped on the row. That means you can query a single project in isolation, or run cross-project queries against the corpus, without picking up front.

2. Post-processing — import_manager.py

Runs after ingest and fixes things in place. All steps are idempotent — safe to re-run with --skip-ingest.

  • dedupe_orig_participants — drops events and media rows from *.orig participant folders, which are re-runs of the same session.
  • fix_recon_taxonomy — rewrites the legacy recon label to reconnaissance (MITRE TA0043).
  • fix_suricata_timestamps — for each participant, detects the offset from the first eve.json and SQL-updates all stored suricata timestamps. This catches DBs ingested before the parser fix.
  • rebuild_media_registry — clears and rebuilds the table of video / cast files by scanning disk. Cast duration comes from the last frame's offset; video duration from ffprobe.
  • fix_syslog_years — same idea as fixing suricata, but for the inferred-year problem in syslog/auth.
  • validate — for each participant with a video, logs whether the panels have events inside the video window and whether the first terminal event lands near video start. Validation is advisory: it reports data-quality findings but never fails the import. A dataset with no video, sparse panels, or no data at all (an empty/skeleton archive) ends as empty with a precise reason, not error — the project's status comes from the real event count.

3. Analysis engine — analysis/

A pure package (see The analysis engine) that computes the digest from a scoped connection. build_digest() returns the full document; build_bundle() packages it as a downloadable .zip. Both accept an optional SQL filter, so the same engine serves a whole project or any slice (one scenario, one subject, one phase) without duplicated logic.

4. API + frontend — api/main.py, frontend/index.html (dashboard), frontend/palantir.html (Threat View)

The API is FastAPI. Query helpers route by scope: q() hits the main corpus, qp(project_id, …) routes to the project's own DB (404 on an unknown id — a typo never silently leaks combined data). Analysis endpoints (/digest, /export/bundle, /explore) build a scoped connection and hand it to the engine, composing optional sub-scope filters (scenario, participant, phase, host, tool, technique) via a column allowlist with parameterized values — injection-safe.

Two frontends, both single HTML files, no bundler/framework/node_modules:

  • index.html/dashboard — Mission Control. Renders the digest in one fetch as inline SVG (no CDN, air-gap safe). Root / redirects here.
  • palantir.html/threat — the synchronized forensic HUD. A master cursor (S.cursorEpoch) drives a video, a terminal cast, and six event panels; a 250ms tick keeps them aligned; panels refetch when the cursor moves >5s. OGV/Theora is transcoded to MP4 on a background thread (cached, real progress reported); in-flight transcodes are reaped on server shutdown so ffmpeg is never orphaned.

Tech stack

  • Python 3.11+
  • FastAPI + uvicorn
  • SQLite
  • ffmpeg / ffprobe (system binaries)
  • Vanilla HTML / CSS / JS — Mission Control is fully self-contained (inline SVG, no CDN, air-gap friendly); Threat View loads the asciinema player from a CDN for terminal replay

Pinned Python deps in requirements.txt. No JS package manager.


Install

git clone https://github.com/vidzza/SANN.git
cd SANN
pip install -r requirements.txt
sudo apt install ffmpeg

On macOS:

brew install ffmpeg

On Windows / WSL, the same apt install works inside WSL. Native Windows installs would need an ffmpeg binary on PATH.

Copy the env template:

cp .env.example .env

You can leave the defaults if you plan to upload data through the UI. If you have a dataset on disk you want to ingest from a local path, edit SANN_DATA_ROOT to point at it.


Running it

python3 -m uvicorn api.main:app --host 0.0.0.0 --port 8000

Open http://localhost:8000/ — it redirects to Mission Control (/dashboard). The Threat View HUD lives at /threat. Everything loads against an empty database on a fresh install — endpoints that need the events table return empty results rather than 500'ing, so you can confirm the server is up before you have any data.

To run on a different port, change --port. To bind only locally, use --host 127.0.0.1.


Loading data

Three ways. Pick whichever fits your workflow.

Option A — Import an archive through the HUD

This is the easiest path and the one I'd hand to someone testing the platform for the first time.

  1. Pack your dataset folder into a .zip, .tar, .tar.gz (or .tgz / .tar.bz2). The archive root should contain one or more user<id>/... subdirectories matching the Dataset layout section below.
  2. Open http://localhost:8000/threat and click ⊕ IMPORT in the top bar.
  3. Fill in the project name (e.g. P032), optionally the attacker IPs (comma-separated), pick the archive, hit Import.
  4. The dialog polls every 3s. The status line walks through Uploading…Ingesting… <N> events so farDone — <N> events. Time depends on size; a typical session takes 30 seconds to a few minutes.
  5. The new project appears in the PROJECT dropdown and you can start scrubbing.

Behind the scenes the API extracts the archive into data/uploads/dataset_<id>/, runs the full pipeline against it in a background thread (ingest plus all the post-processing steps), then syncs media so the video and casts show up. A pipeline crash marks the project error; an archive with no data files ends empty with a precise reason (see Troubleshooting).

Option B — Create a project from a path on disk (dashboard UI)

If your dataset is already extracted somewhere on disk (or it's a big archive you don't want to re-upload), use the dashboard:

  1. Open http://localhost:8000/ (the dashboard, not /threat).
  2. In the Create Project card, choose dataset, fill in the project name, and paste the absolute path into Data directory path. The path can point at either:
    • a directory laid out like the Dataset layout section below, or
    • a .zip / .tar / .tar.gz archive — the server extracts it into data/uploads/dataset_<id>/ for you.
  3. Click Create, then hit Ingest on the new project card to run the pipeline.

The path you paste must resolve under one of the allowed roots (configurable via SANN_ALLOWED_DATA_ROOTS, defaults to SANN_DATA_ROOT's parent, $HOME, /mnt, /tmp, and data/uploads/). WSL-style paths copied from Windows Explorer (\\wsl.localhost\<distro>\... or C:\...) are translated automatically.

Option C — Ingest from the CLI

Faster for large datasets in scripted workflows.

# Edit .env so SANN_DATA_ROOT points at your dataset
python3 import_manager.py

This processes the default dataset and writes to data/sann.db.

For an isolated project (same as what the dashboard does internally):

python3 import_manager.py \
  --data-root /path/to/P032 \
  --db data/project_9c4cee20.db \
  --project-id 9c4cee20 \
  --main-db data/sann.db \
  --attacker-ips "128.16.11.9,114.0.194.2"

If you've already ingested and just want to re-apply the post-processing passes (after editing classifier rules, for example):

python3 import_manager.py --skip-ingest

Mission Control

/dashboard is where you start. Pick a project in the scope selector (top left) and the whole analysis renders in one surface. Three tabs: Mission Control, Explore, Projects.

Top to bottom, Mission Control shows:

  • Dataset header — the cohort (P032), the subjects, how many scenario runs, how many corrections applied.
  • Sub-scope bar — scenario / identity / phase dropdowns that re-scope every panel below to a slice, live. A "Compare slices" button puts two scenarios or two subjects side by side (events, kill-chain coverage, detection coverage, MTTD, tempo, techniques, blind spots).
  • KPIs + data quality — event/participant/scenario/host counts, a completeness score, and the two coverage figures: kill-chain (N/14 tactics) and detection (N/M phases).
  • Kill-chain progression — the 14 tactics as a segmented bar, width ∝ volume, undetected phases flagged. Click a phase to drill in (its techniques, its commands, detection status).
  • Subject & Scenario Runs — each run with its subject identity; click to focus the analysis.
  • Attack narrative and detection efficacy (per-phase time-to-detect + the blind-spot list).
  • Activity timeline (stacked by phase), host roles (red vs blue), participant cards (click for the full dossier: profile, sessions, top commands/tools, phases, link to Threat View), top ATT&CK techniques, alerts, network.

Click any participant card or kill-chain phase to open a drill-down drawer. The Explore tab is a form over the research API — group by any dimension (phase, tool, host, technique, …), pick metrics, filter, and download CSV for pandas/Jupyter.

Threat View

/threat is the synchronized forensic HUD — the right tool when you want to watch a session rather than analyse it in aggregate. Four areas:

Top bar. PROJECT picks the dataset, PARTICIPANT picks the identity, ⊕ IMPORT opens the upload modal, and the row of pills on the right is the MITRE ribbon — the dominant tactic at the cursor lights up.

Media zone. Switchable between VIDEO (screen recording) and TERMINAL CAST (asciinema replay), kept in sync. OGV is transcoded to MP4 on a background thread and cached, so seeking is native once it's ready; a progress bar shows the transcode advancing.

Master scrubber. A heatmap of event density across the session, colored by dominant tactic per bucket. Drag anywhere to jump. Dark bars mark where video and casts cover the timeline.

Six event panels, each a ±120s window around the cursor:

Panel Sources
TERMINAL terminal_recording
AUTH auth
SYSLOG syslog
NETWORK suricata, bt_jsonl
KEYLOGGER uat
PCAP zeek, sensor

Panels show X of Y (last 200) when the window has more, and preserve your scroll position across refetches. The PCAP panel drops L2 sensor_packet noise. Errors appear as a toast, never breaking the UI.


Curation — reclassifying data

Automated MITRE classification isn't perfect. If a command lands in the wrong phase — say POST /login gets tagged initial_access when you'd call it credential_access — you can fix it, without touching the raw ingested data.

In Mission Control, click a phase (or open a participant dossier), find the command, and hit ⚑ reclassify: pick the correct phase, add a note, apply. Every panel updates immediately, because the engine reads the corrected value. It's an auditable, reversible overlay:

  • The original value is snapshotted (attack_phase_original) before the first change — ground truth is never lost.
  • Every correction is recorded in an annotations table (what, when, how many events, your note).
  • The ⚑ Corrections button shows the audit log; each entry has a Revert that restores the original exactly.

Corrections are by command (one fix reclassifies all its events) or by single event, and are only available on dataset projects (each has its own isolated DB; filter projects share the corpus, so editing there is blocked to prevent cross-project leakage).


Exporting analysis

From the Projects tab or the Mission Control header:

  • ⬇ Digest (JSON) — the full analysis document. If you've sub-scoped in Mission Control, the download is that slice.
  • ⬇ Bundle (.zip) — a grouped, analyst-facing pack:
    • digest.json — the full engine document
    • summary.md — a human-readable report (overview, data quality, kill-chain story, detection efficacy + blind spots, top techniques, per-subject highlights)
    • attack_navigator_layer.json — load it in the MITRE ATT&CK Navigator to see observed techniques scored on the matrix
    • participants/<id>.{json,csv} — per-subject dossier + flat event log
    • phases/NN_<phase>.csv — events grouped by kill-chain phase
    • incident_timeline.csv — cross-subject chronology of significant events
    • scenarios/<name>/… — for a multi-scenario dataset, each scenario as its own sub-analysis
  • Raw event exportCSV / JSON / SQLite for the flat events rows.

All exports honour the same sub-scope filters as the dashboard, so what you download matches what you saw.


Multiple projects

Every project has its own SQLite at data/project_<id>.db. Switching projects in the dropdown tears down the current state — pauses the video, disposes the cast player, clears the panels, resets the cursor — and reloads everything from the new project.

Every event also gets written into the main corpus data/sann.db with project_id stamped. That means:

  • Queries scoped to a project (?project_id=da2c86c9) hit the project DB directly.
  • Queries without a project_id hit the corpus and see all projects combined.
  • Cross-project analysis is one SQL query.
  • Sharing a single project's data with someone means handing them one .db file.

Creating a project from a folder path via API:

curl -X POST http://localhost:8000/api/projects \
  -H "Content-Type: application/json" \
  -d '{
    "name": "P032",
    "project_type": "dataset",
    "data_path": "/path/to/P032",
    "attacker_ips": "128.16.11.9,114.0.194.2"
  }'

# then kick off ingest
curl -X POST http://localhost:8000/api/projects/<project_id>/ingest

Deleting a project (DELETE /api/projects/<id>) removes the project row, all events for that project in the main corpus, all media registry rows for that project, and the standalone project DB file.


Dataset layout

The ingester scans for files anywhere under user<id>/, so the layout in between is flexible. What it looks for:

dataset/
└── user<id>/
    └── <scenario>/<run>/<host>/
        ├── *.cast              # asciinema terminal recording
        ├── UAT-*.tsv           # keylogger / typed-text
        ├── auth.log            # SSH / sudo / PAM
        ├── syslog              # systemd / kernel
        ├── eve.json            # Suricata events
        ├── bt.jsonl            # honeytrap behavior
        ├── conn.log            # Zeek connections (JSONL)
        ├── sensor*.log         # raw sensor_packet
        ├── recording.ogv       # screen recording (or .webm)
        ├── *.pcap              # raw capture (metadata only)
        ├── hacktools.log
        └── apt.log

Nothing is mandatory. Missing files just leave their panel empty. The validation pass logs which panels have data for each participant.

The directory immediately under user<id>/ becomes the scenario_name — common names are training, ckc1, ckc2, ckc2c (kill-chain stages), but anything works.


Configuration

.env (loaded by the API, the ingester, and import_manager.py):

Variable Default Purpose
SANN_DATA_ROOT /tmp/obsidian_full/P003 Root of the default dataset on disk
SANN_DB_PATH data/sann.db Main corpus SQLite path
SANN_ATTACKER_IPS (P003 defaults) Comma-separated attacker IPs for C2 classification
SANN_CORS_ORIGINS localhost:8000,127.0.0.1:8000,localhost:3000 CORS allowlist
SANN_ALLOWED_DATA_ROOTS (auto: SANN_DATA_ROOT parent, $HOME, /mnt, /tmp, data/uploads) Extra root paths under which dashboard-supplied data_path values are accepted

API reference

Everything accepts ?project_id=<id> to scope to a single project. Without it, you query the combined corpus.

Health and projects

Method Path
GET /api/health
GET /api/projects
POST /api/projects
POST /api/projects/upload
GET /api/projects/{id}/status
POST /api/projects/{id}/ingest
POST /api/projects/{id}/sync_media
DELETE /api/projects/{id}
GET /api/projects/{id}/tree

Analysis, export and curation (the analysis surface — all accept sub-scope filters ?scenario=&participant=&phase=&host=&tool=&technique=)

Method Path Purpose
GET /api/projects/{id}/digest Full analysis document (JSON). ?download=true to save.
GET /api/projects/{id}/export/bundle Grouped analyst .zip (digest + summary + dossiers + per-phase CSVs + Navigator layer + per-scenario)
GET /api/projects/{id}/explore Group-by/pivot: ?dimensions=&metrics=&filters=&format=json|csv
GET /api/projects/{id}/export/{csv,json,sqlite} Raw event export
GET /api/projects/{id}/annotations List reclassification corrections (audit log)
POST /api/projects/{id}/annotations Reclassify a command/event's phase
DELETE /api/projects/{id}/annotations/{ann_id} Revert a correction

Events and stats

Method Path
GET /api/participants
GET /api/participants/{pid}
GET /api/participants/{pid}/phases
GET /api/participants/{pid}/commands
GET /api/participants/{pid}/timeline
GET /api/phases
GET /api/phases/{phase}
GET /api/timeline
GET /api/alerts
GET /api/network
GET /api/commands
GET /api/commands/top
GET /api/users
GET /api/hosts
GET /api/relationships
GET /api/behavior/{pid}
GET /api/search?q=<term>
GET /api/events/stream
GET /api/events/{event_id}
GET /api/stats
GET /api/stats/participant
GET /api/analysis/overview

Media and timeline

Method Path
GET /api/media
GET /api/media/list
GET /api/media/cast_list
GET /api/media/cast_raw/{media_id}
GET /api/media/cast/{media_id}
GET /api/media/video/{participant_id}?t=<seek>
GET /api/media/keylogger
GET /api/timeline/sync
GET /api/timeline/playback
GET /api/timeline/events
GET /api/timeline/cast/{media_id}
GET /api/timeline/uat/{media_id}
GET /api/timeline/pcap/{media_id}

Testing

There's a dependency-free smoke/regression harness that exercises the engine and the API against whatever real project data you have loaded, and reconciles the digest against direct SQL:

# engine tests only (no server needed)
python3 tests/smoke.py

# engine + live API tests
python3 tests/smoke.py --url http://localhost:8000

It checks that kill-chain / host-role / scenario-sub-scope numbers match SQL, that identities resolve, that the bundle carries every promised file, that sub-scope filters are injection-safe, and that empty projects are handled honestly. Exit code 0 = all passed. Run it after any change to the engine, the endpoints, or the ingester.


Security notes

The server is meant for localhost use. Even so:

  • CORS is restricted to the allowlist in SANN_CORS_ORIGINS. There is no wildcard.
  • Project creation validates that data_path resolves under the configured data root, so an API consumer can't ask the ingester to read /etc/passwd.
  • attacker_ips is regex-checked before it hits the subprocess argv, so shell-injection attempts get rejected with 400.
  • Every limit parameter is clamped at 10000.
  • Filesystem paths are stripped from API responses; only filenames and stable media IDs are exposed.
  • Invalid project_id returns 404 instead of silently falling back to the main corpus.
  • Project deletion validates the project DB path resolves under data/ before unlinking, so a tampered db_path field can't delete arbitrary files.
  • Subprocesses use sys.executable instead of a hardcoded python3, so the active venv is honored.

If you need remote access, tunnel over SSH. Don't expose the port directly.


Troubleshooting

The video pane shows "TRANSCODING…" forever. ffmpeg isn't installed or isn't on PATH. Install it (sudo apt install ffmpeg) and reload. After 30 seconds without a frame, the overlay times out and shows an error toast.

A panel is empty when I expect data. Make sure the cursor is inside the video window. Panels filter to ±120s of the cursor. The import_manager.py validation pass logs the panel-coverage matrix for every participant — rerun with --skip-ingest to see it again.

Suricata events look hours off. DB ingested before the timezone fix. Run python3 import_manager.py --skip-ingest to rewrite the stored timestamps. The detection works off the offset in the raw eve.json.

ZIP uploaded but no media shows up. Project status is ready but the media registry is empty. Hit POST /api/projects/{id}/sync_media manually. The upload flow does this automatically; if it fails, the project still gets marked ready since the events are there.

CORS errors in the browser console. Add your origin to SANN_CORS_ORIGINS in .env and restart.

HTTP 404 from /api/participants?project_id=.... The project ID doesn't exist. List with GET /api/projects. The 404 is intentional — a typo doesn't silently leak combined-corpus data.

Ingest seems to do nothing. The API runs ingest as a subprocess and writes its log to data/logs/ingest_<id>.log. Read that, or run the same import_manager.py command from the CLI to see the output live.

My project imported but shows empty / no analysis. The archive has no ingestable data. Mission Control (and the project card) will say exactly why — e.g. "Archive contains only empty folders (20 directories, 0 data files)." This usually means a broken export: a healthy dataset archive is hundreds of MB to several GB, so if yours is only a few KB it's just a folder skeleton. Re-export the dataset with its contents and create the project again.


Project structure

SANN/
├── api/
│   └── main.py            # FastAPI server (analysis, media, projects, curation)
├── analysis/              # the analysis engine (pure, no web imports)
│   ├── __init__.py        #   build_digest() / build_bundle() orchestration
│   ├── common.py          #   shared helpers + reference data (kill-chain, tools)
│   ├── metrics.py         #   summary, scenarios, identities, kill_chain, participants…
│   ├── narrative.py       #   attack narrative + session reconstruction
│   ├── detection.py       #   detection efficacy (blue-team) + blind spots
│   ├── ttp.py             #   MITRE/TTP depth + ATT&CK Navigator layer
│   ├── hosts.py           #   host-role classification (attacker/pivot/sensor/…)
│   └── exporters.py       #   dossier / phase / timeline / summary.md / zip builders
├── frontend/
│   ├── index.html         # Mission Control dashboard (served at /dashboard, / redirects here)
│   ├── palantir.html      # Threat View HUD (served at /threat)
│   └── timeline.html      # legacy (unused)
├── tests/
│   └── smoke.py           # engine + API regression harness
├── data/                  # SQLite DBs + uploads land here (gitignored)
├── ingest_v2.py           # parsers + MITRE classifier
├── import_manager.py      # post-processing + advisory validation
├── requirements.txt
├── .env.example
└── README.md

Internal tool. Not for redistribution without permission.

About

cybersecurity tool to sync network logs, keylogers, sys logs and video

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages