mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 16:06:32 +00:00
fix: never count a helo spf pass as verifying the sender
This commit is contained in:
@@ -11,7 +11,7 @@ describe('parseAuthenticationResults', () => {
|
|||||||
const r = parseAuthenticationResults(
|
const r = parseAuthenticationResults(
|
||||||
'mx.example; spf=pass smtp.mailfrom=news.example; dkim=pass header.d=news.example header.s=s1; dmarc=pass header.from=news.example policy.dmarc=none; iprev=pass policy.iprev=1.2.3.4',
|
'mx.example; spf=pass smtp.mailfrom=news.example; dkim=pass header.d=news.example header.s=s1; dmarc=pass header.from=news.example policy.dmarc=none; iprev=pass policy.iprev=1.2.3.4',
|
||||||
);
|
);
|
||||||
expect(r.spf).toEqual({ result: 'pass', domain: 'news.example' });
|
expect(r.spf).toEqual({ result: 'pass', identity: 'mailfrom', domain: 'news.example' });
|
||||||
expect(r.dkim).toEqual({ result: 'pass', domain: 'news.example', selector: 's1' });
|
expect(r.dkim).toEqual({ result: 'pass', domain: 'news.example', selector: 's1' });
|
||||||
expect(r.dmarc).toEqual({ result: 'pass', domain: 'news.example', policy: 'none' });
|
expect(r.dmarc).toEqual({ result: 'pass', domain: 'news.example', policy: 'none' });
|
||||||
expect(r.iprev).toEqual({ result: 'pass', ip: '1.2.3.4' });
|
expect(r.iprev).toEqual({ result: 'pass', ip: '1.2.3.4' });
|
||||||
@@ -88,6 +88,10 @@ describe('getSenderVerification', () => {
|
|||||||
it('does not count a HELO pass', () => {
|
it('does not count a HELO pass', () => {
|
||||||
const auth = parseAuthenticationResults('mx; spf=pass smtp.helo=bank.example; spf=none smtp.mailfrom=bank.example');
|
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');
|
expect(getSenderVerification(auth, 'a@bank.example')?.status).toBe('unverified');
|
||||||
|
// Nor a HELO pass that is the only SPF result.
|
||||||
|
const only = parseAuthenticationResults('mx; spf=pass smtp.helo=bank.example; dkim=none; dmarc=none');
|
||||||
|
expect(hasAlignedAuthenticationPass(only, 'a@bank.example')).toBe(false);
|
||||||
|
expect(getSenderVerification(only, 'a@bank.example')?.status).toBe('unverified');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export interface DkimEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthenticationResults {
|
export interface AuthenticationResults {
|
||||||
spf?: { result: SpfResult; domain?: string; all?: SpfEntry[] };
|
spf?: { result: SpfResult; identity?: SpfEntry['identity']; domain?: string; all?: SpfEntry[] };
|
||||||
/** The first signature's result; `all` lists every one when there are several. */
|
/** The first signature's result; `all` lists every one when there are several. */
|
||||||
dkim?: DkimEntry & { all?: DkimEntry[] };
|
dkim?: DkimEntry & { all?: DkimEntry[] };
|
||||||
dmarc?: { result: DmarcResult; domain?: string; policy?: DmarcPolicy };
|
dmarc?: { result: DmarcResult; domain?: string; policy?: DmarcPolicy };
|
||||||
@@ -140,10 +140,10 @@ function alignedWith(fromDomain: string, candidate: string | undefined): boolean
|
|||||||
* the visible sender. A HELO pass never does.
|
* the visible sender. A HELO pass never does.
|
||||||
*/
|
*/
|
||||||
function hasAlignedSpfPass(auth: AuthenticationResults, fromDomain: string): boolean {
|
function hasAlignedSpfPass(auth: AuthenticationResults, fromDomain: string): boolean {
|
||||||
if (auth.spf?.all) {
|
// With one SPF result there is no `all` list, and its identity is checked
|
||||||
return auth.spf.all.some((entry) => entry.identity !== 'helo' && entry.result === 'pass' && alignedWith(fromDomain, entry.domain));
|
// too: `spf=pass smtp.helo=<From domain>` alone used to count.
|
||||||
}
|
const entries = auth.spf?.all ?? (auth.spf ? [auth.spf] : []);
|
||||||
return auth.spf?.result === 'pass' && alignedWith(fromDomain, auth.spf.domain);
|
return entries.some((entry) => entry.identity === 'mailfrom' && entry.result === 'pass' && alignedWith(fromDomain, entry.domain));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A DKIM pass for a signing domain that aligns with the From domain. */
|
/** A DKIM pass for a signing domain that aligns with the From domain. */
|
||||||
@@ -381,6 +381,7 @@ export function parseAuthenticationResults(headers: string | readonly string[]):
|
|||||||
}
|
}
|
||||||
results.spf = {
|
results.spf = {
|
||||||
result: primary.result,
|
result: primary.result,
|
||||||
|
...(primary.identity ? { identity: primary.identity } : {}),
|
||||||
domain: primary.domain,
|
domain: primary.domain,
|
||||||
...(spfResults.length > 1 ? { all: spfResults } : {}),
|
...(spfResults.length > 1 ? { all: spfResults } : {}),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user