@remit/web-client 0.0.128 → 0.0.129
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/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.129",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
|
|
6
6
|
"exports": {
|
|
@@ -246,7 +246,11 @@ describe("buildAuthenticityIntel", () => {
|
|
|
246
246
|
describe("a passing signature over a claim that does not hold", () => {
|
|
247
247
|
// The InfoMedics invoice phish: an attacker's own free Atlassian tenant,
|
|
248
248
|
// so SPF/DKIM/DMARC genuinely pass for a domain nobody recognises, and the
|
|
249
|
-
// provider's own filter already called it spam.
|
|
249
|
+
// provider's own filter already called it spam. dkimDomain deliberately
|
|
250
|
+
// differs from fromDomain here (the delivery host's re-signature,
|
|
251
|
+
// custmx.one.com, is a different party than the sender's own
|
|
252
|
+
// serviceupdatebank.atlassian.net) — the display-name check was run
|
|
253
|
+
// against fromDomain, never dkimDomain, so the copy must name fromDomain.
|
|
250
254
|
const infoMedics = makeThread({
|
|
251
255
|
fromEmail: "jira@serviceupdatebank.atlassian.net",
|
|
252
256
|
fromName: "InfoMedics",
|
|
@@ -266,11 +270,17 @@ describe("buildAuthenticityIntel", () => {
|
|
|
266
270
|
assert.doesNotMatch(result.summary, /We verified/i);
|
|
267
271
|
});
|
|
268
272
|
|
|
269
|
-
|
|
273
|
+
// The copy must name the domain the comparison was actually run
|
|
274
|
+
// against (fromDomain), never the unrelated dkimDomain — a message
|
|
275
|
+
// signed by a relay or ESP infrastructure domain must not read as
|
|
276
|
+
// "the name looks nothing like <that other party>".
|
|
277
|
+
test("leads with the concern, naming the domain the name was actually compared to, and the link destination", () => {
|
|
270
278
|
const result = buildAuthenticityIntel(infoMedics, 0);
|
|
271
279
|
assert.equal(result.verdict, "caution");
|
|
272
|
-
assert.match(result.summary,
|
|
280
|
+
assert.match(result.summary, /^The name it shows/);
|
|
273
281
|
assert.match(result.summary, /"InfoMedics"/);
|
|
282
|
+
assert.match(result.summary, /serviceupdatebank\.atlassian\.net/);
|
|
283
|
+
assert.doesNotMatch(result.summary, /custmx\.one\.com/);
|
|
274
284
|
assert.match(result.summary, /betaal-vordering\.example/);
|
|
275
285
|
assert.doesNotMatch(result.summary, /DKIM|SPF|DMARC/i);
|
|
276
286
|
});
|
|
@@ -117,6 +117,16 @@ function joinDomains(domains: readonly string[]): string {
|
|
|
117
117
|
* out. Empty when everything the backend compared agreed — including when it
|
|
118
118
|
* compared nothing, which is every message the provider's filter did not
|
|
119
119
|
* already call spam.
|
|
120
|
+
*
|
|
121
|
+
* Each clause leads with the concern and names `auth.fromDomain` — the
|
|
122
|
+
* domain `classifyDisplayNameCorrespondence` actually compared the display
|
|
123
|
+
* name against (`senderMismatch.ts` calls it with the From address's own
|
|
124
|
+
* domain, never the DKIM signing domain). Naming `auth.dkimDomain` instead
|
|
125
|
+
* would assert a comparison that was never made: on a message signed by a
|
|
126
|
+
* relay or ESP infrastructure domain, the display name was checked against
|
|
127
|
+
* the sender's own address, not that domain. These clauses are the caution
|
|
128
|
+
* tier's entire summary: there is no separate "verified" sentence in front
|
|
129
|
+
* of them for the signing fact to hide behind.
|
|
120
130
|
*/
|
|
121
131
|
function describeSenderMismatch(
|
|
122
132
|
auth: NonNullable<RemitImapThreadMessageResponse["authenticity"]>,
|
|
@@ -128,11 +138,11 @@ function describeSenderMismatch(
|
|
|
128
138
|
if (claimedBrand) {
|
|
129
139
|
if (correspondence === DisplayNameCorrespondence.Unrelated) {
|
|
130
140
|
clauses.push(
|
|
131
|
-
`The name it shows, "${claimedBrand}", has nothing to do with
|
|
141
|
+
`The name it shows, "${claimedBrand}", has nothing to do with ${auth.fromDomain}.`,
|
|
132
142
|
);
|
|
133
143
|
} else if (correspondence === DisplayNameCorrespondence.Lookalike) {
|
|
134
144
|
clauses.push(
|
|
135
|
-
`The name it shows, "${claimedBrand}", only looks like
|
|
145
|
+
`The name it shows, "${claimedBrand}", only looks like ${auth.fromDomain}.`,
|
|
136
146
|
);
|
|
137
147
|
}
|
|
138
148
|
}
|
|
@@ -198,10 +208,7 @@ export function buildAuthenticityIntel(
|
|
|
198
208
|
fromDomain: auth.fromDomain,
|
|
199
209
|
dkimDomain: auth.dkimDomain,
|
|
200
210
|
claimedBrand: claimed,
|
|
201
|
-
summary:
|
|
202
|
-
`This message really was sent by ${auth.fromDomain}.`,
|
|
203
|
-
...unlike,
|
|
204
|
-
].join(" "),
|
|
211
|
+
summary: unlike.join(" "),
|
|
205
212
|
};
|
|
206
213
|
}
|
|
207
214
|
return {
|
|
@@ -209,7 +216,7 @@ export function buildAuthenticityIntel(
|
|
|
209
216
|
fromDomain: auth.fromDomain,
|
|
210
217
|
dkimDomain: auth.dkimDomain,
|
|
211
218
|
summary: auth.dkimDomain
|
|
212
|
-
? `
|
|
219
|
+
? `This message was signed by ${auth.dkimDomain}.`
|
|
213
220
|
: `Nothing looks unusual about this sender.`,
|
|
214
221
|
};
|
|
215
222
|
}
|
|
@@ -217,9 +224,17 @@ export function buildAuthenticityIntel(
|
|
|
217
224
|
const fromDomain = auth.fromDomain;
|
|
218
225
|
const dkimDomain = auth.dkimDomain;
|
|
219
226
|
const claimedBrand = claimedBrandOf(thread);
|
|
227
|
+
// dkimDomain is known whenever a mismatch was named against a real
|
|
228
|
+
// domain; the fallback covers the (defensive, not currently reachable)
|
|
229
|
+
// case where a mismatch fires with none — say what actually happened
|
|
230
|
+
// (a signature failed to verify) rather than inventing a sender identity
|
|
231
|
+
// we do not have.
|
|
232
|
+
const whatHappened = dkimDomain
|
|
233
|
+
? `it was actually sent from ${dkimDomain}`
|
|
234
|
+
: "its signature failed to verify";
|
|
220
235
|
const summary = claimedBrand
|
|
221
|
-
? `The display name claims "${claimedBrand}", but
|
|
222
|
-
: `This message claims to be from ${fromDomain}, but
|
|
236
|
+
? `The display name claims "${claimedBrand}", but ${whatHappened}${dkimDomain ? ` — not ${fromDomain}` : ""}. Real senders use their own address.`
|
|
237
|
+
: `This message claims to be from ${fromDomain}, but ${whatHappened}.`;
|
|
223
238
|
return {
|
|
224
239
|
verdict: "mismatch",
|
|
225
240
|
fromDomain,
|