Skip to content
HN On Hacker News ↗

Why DMARC's new "np" tag can fail with DNSSEC

▲ 55 points 25 comments by matteocontrini 2d ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is fully human-written

1 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 7 of 7
SEGMENTS · AI 0 of 7
WORD COUNT 1,550
PEAK AI % 3% · §5
Analyzed
Jul 5
backend: pangram/v3.3
Segments scanned
7 windows
avg 221 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 1,550 words · 7 segments analyzed

Human AI-generated
§1 Human · 0%

The recently updated DMARC specification, published as RFC 9989, introduces the new np tag. Its purpose is to specify the policy that receivers should apply when the sender domain is a non-existent subdomain of the domain where the DMARC record is published. We discovered that the definition of “non-existent domain” contained in RFC 9989 clashes with another recent specification, RFC 9824, known as ”Compact Denial of Existence in DNSSEC”, resulting in the np tag not always working as expected. Although DNSSEC usage is far from being widespread, the issue affects all domains using DNSSEC with major DNS providers like Cloudflare, NS1, AWS Route 53 and Azure. We raised the issue with the IETF working group responsible for DMARC: the issue was acknowledged but no solution was agreed upon. In this article, we’ll explain the whole story and try to assess the impact of this incompatibility. The new np tag In May 2026, the IETF published an updated version of the DMARC specification, consisting of three documents. RFC 9989 introduces a new DMARC record tag called np, standing for non-existent subdomain policy. In a DMARC record, it looks like this: v=DMARC1; p=none; sp=quarantine; np=reject; The policy specified by the p tag applies to the domain where the record is published, the sp tag applies to existing subdomains of the policy domain that do not publish their own DMARC record, while the np tag applies to non-existent subdomains. Setting different policies is helpful when you want to “block” malicious email on unused subdomains while keeping a less strict policy on the other subdomains. Non-existent subdomains in the DNS The DMARC RFC defines non-existent domains as follows: For DMARC purposes, a non-existent domain is consistent with the term’s meaning as described in RFC8020. That is, if the response code received for a query for a domain name is NXDOMAIN, then the domain name and any possible subdomains do not exist. This is a common definition, nothing surprising. DNS servers typically return the NXDOMAIN response code to signal that the queried domain name and all its subdomains don’t exist, meaning that they do not have any DNS record associated with them.

§2 Human · 0%

Here’s an example: ~ ❯ dig non-existent-subdomain.rai.it +noall +comments +answer ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 17031 ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 A different situation is when the specific resource record type that was requested is not found, but other record types for the queried domain exist. In that case, the DNS name exists so the server can’t return NXDOMAIN. It would instead use NOERROR with an empty answer section, also known as NODATA. (A previous experimental extension to DMARC, RFC 9091, specified that non-existent domains are those where there is an NXDOMAIN or NODATA response for A, AAAA, and MX records, explicitly mentioning that this is a broader definition than that in RFC 8020. This definition was changed when it was incorporated into RFC 9989.) In the above example, we can determine that the non-existent-subdomain.rai.it name and all its subdomains don’t exist. On the other hand, in the following example we can determine that the domain news.rai.it doesn’t have MX records, but it exists because it has other record types: ~ ❯ dig news.rai.it MX +noall +comments +answer ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37891 ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 In the DNSSEC world, things get a bit more complicated. DNSSEC in short DNSSEC is a security extension for DNS that lets resolvers verify that DNS answers are authentic and haven’t been tampered with.

§3 Human · 2%

It does so by adding cryptographic signatures that resolvers can verify. In the following example we explicitly ask the DNS resolver to return DNSSEC-related data. We can see that the DNS response contains an additional record in the ANSWER section, with type RRSIG. ~ ❯ dig dmarcwise.io +dnssec +noall +comments +answer ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49227 ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 1232 ;; ANSWER SECTION: dmarcwise.io. 892 IN A 94.237.30.173 dmarcwise.io. 892 IN A 94.237.26.215 dmarcwise.io. 892 IN RRSIG A 13 2 900 20260801080000 20260704073000 20768 dmarcwise.io. bPF4BFfnETKC4GGwrmi0xlOzvBaotX8DG5E8/rpmelxoXJ2eA8RJtUrN JVFjFVCq5ZP/Ul9XYgT86c8Vw5Y4Sw== The RRSIG record contains the signature that the resolver verifies. The verification process involves querying other records in the zone (DNSKEY), and in the parent zone (DS), moving upward in the DNS hierarchy until the root zone is reached. Denial of existence in DNSSEC In standard DNS, queries for non-existent domains produce replies with an empty answer section, as we’ve seen above. This doesn’t play well with DNSSEC because an empty ANSWER section contains no records, so there’s nothing to sign. DNSSEC solves this using the NSEC record type.

§4 Human · 1%

Instead of signing the non-existent name, it identifies an existing name coming before the requested name and reports the next existing name in canonical DNS order, proving that the requested name does not exist between them. The NSEC record is then signed so that any alteration can be detected. The DNS response code remains NXDOMAIN. For example, a zone may contain the following domain names: a.example.com z.example.com When we query domain f.example.com, which doesn’t exist, the response will say: a.example.com 300 IN NSEC z.example.com. A RRSIG NSEC It confirms that f.example.com doesn’t exist by letting us know that a.example.com exists and that the following domain in the canonical DNS ordering is z.example.com, with record types A, RRSIG and NSEC. Nothing else exists in between. In practice, it’s slightly more complicated than this, because we also need a second NSEC record to prove that no wildcard record exists that would have covered f.example.com. One exception is when we want to say that the domain name exists, just not with that record type. In this case, we can use just one NSEC record, as above, and list which record types exist. Nowadays, many DNS servers use a variant of NSEC called NSEC3, which uses hashed names instead of the original names to make it harder for attackers to “walk” the DNS zone and enumerate all the domain names contained in it. For this to work, we then need a third NSEC3 record to prove that the “closest encloser” exists. In the worst case we’re now at 8 records in total (SOA + SOA RRSIG + 3x NSEC3 + 3x NSEC3 RRSIG), just to prove that a domain doesn’t exist. In some cases, this can approach the size limit of DNS UDP packets, potentially causing truncated responses and a switch to TCP.

§5 Human · 3%

~ ❯ dig non-existent.rcodezero.at +dnssec +noall +comments +authority ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 28077 ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 8, ADDITIONAL: 1

;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 1232 ;; AUTHORITY SECTION: rcodezero.at. 30 IN SOA ns1.rcodezero.at. rcodezero.ipcom.at. 2025113549 10800 3600 604800 30 rcodezero.at. 30 IN RRSIG SOA 13 2 3600 20260716000000 20260625000000 61192 rcodezero.at. m2lRXWLkudZ/DMpS0VPiaNwkN2Wak44JjLNM2VsfY4pwOuVeEOE+Yip5 g4QaVC4vOhuTDikl6RG2XbTJhJIIjQ== 5fck5kit9i35fg68phiu98n07jqtcbe5.rcodezero.at. 30 IN NSEC3 1 0 0 - 5O2N7HE1TQ836ADQ0G40SEJUHNT866RF A NS SOA TXT AAAA RRSIG DNSKEY NSEC3PARAM 5fck5kit9i35fg68phiu98n07jqtcbe5.rcodezero.at.

§6 Human · 1%

30 IN RRSIG NSEC3 13 3 30 20260716000000 20260625000000 61192 rcodezero.at. Ix74l+UIyGSWfhymQJgrHRceKtzla5yCyF56yZfawS4JLak+ECXniLoM MMEZKF+2Q4jQ+UeLV6+oT54wov1YJw== dqj11be2u7pvv034803fs4s4v1i77o5c.rcodezero.at. 30 IN NSEC3 1 0 0 - EH6FBIEDOMTC5LBAADQPMESG8V80K14I dqj11be2u7pvv034803fs4s4v1i77o5c.rcodezero.at. 30 IN RRSIG NSEC3 13 3 30 20260716000000 20260625000000 61192 rcodezero.at. q7ydqY3L8MgC+HvuFVLSnuGkvoGLbWu8UDGFQgAPYU9wRQTTG2dmULl8 vFvPnXRWwIGzpMBQDzyODDCXnIjjPw== goujsva9gi69p68758u2bmh1b09fm0d4.rcodezero.at. 30 IN NSEC3 1 0 0 - K93HCBG8K15UQVA0LORHV7CSPS32A51P A AAAA RRSIG goujsva9gi69p68758u2bmh1b09fm0d4.rcodezero.at.

§7 Human · 0%

30 IN RRSIG NSEC3 13 3 30 20260716000000 20260625000000 61192 rcodezero.at. /dvDo61kcqHe6UN4YzMCXjGbSgsaC9CRNMmG30BaZPBDLJNrGxVMyMZs ijtpeHOrSfIfW/MPAi/s6Mp8sZ0zhw== Another issue with this denial of existence approach is that some DNS providers, famously Cloudflare, sign DNS answers on the fly, instead of having pre-computed signatures. This is known as online signing. The problem is that to produce the NSEC records, the server needs to know not only the data for the record itself, but also the rest of the zone, to determine the previous and next record. Minimally covering NSEC records An initial attempt to better support online DNSSEC signing was introduced very early in 2006 with RFC 4470, titled ”Minimally Covering NSEC Records and DNSSEC On-line Signing” and informally known as “white lies”. The core idea is that instead of responding with actual domain names in an NSEC record, authoritative servers can make up a previous and next name that are slightly before and after the queried name. While this helps deploy DNSSEC with online signing, it can still require two NSEC records, so the DNS answer is still quite large compared to standard NXDOMAIN answers. Compact denial of existence The turning point came in 2016, when Cloudflare implemented DNSSEC and chose not to use standard NSEC, NSEC3 or white lies, but a new method they initially called ”black lies“. The fundamental idea is to return a NOERROR response instead of NXDOMAIN for a non-existent domain, claiming that the name exists but has no records for the queried type. With this approach, just a single NSEC record is needed. The DNS answer then becomes very compact: as seen in the following example, to prove that non-existent.ietf.org does not exist there’s only one NSEC record claiming the next domain is \000.non-existent.ietf.org.