fix: count a dkim pass the receiver reports by header.i only (bulwarkmail/webmail#1420)

This commit is contained in:
Linus Rath
2026-10-31 03:30:38 +01:00
parent 0e69c86c51
commit 49d4372918
2 changed files with 19 additions and 1 deletions
+10
View File
@@ -85,6 +85,16 @@ describe('getSenderVerification', () => {
expect(getSenderVerification(undefined, 'a@bank.example')).toBeNull();
});
it('takes the signing domain from header.i when the receiver writes no header.d (bulwarkmail/webmail#1420)', () => {
const from = 'noreply@example.org';
expect(getSenderVerification(parseAuthenticationResults('mx.example.net; dkim=pass header.i=@example.org'), from)).toBeNull();
expect(getSenderVerification(parseAuthenticationResults('mx; dkim=pass header.i=bounce@mail.example.org'), from)).toBeNull();
expect(getSenderVerification(parseAuthenticationResults('mx; dkim=pass header.i=@other.example'), from))
.toEqual({ status: 'unverified', domain: 'example.org' });
// header.d still wins when both are there.
expect(parseAuthenticationResults('mx; dkim=pass header.d=a.example header.i=@b.example').dkim?.domain).toBe('a.example');
});
it('does not count a HELO pass', () => {
const auth = parseAuthenticationResults('mx; spf=pass smtp.helo=bank.example; spf=none smtp.mailfrom=bank.example');
expect(getSenderVerification(auth, 'a@bank.example')?.status).toBe('unverified');
+9 -1
View File
@@ -323,6 +323,12 @@ function parseResinfos(header: string): ResInfo[] {
.filter((info): info is ResInfo => info !== null);
}
/** The domain of a DKIM identity (`header.i`): `@domain` or `user@domain`. */
function identityDomain(identity: string | undefined): string | undefined {
const at = identity?.lastIndexOf('@') ?? -1;
return identity && at >= 0 && at < identity.length - 1 ? identity.slice(at + 1) : undefined;
}
const DMARC_SEVERITY: Record<string, number> = {
fail: 3,
permerror: 2,
@@ -394,7 +400,9 @@ export function parseAuthenticationResults(headers: string | readonly string[]):
.filter((info) => info.method === 'dkim')
.map((info) => ({
result: info.result as DkimResult,
domain: info.props['header.d'],
// Some receivers name the signature only by its identity (`header.i`,
// `@domain` or `user@domain`, RFC 8601 2.7.1); its domain is the signer's.
domain: info.props['header.d'] ?? identityDomain(info.props['header.i']),
selector: info.props['header.s'],
}));
if (dkimResults.length > 0) {