https://github.com/integsec/portsnatcher
Catch ephemeral ports the moment they open — fingerprint, hold open, and hand off to the pentester before the window closes. Apache-2.0.
https://github.com/integsec/portsnatcher
burp-suite ephemeral-ports fingerprinting mitm offensive-security pentesting port-scanner red-team rust security
Last synced: 3 months ago
JSON representation
Catch ephemeral ports the moment they open — fingerprint, hold open, and hand off to the pentester before the window closes. Apache-2.0.
- Host: GitHub
- URL: https://github.com/integsec/portsnatcher
- Owner: IntegSec
- License: apache-2.0
- Created: 2026-04-23T02:28:49.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-23T03:25:08.000Z (3 months ago)
- Last Synced: 2026-04-23T05:23:58.018Z (3 months ago)
- Topics: burp-suite, ephemeral-ports, fingerprinting, mitm, offensive-security, pentesting, port-scanner, red-team, rust, security
- Size: 151 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Notice: NOTICE
Awesome Lists containing this project
README
# PortSnatcher
**Catch ephemeral ports the moment they open — fingerprint, hold open, and hand off to the pentester before the window closes.**
[](https://opensource.org/licenses/Apache-2.0)
[](./CHANGELOG.md)
[](https://github.com/IntegSec/PortSnatcher/actions/workflows/ci.yml)
[]()
[](https://integsec.com)
> **Current status — April 2026 / v1.2.0:** the SYN-race capability is
> real on Linux. `RawEngine` runs a `pnet` AF_PACKET SYN spray + pcap
> SYN-ACK receive + kernel-connect handoff at up to 10,000 pps/target
> (requires `CAP_NET_RAW`). macOS and Windows still run the connect-engine
> with a `"raw"` label while the BPF / WinDivert ports are written — flagged
> honestly in `BACKEND_STATUS` and the CHANGELOG, not hidden. `portsnatcher/v1`
> event schema frozen; scope-file format cross-compatible with
> [`IntegSec/agentic-pentest-proxy`](https://github.com/IntegSec/agentic-pentest-proxy).
> [Spec](./docs/superpowers/specs/2026-04-22-portsnatcher-design.md) ·
> [Operator guide](./docs/operator-guide.md) · [CHANGELOG](./CHANGELOG.md).
---
## Why PortSnatcher exists
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.**
## The 30-second demo (target UX)
```bash
# 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 service
```
**From 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.
## How it works
```
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.
## Key features
| | |
|---|---|
| **`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](https://github.com/IntegSec/agentic-pentest-proxy/blob/master/examples/scope-manifest.json) — `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`. |
## Cross-tool scope compatibility
PortSnatcher consumes the **same JSON scope-file format** as IntegSec's [`agentic-pentest-proxy`](https://github.com/IntegSec/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.
```json
{
"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"]
}
}
}
```
## Profiles — three postures, one codebase
| | **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.
## Roadmap
### Shipped (v1.0 → v1.2)
- [x] Design + five phase plans ([spec](./docs/superpowers/specs/2026-04-22-portsnatcher-design.md))
- [x] `ps-core` (scope, config, frozen `portsnatcher/v1` event schema, insta snapshots)
- [x] `ps-engine` — `ConnectEngine` + `RawEngine`
- [x] `ps-fingerprint` — nine-probe ladder with fresh-connection discipline
- [x] `ps-proxy` — dumb tunnel + TLS MITM with `rcgen`-signed CA
- [x] `ps-bus` — SSE + WebSocket + bearer-token auth
- [x] `ps-notify` — terminal / JSONL / webhook / desktop sinks
- [x] `ratatui` TUI via `--tui`
- [x] Linux / macOS / Windows CI matrix + fmt + clippy gate
- [x] Fuzz targets + `cargo-deny` license policy
- [x] Signed `cargo-dist`-style prebuilts via GitHub Releases
- [x] Live-engagement E2E smoke test
- [x] **Real Linux SYN race** (v1.2): `pnet` AF_PACKET + pcap + kernel-connect handoff
### v1.3 — next
- 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
### v1.4+
- 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
### v2+
- UDP support
- Deeper protocol-specific probe modules (LDAP, Kerberos, proprietary binary protocols)
- Distributed mode (multiple PortSnatcher probes feeding one bus)
## Safety and ethics
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.
## Supported platforms
| | 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.
## Install
**Prebuilt binary (recommended).** Grab the archive for your OS+arch from the
[latest GitHub Release](https://github.com/IntegSec/PortSnatcher/releases/latest)
and unpack it somewhere on your `$PATH`. Per release, we ship:
- `portsnatcher-v1.2.0-x86_64-unknown-linux-gnu.tar.gz`
- `portsnatcher-v1.2.0-x86_64-apple-darwin.tar.gz`
- `portsnatcher-v1.2.0-aarch64-apple-darwin.tar.gz`
- `portsnatcher-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):
```bash
sudo setcap cap_net_raw,cap_net_admin=eip "$(which portsnatcher)"
```
**Building from source:**
```bash
git clone https://github.com/IntegSec/PortSnatcher
cd PortSnatcher
cargo build --release --bin portsnatcher
./target/release/portsnatcher --help
```
**`cargo install`** will land once the `CARGO_REGISTRY_TOKEN` repo secret is configured (v1.4 roadmap):
```bash
cargo install integsec-portsnatcher # NOT YET published to crates.io
```
## Contributing
PortSnatcher is open to community contributions. Two rules are non-negotiable:
1. **The `portsnatcher/v1` event schema is frozen.** Adding a new optional field is OK. Removing or renaming a field is a `v2` bump and requires coordinated consumer updates. The `insta` snapshots in `crates/ps-core/tests/snapshots/` are the enforcement mechanism — a PR that changes them must explain why.
2. **Scope-file compatibility with `agentic-pentest-proxy` is a stability contract.** Anything that changes the top-level fields is a breaking change.
See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for dev setup, commit conventions, and the PR checklist. For security-sensitive reports, see [`SECURITY.md`](./SECURITY.md).
## Prior art and influences
PortSnatcher stands on the shoulders of:
- `nmap`, `masscan`, `zmap`, `unicornscan` — port-scanning state of the art
- `pnet` — the raw-socket + pcap crate that makes `RawEngine` tractable on Linux
- `rustls` — the TLS implementation used for the MITM proxy
- `ratatui`, `tokio`, `axum` — Rust's superb async ecosystem
- `rcgen` — on-disk CA generation for the TLS MITM
- Burp Collaborator / Interactsh — prior art for out-of-band handoff patterns
- IntegSec's [`agentic-pentest-proxy`](https://github.com/IntegSec/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.
## License
Apache License 2.0 — see [`LICENSE`](./LICENSE) and [`NOTICE`](./NOTICE).
---
Built by [IntegSec](https://integsec.com) — 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](https://github.com/IntegSec/PortSnatcher/issues) or [start a discussion](https://github.com/IntegSec/PortSnatcher/discussions) — we want the pentest community shaping this.