Skip to content

mikemich/Spctr

Repository files navigation

👁 Spectre — Passive Network Intelligence Platform

License Rust Docker Stars

Spectre is a passive network monitoring tool. It watches the traffic on a network segment and builds a live, queryable picture of every device it sees — who they are, what they run, who they talk to — and surfaces anomalies in real time on a force-directed mesh graph.

Passive capture. Spectre never scans, probes, or injects packets onto the network it watches — every device fact is inferred from frames already on the wire, so it is undetectable on the monitored segment.

It does, by default, make outbound lookups to third-party services to enrich external IPs: ip-api.com (geo/ASN, over cleartext HTTP), reverse DNS, optional AbuseIPDB, and threat-feed downloads — plus any webhooks you configure. All are toggleable in config.toml, and the barebones build (--no-default-features) compiles them out entirely for a truly silent, zero-egress sniffer.

Run it in 30 seconds (prebuilt images)

One-liner (Linux — auto-installs Docker if needed, detects your interface, writes a compose file, and starts the stack):

curl -fsSL https://raw.githubusercontent.com/mikemich/Spctr/main/install.sh | sh
# then open http://localhost:3000

Or drop in a compose file yourself:

# docker-compose.yml — no build needed, pulls from ghcr.io
services:
  daemon:
    image: ghcr.io/mikemich/spctr-daemon:latest
    cap_add: [NET_RAW, NET_ADMIN]
    environment: { SPECTRE_INTERFACE: eth0, SPECTRE_DB_PATH: /data/spectre.db }
    volumes: ["./data:/data"]
    networks: [spectre]
  ui:
    image: ghcr.io/mikemich/spctr-ui:latest
    ports: ["3000:3000"]
    environment: { DAEMON_HOST: daemon }
    depends_on: [daemon]
    networks: [spectre]
networks: { spectre: { driver: bridge } }
SPECTRE_INTERFACE=eth0 docker compose -f docker-compose.ghcr.yml up -d
# open http://localhost:3000

It has three parts:

Component Stack Role
spectre-daemon Rust, libpcap, axum, SQLite Captures & parses packets, tracks devices, serves the API/WebSocket
spectre-ui React, Vite, D3, Tailwind Live mesh graph, device list/detail, alert feed
docker-compose.yml Docker One-command packaging of the whole system

What it observes

For every source IP it sees, Spectre builds a device profile:

  • MAC address + OUI vendor (from a bundled, trimmed IEEE OUI database)
  • First seen / last seen timestamps and total traffic
  • OS fingerprint guessed from the SYN's TTL + TCP window + TCP option order, matched against a bundled signature file (Windows 10/11, Linux 4.x/5.x/6.x, Android, iOS, macOS, FreeBSD, OpenBSD, Cisco, …)
  • Open service ports inferred from the TCP handshake direction
  • DNS / mDNS hostnames parsed passively from responses
  • DHCP hostname (option 12) when a DHCP exchange is seen
  • JA4 TLS client fingerprint computed from scratch off the ClientHello (see FoxIO JA4 spec)
  • HTTP User-Agent seen on port 80
  • External IPs contacted with per-peer byte counts and timestamps
  • Internal peers it has communicated with

IP enrichment

External addresses are enriched in the background (passively, never blocking capture) and shown in the device table — flag, city/org, PTR hostname, ASN, an auto-tag chip, and an AbuseIPDB score:

  • Reverse DNS (PTR) lookups
  • Geo + org + ASN via the free ip-api.com batch endpoint (45 req/min, no key)
  • Reputation via AbuseIPDB (optional — set abuseipdb_key or the ABUSEIPDB_KEY env var)
  • Auto-tagging from org/PTR/ASN patterns: Googlebot, Cloudflare, Shodan/ Censys/generic Scanner, AWS, Azure, DigitalOcean, Tor Exit Node; RFC1918 / link-local / multicast / Tailscale (100.64/10) are tagged Local

Lookups are queued asynchronously, run with a concurrency cap (10) and a token bucket, retry-aware on 429, and every result is pushed to the UI live over the WebSocket. Configure it under [enrichment] in config.toml; trigger a manual refresh per device with POST /api/enrich {"ip": "x.x.x.x"} (the UI's row ↻ button).

Anomaly rules

The rules engine raises an alert when:

  1. a new device appears,
  2. a known device's OS fingerprint changes,
  3. a device contacts a new external IP for the first time,
  4. an internal device initiates a connection to another internal device for the first time, or
  5. a device exposes traffic on a service port it has never used before,
  6. a source scans/sweeps many ports or hosts in a short window,
  7. the MAC bound to a known IP changes (possible ARP spoofing),
  8. a device shows periodic beaconing to one external destination, or
  9. a known attacker campaign reappears from a new IP (behavioral fingerprint match — see below).

High-severity alerts can be pushed to a webhook (Slack / Discord / ntfy / generic JSON) — see [notifications] in config.toml.

Attacker behavioral fingerprinting

IPs rotate, but tools have habits. Spectre derives a stable fingerprint_id for each external host from host-stable L3/L4 SYN signals only — TTL bucket, advertised-window bucket, and TCP option order. Path-dependent signals (packet timing, hop count) are deliberately excluded, so the same scanner produces the same fingerprint whether it hits you from one IP or fifty.

  • When ≥ 4 distinct IPs share one fingerprint over more than an hour, it's promoted to a named Campaign-XXXX and surfaced in the Campaigns tab. A known campaign reappearing from a fresh IP fires a high-severity alert.
  • Conservative passive tool attribution flags distinctive mass-scanner shapes (Masscan/ZMap-like raw SYNs, option-less IoT-bot packets) without guessing at normal OS stacks.
  • The device panel's Behavioral Identity section shows "this IP shares a fingerprint with N others" and links to its campaign.

The per-IP membership behind a fingerprint is local operator data — it's served only on your own daemon API and is never transmitted off the host.

Access control & retention

  • Auth: set [auth] enabled = true with a token (or the SPECTRE_API_TOKEN env var) to require a bearer token on /api and /ws. The UI shows a token prompt; /health stays public for probes.
  • Retention: [retention] device_max_age_secs evicts devices (and their connections) not seen for that long, from memory and the database; 0 keeps everything. The daemon also performs a final DB flush on shutdown.

Quick start (Docker)

docker compose up --build

Then open http://localhost:3000.

  • The daemon and UI share a private bridge network; nginx reaches the daemon by service name. This works on Docker Desktop (macOS/Windows) and Linux alike — the UI will connect and render.
  • The UI runs nginx on :3000 and reverse-proxies /api and /ws to the daemon's API on :7777.
  • The SQLite database and logs are written to ./data (mounted volume), so state survives restarts.

Useful environment variables (set in your shell or a .env file):

Variable Default Meaning
SPECTRE_INTERFACE eth0 Capture interface (any for all)
SPECTRE_TRUSTED_RANGES RFC1918 + link-local Comma-separated CIDRs treated as internal
DAEMON_HOST / DAEMON_PORT daemon / 7777 Where the UI proxies the API
RUST_LOG info Log verbosity (debug, trace, …)

⚠️ What the default Docker setup can and cannot see. In bridge mode the daemon only observes traffic on its own container interface — enough to see the system working end to end, but not your physical LAN. And on Docker Desktop for macOS/Windows, containers run inside a hidden Linux VM, so the daemon can never see your Mac's/PC's real Wi-Fi/Ethernet traffic. That's a Docker virtualization limit, not a Spectre bug. For real monitoring, see "Real capture" below.

Real capture (observe your actual network)

Spectre is designed to run on a Linux host that sits on the network you want to watch (a server, a router/firewall, a Raspberry Pi, or a box plugged into a switch SPAN/mirror port). Two ways:

A) Run the daemon natively on the Linux host (simplest, recommended — see the next section). Capture the real interface (e.g. eth0/wlan0) directly; optionally still run the UI container pointed at it with DAEMON_HOST=host.docker.internal.

B) Run the daemon container with host networking on Linux. Edit docker-compose.yml: under daemon, replace networks: [spectre] with network_mode: host; under ui, set DAEMON_HOST: host.docker.internal and add extra_hosts: ["host.docker.internal:host-gateway"]. Then SPECTRE_INTERFACE=eth0 docker compose up --build. (Host networking only does the right thing on Linux.)


Deploying on a VPS / Linux host

A VPS is a single server, so Spectre monitors that server's traffic — every client that connects to it, their JA4 TLS fingerprints, byte counts, and what the server talks to. (It is not a LAN full of devices, so expect a server-shaped graph, not a houseful of gadgets.)

There's a ready-made compose file that uses host networking and binds the API to localhost:

# 1. Install Docker + the compose plugin (Debian/Ubuntu)
curl -fsSL https://get.docker.com | sh

# 2. Get the code
git clone https://github.com/mikemich/Spctr.git && cd Spctr

# 3. Find your public interface
ip -o -4 route show to default | awk '{print $5}'      # e.g. eth0, ens3, enp1s0

# 4. Launch (replace eth0 with yours)
SPECTRE_INTERFACE=eth0 docker compose -f docker-compose.vps.yml up --build -d

The dashboard is now on port 3000 and the API is bound to 127.0.0.1:7777 (reachable only by the local nginx, never the internet).

🔒 Secure it

Spectre has built-in authentication — enable it before exposing the dashboard anywhere. In config.toml:

[auth]
enabled = true
username = "admin"
password_hash = ""          # generate with the command below
session_ttl_hours = 24
# or, for headless/API use, set a bearer token instead of a password:
# token = "…"  (or the SPECTRE_API_TOKEN env var)

Generate a bcrypt hash (the daemon ships a subcommand that reads the password from stdin):

echo -n 'your-password' | docker compose -f docker-compose.vps.yml exec -T daemon spectre-daemon hash-password
# paste the output into password_hash, then restart

With auth on, /api and /ws reject anything without a valid signed session cookie (or bearer token); the UI shows a login page and /health stays public for probes. Sessions are HMAC-signed with a random per-process secret.

Defence in depth — don't expose the ports publicly either. Even with auth on, keep :3000/:7777 off the open internet. Pick one:

Option 1 — Firewall to your own IP (quickest):

sudo ufw allow OpenSSH
sudo ufw allow from <YOUR.HOME.IP.ADDR> to any port 3000 proto tcp
sudo ufw deny 7777
sudo ufw enable

Option 2 — SSH tunnel (nothing public at all): set the dashboard to listen on localhost by removing the ports/host-exposure, then from your laptop:

ssh -N -L 3000:127.0.0.1:3000 user@your-vps    # open http://localhost:3000

Option 3 — Reverse proxy with TLS + Basic Auth (proper public access). Put Caddy or nginx in front of :3000, e.g. a Caddyfile:

spectre.example.com {
    basic_auth { youruser <bcrypt-hash> }
    reverse_proxy 127.0.0.1:3000
}

Spectre is passive — it never sends packets — so it won't disrupt your server's traffic. It does record metadata (IPs, fingerprints) to SQLite under ./data; treat that as sensitive.

To update later: git pull && docker compose -f docker-compose.vps.yml up --build -d.


Single portable binary (no Docker)

Spectre can build as one self-contained executable that captures and serves the dashboard — the web UI is embedded into the binary. This is the easiest way to run it, and on macOS it captures your real network natively (unlike Docker, which is trapped in a Linux VM).

Download a prebuilt binary from the Releases page (macOS arm64/x86, Linux x86_64), then:

tar -xzf spectre-*.tar.gz && cd spectre-*
# macOS: capture needs root (or BPF access); Linux: root or setcap
sudo ./spectre ./config.toml          # then open http://localhost:7777

Or build it yourself (needs Node + Rust + libpcap):

cd spectre-ui && npm ci && npm run build && cd ..
cargo build --release -p spectre-daemon --features embed-ui
sudo ./target/release/spectre-daemon ./spectre-daemon/config.toml

The dashboard, REST API, and WebSocket are all served from :7777 (the API lives under /api). No nginx, no compose, no proxy.


Barebones mode (Raspberry Pi / discreet sniffer)

For a small edge device you can build a silent variant that makes zero outbound connections — enrichment, threat feeds, and notifications are compiled out:

cargo build --release -p spectre-daemon --no-default-features

A turnkey appliance installer (systemd service, low-priv capture user, localhost-only API, SD-card-friendly retention) lives in rpi/:

sudo ./rpi/install.sh

See rpi/README.md for SPAN-port placement, SSH-tunnel access, tmpfs/USB storage, and cross-compiling for aarch64.


Home Network Mode

Watching your own LAN rather than a public server? Set:

# config.toml
mode = "homelan"      # or the SPECTRE_MODE=homelan env var

homelan mode re-tunes the dashboard for a household network:

  • 🏠 Home (phone-home) view — for every device on your network, the external services it has talked to: "your smart TV talked to 14 Samsung servers today." Click any device or destination to drill in.
  • Friendly device-type icons inferred from OUI vendor + open ports — 📶 router, 📱 phone/tablet, 💻 laptop, 📺 smart TV, 🎮 game console, 🖨️ printer, 🔌 IoT — shown on the mesh graph and the device panel.
  • DHCP hostnames are preferred as device labels.

It changes presentation only; capture and detection are identical to server mode. The Raspberry Pi profile in rpi/ ships with mode = "homelan" already set. On aarch64 (Raspberry Pi 4/5) use the prebuilt arm64 images from Task 1 or cross-compile per rpi/README.md.


Running the daemon natively

You need libpcap and the capability to capture raw packets.

# Build dependencies (Debian/Ubuntu)
sudo apt-get install -y libpcap-dev

# Build
cargo build --release -p spectre-daemon

# Configure (edit interface, trusted_ranges, etc.)
cp spectre-daemon/config.toml ./config.toml
$EDITOR config.toml

# Option A: run as root
sudo ./target/release/spectre-daemon ./config.toml

# Option B: grant the binary capture capabilities (no root needed to run)
sudo setcap cap_net_raw,cap_net_admin=eip ./target/release/spectre-daemon
./target/release/spectre-daemon ./config.toml

The daemon reads its config path from (in order): the first CLI argument, the SPECTRE_CONFIG environment variable, then ./config.toml. Any field can be overridden with SPECTRE_* environment variables (see the table above).

Running the UI natively

cd spectre-ui
npm install
npm run dev        # http://localhost:3000, proxies to http://localhost:7777

Point it at a non-default daemon with SPECTRE_DAEMON=http://host:7777 npm run dev.


Federation — join the sensor network

Spctr nodes can share behavioral fingerprints with a central hub to detect internet-wide scanner campaigns that no single node would see alone.

What gets shared: fingerprint hashes, IP counts, ASN, country, tool guess. What never leaves your node: IP addresses, hostnames, traffic content, TLS domains. The push type has no field that can hold an IP — the guarantee is enforced at compile time. Federation is off by default and, when enabled, never affects local operation if the hub is unreachable.

# Run your own hub (open source, self-hostable):
HUB_ADMIN_TOKEN=<secret> docker compose --profile hub up -d
# If running the hub, open port 7779:
sudo ufw allow 7779/tcp comment "Spctr hub"

# Register a sensor (prints sensor_id + one-time token for config.toml):
spectre-daemon register-sensor --hub http://162.19.253.164:7779 --region <hint>
# 162.19.253.164:7779 — temporary public hub until hub.spctr.io is live

For constrained nodes, the standalone spctr-sensor binary (no UI/DB) does the same with a smaller footprint — it computes byte-identical fingerprints to the full daemon via shared crates:

curl -fsSL https://raw.githubusercontent.com/mikemich/Spctr/main/install-sensor.sh | bash
# defaults to the temporary public hub http://162.19.253.164:7779

Full data contract: spctr-hub/PROTOCOL.md · Deploy guide: docs/deploy-sensor.md

Campaign cards show a 🌐 N sensors badge when a fingerprint is confirmed across the network.

Enabling federation from the dashboard

  1. Open Settings → Federation
  2. Enter the hub URL and click Register with hub
  3. Toggle federation on

Enable/disable happens live — no docker compose restart needed — and the choice is written through to config.toml so it survives restarts.

Or from the CLI:

spectre-daemon register-sensor --hub http://162.19.253.164:7779
# Add the printed sensor_id + token to config.toml under [federation],
# set enabled = true, and restart.

API

The daemon exposes (also mirrored under /api/... for the proxy):

Endpoint Description
GET /devices All known devices as JSON
GET /devices/:ip Full profile for one device
GET /devices/search?q= Fuzzy search across IP/host/org/ASN/tag/country
GET /devices/:ip/traffic?hours=N Per-device traffic series
GET /devices/:ip/fingerprint Behavioral fingerprint + campaign for a device
GET /reputation/:ip Reputation score 0–100 + breakdown (Task 18)
GET /export/blocklist?format=ufw|iptables|nginx|cidr Firewall blocklist of high-reputation IPs (Task 22)
GET /response/policy Blocklist export formats + auto-block threshold
PUT /devices/:ip/note Set a device note
GET /traffic/global?hours=N Global bandwidth series
GET /graph { nodes, edges } for the mesh graph
GET /alerts?limit=N Recent anomaly events (?device=IP to filter)
GET /export?format=csv|json|ndjson Export all devices
GET /rules, PUT /rules/:name/toggle Alert rules
POST /enrich Queue an IP for (re-)enrichment
POST /auth/login, /auth/logout, GET /auth/me Session auth
GET /honeypot/events?limit=N&ip=X Honeypot interactions
GET /honeypot/stats Honeypot summary stats
GET /knocks?limit=N&since=ts Inbound knocks to closed ports on this host (Task 17)
GET /dns/anomalies?limit=N Suspicious DNS queries — exfil/tunnelling (Task 20)
GET /score Spctr Score — posture self-assessment + issues + history (Task 21)
GET /score/badge SVG score badge for the README
GET /fingerprints Behavioral fingerprints (summaries)
GET /fingerprints/:id One fingerprint with its member IPs (local data)
GET /fingerprints/:id/ips Member IPs of a fingerprint (local data)
GET /fingerprints/:id/signals Aggregate + per-member raw signals (audit/debug, local data)
GET /campaigns Fingerprints promoted to named campaigns
GET /hub/campaigns Cross-sensor campaigns from the federation hub (Task 27)
GET /federation/status Federation state (never returns the sensor token)
POST /federation/register Register this node with a hub; saves + persists credentials
POST /federation/toggle Enable/disable federation at runtime (no restart)
POST /federation/save Update hub URL / region hint (persisted)
GET /operators Inferred operator profiles (local-only, Task 26)
GET /operators/:id One operator + inferred behaviour / timezone
GET /devices/:ip/operator The operator a device's fingerprint belongs to
GET /stats Network-wide counters (operators, observed-domain IPs)
GET /health Liveness probe
WS /ws Live events: snapshot, device_new, device_updated, connection_new, alert, honeypot

Adding new OS signatures

OS fingerprints are matched against spectre-daemon/data/signatures.toml, which is embedded into the binary at compile time. To add a signature:

  1. Capture a SYN from the target OS (client SYN, no ACK):

    sudo tcpdump -ni <iface> -v \
      'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'
  2. Read off three things from the SYN:

    • the TTL (it is rounded up to 64 / 128 / 255 at match time),
    • the TCP window size, and
    • the TCP option order.
  3. Append a block to signatures.toml. Option mnemonics are MSS, NOP, WS, SACK, TS, EOL, joined in order:

    [[signature]]
    os = "My OS 1.0"
    ttl = 64
    window = 64240          # use 0 as a wildcard window
    options = "MSS,SACK,TS,NOP,WS"
    device_type = "desktop" # router | mobile | desktop | iot | unknown
  4. Rebuild the daemon (cargo build --release -p spectre-daemon). The matcher scores an exact TTL + option-order match at higher confidence when the window also matches, and falls back to a coarse TTL-family guess when no signature matches.

The trimmed OUI vendor database lives next door in data/oui.txt (<6 hex prefix><TAB><vendor> per line) and is embedded the same way.


Repository layout

spectre/
├── spectre-daemon/        # Rust capture daemon
│   ├── src/
│   │   ├── main.rs        # wiring: capture → process → persist → serve
│   │   ├── capture.rs     # libpcap loop
│   │   ├── parse.rs       # Ethernet/IP/TCP/UDP/ARP parsing (panic-free)
│   │   ├── fingerprint.rs # OS signatures, OUI lookup, JA4
│   │   ├── appl.rs        # DNS/mDNS, DHCP, HTTP User-Agent extraction
│   │   ├── device.rs      # Device/connection state + anomaly detection
│   │   ├── db.rs          # SQLite (batched writes)
│   │   ├── api.rs         # axum routes + WebSocket
│   │   ├── alerts.rs      # alert vocabulary & severities
│   │   └── config.rs      # TOML config + env overrides
│   └── data/
│       ├── oui.txt
│       └── signatures.toml
├── spectre-ui/            # React + Vite + D3 + Tailwind
├── docker-compose.yml
└── README.md

What you'll see on a fresh VPS in 24 hours

Point Spectre at a public-facing box and the internet introduces itself within minutes. Everything below is unsolicited traffic, auto-tagged for you:

Tag Who they are
🔵 Googlebot Google's crawler (verified via reverse DNS on googlebot.com).
🟣 Censys / Shodan Internet-wide scanners that index every reachable service. Their fingerprints will hit your SSH, HTTP, and odd ports within the hour.
🟣 ONYPHE / BinaryEdge / Stretchoid More mass-scan operators cataloguing the internet (tagged Scanner).
🟠 Cloudflare / AWS / Azure / DigitalOcean Cloud/CDN egress — bots, health checks, and whatever's hosted there reaching out.
🟪 Tor Exit Node Traffic arriving from the Tor network.
🔴 Blocklisted IPs on Firehol L1 / Emerging-Threats compromised lists — known-bad hosts probing you right now.

The fun part: watch the mesh graph fill in real time as these get classified, and set a rule (tag contains 'Scanner' and connections > 5) to get a webhook ping the moment someone starts enumerating your ports.


Security

  • Passive only. Spectre never sends a packet — it cannot be detected on the wire and cannot disrupt the network it watches. No active scanning anywhere.
  • Your data never leaves your server. Capture, enrichment results, and history are stored locally in SQLite under ./data. The only outbound traffic is optional enrichment lookups (ip-api / AbuseIPDB / threat feeds) and webhooks you configure — all toggleable in config.toml. Federation is off by default. When enabled, only behavioral fingerprint hashes and coarse metadata (ASN, country) are shared with the hub — never IP addresses or traffic content. Full data contract: PROTOCOL.md
  • Auth built in. Enable [auth] (bcrypt + signed session cookies) before exposing the dashboard publicly; bind the API to localhost behind the UI's reverse proxy. See "Deploying on a VPS".
  • Sensitive by nature. The database records IPs, fingerprints, and contacted domains — treat ./data accordingly.

How it compares

Spectre ntopng Zeek darkstat
Focus Who's on/probing your network + threat intel Traffic analytics Protocol/IDS scripting Bandwidth accounting
Passive (no scanning)
Live mesh graph partial
OS + JA4 fingerprinting partial via scripts
Attacker behavioral fingerprinting (survives IP rotation)
Operator profiling (multi-tool campaign inference, local-only)
Privacy-first federation (hashes only, never raw IPs)
Auto-tagging (Googlebot/Shodan/…)
Threat-feed correlation ✅ built-in via scripts
Single binary / one-line install
Footprint tiny (Rust) heavy heavy tiny

Spectre isn't an IDS like Zeek or a full analyzer like ntopng — it's a zero-config, screenshot-worthy who-and-what view of a network with built-in threat context.

Roadmap

See ROADMAP.md for the full, tracked plan. Highlights:

  • Passive capture, device tracking, OS fingerprinting, JA4
  • IP enrichment (reverse DNS, geo/ASN, AbuseIPDB) + auto-tagging
  • User-configurable alert rules engine (hot-reloaded)
  • Traffic timeline + per-device sparklines
  • Authentication (bcrypt + session cookies)
  • Slide-over device detail panel with notes
  • Mesh graph: tag colors, clustering, filters, PNG export
  • Threat-feed correlation (Firehol / ET / Tor)
  • Global search (Cmd+K) + CSV/JSON export
  • Multi-arch Docker images on GHCR
  • Optional honeypot mode (fake services, logs every interaction)
  • Attacker behavioral fingerprinting + campaign detection (auditable signals)
  • TLS SNI domains tagged by direction (inbound / outbound / observed)
  • Operator profiling — local-only inference linking multi-tool campaigns
  • Federated sensor network (privacy-first, hashes only — no raw IPs): spctr-hub + optional sensor push/pull + standalone spctr-sensor
  • Prebuilt spctr-sensor musl binaries (amd64 + arm64) on every release
  • Telegram / Discord native notifiers
  • JA4S / JA4H / JA4T fingerprints
  • Prometheus /metrics endpoint
  • PCAP import / replay mode

Design notes

  • Never crashes on bad packets. Every parser is bounds-checked and returns None rather than panicking; the capture loop drops undecodable frames.
  • Batched persistence. The hot path only mutates in-memory state and marks rows dirty; a timer flushes to SQLite every 2 seconds inside one transaction.
  • Resilient UI. The frontend keeps showing the last known data when the daemon goes offline, shows a "disconnected" banner, and reconnects the WebSocket automatically with backoff.
  • JA4 from scratch. fingerprint.rs parses the TLS record + ClientHello, strips GREASE, sorts cipher suites and extensions, and hashes per the FoxIO spec to produce the ja4 string (e.g. t13d1516h2_<12hex>_<12hex>).

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors