# MarlinSpike, Full Documentation (English)

> The maintained, multi-user, web-based successor to NSA's GrassMarlin. The original tool reached end-of-life in 2017; CISA disclosed CVE-2026-6807 (XXE in v3.2.1) on 28 April 2026 with no patch coming. MarlinSpike picks up the role: open-source passive OT/ICS topology workbench, captures in, zero packets out, multi-user web workbench for shared OT engagements with Purdue-level inference, vendor fingerprinting, asset-context-driven contextual severity, IOC threat-hunting across reports, live MITRE ATT&CK matrix, bilingual EN/FR UI, and portable JSON report artifacts as the engine ↔ workbench contract.

Source: https://grassmarlin.com/llms-full.txt
Index:  https://grassmarlin.com/llms.txt
Site:   https://grassmarlin.com/
French: https://grassmarlin.com/fr/llms-full.txt

This file is the concatenation of every public documentation page. Per-page markdown is also available at `https://grassmarlin.com/wiki/<slug>.md` and `https://grassmarlin.com/<slug>.md`.

---

# Site

## About MarlinSpike

*Source: https://grassmarlin.com/about*

_Passive OT/ICS topology mapper and analyst workbench. The modern GrassMarlin, built for shared engagements. Open-source core behind Fathom._

MarlinSpike is a passive OT/ICS topology mapper and analyst workbench. The product takes packet captures in, sends no traffic back into the environment, and turns passive observations into topology, asset inventory, responder-grade findings, and portable JSON report artifacts. It is the open-source core behind Fathom and is intentionally built as a shared web workbench rather than a single-user thick client.

## Lineage

MarlinSpike picks up where GrassMarlin left off. Same first principle, passive OT/ICS visibility from packet captures alone, but rebuilt for the way responders actually work today: a shared web workbench instead of a single-user thick client, a portable JSON report contract instead of a session-bound view, and a multi-stage extensibility model that takes Rust engines, Python plugins, and YAML rule packs.

We're not a fork; we're a successor. The product is independent code, ground-up architecture, and a deliberate alignment with what GrassMarlin originally promised the OT community: passive analysis, vendor-neutral protocol coverage, and tooling that respects the operational reality of the plant floor.

Key characteristics: Passive only · Multi-user workbench · Portable JSON report · OT-native protocol coverage · Open source

## Product boundary

MarlinSpike keeps the engine standalone and treats the generated report artifact as the handoff between packet analysis and downstream review.

```
Project → Scan → Report → Workbench → Triage
```

The preferred install path is a reverse-proxied Docker Compose deployment that multiple responders can share during an assessment, outage investigation, or tabletop.

## 5-stage analysis chain

The analysis pipeline stays intentionally legible: ingest and validation, protocol dissection, topology building, risk surfacing, and report generation.

- **Stage 1, Ingest**: Capture file validation and metadata extraction.
- **Stage 2, Dissect**: OT protocol parsing, L2 discovery, and conversation extraction.
- **Stage 3, Topology**: Node and edge graph, Purdue placement, vendor fingerprinting, role assignment.
- **Stage 4, Risk**: Cross-zone issues, suspicious external communications, beaconing, DNS entropy, MITRE ATT&CK mapping.
- **Stage 5, Report**: Portable JSON artifact consumed by the workbench and downstream tooling.

## Protocol coverage

MarlinSpike is built around industrial protocol visibility, then enriches that with network-discovery context so infrastructure relationships are not thrown away.

**OT / ICS**: Modbus, EtherNet/IP, CIP, S7comm, DNP3, IEC 60870-5-104, OPC-UA, BACnet, PROFINET, HART-IP, FINS, GOOSE, MMS, OMRON

**Layer 2 / discovery**: LLDP, CDP, STP, LACP, ARP, VLAN

## Standards support

The public story stays bounded to what the platform actually exposes today. MarlinSpike supports standards-oriented review without pretending to be a broader compliance suite.

**IEC 62443**, Stage 4 remediation guidance is framed around IEC 62443 SR-oriented remediation support for supported finding classes.

**MITRE ATT&CK**, Full ATT&CK implementation in the report workflow including tactic-grouped matrix views, sub-techniques, mitigations, and response guidance, for both ICS and Enterprise domains.

**Purdue / ISA-95**, ISA-95 and Purdue-style zoning remain central to topology layout, asset placement, and cross-level communication review.

## Architecture overview

MarlinSpike is intentionally extensible for working OT/ICS responders, not just systems programmers. Three formal extension surfaces cover the breadth of customization.

**Rust engines**, Packet-facing and event-heavy components such as DPI. The standalone `marlinspike-dpi` substrate ships 34 protocol dissectors and is built into the Docker image at a pinned ref.

**Python plugins**, Report-facing analysis, enrichment, and triage logic. The MITRE ATT&CK plugin, ARP analysis plugin, and APT plugin all live behind this surface and are loaded by module name from env.

**YAML rule packs**, Declarative mappings, suppressions, and local policy. Default packs ship under `rules/<plugin>/base.yaml`; per-deployment overrides via env vars.

Continue with the [wiki](/wiki/) for deployment, architecture, and the report contract, or go to [downloads](/downloads/) for the official repo and package path.

---

## MarlinSpike Downloads

*Source: https://grassmarlin.com/downloads*

_Official repository, source archives, Docker deployment path, and current binary status, in one place._

This page only lists official MarlinSpike distribution surfaces with real links. You should be able to find the repository, source snapshots, deployment path, and the current binary status without guessing which channels are public yet.

## Hosted MarlinSpike, cloudmarlin.com

The fastest way to use MarlinSpike without standing up your own deployment is the hosted version at [cloudmarlin.com](https://cloudmarlin.com). Same engine, same workbench, no Docker stack to install. Sign up, upload a pcap, work the project with your team. The rest of this page is for self-hosting.

## Official repository

These are the main public places to browse code, installation notes, and release history.

**Source repository** [available], The public GitHub repository is the canonical source code surface for MarlinSpike, including the license, issues, and code history. [https://github.com/eris-ot/marlinspike](https://github.com/eris-ot/marlinspike)

**Docs and wiki** [available], Product docs, deployment guidance, architecture notes, and release references. [/wiki/](/wiki/)

**Install guide** [available], Repository-side installation notes for operators who want the checked-in setup and environment details. [INSTALL.md](https://github.com/eris-ot/marlinspike/blob/main/INSTALL.md)

**Version history** [available], Engine and web UI release notes are tracked in the repository until tagged binary releases are published. [releases.md](https://github.com/eris-ot/marlinspike/blob/main/releases.md)

## Source downloads

If you want MarlinSpike right now without waiting for packaged installers, these are the official source-level download surfaces and component repos that resolve today.

**Main branch zip archive** [available], Download the current main branch as a zip snapshot when you want the source tree directly. [Download zip](https://github.com/eris-ot/marlinspike/archive/refs/heads/main.zip)

**Main branch tarball** [available], Grab the current main branch as a tar.gz source archive for Unix-friendly environments and scripted fetches. [Download tar.gz](https://github.com/eris-ot/marlinspike/archive/refs/heads/main.tar.gz)

**Standalone DPI engine source** [available], The packet-facing Rust DPI engine is published separately as marlinspike-dpi for teams that want that parser surface on its own. [https://github.com/eris-ot/marlinspike-dpi](https://github.com/eris-ot/marlinspike-dpi)

## Official packages

MarlinSpike is not pretending to ship native package-manager artifacts yet. The official package surface today is the checked-in Docker deployment path and the files that back it.

**Docker Compose deployment** [available, official path], The currently supported package and install path is source plus Docker Compose for server and lab deployments. See [deployment docs](/wiki/deployment/).

**docker-compose.yml** [available], Review the checked-in Compose manifest used as the baseline deployment for the web application and PostgreSQL. [View compose file](https://github.com/eris-ot/marlinspike/blob/main/docker-compose.yml)

**Docker build recipe** [available], Inspect the multi-stage Docker build that compiles the protected Python modules and assembles the runtime image. [View Dockerfile](https://github.com/eris-ot/marlinspike/blob/main/Dockerfile)

**Package registries** [not published], There are no official Homebrew, apt, yum, winget, Chocolatey, MSI, or similar package registry artifacts published right now.

## Official binaries

The releases surface is real and public, but binary artifacts are not published there yet.

**GitHub releases page** [available], The official release surface already exists on GitHub, but it does not currently contain tagged binary artifacts. [https://github.com/eris-ot/marlinspike/releases](https://github.com/eris-ot/marlinspike/releases)

**Standalone binaries** [not published], No public Linux, macOS, or Windows standalone binaries are published today. The public install path remains source plus Docker.

**OS installers and packages** [not published], There are no signed desktop installers or OS-native packages published yet for direct host installation.

**Checksums and signatures** [not published], Checksum and signature files are not published yet because there are no public binary release artifacts to verify.

## Official install path

Docker Compose is the published package path today. MarlinSpike is a shared web workbench, so the supported install story is still a Git clone plus Docker Compose on a field host, temporary VM, lab server, or internal staging system behind a reverse proxy.

```
git clone https://github.com/eris-ot/marlinspike.git
cd marlinspike
cp .env.example .env
docker compose up -d --build
```

## Current public state

What you can and cannot get right now:

- Official repo is live
- Zip + tarball archives are live
- Docker path is official
- Releases page is real
- No binary assets yet
- No native package-manager artifacts yet

The wiki covers deployment, architecture, the report artifact contract, and the broader project structure. See [/wiki/](/wiki/).

---

## GlassMarlin, The successor to GrassMarlin

*Source: https://grassmarlin.com/glassmarlin*

_One file. PCAP in. Full OT/ICS triage workbench out. No Wireshark, no Python, no Docker, no internet. Defender-on-a-laptop tooling for engagements where the host has nothing._

GlassMarlin is the desktop successor to GrassMarlin. **One file. PCAP in. Full OT/ICS triage workbench out. No Wireshark required. No Python install. No Docker. No internet. No team server.** Defender-on-a-laptop tooling for OT engagements where the host has nothing.

Source repository: [github.com/eris-ot/glassmarlin](https://github.com/eris-ot/glassmarlin).

## Lineage

GrassMarlin was the NSA-released OT topology mapper that field defenders quietly carried on engagement laptops for years. It worked, until it didn't. Abandoned in 2017, Java-bound, single-platform, no longer maintained. [CVE-2026-6807](https://nvd.nist.gov/vuln/detail/CVE-2026-6807) (April 2026) made it actively unsafe to keep using.

GlassMarlin picks up where it left off. Same defender utility, modernised, cross-platform, with the full risk + MITRE ATT&CK + IOC + baseline + sub-PCAP-carve stack on top of topology mapping. Same drop-it-on-a-laptop spirit. Zero external dependencies. Full lineage on the [GrassMarlin heritage page](/wiki/heritage.md).

## No external dependencies. Period.

Every dependency ships inside the binary:

- **Native bundle per OS.** GlassMarlin.msi for Windows. GlassMarlin.dmg for macOS (signed, Gatekeeper-clean). GlassMarlin.AppImage for Linux (any glibc 2.28+ host).
- **Pure-Rust PCAP dissection.** No libpcap, no Npcap, no Wireshark install, no `tshark` shell-out, no `editcap`. `marlinspike-dpi` handles parsing and time-window carve-out natively in Rust.
- **Python runtime baked in.** Bundled via `python-build-standalone`. No `pip install`, no venv, no system Python.
- **Embedded SQLite, no DB server.** Everything that grassmarlin.com stores in Postgres, GlassMarlin keeps in an embedded SQLite file in the per-user data directory.
- **No internet required, ever.** No telemetry, no license check, no MITRE updates fetched at runtime. The ATT&CK runtime and plugin packs are baked into the binary. Runs in SCIFs, bunkers, and on flights.
- **SIEM-ready exports.** Every scan emits report.json + OCSF NDJSON + STIX 2.1 + Sigma rules + ATT&CK Navigator layer JSON, alongside the workbench view.

## What it does

GlassMarlin is the full MarlinSpike triage stack on a binary, not just topology:

- **Topology + asset fingerprinting.** Purdue-level inference, vendor fingerprinting, asset role detection. 30+ OT protocol dissectors: Modbus, S7, DNP3, IEC 60870-5-104, EtherNet/IP, OPC UA, BACnet, PROFINET, OMRON FINS, HART-IP, EtherCAT, Sparkplug B, IEC 61850 (MMS / GOOSE / SV), and more.
- **Risk findings, IEC 62443 mapped.** Cross-Purdue communications, cleartext engineering, beaconing, suspicious external comms, port scans, missing authentication, OPC `SecurityMode=None`, Modbus writes from unexpected sources, each with IEC 62443 SR mapping and remediation guidance.
- **MITRE ATT&CK alignment.** Every finding mapped to techniques (ICS + Enterprise). Tactic-matrix workbench view. One-click export to ATT&CK Navigator layer JSON.
- **IOC threat hunting.** Paste a CISA advisory, ingest a STIX bundle, or hand-curate a list. Scan a capture's nodes, DNS queries, flows, and payloads against IPs / domains / SHA-256 / MD5 / MACs / OUIs.
- **Per-asset baselines + drift.** Walks every capture you've loaded and shows what changed for a given host, new peers, new protocols, new findings since last time, drift in vendor / role / device type.
- **Time-window sub-PCAP carve-out.** Drag a span on the capture timeline, extract just those packets as a sub-PCAP for Wireshark. The drag is local, no upload, no server. Pure Rust, no `editcap`.

## GlassMarlin vs grassmarlin.com

| Aspect | grassmarlin.com (web) | GlassMarlin (desktop) |
|---|---|---|
| Deployment | Docker Compose, reverse proxy, persistent volumes | One signed installer per OS, embedded runtime |
| User model | Multi-user with auth, projects scoped per-user | Single-user, local only |
| OS target | Linux container (any host with Docker) | Windows .msi, macOS .dmg, Linux .AppImage |
| External tooling | tshark in the container, libpcap on the host | None, Rust dissection, no Wireshark needed |
| Database | PostgreSQL service | Embedded SQLite, single file |
| Internet | Optional (for ATT&CK Navigator export) | Never. Period. |
| Engine | Same MarlinSpike engine and plugins | Same MarlinSpike engine and plugins |
| Report artifact | Portable JSON, reviewable anywhere | Portable JSON + OCSF + STIX + Sigma + ATT&CK Navigator |
| Best fit | Engagement teams, shared field hosts, lab servers | Defender on a laptop, air-gapped hosts, SCIFs, plane rides |

## Who it's for

The defender's local tool. The thing you put on the engagement laptop. The thing you run on an air-gapped host, on a flight to the site, in a vendor's SCIF, in a bunker. No infrastructure. No internet. No prep.

- **The engagement laptop.** Throw GlassMarlin on the assessor laptop, fly to site, work the captures the OT operator hands you. No client-side dependencies to negotiate before the work starts.
- **Air-gapped, SCIFs, bunkers.** Hosts with no internet, no Docker, no package manager, and no clearance to install third-party runtimes. GlassMarlin is one file: drop it on a USB, open the PCAP, work the project. ATT&CK / IEC 62443 / IOC packs are baked in, nothing fetched at runtime.
- **Training, tabletops, classrooms.** Drop GlassMarlin on the AD share or the USB you handed out at registration. Twenty students each have their own MarlinSpike running in 30 seconds. No server to provision, no Docker to teach.

## What GlassMarlin isn't

- **Not multi-user.** No auth backends, no multi-tenant scoping, no shared URL. If two analysts need to look at the same project, they each open the file locally, or they pull it into a [grassmarlin.com](/) deployment for cross-engagement collaboration.
- **No live capture.** PCAP-in, workbench-out. If you need a live sensor that talks to the team workbench, that's the `marlinspike-capd` sidecar on the server side, not on the laptop.
- **Not Wireshark.** GlassMarlin is the OT triage layer on top of a PCAP (topology, asset context, ATT&CK alignment, findings) that Wireshark deliberately doesn't try to be. Read with Wireshark when you need packet bytes; drive the engagement with GlassMarlin.

## Download · v0.1.1

Signed installers for all three OSes are available on the GitHub Releases page. Every artifact is listed in `SHA256SUMS` with GPG and OpenTimestamps signatures alongside the release.

**Windows (x86_64):**
- `.msi` (managed deployments): [GlassMarlin_0.1.1_x64_en-US.msi](https://github.com/eris-ot/glassmarlin/releases/download/v0.1.1/GlassMarlin_0.1.1_x64_en-US.msi)
- `.exe` (NSIS, hand-installs): [GlassMarlin_0.1.1_x64-setup.exe](https://github.com/eris-ot/glassmarlin/releases/download/v0.1.1/GlassMarlin_0.1.1_x64-setup.exe)

**macOS (Apple Silicon):**
- `.dmg` (signed and notarised, Gatekeeper-clean): [GlassMarlin_0.1.1_aarch64.dmg](https://github.com/eris-ot/glassmarlin/releases/download/v0.1.1/GlassMarlin_0.1.1_aarch64.dmg)
- Intel macOS: planned, not yet shipped.

**Linux (x86_64):**
- `.AppImage` (any glibc 2.28+ host): [GlassMarlin_0.1.1_amd64.AppImage](https://github.com/eris-ot/glassmarlin/releases/download/v0.1.1/GlassMarlin_0.1.1_amd64.AppImage)
- `.deb` (apt-managed systems): [GlassMarlin_0.1.1_amd64.deb](https://github.com/eris-ot/glassmarlin/releases/download/v0.1.1/GlassMarlin_0.1.1_amd64.deb)

**Verify:**
- [SHA256SUMS](https://github.com/eris-ot/glassmarlin/releases/download/v0.1.1/SHA256SUMS) — checksums of every artifact
- [SHA256SUMS.asc](https://github.com/eris-ot/glassmarlin/releases/download/v0.1.1/SHA256SUMS.asc) — GPG signature
- [SHA256SUMS.ots](https://github.com/eris-ot/glassmarlin/releases/download/v0.1.1/SHA256SUMS.ots) — OpenTimestamps proof

**Other channels:**
- All releases (notes, prior versions): [github.com/eris-ot/glassmarlin/releases](https://github.com/eris-ot/glassmarlin/releases)
- Source: [github.com/eris-ot/glassmarlin](https://github.com/eris-ot/glassmarlin)
- Same engine, team server: [grassmarlin.com](/) (this site)
- Same engine, hosted: [cloudmarlin.com](https://cloudmarlin.com)

---

# Documentation

## Heritage

*Source: https://grassmarlin.com/wiki/heritage/*

_How MarlinSpike inherits the GrassMarlin role, what NSA released in 2017, what stopped working, why CVE-2026-6807 forced a successor, and what carried over._

MarlinSpike is the modern continuation of GrassMarlin. This page is the public record of what that means: what GrassMarlin was, why it stopped being usable, and what carried over into the successor.

## What GrassMarlin was

GrassMarlin was an open-source passive network mapping tool for industrial control systems (ICS), published by the NSA's Information Assurance Directorate around 2017 at [github.com/nsacyber/GRASSMARLIN](https://github.com/nsacyber/GRASSMARLIN). It was a Java desktop application that read packet captures (`.pcap`, `.pcapng`), or live traffic from an interface, and built a network topology, identified industrial protocols, classified devices, and surfaced traffic relationships. No probes, no active scanning, no transmission back into the environment.

For a community used to the trade-off "either you stay safe by not touching the OT network, or you get visibility by scanning it," GrassMarlin was a real third option. It was the right idea: vendor-neutral, passive-only, free, and aware of OT-specific protocols at a time when most network tools treated industrial traffic as opaque.

The tool was widely adopted by ICS security teams, response consultants, and asset owners doing greenfield network discovery on segmented plant-floor environments where active scanning was off the table.

## What changed between 2017 and 2025

GrassMarlin was never aggressively maintained after the initial public releases. NSA's GitHub repository has explicitly listed the project as end-of-life since 2017. The Java desktop architecture aged: a single-user thick client doesn't fit a world where OT response is increasingly a team activity on temporary engagement hosts, where Docker is the default install path, and where reports need to be reviewable across machines without re-installing the tool.

The OT response community moved on to web-based workbenches in adjacent product spaces, but the open-source, passive-only, vendor-neutral slot GrassMarlin filled stayed empty. There was no obvious successor. Operators who wanted that exact role kept running GrassMarlin on increasingly old Java runtimes, accepting the trade-off because the alternative was nothing.

## CVE-2026-6807

On 28 April 2026, CISA published advisory ICSA-26-118-01 disclosing CVE-2026-6807, a medium-severity XML External Entity (XXE) information-disclosure vulnerability in GRASSMARLIN v3.2.1 (CWE-611). Crafted session data triggers improper handling of XML input, which can result in unintended exposure of sensitive information from the host. Public proof-of-concept code is available.

NSA confirmed in its response that the project has been end-of-life since 2017 and will not receive a patch. All versions are affected. CISA's mitigation guidance reduces to "stop running it."

For teams who depended on the role GrassMarlin filled, this was the trigger event. The original tool can no longer be safely used. The role still has to be filled.

## Why MarlinSpike

MarlinSpike was already in development before the CVE landed, the responder workbench architecture, the multi-user model, the portable JSON report contract, the OT-native protocol coverage, the bilingual EN/FR surface, the ATT&CK lens, the asset-context-driven contextual severity, and the IOC threat-hunting pipeline were already designed and shipping in v3.5.0.

What CVE-2026-6807 made explicit was that the role itself needs an actively-maintained, modern, multi-user successor. MarlinSpike picks that up. We took the `grassmarlin.com` domain because we are committing to the role publicly, not just shipping a product that happens to look similar.

What carried over from GrassMarlin's design:

- Passive analysis only. No probes, no transmission, no active scanning.
- OT-native protocol coverage as a first-class concern, not an afterthought.
- Vendor-neutral. Modbus, S7, DNP3, IEC 60870-5-104, CIP, MMS, GOOSE, BACnet, OPC UA, PROFINET, and more.
- Topology reconstruction from observed traffic alone, Purdue-level inference, vendor fingerprinting, role hints.
- Free and open source. AGPL-3.0.
- Aware of segmented plant-floor environments where active scanning is unsafe.

What changed:

- Multi-user web workbench instead of a single-user Java desktop application.
- Docker Compose deployment (1 core / 1 GB RAM) instead of a JAR install on a workstation.
- Portable JSON report artifacts as the engine ↔ workbench contract, reports survive the tool that produced them.
- Active maintenance. Tagged releases. Versioned engine and web UI. Mid-scan recovery. Per-tier concurrency limits.
- Bilingual UI (English / Français). Locale-aware engine output. Architecture supports more locales as a content drop, not an engineering project.
- Asset-context-driven contextual severity. MEDIUM on a safety controller can outrank CRITICAL on a print server when context is in place.
- IOC threat-hunting across every report in a project. Live MITRE ATT&CK matrix (ICS + Enterprise) sourced from plugin output.
- HP-HMI mode for control-room wall displays. Time scrubbing + sub-PCAP carve-out for the analyst loop.

## Migration path

If you have been running GrassMarlin:

1. Stop running v3.2.1 (or any version) given the CVE-2026-6807 advisory.
2. Continue using your existing capture pipeline. Whatever produced `.pcap` / `.pcapng` files for GrassMarlin works for MarlinSpike unchanged, taps, SPAN ports, dumpcap, tshark.
3. Stand up MarlinSpike with `docker compose up -d --build` (see the [getting started](/wiki/getting-started/) guide). Five minutes from `git clone` to a working workbench at `127.0.0.1:5001`.
4. Upload your captures. The analysis chain is run as a per-scan subprocess; topology, asset inventory, and findings appear in the workbench.
5. Share the URL with the rest of your engagement team. Multi-user is the default; everyone works the same project.

The output side is materially different: where GrassMarlin produced an in-memory view bound to the desktop session, MarlinSpike emits a portable JSON report artifact. That report can be reviewed in the MarlinSpike workbench, archived as evidence, or consumed by downstream tooling. The report is the contract.

## Acknowledgments

Credit is due. GrassMarlin's design was right and its release was generous, NSA Information Assurance Directorate published the source code under an open license at a time when the OT visibility tooling space was dominated by closed, expensive, vendor-locked products. The architectural choices (passive only, vendor-neutral, OT-aware) carried the right values forward into a public artifact the community could use, study, and learn from.

MarlinSpike inherits those values directly. The implementation is independent code with a different architectural shape, but the role and the principles are continuous with what GrassMarlin established. We are stewarding a slot the original team opened.

## The deeper architectural argument

This page covers what GrassMarlin was and how MarlinSpike continues the role. The broader argument for *why* OT defence should be sovereign-per-zone, brownfield-friendly, and segmented-network-native lives at [industrialindependence.org](https://industrialindependence.org). MarlinSpike, GlassMarlin, and the Conversational Factory are all implementations of that architectural commitment; the Industrial Independence Architecture is the principle they share.

## References

- CISA Advisory: [ICSA-26-118-01](https://www.cisa.gov/news-events/ics-advisories/icsa-26-118-01)
- CVE: [CVE-2026-6807](https://nvd.nist.gov/vuln/detail/CVE-2026-6807) (NVD)
- NSA's original GrassMarlin repository: [github.com/nsacyber/GRASSMARLIN](https://github.com/nsacyber/GRASSMARLIN)
- MarlinSpike source: [github.com/eris-ot/marlinspike](https://github.com/eris-ot/marlinspike)
- Industrial Independence Architecture: [industrialindependence.org](https://industrialindependence.org)

---

## Getting Started

*Source: https://grassmarlin.com/wiki/getting-started/*

_Understand MarlinSpike fast, then bring it up from source, product model, first deployment commands, and documentation trail._

MarlinSpike is a passive OT and ICS network analysis platform built for shared field use. This page gives you the product model, the first deployment commands, and the shortest doc trail to the rest of the project.

**Passive only**, Packet captures go in, and the platform does not transmit packets back onto the network.

**Shared responder workbench**, The normal user model is a shared web surface for the assessment team, not a thick desktop client.

**Source plus Docker today**, The current supported install path is still source-first with Docker Compose behind a reverse proxy.

## What MarlinSpike is

MarlinSpike is not just a packet parser and not just a topology viewer. It is a field-deployable analyst platform for passive OT and ICS network analysis that turns capture files into topology, asset context, Purdue-level inference, risk findings, suspicious external communication review, and portable JSON report artifacts.

The main product ideas from the project README are straightforward:

- Passive OT and ICS analysis first.
- The modern successor to GrassMarlin: same passive-visibility first principle, rebuilt as a shared web workbench instead of a single-user desktop client.
- A shared workbench model with projects, uploads, scans, history, and review.
- A portable report contract so analysis and review are not trapped in one UI session.

## Quick start

The project documentation keeps the first-run path intentionally short. Clone the repo, set secrets in `.env`, and start the Docker stack.

```
git clone https://github.com/eris-ot/marlinspike.git
cd marlinspike
cp .env.example .env
docker compose up -d --build
```

Open the app at `http://127.0.0.1:5001` or through your reverse proxy. On first boot, MarlinSpike creates an admin user. If `ADMIN_PASSWORD` is blank, a random password is generated and printed in the container logs.

## Core workflow

The workflow that shows up throughout the project docs is:

1. Create or choose a project.
2. Upload or select a capture.
3. Run a scan that produces a report artifact.
4. Review topology, findings, inventory, and drift in the workbench.
5. Export or archive the JSON report artifact for downstream use.

The report artifact is the main contract boundary. MarlinSpike can review it in the workbench, but the report is also meant to travel with the team.

## Documentation trail

If you are new to the project, this is the recommended reading order after this page:

- **Operators**, [Deployment](/wiki/deployment.md): Environment variables, Docker Compose, reverse proxying, volumes, upgrades, and backup expectations.
- **Analysts**, [Architecture](/wiki/architecture.md): The five-stage analysis chain, protocol coverage, outputs, and the current standards-aligned detection story.
- **Developers**, [Repo family](/wiki/repo-family.md): How the suite repo is being split into authoritative component repos for engine, workbench, plugins, and Rust engines.
- **Extenders**, [Extensibility](/wiki/extensibility.md): Where new work belongs across Rust engines, Python plugins, and YAML rule packs.

The deployment page covers the full Docker, reverse proxy, data volume, upgrade, backup, and remote-host story from the checked-in install docs.

Source references: [README.md](https://github.com/eris-ot/marlinspike/blob/main/README.md) · [INSTALL.md](https://github.com/eris-ot/marlinspike/blob/main/INSTALL.md)

---

## Deployment

*Source: https://grassmarlin.com/wiki/deployment/*

_Deploy MarlinSpike as a shared, reverse-proxied Docker workbench, environment setup, persistent volumes, upgrades, and live capture._

The project install docs are very clear about the preferred operating model: Docker Compose for the app and database, a reverse proxy at the edge, and a private app port behind it.

**App stack**, Docker Compose with the MarlinSpike app container plus PostgreSQL.

**Network model**, Keep the app on `127.0.0.1:5001` internally and terminate TLS at the reverse proxy.

**Stateful data**, User content and the database live in persistent Docker volumes so rebuilds do not wipe them.

## Local Docker deployment

The checked-in install flow is the normal starting point for local, lab, or field-host deployment:

1. Copy `.env.example` to `.env`.
2. Set strong values for `DB_PASSWORD`, `SECRET_KEY`, and `ADMIN_PASSWORD`.
3. Build and start the stack.
4. Check the logs and then open the app on the internal port or through your proxy.

```
cp .env.example .env
docker compose up -d --build
docker compose logs -f app
```

If `ADMIN_PASSWORD` is left blank, the first boot generates a random admin password and prints it into the container logs.

## Common commands

The install guide keeps the day-two operations set minimal:

```
docker compose ps
docker compose logs -f app
docker compose down
docker compose restart app
```

## Persistent data

MarlinSpike stores runtime state in named Docker volumes so a rebuild does not remove user uploads, reports, or the database.

| Volume or path | Purpose |
|---|---|
| `marlinspike-data` | Uploads, reports, presets, and archived submissions. |
| `marlinspike-pgdata` | PostgreSQL data. |
| `/app/data/reports` | Generated report artifacts inside the app container. |
| `/app/data/uploads` | Uploaded capture files. |
| `/app/data/submissions` | Archived submissions. |
| `/app/data/presets` | Preset capture storage. |

## Reverse proxy guidance

The project docs recommend keeping the app bound to `127.0.0.1:5001` and placing nginx, Caddy, or Traefik in front of it for TLS termination and public ingress.

- Terminate TLS at the proxy.
- Forward only the internal app port.
- Keep the Flask app bound privately unless you have a deliberate reason to expose it directly.
- Treat the deployment as a shared team surface rather than a general public internet app.

## Upgrades and backups

For a normal code update, pull the latest changes and rebuild the containers:

```
git pull
docker compose up -d --build
```

Before major upgrades, back up both the database and the data volume:

```
docker compose exec db pg_dump -U marlinspike marlinspike > marlinspike.sql
```

Also archive the contents of the data volume or whatever mounted data directory your deployment uses.

## Remote deployment and live capture

The repository includes a generic `deploy.sh` script for remote deployment. The documented pattern is:

```
REMOTE=deploy@example-host ./deploy.sh
```

For staging, the project docs also call out `deploy-dev.sh`.

Live capture is available via the optional `marlinspike-capd` sidecar (Linux only). The web app stays unprivileged and talks to capd over a unix socket; capd holds `CAP_NET_RAW` and supervises `dumpcap` with ring-buffer rotation.

The architecture page explains why the deployment looks this way: shared workbench, portable report artifacts, passive-only analysis, and a clean separation between packet handling and downstream review. See [Architecture](/wiki/architecture.md).

Source references: [INSTALL.md](https://github.com/eris-ot/marlinspike/blob/main/INSTALL.md) · [docker-compose.yml](https://github.com/eris-ot/marlinspike/blob/main/docker-compose.yml) · [Dockerfile](https://github.com/eris-ot/marlinspike/blob/main/Dockerfile)

---

## Architecture

*Source: https://grassmarlin.com/wiki/architecture/*

_Packet dissection, topology, triage, and reporting stay intentionally separate, the five-stage chain, DPI options, protocol coverage, and outputs._

MarlinSpike's architecture is built around a clean responder workflow: passive capture input, a legible multi-stage analysis chain, a portable report artifact, and a shared workbench that does not have to own every downstream use.

**Five-stage chain**, Ingest, dissect, topology, risk, then report.

**Portable report boundary**, The report artifact is the handoff between packet analysis and downstream review.

**Field-first UX**, The workbench is designed for shared OT engagements, not generic packet tinkering.

## Product boundary

MarlinSpike's value is not just packet decoding. The product is the combination of passive analysis, topology reconstruction, asset context, responder-facing findings, and a workbench that can be shared during a live engagement.

The repository documentation keeps returning to the same contract boundary:

- The engine remains capable of producing a finished report artifact headlessly.
- The report artifact becomes the handoff between packet analysis and review.
- The web workbench consumes that report for collaboration, triage, export, and history.
- Core workflows remain usable without making JavaScript mandatory for the primary responder path.

## Five-stage analysis chain

The architecture described in the README breaks down into a simple pipeline:

| Stage | Purpose |
|---|---|
| 1. Capture ingestion | Validate `pcap` or `pcapng`, extract basic metadata, and prepare the input for dissection. |
| 2. Protocol dissection | Parse OT protocols, L2 discovery protocols, and network conversations. |
| 3. Topology construction | Build the node and edge graph, infer Purdue placement, fingerprint vendors, and assign roles. |
| 4. Risk surfacing | Identify cross-zone issues, suspicious external communications, beaconing, DNS entropy anomalies, and other responder-grade findings. |
| 5. Report | Emit the portable JSON artifact the workbench and downstream tooling can consume. |

## DPI options and layering

Stage 2 can currently run two ways:

- The built-in Python and tshark path in `_ms_engine.py`.
- The external Rust dissection path via `marlinspike-dpi`.

The documentation is explicit that the Rust path replaces the packet-facing dissection stage, not the whole product. Topology shaping, triage logic, and report construction remain above it.

## Protocol coverage and findings surface

The current public docs describe MarlinSpike as OT-aware by default, with coverage for protocols such as Modbus, EtherNet/IP, S7, DNP3, PROFINET, OPC UA, BACnet, IEC 104, and L2 discovery families like LLDP, CDP, STP, and LACP.

The responder-facing findings story is equally important. The project docs call out surfaces such as:

- Cross-Purdue and cross-zone communication review
- Cleartext services and write-capable paths
- Suspicious external communications
- Beaconing and connection persistence patterns
- DNS exfiltration indicators
- Selected MITRE ATT&CK mappings and IEC 62443 SR-oriented remediation support

## Outputs and exports

The workbench is built around more than just a topology graph. The docs list these output surfaces as first-class:

- Portable JSON report artifacts
- Topology and relationship review
- Asset inventory and service context
- C2 indicators and risk findings
- PDF, PNG, and CSV export paths from the UI
- Baseline versus drift comparison between reports

The repo-family docs explain the suite repo, component repos, subtree model, and the current transition state for engine and workbench extraction. See [Repo family](/wiki/repo-family.md) and [Extensibility](/wiki/extensibility.md).

Source references: [README.md](https://github.com/eris-ot/marlinspike/blob/main/README.md)

---

## Repo Family

*Source: https://grassmarlin.com/wiki/repo-family/*

_One suite repo, several focused component repos, and a clean artifact boundary, the subtree model and current transition state._

MarlinSpike is moving from a single mixed repository toward a repo family where the suite repo vendors a known-good combination of workbench, engine, plugins, and Rust engines.

**Suite repo**, The top-level marlinspike repo is the integration surface for teams that want one clone.

**Authoritative components**, Engine, workbench, plugins, and Rust engines are each intended to become authoritative in their own repos.

**Subtree model**, The suite repo uses git subtree, not submodules, to vendor component repos.

## Repo model

| Repository | Role |
|---|---|
| `marlinspike` | The suite repo, integration home, and one-clone option. |
| `marlinspike-msengine` | The core analysis engine repo. Internal package and CLI name: `msengine`. |
| `marlinspike-workbench` | The web UI and collaboration surface that reads report artifacts and can optionally invoke the engine. |
| `marlinspike-plugins` | The Python plugin monorepo for report-consuming extensions. |
| `marlinspike-engines` | The Rust engine workspace for packet-facing and event-heavy components. |

## One clone for everything

The suite repo vendors component repos via `git subtree`. The docs frame that as a practical middle ground:

- Users still get normal `git clone` behavior.
- Teams avoid a git-submodule setup burden.
- Component repos can keep their own release cycles.
- The suite repo can pin a known-good combination for operators who want the whole stack together.

## Contract boundary

The portable report artifact is the handoff between components:

1. `msengine` produces a report artifact from captured traffic.
2. `marlinspike-workbench` consumes that artifact for review, triage, and collaboration.
3. Python plugins consume the same finished report and emit sidecar artifacts.
4. Rust engines may produce upstream artifacts or event streams that feed the engine or other components.

The workbench is intentionally usable even when the report was generated elsewhere and no local engine binary is present.

## Current transition state

The docs are honest that the current repository still contains both engine and workbench code while the split is being prepared.

- The root `marlinspike` repo still carries operational engine and Flask UI code today.
- The first bootstrap subtree prefix is `msengine/`.
- Until cutover is complete, root `_ms_engine.py` remains the operational engine source.
- The helper script `scripts/sync-msengine-bootstrap.sh` mirrors the operational engine into the subtree copy.

## Ownership boundaries for contributors

The contribution docs already encourage developers to think in future component boundaries even before the extraction finishes:

- Packet-facing parsing, observables, and engine CLI work belong to the `msengine` boundary.
- Flask routes, templates, auth, projects, and report-review UX belong to the `workbench` boundary.
- ATT&CK, IEC 62443, PERA, and other report-consuming overlays belong to the future `plugins` boundary.
- Packet-heavy Rust parsers belong to the future `engines` boundary.

The extensibility docs define how Rust engines, Python plugins, and YAML rule packs are supposed to interact with the report artifact and each other. See [Extensibility](/wiki/extensibility.md) and [Contributing](/wiki/contributing.md).

Source references: [repo-family.md](https://github.com/eris-ot/marlinspike/blob/main/docs/repo-family.md) · [CONTRIBUTING.md](https://github.com/eris-ot/marlinspike/blob/main/CONTRIBUTING.md) · [COMPATIBILITY.md](https://github.com/eris-ot/marlinspike/blob/main/COMPATIBILITY.md)

---

## Extensibility

*Source: https://grassmarlin.com/wiki/extensibility/*

_Rust engines, Python plugins, and YAML rule packs each have a different job, the formal extension contracts and where new work belongs._

The extensibility contract exists to keep packet-facing work, report-facing logic, and site-policy content from collapsing into one unstable blob.

**Rust engines**, High-throughput packet-facing parsing, structured events, and extracted artifacts.

**Python plugins**, Report-facing enrichment, mapping, correlation, and responder-side overlays.

**YAML rule packs**, Declarative mappings, suppressions, and local policy without turning config into another language.

## Shared principles

1. The portable MarlinSpike report artifact remains the primary handoff between packet analysis and downstream review.
2. Optional extensions must run headlessly and must not require Flask, a browser, or a database connection.
3. Machine-readable outputs should be deterministic and versioned.
4. Packet-facing code should not make site-policy decisions.
5. Report-facing code should not parse raw packet captures directly.
6. YAML must stay declarative.

## Rust engine contract

Rust engines own packet-facing and event-heavy work: reading captures, parsing protocols, normalizing transactions, and emitting structured observations or artifacts.

They do not own final topology scoring, responder-facing finding text, ATT&CK mapping, site-specific policy, or UI behavior.

```
marlinspike-dpi --input <pcap> --capture-id <id> --output <json> --pretty
```

The minimum invocation contract is simple: accept an input capture path, accept a stable capture identifier, write JSON output to a caller-specified path, and return non-zero on failure.

## Python plugin contract

Python plugins consume a finished MarlinSpike report JSON and emit a sidecar artifact for enrichment, correlation, ATT&CK mapping, IEC 62443 mapping, malware matching, or other post-processing.

```
python -m marlinspike_plugin_name \
  --input-report <report.json> \
  --output <artifact.json>
```

The plugin should not mutate the input report in place. The sidecar artifact is authoritative, while a merged report can exist as a convenience copy.

```json
{
  "artifact_type": "plugin_output",
  "plugin_id": "marlinspike-plugin-name",
  "plugin_version": "0.1.0",
  "contract_version": 1,
  "summary": {},
  "data": {},
  "warnings": []
}
```

## YAML rule pack contract

YAML rule packs are the declarative layer. They are meant for mappings, local overrides, enable and disable controls, and policy content that analysts may need to tune without rewriting code during an engagement.

The docs are explicit about what YAML should not become: a general programming language, a hidden parser surface, or a place to bury arbitrary logic that belongs in a plugin.

## Rule of thumb for new work

- If it consumes raw `pcap`, packet streams, or high-volume protocol events, it probably belongs in a Rust engine.
- If it consumes the finished MarlinSpike report artifact, it probably belongs in a Python plugin.
- If analysts should be able to tune it without code changes, it probably belongs in a YAML rule pack.

The repo-family page shows how these extension surfaces line up with the suite repo and the future authoritative component repos. See [Repo family](/wiki/repo-family.md) and [Contributing](/wiki/contributing.md).

Source references: [extensibility-contracts.md](https://github.com/eris-ot/marlinspike/blob/main/docs/extensibility-contracts.md)

---

## Presets

*Source: https://grassmarlin.com/wiki/presets/*

_The public repo does not ship third-party PCAP corpora, but MarlinSpike supports local preset captures for labs and repeatable field libraries._

This page mirrors the honest stance in the project docs: public images and the public repository do not bundle third-party corpora by default, but operators can still maintain a local preset library.

**No bundled corpora in public repo**, The checked-in preset docs explicitly say third-party PCAP corpora are not bundled publicly.

**Local preset folders supported**, You can create your own category folders under `presets/` and load local captures there.

**Admin uploads also work**, Teams can upload captures through the application UI after deployment instead of baking them into the local preset tree.

## Public preset-library policy

The public repository does not bundle third-party PCAP corpora. That keeps redistribution clean and avoids shipping capture data the public project should not be republishing by default.

The same principle applies to public images: local corpora are for your own deployment or lab environment, not something the public site should imply is always included.

## How to add local presets

The checked-in preset README gives a simple pattern:

- Create category folders under `presets/`.
- Add your own `.pcap`, `.pcapng`, or `.cap` files locally.
- Or upload those files later through the admin UI after deployment.

```
presets/
  site-a/
    baseline-shift-a.pcapng
  site-b/
    historian-incident.cap
```

## Why those files stay local

The preset docs note that preset capture files are ignored by both `.gitignore` and `.dockerignore`. That means they are not committed and not baked into public images by default.

This is a useful operational split:

- The public repo stays clean and redistributable.
- Your private deployment can still keep a repeatable capture library for training, testing, or demos.
- Teams do not have to expose customer traffic or third-party data just to use the preset feature.

## Alternative intake paths

You do not need a baked-in preset library to use MarlinSpike effectively. The project docs repeatedly position the main intake path as uploads or captures fed into the workbench during an engagement.

Use presets when you want a local reusable library. Use uploads when you want ad hoc field captures, incident files, or lab exports reviewed immediately.

The deployment page explains how the app stores uploads, reports, and preset data inside the Docker-backed runtime paths. See [Deployment](/wiki/deployment.md).

Source references: [presets/README.md](https://github.com/eris-ot/marlinspike/blob/main/presets/README.md) · [README.md](https://github.com/eris-ot/marlinspike/blob/main/README.md)

---

## Contributing

*Source: https://grassmarlin.com/wiki/contributing/*

_Keep changes focused, respect data-handling boundaries, and run the local checks before opening a PR._

The contribution guide is intentionally pragmatic: keep PRs small, do not commit captures or secrets, and understand which future repo boundary your change belongs to.

**Focused changes**, Small PRs are easier to review than broad refactors.

**Run local checks**, At minimum, run the Python syntax check before opening a PR.

**No sensitive data**, Do not commit captures, secrets, or private environment details.

## Before you start

- Open issues or small discussion threads are welcome for bugs, parser gaps, UI problems, and deployment improvements.
- Keep changes focused rather than mixing many unrelated concerns.
- Avoid committing private captures, secrets, ad hoc local screenshots, or local tool metadata.
- Documentation screenshots under `docs/screenshots/` are the explicit exception to the no-screenshot rule.

## Development notes

The canonical source files called out by the project are `_ms_engine.py`, `_auth.py`, `_models.py`, `_config.py`, and `app.py`. Compatibility shims such as `auth.py`, `models.py`, and `config.py` are not the primary source of truth.

The contribution guide also reinforces the project vocabulary:

- Rust engines handle packet-facing and event-heavy work.
- Python plugins handle report-facing analysis, enrichment, and triage logic.
- YAML rule packs handle declarative mappings, suppressions, and local policy.

## Suite workflow and subtree helpers

The suite repo keeps vendored component copies in subtree prefixes. The contribution docs call out helper commands for working with that model:

```
./scripts/update-subtrees.sh status
./scripts/sync-msengine-bootstrap.sh
```

The planned subtree prefixes are `msengine/`, `workbench/`, `plugins/`, and `engines/`. Today, `msengine/` is the first real bootstrap prefix.

## Local checks

Before opening a PR, the docs ask contributors to run the basic syntax check:

```
python3 -m py_compile app.py _auth.py _config.py _models.py _ms_engine.py
```

If you touched the bootstrap engine subtree, also verify the mirrored package entrypoint:

```
PYTHONPATH=msengine python3 -m msengine --help
```

If you made Docker-related changes, the guide also says to validate the stack locally:

```
docker compose up --build
```

## Security and data handling

- Do not commit secrets, `.env` files, infrastructure credentials, or internal deployment notes.
- Do not commit customer captures or bundled third-party PCAP corpora unless redistribution terms are explicitly documented.
- If you find a security issue, report it privately before opening a public issue.

The releases page tracks recent engine, UI, and live-viewer milestones so contributors can see where major shifts already happened. See [Releases](/wiki/releases.md) and [Repo family](/wiki/repo-family.md).

Source references: [CONTRIBUTING.md](https://github.com/eris-ot/marlinspike/blob/main/CONTRIBUTING.md)

---

## Releases

*Source: https://grassmarlin.com/wiki/releases/*

_The engine and web UI are versioned separately, with a distinct live-viewer track, recent highlights and the full history location._

The checked-in release docs separate engine changes from web UI changes. That keeps packet-analysis releases distinct from interface and workbench updates.

**Current engine version**, v1.9.1, documented on February 26, 2026.

**Current web UI version**, v1.9.0, documented on February 25, 2026.

**Live track**, Separate history is maintained for the live-viewer and streaming-oriented app layer changes.

## Versioning model

The project release docs define a split versioning model:

- The engine version tracks changes to `_ms_engine.py`, tshark behavior, and report format.
- The web UI version tracks `app.py` plus templates and UI behavior.
- The live release document tracks the web and Docker orchestration layer for live or streaming functionality.

## Recent engine highlights

| Version | Date | Highlights |
|---|---|---|
| `v1.9.1` | February 26, 2026 | Vendor infrastructure classification fix for Phoenix Contact and Innominate devices, plus demo refresh. |
| `v1.9.0` | February 25, 2026 | L2 node merge and MAC table support in the report artifact and viewer. |
| `v1.7.0` | February 24, 2026 | Security hardening, major memory and scalability work, sequential scan queue, and safer tshark defaults. |
| `v1.4.0` | February 23, 2026 | Five-tuple port analysis, C2 detection, DNS entropy checks, ATT&CK mapping additions, and new report fields. |

## Recent web UI highlights

| Version | Date | Highlights |
|---|---|---|
| `v1.9.0` | February 25, 2026 | User profiles, per-user upload limits, and new profile APIs and admin limits controls. |
| `v1.8.2` | February 25, 2026 | Bugfix limiting scan log visibility to admin users. |
| `v1.8.1` | February 25, 2026 | Initial separate web UI versioning. |

## Live-viewer track

The separate live release document captures milestones for the live web layer and deployment runtime, including project-system additions, diff viewing, and live topology updates.

| Version | Date | Highlights |
|---|---|---|
| `v1.5.0` | February 23, 2026 | Projects system, upload pipeline overhaul, archival, per-project storage, and migration helpers. |
| `v1.4.0` | February 23, 2026 | Viewer support for port analysis and C2 indicators tied to the engine's v1.4.0 output. |
| `v1.2.0` | February 23, 2026 | Topology diff viewer and live scan viewer support. |

## Full history and backups

The repository release docs go deeper than this summary. They also track backup snapshots for major engine milestones such as `v1.7.0`, `v1.6.0`, `v1.4.0`, and `v1.0.0`.

If you need the exact release notes and line-item changes, use the source links below.

Source references: [releases.md](https://github.com/eris-ot/marlinspike/blob/main/releases.md) · [releases-live.md](https://github.com/eris-ot/marlinspike/blob/main/releases-live.md) · [GitHub Releases](https://github.com/eris-ot/marlinspike/releases)

---
