{"id":50887553,"url":"https://github.com/shuque/minimal_nsec","last_synced_at":"2026-06-15T18:01:38.583Z","repository":{"id":353674512,"uuid":"1220128160","full_name":"shuque/minimal_nsec","owner":"shuque","description":"Minimally covering NSEC analysis","archived":false,"fork":false,"pushed_at":"2026-04-26T00:56:47.000Z","size":43,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-26T02:26:09.299Z","etag":null,"topics":["dns","dnssec","epsilon","minimal","nsec"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shuque.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-24T15:17:31.000Z","updated_at":"2026-04-26T00:58:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/shuque/minimal_nsec","commit_stats":null,"previous_names":["shuque/ultradns_nsec","shuque/minimal_nsec"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/shuque/minimal_nsec","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuque%2Fminimal_nsec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuque%2Fminimal_nsec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuque%2Fminimal_nsec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuque%2Fminimal_nsec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shuque","download_url":"https://codeload.github.com/shuque/minimal_nsec/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuque%2Fminimal_nsec/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34374146,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["dns","dnssec","epsilon","minimal","nsec"],"created_at":"2026-06-15T18:01:37.049Z","updated_at":"2026-06-15T18:01:38.570Z","avatar_url":"https://github.com/shuque.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Minimally Covering NSEC Records\n\nThis project investigates minimally covering NSEC records (RFC 4470) —\nalso known as \"white lies\" — as deployed by commercial DNS providers.\nThe work includes reverse-engineering UltraDNS's specific epsilon\nfunction implementation, developing a generalized approach to detect\nminimally covering NSEC records from any provider, detecting Compact\nDenial of Existence (RFC 9824), and classifying NSEC3 zones as\ntraditional (pre-computed) or white lies (RFC 7129).\n\n## Background\n\nDNSSEC authenticated denial of existence uses NSEC (or NSEC3) records\nto prove that a queried name does not exist. In a traditional\npre-signed zone, the NSEC chain reveals all names in the zone, enabling\nzone enumeration. Minimally covering NSEC records (RFC 4470) address\nthis by having an online signer dynamically generate NSEC records that\nbracket only the queried name, using an \"epsilon function\" to compute\npredecessor and successor names just before and after the query.\n\n## UltraDNS Analysis\n\nDetailed reverse engineering of UltraDNS's epsilon function, based on\nempirical queries against test zones `ultratest.huque.com` and\n`ultratest2.huque.com`.\n\n- [UltraDNS.md](UltraDNS.md) — Full analysis: character alphabet\n  (40 characters), successor function (`qname + '!'`), predecessor\n  function (decrement last character, append `~`, prepend `~` child\n  labels), variable-depth predecessor (depth = subtree height of\n  nearest preceding name), wildcard handling, and special cases for\n  the zone apex and empty non-terminals.\n\n- [data/query\\_results\\_ultradns.txt](data/query_results_ultradns.txt) —\n  Raw query data from both test zones.\n\n### UltraDNS Detector\n\n[detect\\_ultradns\\_nsec.py](detect_ultradns_nsec.py) — Detects\nUltraDNS-style minimally covering NSEC records in a given zone by\nprobing with random queries and checking for the specific UltraDNS\npredecessor/successor patterns.\n\n```\n./detect_ultradns_nsec.py [--doh] [--doh-server URL] [-v] ZONE\n```\n\n## Generalized Minimal NSEC Detection\n\nA provider-agnostic approach to detecting minimally covering NSEC\nrecords, based on measuring the prefix similarity between NSEC record\nlabels and the query name that produced them.\n\n- [Minimal.md](Minimal.md) — Problem statement, approaches considered\n  (canonical sort key distance, label-level distance), why raw distance\n  metrics fail due to DNS tree structure, and the prefix similarity\n  method that provides definitive separation between epsilon and static\n  NSEC zones.\n\n### General Detector\n\n[detect\\_minimal\\_nsec.py](detect_minimal_nsec.py) — Detects minimally\ncovering NSEC records from any provider without knowledge of the\nspecific epsilon algorithm.\n\n```\n# Probe a zone automatically\n./detect_minimal_nsec.py probe [--doh] [--doh-server URL] [-n NUM] ZONE\n\n# Analyze a specific NSEC pair\n./detect_minimal_nsec.py calc ZONE OWNER NEXT --qname QNAME\n```\n\n## Compact Denial of Existence (RFC 9824)\n\nCompact Denial of Existence is a different approach to authenticated\ndenial in DNSSEC. Instead of generating epsilon predecessor/successor\nnames, the server returns NOERROR (rather than NXDOMAIN) for\nnonexistent names, with an NSEC record of the form\n`qname NSEC \\000.qname`. This single NSEC covers only the queried\nname, without revealing any other names in the zone. Implementations\nthat fully support RFC 9824 include the NXNAME meta-type (TYPE128) in\nthe NSEC type bitmap to signal that the name does not exist; some\nimplementations omit NXNAME.\n\n### CDoE Detector\n\n[detect\\_compact\\_nsec.py](detect_compact_nsec.py) — Detects Compact\nDenial of Existence by probing for the CDoE NSEC pattern and checking\nfor the NXNAME signal. Tested against Cloudflare, NS1 (both with\nNXNAME), and AWS Route53 (without NXNAME).\n\n```\n# Probe one or more zones\n./detect_compact_nsec.py [--doh] [-v] [-n NUM] ZONE [ZONE ...]\n\n# Read zones from a file\n./detect_compact_nsec.py [--doh] [-v] -f FILE\n\n# Bypass apex wildcard by probing under a known nonexistent name\n./detect_compact_nsec.py [--doh] [-v] --known-nxd NAME ZONE\n```\n\n## NSEC3 Detection (RFC 5155 / RFC 7129 / RFC 9824)\n\nDetects whether a zone uses traditional (pre-computed) NSEC3, NSEC3\nWhite Lies (online-signed, RFC 7129 Appendix B), or Compact Denial of\nExistence with NSEC3 (RFC 9824 Section 4).\n\n**Traditional vs White Lies**: Probes the zone with random nonexistent\nnames, computes expected NSEC3 hashes using `dns.dnssec.nsec3_hash()`,\nand measures the gap between NSEC3 owner and next hashed owner. White\nlies produce a gap of exactly 2 (H-1 to H+1) for covering records and\n1 for the closest encloser match; pre-computed chains produce gaps\nproportional to 1/N of the hash space (typically 0.1%–1.7%). The\ndetector analyzes all three closest-encloser-proof roles: next closer\nname (NCN) cover, closest encloser (CE) match, and wildcard (WC) cover.\n\n**Compact Denial of Existence with NSEC3**: Detected when probes\nreturn NOERROR with no answer data (NODATA) and the NSEC3 type bitmap\nis minimal — empty, or containing only NXNAME (TYPE128). Reports\nwhether NXNAME is present.\n\n**Salt rotation handling**: NSEC3 parameters (salt, iterations) are\nextracted from the NSEC3 records in each individual response, not from\nthe NSEC3PARAM RR, which can be stale or absent with online signers.\n\n**Wildcard zones**: Wildcard-synthesized NOERROR responses include an\nNSEC3 record in the authority section covering the next closer name;\nthe detector extracts and analyzes these.\n\n### NSEC3 Detector\n\n[detect\\_nsec3.py](detect_nsec3.py) — Detects traditional NSEC3,\nNSEC3 white lies, and CDoE/NSEC3 with hash gap analysis. Tested\nagainst huque.com (traditional, BIND 9), naomi.huque.com (white lies),\nnoire.huque.com (CDoE/NSEC3 with NXNAME), and third-party zones with\nrotating salts and wildcards.\n\n```\n# Probe one or more zones\n./detect_nsec3.py [--doh] [-v] [-n NUM] ZONE [ZONE ...]\n\n# Read zones from a file\n./detect_nsec3.py [--doh] [-v] -f FILE\n\n# Relaxed white lies detection (allow gap up to 2*N for covers)\n./detect_nsec3.py [--doh] --epsilon N ZONE\n```\n\n## Dependencies\n\n- Python 3\n- [dnspython](https://www.dnspython.org/) (`pip install dnspython`)\n- For DoH support: `pip install dnspython[doh]`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshuque%2Fminimal_nsec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshuque%2Fminimal_nsec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshuque%2Fminimal_nsec/lists"}