DKIM header oversigning and why the l= tag is a trap
Which headers to sign, why signing From twice blocks header injection, and how the l= body length tag lets attackers append content to signed mail.

On this page(8 sections)
DKIM proves that certain parts of a message were not changed after signing. The catch is the word "certain." Which headers you sign, and whether you limit how much of the body is covered, decides how much an attacker can change while the signature still verifies.
What DKIM actually covers
A DKIM signature covers:
- The message body, possibly truncated by the
l=tag. - The headers listed in the
h=tag, in the order listed. - The DKIM-Signature header itself (minus the
b=value).
Anything not in that set can be changed, added or removed without breaking the signature. RFC 6376 requires only that the From header be signed. Everything else is a choice made by the signer.
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=k1;
h=from:to:subject:date:message-id:mime-version:content-type;
bh=...; b=...
The header injection problem
Here is the subtle part. When a message contains multiple instances of the same header, the DKIM verifier matches entries in h= against those instances starting from the bottom of the header block and working upward. If h= lists subject once, the verifier checks the last Subject header in the message.
Now imagine an attacker takes a legitimately signed message and adds a second Subject header above the original one. The signature still verifies, because the signed (original) Subject is still there and unchanged. But many mail clients display the first Subject they find, which is the attacker's.
The same trick works with From. A message with two From headers is malformed under RFC 5322, and well-behaved receivers should reject or flag it, but not every system in the path is well behaved. An attacker who can get a validly signed message from your domain can try to replay it with an extra header that changes what the user sees.
Oversigning closes the gap
The defense is to list a header in h= one more time than it appears in the message. When the verifier processes the extra entry and finds no corresponding header, it treats it as a null header. If an attacker later adds an instance of that header, the null entry no longer matches nothing, and the signature fails.
For a message with one From and one Subject header:
h=from:from:subject:subject:to:to:date:message-id:reply-to:reply-to:mime-version:content-type
This is called oversigning. It means "there was exactly one of these, and no one may add another."
Which headers to oversign
Prioritize headers that change what a recipient sees or how they respond:
| Header | Why it matters |
|---|---|
| From | The identity DMARC aligns on and users trust |
| Subject | What the user reads first |
| To, Cc | Who the message appears to address |
| Reply-To | Where replies go; a favorite of phishing |
| Date | Age and context of the message |
| Content-Type, MIME-Version | How the body is interpreted |
| Message-ID | Threading and deduplication |
| List-Unsubscribe, List-Unsubscribe-Post | Required to be signed for one-click unsubscribe |
Oversigning headers that downstream systems legitimately add, such as Received or Authentication-Results, would break your own mail and is not the goal. Those are never signed.
Most modern signing software (OpenDKIM, rspamd and many provider implementations) supports oversigning through configuration. Look for options named "oversign" or check whether headers appear twice in h= on your outgoing mail.
The l= tag is a trap
The l= tag tells verifiers to hash only the first N bytes of the canonicalized body. It was intended to let mailing lists append footers without breaking signatures.
The problem is obvious once stated: anything after byte N is unsigned. An attacker who obtains a message signed with l= can append arbitrary content, including a convincing phishing call to action, and the signature still verifies as your domain.
It gets worse with MIME. An attacker can append new MIME parts, or content that some clients render in place of the original, depending on the structure. Researchers have demonstrated practical attacks along these lines.
There is no good reason to use l= today. Mailing lists that modify messages are better handled through ARC and From rewriting. Check your outgoing mail for l= in the DKIM-Signature header and disable it if present.
How to audit your current signatures
Send a message from each system that signs for your domain to a mailbox you control. In the raw source, find the DKIM-Signature header and check:
- Is
l=present? If so, disable it. - What is in
h=? At minimum: from, to, subject, date, message-id, mime-version, content-type, plus reply-to and list-unsubscribe headers when present. - Are critical headers listed twice? If From and Subject appear once each, you are not oversigning.
- Is the algorithm
rsa-sha256(ored25519-sha256)? RFC 8301 removed rsa-sha1 from what signers may use.
A short script helps if you have many senders:
import email, re, sys
msg = email.message_from_binary_file(open(sys.argv[1], "rb"))
for sig in msg.get_all("DKIM-Signature", []):
tags = dict(t.strip().split("=", 1) for t in re.sub(r"\s+", "", sig).split(";") if "=" in t)
h = [x.lower() for x in tags.get("h", "").split(":")]
print("d=", tags.get("d"), "s=", tags.get("s"), "a=", tags.get("a"))
print(" l= present!" if "l" in tags else " no l= tag")
for name in ("from", "subject", "reply-to", "to"):
print(f" {name}: signed {h.count(name)}x")
What to ask your vendors
If a third party signs mail as your domain, you cannot change its signer configuration directly, but you can ask. Useful questions for a vendor's support or security team:
- Which headers do you include in
h=, and do you oversign From and Subject? - Do you ever set the
l=tag, for example to accommodate footers? - Which algorithm and key length do you use, and how often do you rotate keys?
- If you add tracking or footers, does that happen before or after signing?
A vendor that cannot answer these questions clearly is telling you something about how much attention it pays to authentication. The answers also give you a baseline to re-check after the vendor changes its platform.
Checklist
- Never use the
l=body length tag. - Sign From, To, Subject, Date, Message-ID, MIME-Version, Content-Type, and Reply-To and List-Unsubscribe headers when present.
- Oversign From, Subject, Reply-To and To at minimum.
- Use
rsa-sha256with keys of at least 2048 bits. - Audit signatures from every system that sends as your domain, including vendors.
Key takeaways
- DKIM only protects the headers listed in
h=and the body up tol=; everything else can be changed. - Verifiers match signed headers from the bottom up, which allows extra headers to be injected above signed ones.
- Oversigning, listing a header one more time than it appears, blocks that injection.
- The
l=tag leaves the rest of the body unsigned and should never be used. - Check the raw DKIM-Signature on mail from every sender you use.
Start with Koltrix
Your domain, one inbox, and an API that sends.
A team inbox where AI sorts and drafts (nothing is sent without your click), plus the transactional API and SMTP relay your product sends with. 7 days free, no card.


