mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +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(
|
||||
'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.dmarc).toEqual({ result: 'pass', domain: 'news.example', policy: 'none' });
|
||||
expect(r.iprev).toEqual({ result: 'pass', ip: '1.2.3.4' });
|
||||
@@ -88,6 +88,10 @@ describe('getSenderVerification', () => {
|
||||
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');
|
||||
// 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 {
|
||||
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. */
|
||||
dkim?: DkimEntry & { all?: DkimEntry[] };
|
||||
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.
|
||||
*/
|
||||
function hasAlignedSpfPass(auth: AuthenticationResults, fromDomain: string): boolean {
|
||||
if (auth.spf?.all) {
|
||||
return auth.spf.all.some((entry) => entry.identity !== 'helo' && entry.result === 'pass' && alignedWith(fromDomain, entry.domain));
|
||||
}
|
||||
return auth.spf?.result === 'pass' && alignedWith(fromDomain, auth.spf.domain);
|
||||
// With one SPF result there is no `all` list, and its identity is checked
|
||||
// too: `spf=pass smtp.helo=<From domain>` alone used to count.
|
||||
const entries = auth.spf?.all ?? (auth.spf ? [auth.spf] : []);
|
||||
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. */
|
||||
@@ -381,6 +381,7 @@ export function parseAuthenticationResults(headers: string | readonly string[]):
|
||||
}
|
||||
results.spf = {
|
||||
result: primary.result,
|
||||
...(primary.identity ? { identity: primary.identity } : {}),
|
||||
domain: primary.domain,
|
||||
...(spfResults.length > 1 ? { all: spfResults } : {}),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user