signer: MIME headers and Message-ID signed; a long header line logged (audit M6, M8)

Security audit 2026-10-17, M6 and M8 (nits), stage 6.

M6: Content-Type, MIME-Version and Message-ID were not in h= (as in
OpenDKIM's default list): a relay could change a MIME boundary or the type
and the signature still verified. They are signed when present; Message-ID
is not over-signed, since cleanup adds one a message lacks after the
milters and an absent name in h= would refuse it.

M8: a header line over smtp_line_length_limit (998) is broken by Postfix's
smtp client after signing and the signature fails silently; only body
lines were logged. longHeaderLine names the header, once per message, in
the same words.

Tests: TestSignLinesCoverTheMIMEHeadersAndLongHeaderLinesAreNamed; the h=
expectations gain Message-ID; e2e dkim_shapes expects content-type: in h=
(and the receiver's verification over it). Docs: security.md,
architecture.md, guide; CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-17 22:37:14 +03:00
co-authored by Claude Opus 5.5
parent 820b1bb968
commit 5c5f49bad1
7 changed files with 72 additions and 13 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ func testSignatureOverAwkwardHeaders(t *testing.T, sc *scenario) {
// Both To: lines, the one Cc:, the Subject and List-Unsubscribe-Post are
// signed, the shown ones once more.
signedNames := strings.ToLower(dkimTagsOf(t, delivered)["h"])
for _, want := range []string{"to:to:", "cc:", "subject:", "list-unsubscribe-post:"} {
for _, want := range []string{"to:to:", "cc:", "subject:", "list-unsubscribe-post:", "content-type:"} {
if !strings.Contains(signedNames, want) {
t.Errorf("h=%s lacks %q", signedNames, want)
}