Files
webmail/lib/admin/__tests__/reachable-hosts-observability.test.ts
T
Linus Rath cba1109444 feat: report browser and server errors to an error tracker the admin sets
Admins had logs and nothing else: client errors only reached the
browser console. An admin can now set a Sentry-protocol address (DSN)
for Bugsink, GlitchTip or Sentry under System > Monitoring, or with
`ERROR_REPORTING_DSN`, `ERROR_REPORTING_DSN_FILE`,
`ERROR_REPORTING_SAMPLE_RATE` and `ERROR_REPORTING_ENVIRONMENT`.
Bulwark Lite reads the same from `errorReporting` in policy.json.
Everything stays off while no address is set.

- browsers report errors from the error boundaries, window.onerror,
  unhandled rejections and failed JMAP requests (method and error type
  only), with route and JMAP method breadcrumbs and the chunks' debug IDs
- the server reports what Next hands to onRequestError and what routes
  log with logger.error
- every report is scrubbed first: quoted text, addresses, URLs, file
  names, tokens and the rest after the first line are removed, and the
  server scrubs what browsers post to /api/errors again
- reports are deduplicated and rate-limited per session and process
- errors are counted per day for "errors today" on the admin page
- startup messages and audit entries go through the logger, so
  `LOG_FORMAT=json` covers them
2026-11-01 02:50:19 +01:00

22 lines
1.3 KiB
TypeScript

// @vitest-environment node
import { describe, expect, it } from 'vitest';
import { listReachableHosts, noThirdPartyCspSources } from '@/lib/admin/reachable-hosts';
const policy = { pushRelays: [], pushRelayUrl: '', defaultSidebarApps: [], features: { sidebarAppsEnabled: false, calendarEnabled: false } };
function hosts(config: Record<string, unknown>, env: Record<string, string | undefined> = {}) {
return listReachableHosts({ get: (key) => config[key], policy, env, buildRelayUrl: '' });
}
// No third-party traffic (#903) still allows the hosts the admin set: the
// error tracker is one of them, and the page's CSP must let browsers reach it.
describe('error reporting in the no-third-party list', () => {
it('is listed, without the key, only when a DSN is set', () => {
expect(hosts({}).filter((h) => h.kind === 'Error reporting')).toEqual([]);
const listed = hosts({ errorReportingDsn: 'https://6f0b9c79ed3a4315b76595bc2536ac40@errors.example.org/3' })
.filter((h) => h.kind === 'Error reporting');
expect(listed).toEqual([{ kind: 'Error reporting', host: 'errors.example.org', origin: 'https://errors.example.org', setIn: 'ERROR_REPORTING_DSN', side: 'both' }]);
expect(noThirdPartyCspSources(listed, false).connect).toEqual(['https://errors.example.org']);
});
});