fix: count a dkim pass the receiver reports by header.i only #1420

This commit is contained in:
Linus Rath
2026-10-29 14:18:48 +01:00
parent 6d96f31652
commit f8f2e93cd9
2 changed files with 16 additions and 1 deletions
+8
View File
@@ -434,6 +434,14 @@ describe('getSenderVerification', () => {
expect(getSenderVerification(parseAuthenticationResults('mx; dmarc=pass header.from=shop.example'), from)).toBeNull();
});
it('takes the signing domain from header.i when the receiver writes no header.d (#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' });
});
it('does not take a pass for an unrelated domain as verifying the From domain', () => {
// The sender passes SPF for their own envelope domain (or signs with
// their own key) and writes someone else's From; the From domain
+8 -1
View File
@@ -228,6 +228,11 @@ const DMARC_SEVERITY: Record<string, number> = {
* DKIM, DMARC and iprev come from the topmost header only, and the others
* can only escalate SPF to a failure, never supply a pass.
*/
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;
}
export function parseAuthenticationResults(headers: string | readonly string[]): AuthenticationResults {
const results: AuthenticationResults = {};
const list = typeof headers === 'string' ? [headers] : headers;
@@ -285,7 +290,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) {