Skip to content

Tier 1: DNS-layer security analysis (DNSSEC, CAA, RDAP parsing) - #5

Merged
MSAdministrator merged 2 commits into
mainfrom
feature/tier1-dnssec-rdap-caa
Jul 2, 2026
Merged

Tier 1: DNS-layer security analysis (DNSSEC, CAA, RDAP parsing)#5
MSAdministrator merged 2 commits into
mainfrom
feature/tier1-dnssec-rdap-caa

Conversation

@MSAdministrator

Copy link
Copy Markdown
Owner

Summary

Adds a --security flag to run and a standalone security command that validate and interpret DNS data the tool previously only fetched raw. This is Tier 1 of the security-signals work identified in the DNS/security-engineer review.

What's new

🔐 DNSSEC chain validation (dnssec.py)

Goes beyond fetching DNSKEY — it verifies the actual chain of trust:

  • Verifies the DNSKEY RRset self-signature (dns.dnssec.validate).
  • Confirms the parent DS hashes to the zone's key (make_ds SHA256/SHA384).
  • Distinguishes secure / bogus / insecure — a validating-resolver SERVFAIL separates a broken chain from a merely unsigned zone.
  • Reads the AD flag and flags weak algorithms (RSASHA1, DSA).
  • Fetches records with the CD flag so bogus RRsets can be inspected locally instead of being hidden behind a resolver SERVFAIL.

🔏 CAA policy analysis (caa.py)

  • Fetches the issuance policy and climbs the DNS tree (FQDN → apex) the way a CA does, so the reported policy is the one actually applied.
  • Reports allowed CAs (issue/issuewild), iodef contacts, and derived flags: allows_any_ca, forbids_issuance, missing-iodef.

📇 RDAP structured parsing (rdap.py)

  • New parse_domain() / report() extract registrar + IANA ID, registration/expiry/last-changed dates, EPP statuses, nameservers, and DNSSEC delegation from the raw RDAP JSON (previously returned unparsed).
  • Adds a request timeout (was unbounded — could hang forever).
  • Raw ip/domain/autnum methods unchanged.

Wiring

  • profiler.run(domain, security=True) attaches a security section (dnssec/caa/rdap).
  • profiler.security(domain) runs it standalone.
  • All lookups degrade to an error/empty field rather than raising.

Dependency

  • Adds cryptography>=42.0.0 (required for DNSSEC signature verification). Lock adds only cryptography + cffi + pycparser.

Testing

Verified against live domains:

Domain Result
cloudflare.com secure (alg 13, DS matches, CAA present, DNSSEC delegated)
google.com insecure (unsigned)
dnssec-failed.org bogus (self-signed, parent DS mismatch)
  • uv run pytest: 232 passed (30 new hermetic tests: test_dnssec.py, test_caa.py, test_rdap.py + profiler wiring).
  • uv run mypy src/domain_profiler: clean.

Tier 2 (TLS cert inspection, subdomain-takeover, wildcard DNS, typosquat) will follow as a separate PR.

🤖 Generated with Claude Code

Adds a --security flag to `run` and a standalone `security` command that
validate/interpret data the tool previously only fetched raw.

DNSSEC (dnssec.py) — validates the full chain of trust rather than just
fetching DNSKEY: verifies the DNSKEY RRset self-signature and confirms
the parent DS hashes to the zone KSK. Distinguishes secure / bogus /
insecure (a validating-resolver SERVFAIL separates bogus from unsigned),
reads the AD flag, and flags weak signing algorithms (RSASHA1, DSA).
Uses the CD flag when fetching records so bogus RRsets can be inspected
locally. Requires the new `cryptography` dependency.

CAA (caa.py) — fetches and interprets the issuance policy, climbing the
DNS tree the way a CA does (FQDN up toward the apex). Reports allowed
CAs, "any CA may issue", explicit-forbid, and missing iodef contact.

RDAP (rdap.py) — adds parse_domain()/report() extracting registrar +
IANA ID, registration/expiry/last-changed dates, EPP statuses,
nameservers, and DNSSEC delegation from the raw RDAP JSON. Also adds a
request timeout (was unbounded). Raw ip/domain/autnum methods unchanged.

Wired into Profiler via run(..., security=True) and the security()
command. All lookups degrade to an error/empty field rather than raising.

Verified against live domains (cloudflare.com=secure, google.com=
insecure, dnssec-failed.org=bogus). 232 tests pass, mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds “Tier 1” DNS-layer security analysis to domain-profiler, introducing DNSSEC validation, CAA policy interpretation, and structured RDAP parsing, and wires these into both the existing run command (via --security) and a new standalone security command.

Changes:

  • Add DNSSEC chain validation (DNSKEY self-signature + parent DS verification) and expose posture signals (secure / bogus / insecure) plus weak-algorithm detection.
  • Add CAA tree-climb analysis to report the effective issuance policy a CA would apply.
  • Add RDAP parsing/reporting with timeouts and profiler wiring, plus new unit tests and dependency updates (cryptography).

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
uv.lock Locks new transitive dependencies for cryptography (incl. cffi, pycparser).
pyproject.toml Adds cryptography>=42.0.0 to support DNSSEC crypto validation.
README.md Documents --security and the new security command and outputs.
src/domain_profiler/profiler.py Wires security analysis into run(..., security=True) and adds security() entrypoint.
src/domain_profiler/dnssec.py Implements DNSSEC chain validation logic and posture classification.
src/domain_profiler/caa.py Implements CAA querying + DNS tree climb + policy interpretation.
src/domain_profiler/rdap.py Adds RDAP parsing/reporting and request timeouts.
tests/test_dnssec.py Adds hermetic DNSSEC validation tests using mocked DNS transport.
tests/test_caa.py Adds hermetic CAA analysis tests (including tree-climb behavior).
tests/test_rdap.py Adds RDAP parsing/reporting tests for structured extraction.
tests/test_profiler.py Adds profiler wiring tests for security=True and security() delegation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/domain_profiler/dnssec.py Outdated
Comment thread src/domain_profiler/dnssec.py Outdated
Comment thread src/domain_profiler/caa.py Outdated
Comment thread src/domain_profiler/profiler.py
- DNSSEC: retry over TCP on truncated UDP answers (udp_with_fallback) so
  large DNSKEY/DS RRsets aren't misread as missing keys.
- DNSSEC: distinguish a missing parent DS (unsigned delegation → insecure
  island of trust) from a present-but-mismatched DS (broken anchor →
  bogus). _validate_ds now returns match/mismatch/absent/error.
- CAA: when every tree-climb lookup errors, report policy_unknown
  (allows_any_ca=False) instead of the misleading "any CA may issue".
- profiler.run(): move the new `security` param after `dkim_selector` to
  preserve positional-arg backward compatibility.

Tests updated + added (DS mismatch/absent/error, CAA unknown-vs-open,
positional dkim_selector). 236 pass, mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MSAdministrator
MSAdministrator merged commit 75fa9ca into main Jul 2, 2026
7 checks passed
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.

2 participants