Tier 1: DNS-layer security analysis (DNSSEC, CAA, RDAP parsing) - #5
Merged
Conversation
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>
There was a problem hiding this comment.
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.
- 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a
--securityflag torunand a standalonesecuritycommand 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:DNSKEYRRset self-signature (dns.dnssec.validate).DShashes to the zone's key (make_dsSHA256/SHA384).secure/bogus/insecure— a validating-resolverSERVFAILseparates a broken chain from a merely unsigned zone.🔏 CAA policy analysis (
caa.py)issue/issuewild),iodefcontacts, and derived flags:allows_any_ca,forbids_issuance, missing-iodef.📇 RDAP structured parsing (
rdap.py)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).ip/domain/autnummethods unchanged.Wiring
profiler.run(domain, security=True)attaches asecuritysection (dnssec/caa/rdap).profiler.security(domain)runs it standalone.error/empty field rather than raising.Dependency
cryptography>=42.0.0(required for DNSSEC signature verification). Lock adds onlycryptography+cffi+pycparser.Testing
Verified against live domains:
secure(alg 13, DS matches, CAA present, DNSSEC delegated)insecure(unsigned)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