fix(support): redact host names under Brazilian, Portuguese and other app-region TLDs

The support report's host-name step only knew a short list of top-level
domains, so saude.minhacasa.br:3000 or vida.example.com.br reached a
report meant for a public issue unredacted, as did hosts under .pt, .be,
.lu, .li, the Nordic codes and the English-speaking ccTLDs. The list now
covers the country codes of every app language's home regions, pinned
by a test against the shipped locales.
This commit is contained in:
Marc-André Bombeck committed 2026-10-22 18:09:22 +02:00
1 parent 38941c55d8
commit 47ac2c6889
2 files changed
+106 -7

No files matched your search

@@ -21,11 +21,13 @@ import {
type SupportReportFacts,
} from "../report";
import {
HOST_TLDS,
redactSupportText,
SUPPORT_REDACTION_STEPS,
type SupportRedactionContext,
} from "../redact";
import type { WideEvent } from "@/lib/logging/types";
import { locales, type Locale } from "@/lib/i18n/config";
const ACCOUNT_EMAIL = "anna.schmidt@example.org";
const ACCOUNT_ID = "cmq7x0abcdefghijklmnopqrs";
@@ -366,3 +368,64 @@ describe("error classes never carry the message", () => {
expect(JSON.stringify(top)).not.toContain(SEEDS.ids);
});
});
describe("host names under every app language's country codes", () => {
// Where each shipped language is at home. A new locale fails here until its
// countries are listed, and a listed country fails until HOST_TLDS has it.
const HOST_TLD_LOCALE_REGIONS: Record<Locale, readonly string[]> = {
de: ["de", "at", "ch", "li", "lu", "be"],
en: ["uk", "ie", "us", "ca", "au", "nz"],
fr: ["fr", "be", "ch", "lu", "ca"],
es: ["es", "mx", "ar"],
it: ["it", "ch"],
pl: ["pl"],
ko: ["kr"],
nl: ["nl", "be"],
"pt-BR": ["br", "pt", "ao", "mz"],
};
it("covers every shipped locale", () => {
expect(Object.keys(HOST_TLD_LOCALE_REGIONS).sort()).toEqual(
[...locales].sort(),
);
});
it.each(Object.entries(HOST_TLD_LOCALE_REGIONS))(
"redacts a host under each of %s's country codes",
(_locale, tlds) => {
for (const tld of tlds) {
expect(HOST_TLDS, tld).toContain(tld);
const text = redactSupportText(`reached nas.familie.${tld} late`, {
knownValues: [],
});
expect(text, tld).toBe("reached [host] late");
}
},
);
it("redacts Brazilian and Portuguese hosts, with a port or a second-level registry", () => {
const cases = [
"saude.minhacasa.br:3000",
"vida.example.com.br",
"healthlog.casa.pt",
"nas.lan.com.pt:8443",
];
for (const host of cases) {
const text = redactSupportText(`unexpected_silence ${host}`, {
knownValues: [],
});
expect(text, host).not.toMatch(/minhacasa|example|casa|lan\.com/);
expect(text).toContain("[host]");
}
});
it("still leaves report prose and action names alone", () => {
const text = redactSupportText(
"- Actions: mental-health.create, coach.budget.exceeded, measurement.batch.ingest\n- Node: v22.11.0",
{ knownValues: [] },
);
expect(text).toContain("mental-health.create");
expect(text).toContain("coach.budget.exceeded");
expect(text).toContain("measurement.batch.ingest");
});
});
+43 -7
View File
@@ -39,8 +39,16 @@ function escapeRegExp(value: string): string {
*/
const KNOWN_VALUE_MIN_LENGTH = 5;
/** Top-level domains a host name in a self-hosted setup ends in. */
const HOST_TLDS = [
/**
* Top-level domains a host name in a self-hosted setup ends in: the generic
* ones, the home-network suffixes, and the country codes of every place the
* app's languages are at home (`HOST_TLD_LOCALE_REGIONS` in the test pins
* that list against `locales`), plus the neighbours a self-hoster there is
* as likely to register under. A second-level registry (`com.br`, `co.uk`)
* needs nothing extra: the label before it is matched as a subdomain.
*/
export const HOST_TLDS = [
// Generic.
"com",
"net",
"org",
@@ -50,18 +58,44 @@ const HOST_TLDS = [
"cloud",
"info",
"me",
"co",
"xyz",
"online",
"site",
"eu",
// Country codes.
"de",
"at",
"ch",
"eu",
"uk",
"li",
"lu",
"be",
"nl",
"fr",
"es",
"pt",
"br",
"ao",
"mz",
"it",
"pl",
"nl",
"cz",
"sk",
"kr",
"jp",
"uk",
"ie",
"us",
"ca",
"au",
"nz",
"mx",
"ar",
"se",
"dk",
"no",
"fi",
// Home-network and private suffixes.
"local",
"lan",
"home",
@@ -69,7 +103,9 @@ const HOST_TLDS = [
"arpa",
"localdomain",
"ts",
].join("|");
];
const HOST_TLD_PATTERN = HOST_TLDS.join("|");
export const SUPPORT_REDACTION_STEPS: readonly SupportRedactionStep[] = [
{
@@ -132,7 +168,7 @@ export const SUPPORT_REDACTION_STEPS: readonly SupportRedactionStep[] = [
apply: (text) =>
text.replace(
new RegExp(
`\\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+(?:${HOST_TLDS})\\b`,
`\\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+(?:${HOST_TLD_PATTERN})\\b`,
"gi",
),
"[host]",