Skip to content

Repository files navigation

Platform Python Flask License

πŸ›‘οΈ tinySIEM

A lightweight, real-time Sysmon event monitor and web dashboard for Windows.

tinySIEM reads from the Windows Event Log (Microsoft-Windows-Sysmon/Operational), streams events via Server-Sent Events to a modern browser-based dashboard, and lets you filter, search, and analyze system activity in real time.


✨ Features

Feature Description
Real-time Streaming Events are pushed to the browser via SSE β€” no polling, no refresh needed.
Interactive Event Table Paginated table with adjustable row density and a click-to-lock tooltip showing every field of an event.
Column Filters Value pickers on Event ID, Process, User, and Event Info, plus a From/To time range on the Time column.
Inclusion / Exclusion Filters Rule sets that show only matching events, or hide them. Rules within a set are OR-combined, exclusions win over inclusions, and both survive a reload.
Event Volume Chart Live Chart.js volume strip β€” expandable, with a Detailed mode for finer time buckets and click-a-bar to filter to that event ID.
Process Tree Parent/child relationships built from Sysmon Event ID 1, as an indented list or a pannable D3 graph.
Statistics Event ID distribution pie chart and a per-image event count table, colour-matched to the volume chart.
Dark / Light Mode Toggle between themes, applied before first paint so there's no flash.
Quick Start Modal Nothing is read until you configure how much history to load β€” by event count, timeframe, or an explicit date range β€” with a progress bar while it loads.
Restart Monitor Reconfigure and restart the monitor without reloading the page.
Persisted Preferences Theme, row density, rows per page, chart state, live mode, and filter rules are all remembered.
One-Click Launch run.ps1 handles admin elevation, venv creation, dependency install, and browser launch.

πŸ“‹ Prerequisites

  • Windows 10 / 11 / Server β€” relies on the Windows Event Log API.
  • Sysmon installed and running.
  • Python 3.8+ β€” installed and available in your PATH.
  • Administrator Privileges β€” required to read the Sysmon event channel.

πŸš€ Quick Start

# Clone the repository
git clone https://github.com/yourusername/tinysiem.git
cd tinysiem

# Run the app (handles everything automatically)
.\run.ps1

That's it. The script will:

  1. ⬆️ Request Administrator privileges (UAC prompt)
  2. πŸ“¦ Create a Python virtual environment if one doesn't exist
  3. πŸ“₯ Install dependencies from requirements.txt β€” but only as part of step 2
  4. 🌐 Launch the Flask server on http://localhost:5000
  5. πŸ–₯️ Open your default browser after 5 seconds

Tip

On subsequent runs, the venv is reused β€” startup is near-instant.

Note

Because the pip install runs only when the venv is first created, an existing venv is never re-checked. After pulling a change that adds a dependency, delete venv\ (or run pip install -r requirements.txt inside it) to pick it up.


πŸ–₯️ Usage

  1. Start Monitoring β€” the app launches with an empty dashboard and reads nothing until you pick a range in the Quick Start modal:

    • Count β€” load the last N events (default: 500)
    • Timeframe β€” load events from the last N hours or days
    • Specific Date β€” load events between an explicit from and to timestamp

    A progress bar tracks the historical read; live events start streaming as soon as it finishes.

  2. Filter Events β€” two complementary mechanisms:

    • Click the ⏷ filter icon on any column header for a value picker (Event ID, Process, User, Event Info) or a From/To range on Time.
    • Click Filters in the nav bar for the rule editor. Inclusions show only events matching a rule; Exclusions hide them. Pick a field, choose values from the loaded events or type a free-text rule, and the summary line reports how many events are hidden. The badge on the button counts active rules, and Reset Filters inside the modal clears both rule sets and the column filters.
  3. Process Tree β€” click Tree for parent/child relationships derived from Sysmon Event ID 1, either as List View or a pannable, zoomable Graph View.

  4. Statistics β€” click Stats for an Event IDs distribution pie chart, or switch to the Image view for event counts per process image (filterable by image name and scopable to a single event ID).

  5. Chart β€” toggle the volume strip on/off with the Chart switch, expand it to full height with the β€’ button, flip Detailed for finer time buckets, and click a bar to filter the table to that event ID.

  6. Live Mode & Refresh β€” the Live toggle pauses the stream. While paused, incoming events queue up and a Refresh button appears with the pending count; click it to flush them into the table.

  7. Status Bar β€” along the bottom: rows per page (25–500, default 100), the total event count, the pager, row density (Compact / Cozy / Comfortable), and the last-updated time.

  8. Restart β€” click Restart to reconfigure the monitor (change count/timeframe) without a full page reload.


πŸ—οΈ Architecture

tinySIEM
β”œβ”€β”€ server/                # Python backend package
β”‚   β”œβ”€β”€ __init__.py        # Flask app factory
β”‚   β”œβ”€β”€ __main__.py        # Entry point (python -m server)
β”‚   β”œβ”€β”€ config.py          # Configuration (env-var overridable)
β”‚   β”œβ”€β”€ monitor.py         # Sysmon event reader & subscriber (win32evtlog)
β”‚   β”œβ”€β”€ history_spec.py    # Pure parsing of history requests & timestamps
β”‚   β”œβ”€β”€ event_parser.py    # XML β†’ dict event parser
β”‚   β”œβ”€β”€ client_manager.py  # SSE client manager with broadcast & history buffer
β”‚   β”œβ”€β”€ monitor_service.py # Owns the monitor + distributor threads
β”‚   β”œβ”€β”€ routes.py          # Flask routes (Blueprint factory)
β”‚   └── utils.py           # Admin privilege check utilities
β”œβ”€β”€ tests/                 # pytest suite (runs without Windows/Sysmon)
β”œβ”€β”€ run.ps1                # One-click launcher (elevation + venv + deps)
β”œβ”€β”€ pyproject.toml         # Dependencies, ruff & pytest config (single source of truth)
β”œβ”€β”€ requirements.txt       # pip entry point for runtime deps (`-e .`)
β”œβ”€β”€ requirements-dev.txt   # pip entry point for dev + build tools (`-e .[dev,build]`)
β”œβ”€β”€ build.ps1              # Runs every packaging backend in turn
β”œβ”€β”€ build-lib.ps1          # Shared helpers for the build_*.ps1 scripts
β”œβ”€β”€ build_cx.ps1           # cx_Freeze backend
β”œβ”€β”€ build_pyinstaller.ps1  # PyInstaller backend
β”œβ”€β”€ build_nuitka.ps1       # Nuitka backend
β”œβ”€β”€ setup.py               # cx_Freeze build definition (used only by build_cx.ps1)
β”œβ”€β”€ templates/
β”‚   β”œβ”€β”€ index.html         # Dashboard SPA (Tailwind CSS + Chart.js)
β”‚   └── _macros.html       # Reusable markup (toggles, filter dropdowns, modals)
└── static/
    β”œβ”€β”€ css/style.css      # Component classes (.nav-btn, .toggle-track, .modal-*, .filter-row)
    β”œβ”€β”€ js/
    β”‚   β”œβ”€β”€ app.js         # Composition root β€” creates the modules and wires them together
    β”‚   β”œβ”€β”€ core/          # State and data, no interaction concerns
    β”‚   β”‚   β”œβ”€β”€ store.js       # Event list, filter criteria, pagination (memoized filtering)
    β”‚   β”‚   β”œβ”€β”€ utils.js       # Timestamps, escaping, event field accessors
    β”‚   β”‚   β”œβ”€β”€ constants.js   # Sysmon field map, palette, chart label helpers
    β”‚   β”‚   β”œβ”€β”€ storage.js     # Guarded localStorage β€” never throws at a caller
    β”‚   β”‚   β”œβ”€β”€ theme.js       # Dark-mode toggle + change notifications
    β”‚   β”‚   β”œβ”€β”€ density.js     # Row density (compact / cozy / comfortable)
    β”‚   β”‚   └── dom.js         # id-map -> element-map resolution
    β”‚   β”œβ”€β”€ ui/            # Reusable interaction patterns
    β”‚   β”‚   β”œβ”€β”€ modal.js       # Open/close, Escape and backdrop dismissal
    β”‚   β”‚   β”œβ”€β”€ tabs.js        # Segmented controls, selection via aria-selected
    β”‚   β”‚   β”œβ”€β”€ dropdown.js    # One-open-at-a-time panels with outside-click close
    β”‚   β”‚   β”œβ”€β”€ elements.js    # Safe DOM builders (textContent, never innerHTML)
    β”‚   β”‚   └── chart-base.js  # Shared Chart.js options and hit-testing
    β”‚   └── *.js           # Features: chart, events, table, tooltip, startup, stats,
    β”‚                      #   processtree, filters, filter-engine, filter-modal,
    β”‚                      #   rules, inclusions, exclusions
    └── lib/               # Vendored libraries (Tailwind, Chart.js, D3.js, Font Awesome, Google Fonts)

Note

The frontend has no build step β€” plain ES modules loaded directly by the browser, with every library vendored under static/lib/. index.html loads exactly one script (js/app.js); everything else arrives through imports.

Data Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Windows Event Log                                                         β”‚
β”‚  Microsoft-Windows-Sysmon/Operational                                      β”‚
└──────────────┬───────────────────────────────────┬───────────────────────── β”˜
               β”‚ Historical events                 β”‚ New events (subscription)
               β”‚ (EvtQuery + EvtNext)              β”‚ (EvtSubscribe callback)
               β–Ό                                   β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  SysmonMonitor  (monitor.py)                                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚
β”‚  β”‚  read_past_events() β”‚    β”‚  _on_event() callback    β”‚                   β”‚
β”‚  β”‚  (count / timeframe)β”‚    β”‚  (real-time push)        β”‚                   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                 β”‚
β”‚                           β–Ό                                                β”‚
β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                    β”‚
β”‚              β”‚  EventParser.parse()   β”‚                                    β”‚
β”‚              β”‚  XML ──► Python dict   β”‚                                    β”‚
β”‚              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚  Thread-Safe Queue     β”‚
               β”‚  (maxsize=5000)        β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Distributor Thread  (monitor_service.py)                                  β”‚
β”‚  Moves events from queue ──► ClientManager                                 β”‚
└───────────────────────────┬──────────────────────────────────────────────── β”˜
                            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  ClientManager  (client_manager.py)                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚
β”‚  β”‚  History Buffer   β”‚    β”‚  Broadcast to connected clients        β”‚       β”‚
β”‚  β”‚  (deque, last 500)β”‚    β”‚                                        β”‚       β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚  Client 1 Queue ──► SSE /stream ──► 🌐 β”‚       β”‚
β”‚                          β”‚  Client 2 Queue ──► SSE /stream ──► 🌐 β”‚       β”‚
β”‚                          β”‚  Client N Queue ──► SSE /stream ──► 🌐 β”‚       β”‚
β”‚                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Browser Dashboard  (index.html + JS modules)                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ EventSrc β”‚  β”‚ Event Table  β”‚  β”‚ Chart.js β”‚  β”‚ Filters / Search      β”‚  β”‚
β”‚  β”‚ listener │─►│ (paginated)  β”‚  β”‚ (volume) β”‚  β”‚ (ID, Process, User…)  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  1. SysmonMonitor reads historical events and subscribes to new ones via win32evtlog.
  2. Parsed events are placed into a thread-safe queue.
  3. A distributor thread moves events from the queue to ClientManager.
  4. ClientManager broadcasts to all connected SSE clients and maintains a history ring buffer so new clients receive recent events immediately.

βš™οΈ Configuration

Every setting has a safe default and can be overridden with an environment variable β€” no code edit required. Defaults live in server/config.py.

Variable Default Description
TINYSIEM_HOST 127.0.0.1 Interface to bind. Loopback only by default.
TINYSIEM_PORT 5000 Web server port.
TINYSIEM_DEBUG false Flask debug mode.
TINYSIEM_LOG_TYPE Microsoft-Windows-Sysmon/Operational Event channel to read.
TINYSIEM_HISTORY_SIZE 500 Events kept in memory and replayed to new clients.
TINYSIEM_QUEUE_SIZE 5000 Internal monitor queue capacity.
# Example: expose on the LAN
$env:TINYSIEM_HOST = '0.0.0.0'
.\run.ps1

Note

run.ps1 always prints and opens http://localhost:5000. It doesn't read TINYSIEM_HOST / TINYSIEM_PORT, so if you override either, browse to the right address yourself β€” the auto-opened tab will land on the old one.

Warning

TINYSIEM_DEBUG=true enables the Werkzeug interactive debugger, which allows arbitrary code execution by anyone who can reach the port. tinySIEM runs elevated, so never combine it with a non-loopback TINYSIEM_HOST.


πŸ› οΈ Development

# Manual setup (if not using run.ps1)
python -m venv venv
.\venv\Scripts\Activate
pip install -r requirements.txt

# Run with auto-reload disabled (required for threading)
python -m server

Note

The Flask reloader is intentionally disabled (use_reloader=False) because the app uses background threads for event monitoring. Hot-reload would restart those threads unexpectedly.

Note

Dependencies are declared once, in the [project] and [project.optional-dependencies] tables of pyproject.toml. The two requirements*.txt files are thin -e . pointers at those tables, kept so the familiar pip install -r command still works.

Tests & linting

pip install -r requirements-dev.txt
python -m pytest        # unit tests
python -m ruff check .  # lint

The test suite covers the pure backend logic β€” history-request parsing for all three modes (count, timeframe, and explicit date range), timestamp normalization, event XML parsing, and the SSE client manager including its handling of control messages, which are delivered live but never replayed from history. It stubs out win32evtlog, so it runs on any platform without Sysmon installed.


πŸ“¦ Building a Standalone Executable

The project includes build scripts for packaging as a standalone .exe:

Script Tool Artifact
build_pyinstaller.ps1 PyInstaller build\tinysiem_pyinstaller.exe
build_nuitka.ps1 Nuitka build\tinysiem_nuitka.exe
build_cx.ps1 cx_Freeze build\tinysiem_cx\tinysiem.exe

Run .\build.ps1 to build all three, or a single script to build just that one. Shared logic (interpreter discovery, result reporting) lives in build-lib.ps1, which the backend scripts dot-source.

pip install -r requirements-dev.txt   # installs the packaging tools
.\build.ps1

πŸ› Troubleshooting

Problem Solution
UAC prompt denied Right-click run.ps1 β†’ Run with PowerShell, then accept the elevation prompt.
"Python is not installed or not in PATH" Install Python 3.8+ and ensure Add to PATH is checked.
"Server package not found" run.ps1 must sit next to the server\ directory β€” run it from the project root, not a copy.
No events appear Verify Sysmon is installed and running: sc query Sysmon or sc query Sysmon64.
Port 5000 already in use Set $env:TINYSIEM_PORT to a free port, or stop the process using 5000.
Dashboard unreachable from another machine By default the server binds loopback only. Set $env:TINYSIEM_HOST = '0.0.0.0' to expose it.
pip install fails with connection error Check your internet connection. A firewall or proxy may be blocking PyPI.

πŸ“„ License

This project is licensed under the MIT License.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages