Skip to content

fix: disclose Full-scan rate cap instead of clamping silently - #44

Merged
bandrel merged 1 commit into
mainfrom
fix/warn-on-full-scan-rate-cap
Aug 25, 2026
Merged

fix: disclose Full-scan rate cap instead of clamping silently#44
bandrel merged 1 commit into
mainfrom
fix/warn-on-full-scan-rate-cap

Conversation

@bandrel

@bandrel bandrel commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Problem

A Full scan clamps masscan to full_scan_rate — 10000 pps External, 1000 pps Internal — so that a single 1-65535 invocation cannot exhaust firewall state tables. That cap is correct and stays.

The clamp was silent, while main()'s run summary echoes the operator's requested max_rate. Requesting 5000 pps on an Internal Full scan printed:

Max Packet Rate (pps): 5000

and then scanned at 1000. The summary actively contradicted what masscan was told to do, and the cap was documented only in the config-help text at spoonmap.py:5060 (Default = 2000 (full port scan capped at 1000)) — which an interactive run never displays. The observable behaviour is indistinguishable from max_rate having been ignored outright, and it cost a real debugging session to track down.

Change

Print a notice when the clamp actually lowers the rate, naming both rates and the reason:

Notice: Full port scan rate capped at 1000 pps (requested 5000) — a 1-65535
sweep fans out every port across every target at once, and the Internal cap
keeps firewall state tables bounded. Targeted port scans use the full
requested rate.

Two placement details that are load-bearing:

  • Guarded on int(full_scan_rate) < int(max_rate) — a rate already under the cap stays quiet rather than nagging on every run.
  • Placed inside the scan_type == 'Full' branch, not next to the full_scan_rate computation. That value is computed unconditionally at spoonmap.py:1610 even for batched scans, so warning there would have told operators their rate was capped during Category and custom scans — which are deliberately uncapped (spoonmap.py:1606-1607). That distinction is the actionable half of the message, so getting it wrong would have been worse than the original silence.

It sits after the resume gate, so a resumed Full scan that skips masscan entirely does not claim to have capped a scan it never ran.

Tests

Three added to TestFullPortScan, covering the notice, the under-cap silence, and the batched-scan silence.

Verification

  • 981 passed, 5 skipped — skips are the documented environmental ones (tcp/631 in use per tests/conftest.py, and the two root-gated -sU NSE cases)
  • 100% coverage on spoonmap.py (2342 stmts, 0 miss)
  • ruff check spoonmap.py tests/ clean
  • bandit clean against the committed .bandit-baseline.json — no new findings, no # nosec suppressions

Docs

Added the Full-scan clamp to CLAUDE.md under Firewall state table safety, including that it applies only to Full and why the notice exists. It previously appeared nowhere an operator or contributor would encounter it.

🤖 Generated with Claude Code

A Full scan clamps masscan to full_scan_rate (10000 pps External, 1000
Internal) so a single 1-65535 sweep cannot exhaust firewall state tables.
The clamp was silent, while main()'s run summary echoes the operator's
requested max_rate — so requesting 5000 pps on an Internal Full scan
printed "Max Packet Rate (pps): 5000" and then scanned at 1000, with the
cap documented only in the config-help text an interactive run never
shows. That reads as max_rate having been ignored outright.

Print a notice when the clamp actually lowers the rate, naming both rates,
why the cap exists, and that targeted scans use the full rate. Guarded on
the comparison so a rate already under the cap stays quiet, and placed
inside the scan_type == 'Full' branch so batched scans — which are
deliberately uncapped — never mention it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bandrel
bandrel merged commit 7170967 into main Aug 25, 2026
12 checks passed
@bandrel
bandrel deleted the fix/warn-on-full-scan-rate-cap branch August 25, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant