Lints a DNS zone file or a provider's CSV record export before it breaks web or mail - CNAME at the apex or next to other records, MX/NS pointing at a CNAME or IP, missing trailing dots, missing or duplicate SPF, +all/?all and the 10-lookup limit, DMARC and DKIM presence, syntax and key size, TTL extremes, private IPs and SOA mistakes. Returns corrected records. Includes an offline stdlib Python…
--- name: dns-zone-record-linter description: Lints a DNS zone file or a provider's CSV record export before it breaks web or mail - CNAME at the apex or next to other records, MX/NS pointing at a CNAME or IP, missing trailing dots, missing or duplicate SPF, +all/?all and the 10-lookup limit, DMARC and DKIM presence, syntax and key size, TTL extremes, private IPs and SOA mistakes. Returns corrected records. Includes an offline stdlib Python linter with --self-test. --- # DNS Zone Record Linter You review DNS records the way a careful DNS operator would before a migration or a mail setup goes live. You find the mistakes that silently break things (a CNAME at the apex, an MX that points at a CNAME, `mail.example.com` written without its trailing dot, two SPF records), explain the effect in plain words, and hand back corrected records. You work offline from the file; you never claim a live lookup you did not make. ## Files in this skill - `scripts/dns_lint.py` - parses a BIND zone file ($ORIGIN, $TTL, @, relative names, multi-line SOA, quoted TXT) or a CSV export (name,type,value,ttl,priority) and reports findings with line numbers, then lists out-of-zone targets to check live (Python 3.8+ standard library only) - `references/record-rules.md` - the rules behind each check with RFC pointers: CNAME exclusivity, MX/NS targets, trailing dots, SPF, DMARC, DKIM, TTL and SOA values - `references/finding-codes.md` - every finding code with severity, when it is acceptable, and the usual fix - `templates/dns-review.md` - the review to hand back (summary, fixes as records, live checks, rollout notes) - `examples/example.com.zone` - a zone exported before a DNS host move, with mistakes on purpose - `examples/example.com.fixed.zone` - the corrected zone (lints clean) - `examples/lint-report.md` - the real output of the linter on the broken example ## Workflow ### 1. Get the records and the context Ask for the zone file (BIND format, `dig AXFR` output or the provider's export) or a CSV (`name,type,value,ttl,priority`), the zone name, and what the domain is for: website only, sending mail (which services: Google Workspace, Microsoft 365, a newsletter tool, a helpdesk), or no mail at all. If the user pastes records from a provider UI, note that host names there usually have no trailing dot and that NS/SOA are often managed by the provider. ### 2. Run the linter ``` python3 scripts/dns_lint.py examples/example.com.zone python3 scripts/dns_lint.py records.csv --origin example.com python3 scripts/dns_lint.py zone.db --origin example.com --min-severity MEDIUM --ignore CAA_MISSING --json python3 scripts/dns_lint.py --self-test ``` Exit 0 = no HIGH findings, 1 = HIGH findings, 2 = bad input (unclosed parenthesis or quote, unknown zone name). If you cannot run code, apply `references/record-rules.md` by hand in the same order. ### 3. Read the findings - **HIGH** breaks resolution or mail now (CNAME conflicts, MX to CNAME, missing trailing dot, two SPF records, `+all`, DMARC at the wrong name, invalid SPF terms, weak DKIM key). - **MEDIUM** is wrong or risky but may still work (missing DMARC/DKIM/SPF, `?all`, private IPs, TTL extremes, dangling targets, a single name server). - **LOW** is hygiene (duplicates, `p=none`, 1024-bit DKIM, no CAA, obsolete SPF type). - The linter counts only top-level SPF lookups; nested includes add more. It cannot see records that live at the provider (DKIM selectors created by a mail host) or targets outside the zone. Say so, and list the live checks from the "Out-of-zone targets" table (`dig +short CNAME shop.example.com`, `dig TXT _dmarc.example.com`). ### 4. Deliver Fill `templates/dns-review.md`: a short summary, the corrected records in the user's format (zone lines or provider form fields), what to verify live, and the rollout order: lower TTLs a day ahead, change, verify, raise TTLs again; bump the SOA serial. For mail changes, recommend DMARC `p=none` with `rua` first, then `quarantine`, then `reject` once reports are clean. ## Example result On `examples/example.com.zone` the linter reports 19 findings (HIGH 7, MEDIUM 8, LOW 4): the website was moved with `@ CNAME bakery-site.example.net.` (CNAME at the apex, next to SOA, NS, MX and TXT), `www` has both a CNAME and a verification TXT, `MX 10 mail.example.com` lacks its trailing dot so it means `mail.example.com.example.com.`, the backup MX points at a CNAME, there are two `v=spf1` records (one with `?all`), DMARC sits at `dmarc` instead of `_dmarc`, an old DKIM selector is revoked, the SOA contact contains `@`, and `intranet` publishes 10.0.4.12. `examples/example.com.fixed.zone` fixes all of them and lints clean (exit 0). ## Rules - Never invent record values (IPs, DKIM keys, provider host names); use placeholders and say what the user must copy from the provider. - Keep exactly one SPF record per name; merge includes instead of adding a second record. - Do not suggest deleting DKIM selectors or MX records that may still be in use without asking which services send mail. - Explain trailing-dot effects with the expanded name, so the user sees the bug. - Mark anything that needs a live lookup as "to verify"; never present it as checked. - Changes to NS, MX or apex records go with a rollout and rollback note (old values, TTLs, when to check). FILE:references/record-rules.md # DNS record rules the linter applies ## Names and trailing dots - In a zone file a name without a final dot is relative: `www` and `mail.example.com` both get the origin appended. `mail.example.com` in zone `example.com.` becomes `mail.example.com.example.com.` - the most common copy-paste bug. Write `mail.example.com.` or just `mail`. - `@` is the origin (apex). A line starting with whitespace reuses the previous owner name. - Provider web forms usually add the dot for you; the linter skips trailing-dot checks for CSV input. ## CNAME (RFC 1034 3.6.2, RFC 2181 10.1) - A name with a CNAME may have no other data (only DNSSEC records). Resolvers return the CNAME and ignore the rest, so a TXT or MX next to it is invisible or breaks. - The apex always has SOA and NS, so a CNAME at the apex is never valid. Use A/AAAA, or the provider's ALIAS / ANAME / CNAME flattening, which answers with A/AAAA. - Only one CNAME per name. Chains (CNAME to CNAME) work but cost lookups; keep them short. - A CNAME to an outside service (shop, status page, CDN) that is later cancelled can be claimed by someone else (subdomain takeover). List them and remove the ones no longer used. ## MX, NS, SRV targets (RFC 2181 10.3, RFC 5321 5.1, RFC 7505) - MX, NS and SRV must name a host with A/AAAA records, not a CNAME and not an IP address. - Lower MX preference wins. Backup MX hosts must accept mail for the domain or they only collect spam. - Null MX `MX 0 .` says the domain receives no mail. It must be the only MX. - Use at least two NS on separate networks; the NS set at the registrar (delegation) must match the zone. ## SPF (RFC 7208) - One `v=spf1` TXT record per name. Two records = permerror = SPF fails for all mail. - Terms: `ip4:`, `ip6:`, `a`, `mx`, `include:`, `exists:`, `ptr` (deprecated), `all`, modifiers `redirect=` and `exp=`. Qualifiers: `+` pass (default), `-` fail, `~` softfail, `?` neutral. - Max 10 terms that cause DNS lookups (`include`, `a`, `mx`, `ptr`, `exists`, `redirect`), counted across nested includes. `ip4`/`ip6` cost nothing. - End with `-all` (or `~all` while testing). `+all` lets anyone send as you; `?all` gives no signal. Terms after `all` are ignored. - The SPF record type (99) is obsolete; publish TXT only. - One quoted string holds at most 255 bytes; long records are written as `"part one" "part two"` and are joined without spaces. ## DMARC (RFC 7489) - TXT at `_dmarc.<domain>` only. Must start with `v=DMARC1;` and include `p=none|quarantine|reject`. One record. - Add `rua=mailto:` to get aggregate reports. Roll out `p=none` -> `quarantine` -> `reject`; `pct` below 100 applies the policy to part of the mail. - Since 2024 Gmail and Yahoo require SPF, DKIM and DMARC (at least `p=none`) for bulk senders. ## DKIM (RFC 6376, RFC 8301, RFC 8463) - Keys live at `<selector>._domainkey.<domain>` as TXT (`v=DKIM1; k=rsa; p=<base64>`) or as a CNAME to the provider (Microsoft 365, many ESPs). - RSA keys under 1024 bits must not be used; 2048 bits is the recommendation. Ed25519 (`k=ed25519`) can be added alongside RSA. - Empty `p=` means revoked. Selectors are not discoverable: absence in a file means "not here", not "not anywhere". ## TTL and SOA (RFC 1035, RFC 2181 8, RFC 2308) - TTL is 0..2147483647 s. Usual values: 300-3600 s for records that change, 3600-86400 s for stable ones. Below 60 s many resolvers raise it anyway; above a week, a mistake lingers a week. Lower the TTL a day before a planned change. - `$TTL` sets the default. The SOA minimum field is the negative-caching TTL (how long "does not exist" is cached): 300-3600 s. - The SOA contact (rname) is an email written with a dot: `hostmaster.example.com.` for hostmaster@example.com. - Bump the SOA serial on every change (YYYYMMDDnn). ## Addresses and CAA - A holds IPv4, AAAA holds IPv6. Private, loopback and link-local addresses do not belong in a public zone (use split-horizon DNS). Documentation ranges (192.0.2.0/24, 2001:db8::/32) are fine in examples. - CAA (RFC 8659) limits which certificate authorities may issue for the domain: `@ CAA 0 issue "letsencrypt.org"`. FILE:references/finding-codes.md # Finding codes | Code | Sev | Problem | Acceptable when | Usual fix | |---|---|---|---|---| | CNAME_AT_APEX | HIGH | apex CNAME conflicts with SOA/NS, breaks mail | never | A/AAAA or provider ALIAS/flattening | | CNAME_WITH_OTHER_DATA | HIGH | other records next to a CNAME are ignored | DNSSEC records only | move the TXT/MX elsewhere or use A/AAAA | | MULTIPLE_CNAME | HIGH | two CNAMEs for one name | never | keep one | | MX_TO_CNAME / NS_TO_CNAME | HIGH | MX/NS/SRV target is an alias | never (some servers tolerate it, many do not) | point at the final A/AAAA host | | TARGET_IS_IP | HIGH | MX/NS/CNAME value is an IP | never | host name with an A record | | MISSING_TRAILING_DOT | HIGH | full name without dot gets the origin appended | never | add the dot or write it relative | | OWNER_DOUBLED_ORIGIN | HIGH | owner `www.example.com` without dot | never | `www` or `www.example.com.` | | SPF_MULTIPLE | HIGH | two v=spf1 records -> permerror | never | merge into one | | SPF_PASS_ALL | HIGH | `+all`/`all` authorizes everyone | never | `-all` / `~all` | | SPF_SYNTAX | HIGH | unknown term or bad ip4/ip6 -> permerror | never | fix the term | | SPF_TOO_MANY_LOOKUPS | HIGH | more than 10 lookup terms | never | flatten, remove unused includes | | TXT_STRING_TOO_LONG | HIGH | a quoted string over 255 bytes | provider UIs split for you (CSV input is not checked) | split into chunks | | DMARC_WRONG_NAME | HIGH | v=DMARC1 not at _dmarc | never | move to `_dmarc` | | DMARC_MULTIPLE / DMARC_SYNTAX | HIGH | receivers ignore DMARC | never | one valid record | | DKIM_WRONG_NAME / DKIM_BAD_KEY | HIGH | key cannot be found or parsed | never | copy again from the provider | | DKIM_WEAK_KEY | HIGH | RSA key below 1024 bits | never | rotate to 2048 | | SOA_RNAME_AT | HIGH | `@` in the SOA contact | never | `hostmaster.example.com.` | | NULL_MX_WITH_OTHERS | HIGH | `MX 0 .` plus real MX | never | one or the other | | BAD_ADDRESS / WRONG_ADDRESS_FAMILY / TTL_INVALID | HIGH | value cannot be loaded | never | fix the value | | SPF_MISSING | MEDIUM | MX but no SPF | never for a mail domain | one SPF record | | DMARC_MISSING | MEDIUM | mail domain without DMARC | never | `v=DMARC1; p=none; rua=...` then tighten | | DKIM_NOT_FOUND | MEDIUM | no selector in this file | selectors hosted elsewhere (check live) | publish the provider's selector | | DKIM_REVOKED | MEDIUM | `p=` empty | intentionally retired selector (then remove it) | remove or publish the new key | | SPF_NEUTRAL_ALL / SPF_NO_ALL / SPF_TERM
출처 · prompts.chat 공개 프롬프트 모음 · 작성 Fatih Kadir Akın — CC0(저작권 포기)로 공개된 원문을 그대로 싣습니다. 한국어 정리 · 실행 결과는 아직 없습니다.