@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.128",
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
- test("names the display name and the link destination", () => {
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, /really was sent by/);
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 that domain.`,
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 that domain.`,
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
- ? `We verified this message was really sent by ${auth.fromDomain}.`
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 this message was actually sent from ${dkimDomain ?? "another sender"} — not ${fromDomain}. Real senders use their own address.`
222
- : `This message claims to be from ${fromDomain}, but it was actually sent from ${dkimDomain ?? "a different sender"}.`;
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,