@remit/mailbox-service 0.0.62 → 0.0.63

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/mailbox-service",
3
- "version": "0.0.62",
3
+ "version": "0.0.63",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -71,7 +71,7 @@ describe("classifyDisplayNameCorrespondence", () => {
71
71
  );
72
72
  });
73
73
 
74
- it("claims nothing when the display name is the address itself", () => {
74
+ it("claims nothing when the display name is an address at the sending domain", () => {
75
75
  assert.equal(
76
76
  classifyDisplayNameCorrespondence(
77
77
  "billing@serviceupdatebank.atlassian.net",
@@ -81,6 +81,60 @@ describe("classifyDisplayNameCorrespondence", () => {
81
81
  );
82
82
  });
83
83
 
84
+ it("claims nothing when the display name spells the envelope address itself", () => {
85
+ assert.equal(
86
+ classifyDisplayNameCorrespondence("matthijs@ischen.nl", "ischen.nl"),
87
+ DisplayNameCorrespondence.NoClaim,
88
+ );
89
+ });
90
+
91
+ it("claims nothing when the display name is an address on a sibling subdomain", () => {
92
+ assert.equal(
93
+ classifyDisplayNameCorrespondence(
94
+ "noreply@example.co.uk",
95
+ "mail.example.co.uk",
96
+ ),
97
+ DisplayNameCorrespondence.NoClaim,
98
+ );
99
+ });
100
+
101
+ it("is a foreign address when the display name spells an address over another domain", () => {
102
+ assert.equal(
103
+ classifyDisplayNameCorrespondence(
104
+ "matthijs@ischen.nl",
105
+ "secresaludguaviare.gov.co",
106
+ ),
107
+ DisplayNameCorrespondence.ForeignAddress,
108
+ );
109
+ });
110
+
111
+ it("is a foreign address when the spelled-out address is decorated with a name", () => {
112
+ assert.equal(
113
+ classifyDisplayNameCorrespondence(
114
+ "Matthijs (matthijs@ischen.nl)",
115
+ "secresaludguaviare.gov.co",
116
+ ),
117
+ DisplayNameCorrespondence.ForeignAddress,
118
+ );
119
+ });
120
+
121
+ it("compares an at sign that spells no address as an ordinary name", () => {
122
+ assert.equal(
123
+ classifyDisplayNameCorrespondence(
124
+ "Support @ InfoMedics",
125
+ "infomedics.nl",
126
+ ),
127
+ DisplayNameCorrespondence.Corresponds,
128
+ );
129
+ assert.equal(
130
+ classifyDisplayNameCorrespondence(
131
+ "Support @ InfoMedics",
132
+ "serviceupdatebank.atlassian.net",
133
+ ),
134
+ DisplayNameCorrespondence.Unrelated,
135
+ );
136
+ });
137
+
84
138
  it("does not read the public suffix as a match for a brand containing it", () => {
85
139
  assert.equal(
86
140
  classifyDisplayNameCorrespondence("Netflix", "mailer.example.net"),
@@ -213,6 +267,25 @@ describe("extractSenderMismatch", () => {
213
267
  );
214
268
  });
215
269
 
270
+ it("flags a display name spelling the recipient's own address over a foreign domain", async () => {
271
+ const parsed = await parse([
272
+ "From: matthijs@ischen.nl <aramirez@secresaludguaviare.gov.co>",
273
+ "To: matthijs@ischen.nl",
274
+ "Subject: Re: factura",
275
+ "X-HalOne-Spam-Probability: 1",
276
+ "",
277
+ "hola",
278
+ ]);
279
+ assert.equal(
280
+ extractSenderMismatch(parsed, {
281
+ fromDomain: "secresaludguaviare.gov.co",
282
+ spamClassified: true,
283
+ bulkSender: false,
284
+ }).displayNameCorrespondence,
285
+ DisplayNameCorrespondence.ForeignAddress,
286
+ );
287
+ });
288
+
216
289
  it("leaves the display name uncompared for a bulk sender", async () => {
217
290
  const parsed = await parse([
218
291
  "From: Dutch Cycling Weekly <bounce-9f2@mailer.esp.example>",
@@ -76,6 +76,22 @@ const editDistance = (a: string, b: string): number => {
76
76
  return previous[b.length];
77
77
  };
78
78
 
79
+ const ADDRESS_SHAPE = /[^\s<>@,;:"]+@([a-z0-9-]+(?:\.[a-z0-9-]+)+)/i;
80
+
81
+ const registrableDomain = (host: string): string =>
82
+ getDomain(host) ?? host.toLowerCase();
83
+
84
+ /**
85
+ * The registrable domain of the address a display name spells out, or
86
+ * `undefined` when it holds an `@` without an address behind it — `Bob @ Acme`
87
+ * asserts no address and is left to the ordinary comparison.
88
+ */
89
+ const claimedAddressDomain = (displayName: string): string | undefined => {
90
+ const host = ADDRESS_SHAPE.exec(displayName)?.[1];
91
+ if (host === undefined) return undefined;
92
+ return registrableDomain(host);
93
+ };
94
+
79
95
  /**
80
96
  * The distance a name of this length may be from a domain label and still be
81
97
  * read as an imitation of it. Tight on purpose: an unrelated brand name and an
@@ -109,14 +125,27 @@ const lookalikeThreshold = (length: number): number => {
109
125
  * A bounded edit distance is the secondary test, and only reaches names that
110
126
  * nearly match a label — `InfoMedics` against `1nfomedics.nl`. It cannot promote
111
127
  * an unrelated name on its own.
128
+ *
129
+ * A name that spells out an address is decided before any of that. Over the
130
+ * sending domain it claims nothing — `billing@` shown over
131
+ * `serviceupdatebank.atlassian.net` names the same party the envelope does.
132
+ * Over any other registrable domain it is the strongest non-correspondence
133
+ * signal there is: the message asserts, in the one field the recipient reads,
134
+ * that it comes from an address that cannot have sent it.
112
135
  */
113
136
  export const classifyDisplayNameCorrespondence = (
114
137
  displayName: string | undefined,
115
138
  fromDomain: string,
116
139
  ): CorrespondenceValue => {
117
140
  const raw = (displayName ?? "").trim();
118
- if (raw === "" || raw.includes("@")) {
119
- return DisplayNameCorrespondence.NoClaim;
141
+ if (raw === "") return DisplayNameCorrespondence.NoClaim;
142
+
143
+ const claimed = claimedAddressDomain(raw);
144
+ if (claimed !== undefined) {
145
+ if (claimed === registrableDomain(fromDomain)) {
146
+ return DisplayNameCorrespondence.NoClaim;
147
+ }
148
+ return DisplayNameCorrespondence.ForeignAddress;
120
149
  }
121
150
 
122
151
  const name = normalize(raw);