{"id":49244883,"url":"https://github.com/integsec/portsnatcher","last_synced_at":"2026-04-24T21:01:00.610Z","repository":{"id":353247016,"uuid":"1218575816","full_name":"IntegSec/PortSnatcher","owner":"IntegSec","description":"Catch ephemeral ports the moment they open — fingerprint, hold open, and hand off to the pentester before the window closes. Apache-2.0.","archived":false,"fork":false,"pushed_at":"2026-04-23T03:25:08.000Z","size":155,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-23T05:23:58.018Z","etag":null,"topics":["burp-suite","ephemeral-ports","fingerprinting","mitm","offensive-security","pentesting","port-scanner","red-team","rust","security"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/IntegSec.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-23T02:28:49.000Z","updated_at":"2026-04-23T03:25:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/IntegSec/PortSnatcher","commit_stats":null,"previous_names":["integsec/portsnatcher"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/IntegSec/PortSnatcher","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntegSec%2FPortSnatcher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntegSec%2FPortSnatcher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntegSec%2FPortSnatcher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntegSec%2FPortSnatcher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/IntegSec","download_url":"https://codeload.github.com/IntegSec/PortSnatcher/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntegSec%2FPortSnatcher/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32240613,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"ssl_error","status_checked_at":"2026-04-24T13:21:15.005Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["burp-suite","ephemeral-ports","fingerprinting","mitm","offensive-security","pentesting","port-scanner","red-team","rust","security"],"created_at":"2026-04-24T21:00:42.095Z","updated_at":"2026-04-24T21:01:00.594Z","avatar_url":"https://github.com/IntegSec.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# PortSnatcher\n\n**Catch ephemeral ports the moment they open — fingerprint, hold open, and hand off to the pentester before the window closes.**\n\n[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n[![Status](https://img.shields.io/badge/status-v1.2.0%20%E2%80%94%20real%20SYN%20race%20on%20Linux-brightgreen)](./CHANGELOG.md)\n[![CI](https://github.com/IntegSec/PortSnatcher/actions/workflows/ci.yml/badge.svg)](https://github.com/IntegSec/PortSnatcher/actions/workflows/ci.yml)\n[![Platform](https://img.shields.io/badge/platform-linux_%7C_macOS_%7C_windows-lightgrey)]()\n[![Made by IntegSec](https://img.shields.io/badge/made_by-IntegSec-black)](https://integsec.com)\n\n\u003e **Current status — April 2026 / v1.2.0:** the SYN-race capability is\n\u003e real on Linux. `RawEngine` runs a `pnet` AF_PACKET SYN spray + pcap\n\u003e SYN-ACK receive + kernel-connect handoff at up to 10,000 pps/target\n\u003e (requires `CAP_NET_RAW`). macOS and Windows still run the connect-engine\n\u003e with a `\"raw\"` label while the BPF / WinDivert ports are written — flagged\n\u003e honestly in `BACKEND_STATUS` and the CHANGELOG, not hidden. `portsnatcher/v1`\n\u003e event schema frozen; scope-file format cross-compatible with\n\u003e [`IntegSec/agentic-pentest-proxy`](https://github.com/IntegSec/agentic-pentest-proxy).\n\u003e [Spec](./docs/superpowers/specs/2026-04-22-portsnatcher-design.md) ·\n\u003e [Operator guide](./docs/operator-guide.md) · [CHANGELOG](./CHANGELOG.md).\n\n---\n\n## Why PortSnatcher exists\n\nModern 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:\n\n- `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.\n- `knockd` / port-knocking tools — detect, don't exploit.\n- Burp Collaborator / Interactsh — listen on your infrastructure, don't hunt on theirs.\n- `pwncat`, `responder` — post-connection, don't race.\n\nPortSnatcher 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.\n\n**It's the tool you reach for when `nmap` gave you nothing but you know services are flashing open on that host.**\n\n## The 30-second demo (target UX)\n\n```bash\n# On the pentester's laptop — targeting a scope-file-defined engagement\n$ portsnatcher --config engagement.toml\n[09:14:02] PortSnatcher v1.2.0 — engagement ENG-2026-0142\n[09:14:02] Profile: internal  |  Engine: raw  |  Targets: 10.20.0.0/16 (256 hosts)\n[09:14:02] Ports: ephemeral-iana (49152-65535)  |  Rate cap: 50000 pps\n[09:14:02] Event bus: http://127.0.0.1:7177/events  (token in ~/.config/portsnatcher/bus-token)\n[09:14:02] Scope file: ./scope.json (compatible with agentic-pentest-proxy)\n[09:14:02] Watching...\n\n[09:14:47] CATCH  10.20.5.17:54283  (window: 180ms)  engine=raw  syn_rtt=2ms\n           └─ fingerprint: HTTP/1.1, Server: nginx/1.25.3, TLS: no\n           └─ hold-open:   localhost:7101  (dumb_tunnel, keepalive engaged)\n           └─ desktop toast sent, webhook POST ok\n           → attach Burp to 127.0.0.1:7101 to work the service\n```\n\n**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.\n\n## How it works\n\n```\n targets.json  ────┐\n                    │\n scope.json   ────┐ │      ┌─────────────────────────────────────────┐\n                  │ │      │           PortSnatcher                  │\n config.toml  ────┼─┴──────┤                                         │\n                  │        │  ┌──────────┐   ┌──────────────┐        │\n                  └────────┼──▶ Engine   │──▶│ Fingerprinter│        │\n                           │  │(raw|conn)│   │ (probe ladder│        │\n                           │  └──────────┘   └──────────────┘        │\n                           │        │              │                 │\n                           │        ▼              ▼                 │\n                           │   ┌─────────────────────────┐           │\n                           │   │    Event Bus (v1)       │           │\n                           │   │  tokio::broadcast +     │           │\n                           │   │  SSE/WebSocket server   │           │\n                           │   └──────┬──────────────────┘           │\n                           │          │                              │\n                           │  ┌───────┼────────┬───────────┐         │\n                           │  ▼       ▼        ▼           ▼         │\n                           │ TUI   JSONL  Toast/Webhook  Hold-Open   │\n                           │                              Proxy      │\n                           │                                │        │\n                           └────────────────────────────────┼────────┘\n                                                            ▼\n                                              127.0.0.1:71xx (attach here)\n```\n\nEverything 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.\n\n## Key features\n\n| | |\n|---|---|\n| **`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). |\n| **`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`. |\n| **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). |\n| **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. |\n| **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. |\n| **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. |\n| **Real-time handoff** | Desktop toasts, Slack/Discord/ntfy/PagerDuty webhooks, terminal TUI (`--tui`), SSE/WebSocket bus — all fed from the same stream. |\n| **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`. |\n\n## Cross-tool scope compatibility\n\nPortSnatcher 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.\n\n```json\n{\n  \"engagement_id\": \"ENG-2026-0142\",\n  \"client\": \"Acme Corp\",\n  \"operator\": \"you@yourshop.com\",\n  \"authorized_targets\": {\n    \"ip_ranges\": [\"10.10.10.0/24\"],\n    \"domains\": [\"*.acme.com\"]\n  },\n  \"excluded_targets\": [\"10.10.10.99\"],\n  \"authorized_techniques\": [\"recon\", \"web_app\", \"api_testing\", \"ssl_tls\"],\n  \"excluded_techniques\": [\"dos\", \"destructive\"],\n  \"engagement_window\": {\n    \"start\": \"2026-04-22T08:00:00Z\",\n    \"end\":   \"2026-05-06T17:00:00Z\"\n  },\n  \"portsnatcher\": {\n    \"port_policy\": {\n      \"include\": [\"ephemeral-iana\", \"22\", \"80\", \"443\"]\n    }\n  }\n}\n```\n\n## Profiles — three postures, one codebase\n\n| | **internal** | **external** | **ctf** |\n|---|---|---|---|\n| Default engine | raw | raw | raw |\n| Global pps cap | 50,000 | 1,000 | 100,000 |\n| Per-target cap | 2,000 | 200 | 10,000 |\n| Probe ladder | full | minimal | full + aggressive |\n| Toast on catch | yes | no | yes |\n| Scope file required | yes | yes (strict) | no (lab) |\n\n`--profile` is a one-flag way to pick a coherent default posture. Override any individual setting when you need to.\n\n## Roadmap\n\n### Shipped (v1.0 → v1.2)\n- [x] Design + five phase plans ([spec](./docs/superpowers/specs/2026-04-22-portsnatcher-design.md))\n- [x] `ps-core` (scope, config, frozen `portsnatcher/v1` event schema, insta snapshots)\n- [x] `ps-engine` — `ConnectEngine` + `RawEngine`\n- [x] `ps-fingerprint` — nine-probe ladder with fresh-connection discipline\n- [x] `ps-proxy` — dumb tunnel + TLS MITM with `rcgen`-signed CA\n- [x] `ps-bus` — SSE + WebSocket + bearer-token auth\n- [x] `ps-notify` — terminal / JSONL / webhook / desktop sinks\n- [x] `ratatui` TUI via `--tui`\n- [x] Linux / macOS / Windows CI matrix + fmt + clippy gate\n- [x] Fuzz targets + `cargo-deny` license policy\n- [x] Signed `cargo-dist`-style prebuilts via GitHub Releases\n- [x] Live-engagement E2E smoke test\n- [x] **Real Linux SYN race** (v1.2): `pnet` AF_PACKET + pcap + kernel-connect handoff\n\n### v1.3 — next\n- macOS BPF port of `SynRace`\n- Windows WinDivert port of `SynRace`\n- IPv6 support in target plan, scope guard, and SynRace\n- Multi-NIC per-target source-IP routing\n\n### v1.4+\n- First-party **Burp Suite extension** (Montoya API) subscribing to the event bus\n- Caido / ZAP plugins following the same pattern\n- Web dashboard for the event bus\n- crates.io publish (`cargo install integsec-portsnatcher`) once the registry token is configured\n\n### v2+\n- UDP support\n- Deeper protocol-specific probe modules (LDAP, Kerberos, proprietary binary protocols)\n- Distributed mode (multiple PortSnatcher probes feeding one bus)\n\n## Safety and ethics\n\nPortSnatcher 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.\n\n**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.\n\n## Supported platforms\n\n| | Linux | macOS | Windows |\n|---|---|---|---|\n| `ConnectEngine` | yes | yes | yes |\n| `RawEngine` — real SYN race (v1.2+) | **yes** (`pnet` AF_PACKET + pcap) | not yet (v1.3: BPF) | not yet (v1.3: WinDivert) |\n| `RawEngine` — connect-labelled fallback | only if `SynRace` fails to init | yes (current) | yes (current) |\n| Optional `nftables` RST-drop kassist | installed when `nft` available | n/a | n/a |\n| Requires elevation for `RawEngine` | `CAP_NET_RAW` (`setcap cap_net_raw,cap_net_admin=eip`) | root | admin |\n\nLinux 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.\n\n**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.\n\n## Install\n\n**Prebuilt binary (recommended).** Grab the archive for your OS+arch from the\n[latest GitHub Release](https://github.com/IntegSec/PortSnatcher/releases/latest)\nand unpack it somewhere on your `$PATH`. Per release, we ship:\n\n- `portsnatcher-v1.2.0-x86_64-unknown-linux-gnu.tar.gz`\n- `portsnatcher-v1.2.0-x86_64-apple-darwin.tar.gz`\n- `portsnatcher-v1.2.0-aarch64-apple-darwin.tar.gz`\n- `portsnatcher-v1.2.0-x86_64-pc-windows-msvc.zip`\n\nOn Linux, grant the real-SYN-race engine the capabilities it needs\n(otherwise it falls back to the connect-labelled scheduler):\n\n```bash\nsudo setcap cap_net_raw,cap_net_admin=eip \"$(which portsnatcher)\"\n```\n\n**Building from source:**\n\n```bash\ngit clone https://github.com/IntegSec/PortSnatcher\ncd PortSnatcher\ncargo build --release --bin portsnatcher\n./target/release/portsnatcher --help\n```\n\n**`cargo install`** will land once the `CARGO_REGISTRY_TOKEN` repo secret is configured (v1.4 roadmap):\n\n```bash\ncargo install integsec-portsnatcher  # NOT YET published to crates.io\n```\n\n## Contributing\n\nPortSnatcher is open to community contributions. Two rules are non-negotiable:\n\n1. **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.\n2. **Scope-file compatibility with `agentic-pentest-proxy` is a stability contract.** Anything that changes the top-level fields is a breaking change.\n\nSee [`CONTRIBUTING.md`](./CONTRIBUTING.md) for dev setup, commit conventions, and the PR checklist. For security-sensitive reports, see [`SECURITY.md`](./SECURITY.md).\n\n## Prior art and influences\n\nPortSnatcher stands on the shoulders of:\n- `nmap`, `masscan`, `zmap`, `unicornscan` — port-scanning state of the art\n- `pnet` — the raw-socket + pcap crate that makes `RawEngine` tractable on Linux\n- `rustls` — the TLS implementation used for the MITM proxy\n- `ratatui`, `tokio`, `axum` — Rust's superb async ecosystem\n- `rcgen` — on-disk CA generation for the TLS MITM\n- Burp Collaborator / Interactsh — prior art for out-of-band handoff patterns\n- IntegSec's [`agentic-pentest-proxy`](https://github.com/IntegSec/agentic-pentest-proxy) — sister tool and source of the scope-file format\n\nWe 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.\n\n## License\n\nApache License 2.0 — see [`LICENSE`](./LICENSE) and [`NOTICE`](./NOTICE).\n\n---\n\nBuilt 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.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintegsec%2Fportsnatcher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fintegsec%2Fportsnatcher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintegsec%2Fportsnatcher/lists"}