Skip to content

fix: detect port changes on one address of a DNS target - #1248

Merged
crypt0rr merged 4 commits into
mainfrom
fix/dns-per-address-ports
Oct 7, 2026
Merged

crypt0rr merged 4 commits into
mainfrom
fix/dns-per-address-ports

Conversation

@crypt0rr

@crypt0rr crypt0rr commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the false negative in #1222. A DNS name is one logical unit whose ports are merged across its resolved addresses, and nothing compared which addresses expose a port. A port opening on the IPv6 address of a dual-stack name while IPv4 already had it, or closing on one address while another kept it, produced no change, incident or notification, even in the default address_sensitive mode.

Comparison (internal/engine)

  • In address-sensitive mode, for a DNS unit's port that is positive in both the baseline and the scan and has address evidence on both sides, EdgeWatch compares the exposing addresses. It considers only addresses present in both DNS answers; answer changes are still reported as dns-added / dns-removed.
  • Each difference is a new port-address change with key port-address|<target>|<proto>|<port>|<address>:
    • Target stays the DNS name, and the new Change.address field carries the IP.
    • An opening is critical (warning for open|filtered); a closure is info.
    • Notification text, for example: edge.example tcp/22 on 2001:db8::10: not-open -> open.
  • Each address is its own incident, so confirmations, suppression, reminders and recovery work as they do for other kinds.

Protections

  • A closure on an address whose host discovery completed as down is left to that host's state change.
  • Ports without address evidence, such as older baselines, are treated as unknown. The next complete scan with no down address fills in their addresses silently.
  • Incomplete scans report openings on addresses that completed, and defer closures.
  • A per-address finding that a complete scan can no longer compare (the address left the answer, the host is down, the port is gone) is retired rather than announced as a recovery.
  • aggregate mode drops port-address changes entirely.

Baseline learning

  • In address-sensitive mode only, the candidate hash also covers which addresses expose each DNS port, so identical scans still converge.
  • Snapshot.Hash() is unchanged, and snapshots without DNS port evidence hash exactly as before.

Accepting an incident (internal/store/incident_actions.go)

  • Acceptance adds or removes the address in the baseline port's evidence and the address's host view.
  • Accepting a closure recomputes the expected service from the remaining addresses (acceptedServiceForEvidence). If an open service incident reports exactly that service, it is resolved in the same action.

Console and docs

  • New labels "Port opened on address" / "Port closed on address", with the target shown as edge.example (2001:db8::10) in incidents, scan changes, pending confirmations and Activity.
  • Public status never shows changes, so the new kind is not exposed there.
  • The DNS section and the Accept bullet in jobs-baselines-incidents.md, the job editor's DNS comparison help, and api-compatibility.md (a new port-address section) are updated.

Compatibility (call out in release notes)

  • Drift after the upgrade: existing address-sensitive baselines for DNS names may report per-address drift that built up silently before the upgrade. Review these incidents after upgrading.
  • Baselines still being learned: a DNS job that is learning its baseline may need one more sample.
  • API: additive. A new change kind port-address and a new address field on changes, omitted for other kinds. There are no schema changes.
  • Downgrade: older versions do not recognise the kind. Accepting such an incident there fails, and the incident eventually reads as recovered.

Validation

  • gofmt, go vet ./...
  • go test -race:
    • internal/engine, internal/model, internal/web, internal/app and internal/scanner;
    • the incident, accept, suppress, runtime, baseline, cycle and finalize tests in internal/store.
    • One web SSE test timed out once under load and passed on rerun.
  • After rebasing onto main: npm run lint, Vitest (45 files, 478 tests passed), and the engine, model and incident store tests again.
  • npm --prefix docs run build

New tests:

  • internal/engine/dns_port_address_test.go:
    • opening and closing on one address of a dual-stack name, through both the direct and resumable scanner paths;
    • aggregate mode, legacy baselines without evidence, down hosts, incomplete scans, retirement, and convergence.
    • These fail when the per-address comparison is removed.
  • internal/store/incident_port_address_test.go: accepting a per-address incident updates the baseline evidence, and the next identical scan reports nothing.
  • Console: labels and Activity descriptions.

Fixes #1222

A DNS target is compared as one logical unit whose ports are merged
across its resolved addresses, so a port that opened on one address
while another already exposed it, or closed on one address while
another kept it open, produced no change, incident or notification,
also in the default address-sensitive mode.

Address-sensitive mode now compares, for a port that is positive in
both the baseline and the scan, which addresses in both DNS answers
expose it. Each difference is a port-address change with the address
in the new address field, reported as for example
"edge.example tcp/22 on 2001:db8::10: not-open -> open". Ports without
address evidence are unknown and take their addresses from the next
complete scan, closures on a down host are left to its host change,
incomplete scans report openings on complete addresses and defer
closures, and findings a complete scan cannot compare are retired
rather than recovered. Aggregate mode ignores per-address ports.

Baseline samples converge only on the same per-address ports.
Accepting a port-address incident updates the baseline evidence and
host view; accepting a closure recomputes the service from the
remaining addresses and resolves a service incident reporting it. The
console labels the new kind with its address. Existing baselines can
report per-address drift that built up before the upgrade.

Fixes #1222
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Deploying edgewatch with  Cloudflare Pages  Cloudflare Pages

Latest commit: 58fc9e0
Status: ✅  Deploy successful!
Preview URL: https://7e6defb6.edgewatch-cpd.pages.dev
Branch Preview URL: https://fix-dns-per-address-ports.edgewatch-cpd.pages.dev

View logs

@crypt0rr
crypt0rr merged commit c413031 into main Oct 7, 2026
15 checks passed
@crypt0rr
crypt0rr deleted the fix/dns-per-address-ports branch October 7, 2026 17:28
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.

[P1] Port changes on one address of a multi-address DNS target are not detected

1 participant