spamscanner 7.0.3 → 7.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ import type * as m from './arf.js' with {'resolution-mode': 'import'};
2
+ export declare const parseReportFields: typeof m.parseReportFields;
3
+ export declare const findPart: typeof m.findPart;
4
+ export declare const isArfMessage: typeof m.isArfMessage;
5
+ export declare const parse: typeof m.parse;
6
+ export declare const tryParse: typeof m.tryParse;
7
+ export declare const create: typeof m.create;
8
+ export declare const VALID_FEEDBACK_TYPES: typeof m.VALID_FEEDBACK_TYPES;
9
+ export declare const ArfParser: typeof m.ArfParser;
10
+ declare const _default: typeof m.default;
11
+ export {_default as default};
@@ -28,52 +28,55 @@ export function findPart(raw: string, boundary: string, types: string[]): {
28
28
  export function isArfMessage(parsed: object): boolean;
29
29
  /**
30
30
  * Parse an ARF (RFC 5965) abuse report.
31
- * @param {Buffer|string} source - the raw report message
31
+ * @param {Buffer|Uint8Array|string} source - the raw report message
32
32
  * @returns {Promise<object>} feedbackType, userAgent, version, arrivalDate,
33
33
  * sourceIp, originalMailFrom, originalRcptTo, reportingMta,
34
34
  * originalEnvelopeId, authenticationResults, reportedDomain, reportedUri,
35
- * incidents, humanReadable, originalMessage, originalHeaders (always an
36
- * object of lowercase header name to value) and rawFeedbackReport
35
+ * incidents, humanReadable, originalMessage, originalHeaders (an object of
36
+ * lowercase header name to value, or null when the report has no original
37
+ * message) and rawFeedbackReport
37
38
  * @throws {Error} when the message is not a valid ARF report
38
39
  */
39
- export function parse(source: Buffer | string): Promise<object>;
40
+ export function parse(message: any): Promise<object>;
40
41
  /**
41
42
  * Parse an ARF report, or return null when the message is not one.
42
- * @param {Buffer|string} source
43
+ * @param {Buffer|Uint8Array|string} source
43
44
  * @returns {Promise<object|null>}
44
45
  */
45
- export function tryParse(source: Buffer | string): Promise<object | null>;
46
+ export function tryParse(source: Buffer | Uint8Array | string): Promise<object | null>;
46
47
  /**
47
- * Write an ARF report.
48
+ * Write an ARF report. (A string message gives a string report, and a
49
+ * Buffer a Buffer, its bytes kept as they are, whatever their character set.)
50
+ * @template {string|Buffer} M
48
51
  * @param {object} options
49
52
  * @param {string} options.feedbackType - abuse, fraud, virus, other, not-spam, ...
50
53
  * @param {string} options.userAgent
51
54
  * @param {string} options.from
52
55
  * @param {string} options.to
53
- * @param {string|Buffer} options.originalMessage
56
+ * @param {M} options.originalMessage
54
57
  * @param {string} [options.humanReadable]
55
58
  * @param {string} [options.subject]
56
59
  * @param {string} [options.sourceIp]
57
60
  * @param {string} [options.originalMailFrom]
58
- * @param {string[]} [options.originalRcptTo]
59
- * @param {Date} [options.arrivalDate]
61
+ * @param {string|string[]} [options.originalRcptTo]
62
+ * @param {Date|string|number} [options.arrivalDate]
60
63
  * @param {string} [options.reportingMta]
61
- * @returns {string}
64
+ * @returns {M extends Buffer ? Buffer : string} the report
62
65
  */
63
- export function create(options?: {
66
+ export function create<M extends string | Buffer>(options?: {
64
67
  feedbackType: string;
65
68
  userAgent: string;
66
69
  from: string;
67
70
  to: string;
68
- originalMessage: string | Buffer;
71
+ originalMessage: M;
69
72
  humanReadable?: string;
70
73
  subject?: string;
71
74
  sourceIp?: string;
72
75
  originalMailFrom?: string;
73
- originalRcptTo?: string[];
74
- arrivalDate?: Date;
76
+ originalRcptTo?: string | string[];
77
+ arrivalDate?: Date | string | number;
75
78
  reportingMta?: string;
76
- }): string;
79
+ }): M extends Buffer ? Buffer : string;
77
80
  /**
78
81
  * Feedback types from RFC 5965, RFC 6591 (auth-failure) and RFC 7489 (dmarc).
79
82
  */
@@ -21,7 +21,9 @@ export function sniff(buffer: Buffer): {
21
21
  * is read, as well as the last one found: archive programs differ in which
22
22
  * they use, so a directory hidden behind another record is read too. ZIP64
23
23
  * archives, and archives after other data (self-extracting archives), are
24
- * read as well.
24
+ * read as well. Each entry is read once, however many directories overlap
25
+ * it; a name longer than 1 KiB keeps its first and last 512 bytes (with "…"
26
+ * between them), and reading stops after 1 MiB of names.
25
27
  * @param {Buffer} buffer
26
28
  * @param {number} [limit] - most entries to read
27
29
  * @returns {Array<{name: string, encrypted: boolean, size: number}>}
@@ -39,11 +41,15 @@ export function zipEntries(buffer: Buffer, limit?: number): Array<{
39
41
  * - disguised_executable: content is a program but the name says otherwise
40
42
  * - double_extension: "invoice.pdf.exe"
41
43
  * - rtl_override: a right-to-left override hides the real extension (a file named "invoice", U+202E, "fdp.exe" shows as "invoiceexe.pdf")
44
+ * - risky_attachment: a type that is risky by name but not a program
45
+ * (a certificate, an Internet shortcut, a theme; see RISKY_EXTENSIONS)
42
46
  * - executable_in_archive: a ZIP holds a program, read without unpacking
43
47
  * (extension: the first program's, extensions: every program's)
48
+ * - script_in_archive: a ZIP holds scripts (JavaScript, Python, shell, Perl)
49
+ * but no other programs
44
50
  * - encrypted_archive: a password-protected ZIP, which virus scanners cannot open
45
51
  * - macro: an Office document with a VBA project
46
- * - pdf_active: a PDF with JavaScript, launch actions or embedded files
52
+ * - pdf_active: a PDF with JavaScript or launch actions
47
53
  * - rtf_object: an RTF document with an embedded OLE object
48
54
  * - html_attachment: an HTML or SVG file, the usual carrier of fake login pages
49
55
  *
@@ -69,4 +75,12 @@ export function inspectAttachments(attachments?: Array<{
69
75
  * campaigns.
70
76
  */
71
77
  export const EXECUTABLE_EXTENSIONS: Set<string>;
78
+ /**
79
+ * File extensions Outlook blocks that are risky by name rather than programs:
80
+ * certificates (installing one trusts whoever holds its key), Internet
81
+ * shortcuts, Windows themes, and data files people send every day (MATLAB
82
+ * data, Outlook data, temporary files). Reported as risky_attachment, with
83
+ * few points.
84
+ */
85
+ export const RISKY_EXTENSIONS: Set<string>;
72
86
  import { Buffer } from 'node:buffer';
@@ -8,11 +8,28 @@
8
8
  * @returns {(name: string, type: string) => Promise<any[]>}
9
9
  */
10
10
  export function createResolver(timeout?: number, servers?: string[]): (name: string, type: string) => Promise<any[]>;
11
+ /**
12
+ * A resolver that answers through `resolver` at most `maxQueries` times and
13
+ * until `deadline` (a time in milliseconds, as Date.now() gives it): any
14
+ * lookup after either, or still unanswered at the deadline, fails at once
15
+ * with ETIMEOUT, so a message's checks stop sending queries when its time is
16
+ * up and never send more than `maxQueries`.
17
+ * @param {(name: string, type: string) => Promise<any[]>} resolver
18
+ * @param {{maxQueries?: number, deadline?: number}} [limits]
19
+ * @returns {(name: string, type: string) => Promise<any[]>}
20
+ */
21
+ export function limitResolver(resolver: (name: string, type: string) => Promise<any[]>, { maxQueries, deadline }?: {
22
+ maxQueries?: number;
23
+ deadline?: number;
24
+ }): (name: string, type: string) => Promise<any[]>;
11
25
  /**
12
26
  * Overall DKIM result from mailauth's per-signature results: "pass" when any
13
27
  * signature passes, "fail" when signatures exist and none passes, "none"
14
- * without signatures. `aligned` is the From domain a passing signature is
15
- * aligned with, if any.
28
+ * without signatures. `aligned` is the signing domain of a passing signature
29
+ * aligned with the From domain (relaxed: they share their registrable domain),
30
+ * in ASCII, if any. A signature whose body length (l=) is shorter than the
31
+ * body (mailauth's `status.underSized`) is aligned with nothing: it signs only
32
+ * the start of the body, and anyone can append to it.
16
33
  * @param {object} dkim
17
34
  * @returns {{status: {result: string, comment: string}, results: object[], aligned: string|null}}
18
35
  */
@@ -26,16 +43,35 @@ export function summarizeDkim(dkim: object): {
26
43
  };
27
44
  /**
28
45
  * Turn mailauth's output into this module's result shape, filling in "none"
29
- * for anything mailauth left out.
46
+ * for anything mailauth left out, and checking what mailauth does not:
47
+ *
48
+ * - the policy (`p`, `sp`) and alignment modes (`adkim`, `aspf`) of the
49
+ * DMARC record are read without spaces and in any case, as RFC 7489 reads
50
+ * them ("p= None" is none, "adkim=S" strict), and strict alignment is
51
+ * enforced: under it only a signature or SPF pass of the From domain itself
52
+ * aligns, and DMARC fails without one;
53
+ * - a message without exactly one From address, whose DMARC mailauth does
54
+ * not check, fails DMARC, as RFC 7489 (6.6.1) allows;
55
+ * - `arc.sealer` is the domain of the newest ARC seal of a passing chain,
56
+ * when its ARC-Message-Signature is of the same domain, and `arc.trusted`
57
+ * whether it is one of `trustedArcSealers` (or under one) and the results
58
+ * it sealed show the message passed DMARC, or DKIM or SPF aligned with the
59
+ * From domain, when it got it.
30
60
  * @param {object} output
61
+ * @param {object} [options]
62
+ * @param {string[]} [options.trustedArcSealers] - forwarders whose ARC seal
63
+ * is believed
31
64
  * @returns {object}
32
65
  */
33
- export function normalizeAuthOutput(output?: object): object;
66
+ export function normalizeAuthOutput(output?: object, options?: {
67
+ trustedArcSealers?: string[];
68
+ }): object;
34
69
  /**
35
70
  * Check SPF, DKIM, DMARC, ARC and BIMI for a message, using mailauth.
36
71
  *
37
72
  * Needs the connecting client's IP address: without it nothing is checked and
38
73
  * every result is "none". DNS errors give "temperror" results, never a pass.
74
+ * Only the first MAX_DKIM_SIGNATURES DKIM signatures are checked.
39
75
  *
40
76
  * @param {Buffer|string} message - the raw message
41
77
  * @param {object} options
@@ -44,8 +80,16 @@ export function normalizeAuthOutput(output?: object): object;
44
80
  * @param {string} [options.sender] - envelope sender (MAIL FROM)
45
81
  * @param {string} [options.mta] - this server's hostname, for the headers
46
82
  * @param {Function} [options.resolver] - custom DNS resolver (name, type)
47
- * @param {number} [options.timeout] - DNS timeout in milliseconds
83
+ * @param {number} [options.timeout] - milliseconds for a DNS lookup, and for
84
+ * all of the message's: no lookup is sent, nor waited for, after this long
85
+ * (and 100 ms more)
86
+ * @param {number} [options.maxDnsQueries] - DNS lookups allowed for the
87
+ * message (MAX_DNS_QUERIES); any more fail with a temporary error
48
88
  * @param {string[]} [options.dnsServers] - name servers to use
89
+ * @param {string[]} [options.trustedArcSealers] - forwarders (such as a
90
+ * mailing list's domain) whose ARC seal is believed: a message they passed
91
+ * on, which passed when they got it, gets no points for failing SPF, DKIM
92
+ * and DMARC (see normalizeAuthOutput)
49
93
  * @returns {Promise<object>} dkim, spf, dmarc, arc, bimi, receivedChain and
50
94
  * headers (Received-SPF and Authentication-Results, ready to prepend)
51
95
  */
@@ -56,10 +100,22 @@ export function authenticate(message: Buffer | string, options?: {
56
100
  mta?: string;
57
101
  resolver?: Function;
58
102
  timeout?: number;
103
+ maxDnsQueries?: number;
59
104
  dnsServers?: string[];
105
+ trustedArcSealers?: string[];
60
106
  }): Promise<object>;
61
107
  /**
62
108
  * Score authentication results: failures add points, passes take some away.
109
+ *
110
+ * A DMARC failure scores `dmarcFail` when the domain's policy is quarantine
111
+ * or reject (or not known), and SPF failing as well adds nothing more: DMARC
112
+ * already counts it. When the policy is none it scores `dmarcFailNone`, on
113
+ * top of SPF's points, so a domain with policy none is never easier to
114
+ * spoof than one without DMARC. A DKIM signature of only part of the body
115
+ * (l=) earns no DKIM_PASS, nor a DMARC pass that rests on one alone
116
+ * DMARC_PASS. An ARC seal counts only from a trusted forwarder (`arc.trusted`,
117
+ * see authenticate's trustedArcSealers): its pass gets ARC_PASS, and no
118
+ * points for failing SPF, DKIM or DMARC, as forwarding is what broke them.
63
119
  * @param {object} auth - from authenticate
64
120
  * @param {object} [weights] - overrides for DEFAULT_AUTH_WEIGHTS
65
121
  * @returns {{score: number, tests: Array<{name: string, score: number}>}}
@@ -73,7 +129,8 @@ export function calculateAuthScore(auth: object, weights?: object): {
73
129
  };
74
130
  /**
75
131
  * The Authentication-Results header (RFC 8601) for a result, without the
76
- * header name. mailauth's own headers are used when present.
132
+ * header name. mailauth's own headers are used when present, with as many
133
+ * of their results as fit in 8 KiB.
77
134
  * @param {object} auth
78
135
  * @param {string} [hostname]
79
136
  * @returns {string}
@@ -85,6 +142,15 @@ export function formatAuthResultsHeader(auth: object, hostname?: string): string
85
142
  * @returns {string}
86
143
  */
87
144
  export function summarizeAuth(auth: object): string;
145
+ export { MAX_DKIM_SIGNATURES } from "./parse.js";
146
+ /**
147
+ * DNS lookups allowed for one message: room for SPF at the limits of RFC
148
+ * 7208 (10 lookups, each MX mechanism with up to 10 hosts, A and AAAA, about
149
+ * 220 in all, as mailauth counts them), MAX_DKIM_SIGNATURES keys (see
150
+ * boundedForAuth), the
151
+ * newest ARC seal, DMARC and BIMI.
152
+ */
153
+ export const MAX_DNS_QUERIES: 300;
88
154
  export namespace DEFAULT_AUTH_WEIGHTS {
89
155
  let dkimPass: number;
90
156
  let dkimFail: number;
@@ -93,6 +159,7 @@ export namespace DEFAULT_AUTH_WEIGHTS {
93
159
  let spfSoftfail: number;
94
160
  let dmarcPass: number;
95
161
  let dmarcFail: number;
162
+ let dmarcFailNone: number;
96
163
  let arcPass: number;
97
164
  let arcFail: number;
98
165
  }
@@ -17,7 +17,7 @@ export function clamdAddress(options?: object): {
17
17
  * @param {string} [options.socket] - clamd's Unix socket
18
18
  * @param {string} [options.host] - or its TCP host
19
19
  * @param {number} [options.port] - and port (3310)
20
- * @param {number} [options.timeout] - milliseconds
20
+ * @param {number} [options.timeout] - milliseconds for the whole scan
21
21
  * @param {number} [options.chunkSize]
22
22
  * @returns {Promise<{infected: boolean, viruses: string[], reply: string}>}
23
23
  */
@@ -27,6 +27,9 @@ export const MODEL_VERSION: 2;
27
27
  export class Classifier {
28
28
  /**
29
29
  * Load a model written by toJSON. Options passed here override the model's.
30
+ * A model is data that may come from anywhere, so everything in it is
31
+ * checked: a damaged one throws a TypeError starting "Corrupt classifier
32
+ * model".
30
33
  * @param {object|string} json
31
34
  * @param {object} [options]
32
35
  * @returns {Classifier}
@@ -67,7 +70,7 @@ export class Classifier {
67
70
  /**
68
71
  * Score a message.
69
72
  * @param {string[]} features
70
- * @returns {{probability: number, category: 'spam'|'ham'|'unsure', spam: number, ham: number, clues: Array<{feature: string, probability: number}>}}
73
+ * @returns {{probability: number, category: 'spam'|'ham'|'unsure', spam: number, ham: number, clues: Array<{feature: string, probability: number}>, coverage: {feature: string|null, spam: number, ham: number, confidence: number}}}
71
74
  */
72
75
  classify(features: string[]): {
73
76
  probability: number;
@@ -78,6 +81,12 @@ export class Classifier {
78
81
  feature: string;
79
82
  probability: number;
80
83
  }>;
84
+ coverage: {
85
+ feature: string | null;
86
+ spam: number;
87
+ ham: number;
88
+ confidence: number;
89
+ };
81
90
  };
82
91
  /**
83
92
  * How well the training data covers the language of a message, from the
@@ -1,6 +1,20 @@
1
+ export function isLookupName(name: any): boolean;
2
+ /**
3
+ * An IP address as blocklists and allowlists know it: without an IPv6 zone
4
+ * ("fe80::1%eth0"), and an IPv4 address mapped into IPv6 ("::ffff:192.0.2.1",
5
+ * as a dual-stack server reports IPv4 clients) as the IPv4 address, and any
6
+ * other IPv6 address in its canonical form (RFC 5952: "2001:db8::1", and an
7
+ * IPv4 address translated into IPv6 or under the NAT64 prefix in dotted form:
8
+ * "::ffff:0:192.0.2.1", "64:ff9b::192.0.2.1"). Anything
9
+ * that is not an IP address is returned as it is.
10
+ * @param {string} ip
11
+ * @returns {string}
12
+ */
13
+ export function normalizeIp(ip: string): string;
1
14
  /**
2
15
  * The DNS name to query for an IP address in a blocklist zone: reversed
3
16
  * octets for IPv4 ("4.3.2.1.zen.spamhaus.org"), reversed nibbles for IPv6.
17
+ * An IPv4 address mapped into IPv6 is looked up as IPv4.
4
18
  * @param {string} ip
5
19
  * @param {string} zone
6
20
  * @returns {string|null}
@@ -8,8 +22,12 @@
8
22
  export function reverseName(ip: string, zone: string): string | null;
9
23
  /**
10
24
  * Whether an answer means "listed": an address in 127.0.0.0/8 that is not one
11
- * of the error codes lists return when queries are refused (127.255.255.x at
12
- * Spamhaus, 127.0.0.255 at SURBL, 127.0.0.1 at URIBL for blocked resolvers).
25
+ * of the error codes lists return when queries are refused or wrong: at
26
+ * Spamhaus, 127.255.255.x (127.255.255.252 for a misspelled zone,
27
+ * .254 for queries through a public resolver, .255 for too many queries) and
28
+ * 127.0.1.255 (an address looked up in the DBL), 127.0.0.255 anywhere, and
29
+ * 127.0.0.1 at SURBL and URIBL, which answer it to resolvers they block
30
+ * (SpamAssassin's SURBL_BLOCKED and URIBL_BLOCKED).
13
31
  * @param {string} address
14
32
  * @param {string} zone
15
33
  * @returns {boolean}
@@ -69,6 +87,16 @@ export class DnsChecker {
69
87
  * @returns {Promise<string[]>}
70
88
  */
71
89
  resolve4(name: string, servers?: string[] | null): Promise<string[]>;
90
+ /**
91
+ * A records for a name, an empty list when the name does not exist or has
92
+ * no A records, and null when no answer came: the lookup timed out or
93
+ * failed (SERVFAIL, refused, unreachable). Only answers are cached, so a
94
+ * failed lookup is tried again for the next message.
95
+ * @param {string} name
96
+ * @param {string[]|null} [servers]
97
+ * @returns {Promise<string[]|null>}
98
+ */
99
+ answer(name: string, servers?: string[] | null): Promise<string[] | null>;
72
100
  /**
73
101
  * Look an IP address up in IP blocklists.
74
102
  * @param {string} ip
@@ -94,13 +122,15 @@ export class DnsChecker {
94
122
  /**
95
123
  * Ask Cloudflare's filtering resolvers about host names. A name blocked by
96
124
  * the malware resolver is "malware"; one blocked only by the family
97
- * resolver is "adult".
125
+ * resolver is "adult". The hosts that got no answer (from the malware
126
+ * resolver, or from the family resolver when asked) are listed in the
127
+ * result's (non-enumerable) `unanswered` property.
98
128
  * @param {string[]} hosts
99
129
  * @param {object} [options]
100
130
  * @param {boolean} [options.adult] - also ask the family resolver
101
131
  * @param {string[]} [options.malwareServers]
102
132
  * @param {string[]} [options.familyServers]
103
- * @returns {Promise<Array<{host: string, category: 'malware'|'adult'}>>}
133
+ * @returns {Promise<Array<{host: string, category: 'malware'|'adult'}> & {unanswered: string[]}>}
104
134
  */
105
135
  checkCloudflare(hosts?: string[], options?: {
106
136
  adult?: boolean;
@@ -109,5 +139,7 @@ export class DnsChecker {
109
139
  }): Promise<Array<{
110
140
  host: string;
111
141
  category: "malware" | "adult";
112
- }>>;
142
+ }> & {
143
+ unanswered: string[];
144
+ }>;
113
145
  }
@@ -16,8 +16,9 @@ export function foldHeader(name: string, value: string, newline?: string): strin
16
16
  * - X-Spam-Level: one star per point (Sieve: header :contains "X-Spam-Level" "*****")
17
17
  * - X-Spam-Status: Yes or No, with score, threshold, tests and version
18
18
  * - X-Spam-Action: accept, tag or reject
19
+ * - X-Spam-Virus-Scan: failed, when ClamAV could not scan an attachment
19
20
  *
20
- * @param {{score: number, threshold: number, isSpam: boolean, action: string, tests: Array<{name: string}>}} result
21
+ * @param {{score: number, threshold: number, isSpam: boolean, action: string, tests: Array<{name: string}>, results?: {virusScanErrors?: Array<object>}}} result
21
22
  * @param {object} [options]
22
23
  * @param {string} [options.version]
23
24
  * @returns {Array<[string, string]>} header name and value pairs
@@ -30,6 +31,9 @@ export function spamHeaders(result: {
30
31
  tests: Array<{
31
32
  name: string;
32
33
  }>;
34
+ results?: {
35
+ virusScanErrors?: Array<object>;
36
+ };
33
37
  }, options?: {
34
38
  version?: string;
35
39
  }): Array<[string, string]>;
@@ -43,6 +47,31 @@ export function splitMessage(raw: Buffer): {
43
47
  body: Buffer;
44
48
  newline: string;
45
49
  };
50
+ /**
51
+ * A subject tag as it can go in a header: on one line, and, when it is not
52
+ * printable ASCII, holds "=?" (which would read as the start of an encoded
53
+ * word) or a word too long to fold, as encoded words (RFC 2047) of at most
54
+ * 64 characters, split only between whole characters and separated by
55
+ * spaces, so "[垃圾]" survives and a tag cannot add header lines. A tag empty
56
+ * after cleanup is "".
57
+ * @param {string} tag
58
+ * @param {boolean} [spaceAfter] - for a tag before an encoded word: the space
59
+ * after an encoded tag goes inside it, since the space between two encoded
60
+ * words is not shown
61
+ * @returns {string}
62
+ */
63
+ export function subjectTagText(tag: string, spaceAfter?: boolean): string;
64
+ /**
65
+ * A Subject header's value with a tag before it (see subjectTagText), folded
66
+ * between the tag's words so a line with an encoded word stays within 76
67
+ * characters, and before the subject when the line would be too long, or
68
+ * null when the tag is empty or the subject already starts with it.
69
+ * @param {string} tag
70
+ * @param {string} current - the subject's value, as written, perhaps folded
71
+ * @param {string} [newline]
72
+ * @returns {string|null} the value, after "Subject: "
73
+ */
74
+ export function taggedSubject(tag: string, current: string, newline?: string): string | null;
46
75
  /**
47
76
  * Add headers to a raw message and optionally tag its subject.
48
77
  *
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Whether a name is a brand with one letter left out inside it, or with one
3
+ * of its letters doubled.
4
+ * @param {string} name
5
+ * @param {string} brand
6
+ * @returns {boolean}
7
+ */
8
+ export function isDroppedOrDoubled(name: string, brand: string): boolean;
1
9
  /**
2
10
  * Map every lookalike character of a domain label to the Latin letter it
3
11
  * imitates (Cyrillic "рауpаl" becomes "paypal").
@@ -24,6 +32,14 @@ export function editDistance(a: string, b: string, max?: number): number;
24
32
  * @returns {string|null}
25
33
  */
26
34
  export function brandWithPhishingWord(name: string, brands: Set<string>): string | null;
35
+ /**
36
+ * Whether two names differ only by two neighbouring letters swapped (not
37
+ * when they are the same).
38
+ * @param {string} a
39
+ * @param {string} b
40
+ * @returns {boolean}
41
+ */
42
+ export function isSwap(a: string, b: string): boolean;
27
43
  /**
28
44
  * Brands most often imitated in phishing: banks, payment services, mail and
29
45
  * cloud providers, shops, couriers and tax offices. A domain whose name is a
@@ -31,6 +47,7 @@ export function brandWithPhishingWord(name: string, brands: Set<string>): string
31
47
  * never flagged.
32
48
  */
33
49
  export const BRANDS: string[];
50
+ export const PHISHING_WORDS: string[];
34
51
  /**
35
52
  * Detects domains made to look like well-known brands: lookalike letters from
36
53
  * other scripts (Cyrillic "а" for Latin "a"), mixed scripts in one label,
@@ -79,7 +96,7 @@ export default class HomographDetector {
79
96
  recommendations: string[];
80
97
  confidence: number;
81
98
  };
82
- analyse(input: any): {
99
+ analyse(input: any, written?: any): {
83
100
  domain: any;
84
101
  unicode: any;
85
102
  isIDN: boolean;
@@ -12,6 +12,35 @@
12
12
  export function decodeEntities(value: string, { attribute }?: {
13
13
  attribute?: boolean;
14
14
  }): string;
15
+ /**
16
+ * The values of the attributes of an HTML document that may hold words (see
17
+ * NO_WORD_ATTRIBUTES), with their character references decoded, where the
18
+ * tag that holds each starts and ends, and whether it is a form's help text
19
+ * (see FIELD_HELP), in order: not
20
+ * those in comments or in the raw text of scripts and styles, and none after
21
+ * a tag the input ends inside, as browsers read them. In time linear in the
22
+ * input.
23
+ * @param {string} html
24
+ * @returns {Array<{text: string, start: number, end: number, help: boolean}>}
25
+ */
26
+ export function attributeText(html: string): Array<{
27
+ text: string;
28
+ start: number;
29
+ end: number;
30
+ help: boolean;
31
+ }>;
32
+ /**
33
+ * Where a form's help text is in an HTML document (see FIELD_HELP): the
34
+ * values of those attributes and the text of each <label> of at most
35
+ * MAX_HELP characters, as places in the input, in order of where they start
36
+ * (a label's text may hold another's). In time linear in the input.
37
+ * @param {string} html
38
+ * @returns {Array<{from: number, to: number}>}
39
+ */
40
+ export function formHelp(html: string): Array<{
41
+ from: number;
42
+ to: number;
43
+ }>;
15
44
  /**
16
45
  * The text of HTML as a mail client shows it, read in linear time: without
17
46
  * tags, comments, scripts, styles and the title, with character references
@@ -0,0 +1,22 @@
1
+ import type * as m from './index.js' with {'resolution-mode': 'import'};
2
+ type SpamScanner = m.SpamScanner;
3
+ declare const SpamScanner: typeof m.SpamScanner & Omit<typeof m, 'default'> & {default: typeof m.SpamScanner};
4
+ declare namespace SpamScanner {
5
+ export type SpamScanner = m.SpamScanner;
6
+ export type Classifier = m.Classifier;
7
+ export type MilterServer = m.MilterServer;
8
+ export type SmtpSession = m.SmtpSession;
9
+ export type ReputationOptions = m.ReputationOptions;
10
+ export type LegacyOptions = m.LegacyOptions;
11
+ export type SpamScannerOptions = m.SpamScannerOptions;
12
+ export type ScannerSettings = m.ScannerSettings;
13
+ export type ScanOptions = m.ScanOptions;
14
+ export type ScanTest = m.ScanTest;
15
+ export type ScanFinding = m.ScanFinding;
16
+ export type ReputationAnswer = m.ReputationAnswer;
17
+ export type ReputationResult = m.ReputationResult;
18
+ export type ScanDetails = m.ScanDetails;
19
+ export type ScanResult = m.ScanResult;
20
+ export type LLMClassifier = m.LLMClassifier;
21
+ }
22
+ export = SpamScanner;