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.
| 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. |
- 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.
# Clone the repository
git clone https://github.com/yourusername/tinysiem.git
cd tinysiem
# Run the app (handles everything automatically)
.\run.ps1That's it. The script will:
- β¬οΈ Request Administrator privileges (UAC prompt)
- π¦ Create a Python virtual environment if one doesn't exist
- π₯ Install dependencies from
requirements.txtβ but only as part of step 2 - π Launch the Flask server on
http://localhost:5000 - π₯οΈ 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.
-
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.
-
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.
-
Process Tree β click Tree for parent/child relationships derived from Sysmon Event ID 1, either as List View or a pannable, zoomable Graph View.
-
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).
-
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.
-
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.
-
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.
-
Restart β click Restart to reconfigure the monitor (change count/timeframe) without a full page reload.
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.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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β¦) β β
β ββββββββββββ ββββββββββββββββ ββββββββββββ βββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SysmonMonitorreads historical events and subscribes to new ones viawin32evtlog.- Parsed events are placed into a thread-safe queue.
- A distributor thread moves events from the queue to
ClientManager. ClientManagerbroadcasts to all connected SSE clients and maintains a history ring buffer so new clients receive recent events immediately.
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.ps1Note
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.
# 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 serverNote
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.
pip install -r requirements-dev.txt
python -m pytest # unit tests
python -m ruff check . # lintThe 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.
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| 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. |
This project is licensed under the MIT License.