A local collection of Rust cybersecurity tools, sharing one Cargo workspace and one optional web dashboard. Every tool works two ways: directly on the command line for people comfortable there, or through a local, point-and-click web UI for people who aren't.
- The Rust toolchain (
cargo) - Linux — several pieces are Linux-specific (packet capture via raw sockets, interface listing via
/sys/class/net, granting capabilities viasetcap)
Rusty Tools/
Portofino/ Multithreaded TCP port scanner
PickAPeckOfPacketParsers/ Live packet capture & parsing (aka "4P")
OneForTheHoney/ Decoy-service honeypot listener
Bunyan/ Systemd-journal auth log analyzer
FeeFiFoFIM/ File integrity monitor
HardHat/ Security config auditor
dashboard/ Local web UI for the tools above
Each tool directory is an independent, self-contained binary crate — the dashboard just launches them as subprocesses and streams their output. All three are members of one Cargo workspace (Cargo.toml at this level), so a single cargo build --release here builds everything.
cargo build --release
Every tool is a normal binary you can run directly:
cargo run -p Portofino -- --help
cargo run -p PickAPeckOfPacketParsers -- --help
cargo run -p OneForTheHoney -- --help
cargo run -p Bunyan -- --help
cargo run -p FeeFiFoFIM -- --help
cargo run -p HardHat
Portofino is also this workspace's default member, so a bare cargo run (or cargo run --release) from this directory goes straight to it — and its first prompt lets you pick [C]ommand Line (its own interactive wizard) or [I]nteractive (which launches the web dashboard below). So in practice, that one command is the entry point into everything.
cargo run -p dashboard --release
or double-click dashboard/run.sh. Both build and start a small local web server that opens your browser automatically to a tool picker — click a tool, fill in a short form, and watch its output stream in live. It binds to 127.0.0.1 only and is never reachable from another machine.
This is a personal toolkit for testing systems and networks you own or are explicitly authorized to test. Portofino's scanning and 4P's packet capture are both active, and in 4P's case privileged — only point them at hosts/interfaces you have permission to examine. OneForTheHoney binds to every network interface by default, which means it will log connection attempts from any device that can reach this machine on that network — not just your own. Only run it on networks you own or are explicitly authorized to monitor.
Multithreaded scanner for a single IP, a range (10.0.0.1-50), or a CIDR block (10.0.0.0/24), with live progress and an "Open Ports" summary that stays readable even when scanning hundreds of hosts.
# Interactive wizard — just answer the prompts
cargo run -p Portofino
# Non-interactive / scriptable
cargo run -p Portofino -- --threads 500 --target 10.0.0.0/24 --ports common
--threads/-n: shared across every host being scanned at once, clamped to 2000.--target/-t: single IP, range, or CIDR block (/16or smaller).--ports/-p:all(full 1–65535 sweep) orcommon(a curated list of ~20 frequently-exposed ports) — defaults toall.
Sniffs a chosen network interface and decodes each frame — Ethernet → IPv4/IPv6/ARP → TCP/UDP/ICMP — with basic DNS and plaintext HTTP decoding, live port-scan/ARP-spoof alerting, and optional .pcap export.
Capturing raw packets means the OS hands your program a copy of every frame arriving on a network interface — not just traffic addressed to that program. Because that's powerful, Linux gates it behind a specific permission, CAP_NET_RAW, that a program doesn't have by default.
This isn't specific to this tool — every packet-capture tool works this way, including tcpdump, Wireshark (via its dumpcap helper), and nmap's raw-packet scan modes. It's the same OS-level safeguard in every case, and it exists precisely so nothing can silently sniff network traffic without someone deliberately granting that access. 4P never captures anything unless you've explicitly done one of the two things below.
From the workspace root, after building:
sudo setcap cap_net_raw+ep target/release/PickAPeckOfPacketParsers
(On this machine specifically, that's sudo setcap cap_net_raw+ep ~/RustroverProjects/"Rusty Tools"/target/release/PickAPeckOfPacketParsers — adjust the path if you cloned it somewhere else.)
After this, the compiled binary runs without sudo — directly, or through the dashboard (which always runs as your normal user, never as root, so this is the only way it works through the web UI).
Note: the capability is attached to that specific compiled file. Rebuilding (e.g. cargo build after a source change) produces a new file and wipes it — re-run the command above once after any rebuild. Once the code is stable this is rare in practice, and it's the same tradeoff Wireshark's own capture helper makes on Linux — not a shortcut specific to this project. (See "avoiding this after every rebuild" below if it ever becomes annoying enough to be worth fixing properly.)
sudo ./target/release/PickAPeckOfPacketParsers <interface>
Simpler for a one-off test, but asks for your password every time and doesn't work through the dashboard.
Either way: build normally first, and only elevate the final run/setcap step. Don't run cargo build/cargo run itself with sudo — that leaves root-owned files in the shared workspace target/ directory and breaks your next unprivileged build.
# List interfaces
cargo run -p PickAPeckOfPacketParsers -- --list
# Capture everything on an interface
./target/release/PickAPeckOfPacketParsers wlp13s0
# Only TCP traffic on port 443
./target/release/PickAPeckOfPacketParsers wlp13s0 --protocol tcp --port 443
# Only traffic to/from a specific host
./target/release/PickAPeckOfPacketParsers wlp13s0 --host 192.168.1.1
# Save the (filtered) capture to a .pcap file, openable in Wireshark
./target/release/PickAPeckOfPacketParsers wlp13s0 --pcap-out captures/session.pcap
Through the dashboard: click the PPPP card, pick an interface from the auto-populated dropdown, optionally set a filter, and click Start capture.
Output line prefixes:
| Prefix | Meaning |
|---|---|
TCP / UDP / ICMP / ICMPv6 / ARP |
Base per-protocol summary line |
DNS |
Decoded query/response name + record type (A, AAAA, CNAME, ...) |
HTTP |
Plaintext request/status line + Host header, when a TCP/80 payload looks like HTTP |
ALERT |
A possible port scan (one source hitting many destination ports fast) or ARP spoof (an IP suddenly claimed by a new MAC), detected live |
Not possible without meaningfully more architecture. setcap attaches to a specific file's data, and cargo produces a new file on every rebuild. The pattern real capture tools use to dodge this — Wireshark's dumpcap — is to split the privileged part into a small, separate helper binary that almost never changes, so its capability survives indefinitely regardless of how often the main program is rebuilt. That's a real chunk of added complexity (a second binary, a way to hand off the open capture socket) that isn't worth it unless re-running setcap becomes a genuine annoyance — it's not built here for that reason, but it's a well-understood next step if it ever is.
A honeypot: binds a curated list of the ports scanners and bots most commonly probe (FTP, SSH, Telnet, SMTP, HTTP(S), SMB, MSSQL, MySQL, RDP, VNC) and logs every connection attempt against them, with a fake plaintext banner on the protocols that have one and live scanner detection when one source touches several decoy ports in quick succession.
Several of the default decoy ports (21, 22, 23, 25, 80, 443) are below 1024, and Linux only lets a process bind those without a specific permission, CAP_NET_BIND_SERVICE — the same OS-level gate every real service on those ports (sshd, an actual web server, etc.) has to satisfy. OneForTheHoney binds each decoy port independently: if one fails for lack of privilege, it prints a warning for that port and keeps the rest running, so it's still useful unprivileged (any ports ≥1024 in the list still work).
From the workspace root, after building:
sudo setcap cap_net_bind_service+ep target/release/OneForTheHoney
After this, the compiled binary can bind the privileged ports without sudo — directly, or through the dashboard. As with 4P's cap_net_raw, the capability is attached to that specific compiled file, so re-run this once after any rebuild.
sudo ./target/release/OneForTheHoney
Simpler for a one-off, but asks for your password every time and doesn't work through the dashboard.
# Start with defaults: bind 0.0.0.0, the full curated port list, banners on
./target/release/OneForTheHoney
# Only a couple of ports
./target/release/OneForTheHoney --ports 22,80,3389
# Loopback only, for local testing
./target/release/OneForTheHoney --bind 127.0.0.1
# No fake banners — bare accept-and-log
./target/release/OneForTheHoney --no-banner
Through the dashboard: click the OneForTheHoney card, optionally adjust the bind address or port list, and click Start listening.
Output line prefixes:
| Prefix | Meaning |
|---|---|
CONN |
A connection landed on a decoy port — source IP, source port, decoy port, and service name |
DATA |
Whatever the connecting side sent within 3 seconds of connecting (or of the banner, if one was sent) |
ALERT |
One source touched 3+ distinct decoy ports within 10 seconds — likely a scanner |
Watches the systemd journal's auth facility — sshd and sudo activity — and prints only what's actually signal: logins (success/fail), sudo command executions and auth failures, and live alerts for brute-force and compromise patterns. Named for the log-splitting kind of Bunyan, not the WHOIS-adjacent kind.
Unlike 4P and OneForTheHoney, Bunyan doesn't need sudo or setcap — reading the journal's auth facility (journalctl SYSLOG_FACILITY=10) works as a normal user on this machine out of the box.
By default Bunyan tails the journal live (journalctl -f -o json SYSLOG_FACILITY=10 under the hood). Since this machine doesn't run sshd, the practical way to exercise (or just try out) the brute-force detection is to point it at a saved export instead:
# Live (default) — watches sshd/sudo activity as it happens
./target/release/Bunyan
# Save a chunk of the real journal to replay/inspect later
journalctl SYSLOG_FACILITY=10 -o json --no-pager -n 200 > sample.jsonl
# Replay a saved export — reads to the end, then exits
./target/release/Bunyan --file sample.jsonl
--file accepts anything in the same JSON-lines shape journalctl -o json produces — including a hand-crafted sample with synthetic sshd/sudo entries, useful for testing the alert thresholds without needing a real attack (or a running sshd) to generate one.
Through the dashboard: click the Bunyan card, optionally point it at a saved export, and click Start.
Bunyan spawns journalctl -f as a child process for live mode. If Bunyan is killed outright rather than allowed to exit on its own — notably, the dashboard's Stop button, which signals only Bunyan's own process — that journalctl child can be left running in the background. Harmless (it just keeps tailing quietly), but worth knowing about; a full fix would mean either process-group signaling in the dashboard or replacing the long-lived -f stream with short-lived polling, neither of which felt worth the added complexity for a personal tool. Plain Ctrl+C on the command line doesn't have this problem — it's delivered to the whole foreground process group, journalctl included.
Output line prefixes:
| Prefix | Meaning |
|---|---|
LOGIN |
An sshd login attempt — ok (with method) or fail (tagged [invalid user] when the username itself doesn't exist) |
SUDO |
A sudo command execution (invoking user -> target user command) or a sudo auth failure |
ALERT |
5+ failed SSH logins from one source within 60s (brute force), or a successful login from a source with 3+ recent failures (possible compromised credential) |
Baselines a directory's contents (a SHA-256 hash of every file), then checks it again later and reports anything added, removed, or changed. Classic host-based integrity monitoring — pairs with Bunyan as the toolkit's second host-based (rather than network-based) detection skill.
Like Bunyan, this is plain file I/O — no sudo or setcap required.
# Create (or refresh) a baseline for a directory
./target/release/FeeFiFoFIM /etc --init
# One-shot check against that baseline
./target/release/FeeFiFoFIM /etc
# Keep checking every 30 seconds, printing only newly-detected changes
./target/release/FeeFiFoFIM /etc --watch 30
--baseline <file> overrides where the baseline is stored/read (default: fim-baseline.json in the current directory) — keep it outside the directory being monitored, or the baseline file itself will show up as a change on the next run. Through the dashboard, leaving the baseline field blank auto-names one after the monitored path and stores it alongside the tool, so repeated runs against the same directory stay consistent without you having to track a file path yourself.
--watch re-checks against the original baseline on every tick, but only prints changes that are new since the previous tick — so an ongoing difference doesn't get reprinted forever. A .git directory (if present) and any symlinks in the monitored tree are skipped automatically.
Through the dashboard: click the FeeFiFoFIM card, set a directory, and use Create/Update Baseline or Check (with an optional watch interval).
Output line prefixes:
| Prefix | Meaning |
|---|---|
ADDED |
A file exists now that wasn't in the baseline |
REMOVED |
A file in the baseline no longer exists |
MODIFIED |
A file's content hash no longer matches the baseline |
There's no separate ALERT line here — unlike the other tools, every line above already is the alert.
A proactive posture check, unlike every other tool here — it reads local system configuration once and reports hardening gaps, rather than watching for events over time. Checks SSH config, /etc/passwd for unexpected UID-0 accounts, firewall service status, unusual setuid binaries, and a watchlist of sensitive files for world-writable permissions — plus, when run as root, sudoers NOPASSWD: ALL rules and /etc/shadow for empty-password accounts.
This one has a different privilege story than the rest of the toolkit. Bunyan and FeeFiFoFIM never need privilege; 4P and OneForTheHoney need it for everything. HardHat sits in between: it runs fully unprivileged and still produces real findings (SSH config, firewall presence, account anomalies, SUID audit, file permissions), but two checks — sudoers and shadow — need root to read their target files at all, and print as SKIP (not a failure) when they can't.
# Unprivileged — most checks run, sudoers/shadow show as SKIP
./target/release/HardHat
# Full audit, including sudoers and shadow
sudo ./target/release/HardHat
Dashboard note: the dashboard always runs tools as your normal user, by design, and never elevates — so the sudoers and shadow checks will always show as skipped there. Run the CLI directly with sudo for the full audit.
Output line prefixes:
| Prefix | Meaning |
|---|---|
PASS |
The check found nothing concerning |
WARN |
Worth a look, but not necessarily wrong (e.g. password auth enabled, an unusual setuid binary) |
FAIL |
A clear hardening gap (e.g. PermitRootLogin yes, a world-writable /etc/passwd, an empty-password account) |
SKIP |
The check needs root and this run doesn't have it |
There's no separate ALERT line here either — same reasoning as FeeFiFoFIM: WARN/FAIL/SKIP already communicate what matters directly.
A small Axum server that's a thin, tool-agnostic launcher: each tool page builds a query string from a form, opens a Server-Sent Events connection, and the server builds (quietly) and runs that tool as a subprocess, streaming its stdout/stderr back live. A shared per-tool Stop button works by killing the subprocess (or aborting the build) via a cancellation signal, so it works even for 4P's capture, which otherwise runs forever.
Always binds to 127.0.0.1:7878 — by design, never configurable to anything else, since it launches active scans/captures on demand.