Catch ephemeral ports the moment they open — fingerprint, hold open, and hand off to the pentester before the window closes.
Current status — April 2026 / v1.2.0: the SYN-race capability is real on Linux.
RawEngineruns apnetAF_PACKET SYN spray + pcap SYN-ACK receive + kernel-connect handoff at up to 10,000 pps/target (requiresCAP_NET_RAW). macOS and Windows still run the connect-engine with a"raw"label while the BPF / WinDivert ports are written — flagged honestly inBACKEND_STATUSand the CHANGELOG, not hidden.portsnatcher/v1event schema frozen; scope-file format cross-compatible withIntegSec/agentic-pentest-proxy. Spec · Operator guide · CHANGELOG.
Modern pentest targets expose services on ephemeral, short-lived TCP ports — cloud metadata helpers, auto-scaling admin interfaces, flapping internal services, reconnection-window sockets, debug listeners that open for milliseconds during a deploy. Classic scanners are the wrong shape for this:
nmap,masscan,zmap,unicornscan— one-shot. A port that's open for 200ms during your scan is either in the results or isn't; by the time you see it, it's gone.knockd/ port-knocking tools — detect, don't exploit.- Burp Collaborator / Interactsh — listen on your infrastructure, don't hunt on theirs.
pwncat,responder— post-connection, don't race.
PortSnatcher is the first tool purpose-built for the race. It watches a scoped set of targets continuously, wins the race to SYN/ACK the moment a port opens, fingerprints the service on fresh connections (one probe per connection, nothing destructive unless authorized), stands up a local hold-open tunnel so you can attack through Burp or any TCP-speaking tool before the port closes, and pokes you via desktop toast / webhook the moment it catches.
It's the tool you reach for when nmap gave you nothing but you know services are flashing open on that host.
# On the pentester's laptop — targeting a scope-file-defined engagement
$ portsnatcher --config engagement.toml
[09:14:02] PortSnatcher v1.2.0 — engagement ENG-2026-0142
[09:14:02] Profile: internal | Engine: raw | Targets: 10.20.0.0/16 (256 hosts)
[09:14:02] Ports: ephemeral-iana (49152-65535) | Rate cap: 50000 pps
[09:14:02] Event bus: http://127.0.0.1:7177/events (token in ~/.config/portsnatcher/bus-token)
[09:14:02] Scope file: ./scope.json (compatible with agentic-pentest-proxy)
[09:14:02] Watching...
[09:14:47] CATCH 10.20.5.17:54283 (window: 180ms) engine=raw syn_rtt=2ms
└─ fingerprint: HTTP/1.1, Server: nginx/1.25.3, TLS: no
└─ hold-open: localhost:7101 (dumb_tunnel, keepalive engaged)
└─ desktop toast sent, webhook POST ok
→ attach Burp to 127.0.0.1:7101 to work the serviceFrom port opening to live Burp-ready tunnel: typically ~2 seconds. The toast fires while the fingerprinter is still running — the tunnel is up before the banner shows up on your screen.
targets.json ────┐
│
scope.json ────┐ │ ┌─────────────────────────────────────────┐
│ │ │ PortSnatcher │
config.toml ────┼─┴──────┤ │
│ │ ┌──────────┐ ┌──────────────┐ │
└────────┼──▶ Engine │──▶│ Fingerprinter│ │
│ │(raw|conn)│ │ (probe ladder│ │
│ └──────────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────┐ │
│ │ Event Bus (v1) │ │
│ │ tokio::broadcast + │ │
│ │ SSE/WebSocket server │ │
│ └──────┬──────────────────┘ │
│ │ │
│ ┌───────┼────────┬───────────┐ │
│ ▼ ▼ ▼ ▼ │
│ TUI JSONL Toast/Webhook Hold-Open │
│ Proxy │
│ │ │
└────────────────────────────────┼────────┘
▼
127.0.0.1:71xx (attach here)
Everything communicates through a versioned JSON event bus. The same stream you see in the TUI is what a webhook receives, what events.jsonl records, and what the future Burp extension will subscribe to over HTTP.
RawEngine (Linux, real SYN race since v1.2) |
pnet AF_PACKET SYN spray + pcap SYN-ACK receive + kernel-connect handoff. Sub-100ms detection of ephemeral ports at up to ~10,000 pps/target. Requires CAP_NET_RAW. On macOS/Windows the same engine falls back to a connect-labelled scheduler pending the BPF / WinDivert ports (v1.3). |
ConnectEngine |
Unprivileged async connect() with FuturesUnordered concurrency. Works in containers, locked-down jumpboxes, CI. Same events, same pipeline. Use when you don't have CAP_NET_RAW. |
| Probe ladder, not probe list | Passive banner read first (zero bytes sent), then TLS ClientHello, then port-aware protocol probes — one per fresh connection. Never chains a destructive probe after a soft one. Nine probes in v1 (HTTP, TLS, SSH, Redis, Mongo, Postgres, SMB, banner). |
| Hold-open tunnel | The moment a port is caught, we stand up localhost:71xx piping bytes to the target. You point Burp / ncat / your custom exploit at it before the port closes. Optional TLS MITM for HTTPS catches, using a reusable on-disk CA. |
| Scope-aware probes | Probes are tagged with technique categories — recon, web_app, api_testing, ssl_tls, destructive. Probes without authorized coverage are skipped and audit-logged. |
| Stable event schema | portsnatcher/v1 events are a frozen JSON contract. Additive-only forever. Your Burp extension, SOC pipeline, or custom tooling can depend on it. |
| Real-time handoff | Desktop toasts, Slack/Discord/ntfy/PagerDuty webhooks, terminal TUI (--tui), SSE/WebSocket bus — all fed from the same stream. |
| Safety first | Scope file required; hard-deny on loopback/link-local; global + per-target pps caps; explicit-target rule; --i-know-what-im-doing flag for overrides (and yes, it's literally named that). Every decision logged to audit.log. |
PortSnatcher consumes the same JSON scope-file format as IntegSec's agentic-pentest-proxy. One file describes your engagement; both tools honor it. PortSnatcher adds a namespaced portsnatcher section for port-specific policy that the proxy ignores cleanly.
{
"engagement_id": "ENG-2026-0142",
"client": "Acme Corp",
"operator": "you@yourshop.com",
"authorized_targets": {
"ip_ranges": ["10.10.10.0/24"],
"domains": ["*.acme.com"]
},
"excluded_targets": ["10.10.10.99"],
"authorized_techniques": ["recon", "web_app", "api_testing", "ssl_tls"],
"excluded_techniques": ["dos", "destructive"],
"engagement_window": {
"start": "2026-04-22T08:00:00Z",
"end": "2026-05-06T17:00:00Z"
},
"portsnatcher": {
"port_policy": {
"include": ["ephemeral-iana", "22", "80", "443"]
}
}
}| internal | external | ctf | |
|---|---|---|---|
| Default engine | raw | raw | raw |
| Global pps cap | 50,000 | 1,000 | 100,000 |
| Per-target cap | 2,000 | 200 | 10,000 |
| Probe ladder | full | minimal | full + aggressive |
| Toast on catch | yes | no | yes |
| Scope file required | yes | yes (strict) | no (lab) |
--profile is a one-flag way to pick a coherent default posture. Override any individual setting when you need to.
- Design + five phase plans (spec)
-
ps-core(scope, config, frozenportsnatcher/v1event schema, insta snapshots) -
ps-engine—ConnectEngine+RawEngine -
ps-fingerprint— nine-probe ladder with fresh-connection discipline -
ps-proxy— dumb tunnel + TLS MITM withrcgen-signed CA -
ps-bus— SSE + WebSocket + bearer-token auth -
ps-notify— terminal / JSONL / webhook / desktop sinks -
ratatuiTUI via--tui - Linux / macOS / Windows CI matrix + fmt + clippy gate
- Fuzz targets +
cargo-denylicense policy - Signed
cargo-dist-style prebuilts via GitHub Releases - Live-engagement E2E smoke test
- Real Linux SYN race (v1.2):
pnetAF_PACKET + pcap + kernel-connect handoff
- macOS BPF port of
SynRace - Windows WinDivert port of
SynRace - IPv6 support in target plan, scope guard, and SynRace
- Multi-NIC per-target source-IP routing
- First-party Burp Suite extension (Montoya API) subscribing to the event bus
- Caido / ZAP plugins following the same pattern
- Web dashboard for the event bus
- crates.io publish (
cargo install integsec-portsnatcher) once the registry token is configured
- UDP support
- Deeper protocol-specific probe modules (LDAP, Kerberos, proprietary binary protocols)
- Distributed mode (multiple PortSnatcher probes feeding one bus)
PortSnatcher is a professional pentest tool, not a script-kiddie toy. It refuses to run without a scope file. It refuses internet-wide scanning. It logs every decision — including every packet it chose not to send — to an audit trail that belongs in your client report. The defaults are conservative; the bypass flag is intentionally awkward to type.
Use it on engagements you're authorized to perform. If your engagement letter doesn't cover PortSnatcher's behavior, don't run it. The Apache-2.0 license gives you rights to the code; it does not give you rights to the networks you point it at.
| Linux | macOS | Windows | |
|---|---|---|---|
ConnectEngine |
yes | yes | yes |
RawEngine — real SYN race (v1.2+) |
yes (pnet AF_PACKET + pcap) |
not yet (v1.3: BPF) | not yet (v1.3: WinDivert) |
RawEngine — connect-labelled fallback |
only if SynRace fails to init |
yes (current) | yes (current) |
Optional nftables RST-drop kassist |
installed when nft available |
n/a | n/a |
Requires elevation for RawEngine |
CAP_NET_RAW (setcap cap_net_raw,cap_net_admin=eip) |
root | admin |
Linux is where the headline capability actually lives today. macOS and Windows are solid for the ConnectEngine, the hold-open proxy, TLS MITM, the TUI, and the full event-bus stack — they just don't do the real sub-100ms SYN race yet. That's v1.3.
Honest check: the real race only improves catch-rate for ports that open for milliseconds-to-seconds. If your target is serving a long-lived HTTPS endpoint on 443, the ConnectEngine on any OS is already fine.
Prebuilt binary (recommended). Grab the archive for your OS+arch from the
latest GitHub Release
and unpack it somewhere on your $PATH. Per release, we ship:
portsnatcher-v1.2.0-x86_64-unknown-linux-gnu.tar.gzportsnatcher-v1.2.0-x86_64-apple-darwin.tar.gzportsnatcher-v1.2.0-aarch64-apple-darwin.tar.gzportsnatcher-v1.2.0-x86_64-pc-windows-msvc.zip
On Linux, grant the real-SYN-race engine the capabilities it needs (otherwise it falls back to the connect-labelled scheduler):
sudo setcap cap_net_raw,cap_net_admin=eip "$(which portsnatcher)"Building from source:
git clone https://github.com/IntegSec/PortSnatcher
cd PortSnatcher
cargo build --release --bin portsnatcher
./target/release/portsnatcher --helpcargo install will land once the CARGO_REGISTRY_TOKEN repo secret is configured (v1.4 roadmap):
cargo install integsec-portsnatcher # NOT YET published to crates.ioPortSnatcher is open to community contributions. Two rules are non-negotiable:
- The
portsnatcher/v1event schema is frozen. Adding a new optional field is OK. Removing or renaming a field is av2bump and requires coordinated consumer updates. Theinstasnapshots incrates/ps-core/tests/snapshots/are the enforcement mechanism — a PR that changes them must explain why. - Scope-file compatibility with
agentic-pentest-proxyis a stability contract. Anything that changes the top-level fields is a breaking change.
See CONTRIBUTING.md for dev setup, commit conventions, and the PR checklist. For security-sensitive reports, see SECURITY.md.
PortSnatcher stands on the shoulders of:
nmap,masscan,zmap,unicornscan— port-scanning state of the artpnet— the raw-socket + pcap crate that makesRawEnginetractable on Linuxrustls— the TLS implementation used for the MITM proxyratatui,tokio,axum— Rust's superb async ecosystemrcgen— on-disk CA generation for the TLS MITM- Burp Collaborator / Interactsh — prior art for out-of-band handoff patterns
- IntegSec's
agentic-pentest-proxy— sister tool and source of the scope-file format
We are not aware of any prior tool combining continuous SYN-level racing, short-window fingerprinting on fresh connections, and pentester-in-the-loop hold-open handoff in one package. If you know of one, open an issue — we'd love to credit it here.
Apache License 2.0 — see LICENSE and NOTICE.
Built by IntegSec — our first open-source Rust tool. Found a bug, want a feature, or know a use case we haven't thought of? Open an issue or start a discussion — we want the pentest community shaping this.