@tallyrow/safesignal 1.0.1 → 1.2.0
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/README.md +97 -5
- package/dist/index.cjs +81 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -3
- package/dist/index.d.ts +26 -3
- package/dist/index.mjs +81 -13
- package/dist/index.mjs.map +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.mjs.map +1 -1
- package/dist/transport-beacon.cjs +65 -50
- package/dist/transport-beacon.cjs.map +1 -1
- package/dist/transport-beacon.d.cts +1 -1
- package/dist/transport-beacon.d.ts +1 -1
- package/dist/transport-beacon.mjs +65 -50
- package/dist/transport-beacon.mjs.map +1 -1
- package/dist/transport-otlp.cjs +541 -0
- package/dist/transport-otlp.cjs.map +1 -0
- package/dist/transport-otlp.d.cts +62 -0
- package/dist/transport-otlp.d.ts +62 -0
- package/dist/transport-otlp.mjs +539 -0
- package/dist/transport-otlp.mjs.map +1 -0
- package/dist/{types-D-xVvmvX.d.cts → types-BiRyHi1e.d.cts} +20 -1
- package/dist/{types-D-xVvmvX.d.ts → types-BiRyHi1e.d.ts} +20 -1
- package/package.json +12 -3
package/dist/testing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing/secret-fixtures.ts","../src/testing/assert-transport-contract.ts"],"names":[],"mappings":"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","file":"testing.cjs","sourcesContent":["/**\n * Stable bag of secret-looking values for testing. Every value here is\n * fake but shaped like the real thing — long enough that finding it in\n * a URL, query string, log payload, etc. is meaningful evidence of a\n * leak (no false positives from short substrings).\n *\n * Consumers (and the package's own tests T028, T041, T058) place these\n * values in attributes/context/etc. then assert downstream sinks never\n * see any of them.\n *\n * The keys mirror the documented default redaction denylist in\n * `contracts/redaction.md` so any new key here should track a denylist\n * entry — making the fixture the canonical \"things the package promises\n * to mask\" reference for consumers.\n */\n\n/**\n * Concrete shape of {@link makeSecretFixture}'s return value. Named\n * `string` fields (rather than `Record<string, string>`) so callers get\n * `string` — not `string | undefined` — per-field under the package's\n * `noUncheckedIndexedAccess` setting. Not part of the public `/testing`\n * surface; it only types the helper's return.\n *\n * Declared as a `type` (not an `interface`) on purpose: a type alias\n * whose properties are all `string` gets an *implicit* index signature,\n * so the fixture stays assignable to `Record<string, string>` / OTel\n * `Attributes` — while `keyof SecretFixture` remains the precise named-key\n * union, keeping dynamic `fixture[someKey]` access typed `string`.\n */\ntype SecretFixture = {\n readonly password: string;\n readonly passwd: string;\n readonly token: string;\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly bearerToken: string;\n readonly authorization: string;\n readonly auth: string;\n readonly cookie: string;\n readonly setCookie: string;\n readonly secret: string;\n readonly apiKey: string;\n readonly sessionId: string;\n readonly sid: string;\n readonly ssn: string;\n readonly creditCard: string;\n readonly cardNumber: string;\n readonly cvv: string;\n readonly jwt: string;\n};\n\n/**\n * Return a stable record of secret-looking values keyed by category.\n * Values are deterministic across calls so tests can assert against\n * exact strings. Never mutate the returned object across tests — call\n * `makeSecretFixture()` again to get a fresh copy.\n */\nexport function makeSecretFixture(): SecretFixture {\n return {\n password: 'p4ssw0rd-correct-horse-battery-staple',\n passwd: 'p4ssw0rd-shadow-file-style',\n token: 'tok_AAAABBBBCCCCDDDD1234EEEEFFFFGGGG',\n accessToken: 'access_AAAA1111BBBB2222CCCC3333DDDD4444EEEE5555',\n refreshToken: 'refresh_FFFF6666GGGG7777HHHH8888IIII9999JJJJ0000',\n bearerToken:\n 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.bearerFixtureSignature',\n authorization: 'Basic dXNlcjpwYXNzOmZpeHR1cmUtbm90LXJlYWw',\n auth: 'auth_KKKK1111LLLL2222MMMM3333NNNN4444',\n cookie:\n 'sessionId=fixture-session-id-not-real-abc123; Secure; HttpOnly; SameSite=Strict',\n setCookie:\n 'auth=fixture-auth-cookie-not-real-xyz789; Path=/; Secure; HttpOnly',\n secret: 'sk_test_FIXTURE_4eC39HqLyjWDarjtT1zdp7dc_NOT_REAL',\n apiKey: 'pk_live_FIXTURE_ABCD1234EFGH5678IJKL9012MNOP_NOT_REAL',\n sessionId: 'sess_01HXYZ123ABCDEFGHIJKLMNOPQR',\n sid: 'sid_FIXTURE_QQQQ1111RRRR2222SSSS3333',\n ssn: '123-45-6789',\n creditCard: '4242 4242 4242 4242',\n cardNumber: '5555555555554444',\n cvv: '123',\n jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.fixtureJwtSignatureNotReal',\n };\n}\n\n/**\n * The complete list of fixture VALUES. Useful when scanning an\n * arbitrary string (e.g., a captured URL or POST body) for any leaked\n * fixture: `if (FIXTURE_VALUES.some((v) => url.includes(v))) { leak! }`.\n */\nexport const FIXTURE_VALUES: ReadonlyArray<string> = Object.values(\n makeSecretFixture(),\n);\n","/**\n * `assertTransportContract(transport)` — runs the documented Transport\n * contract battery against a consumer-provided `Transport`. Throws on the\n * first violation with a clear diagnostic message.\n *\n * Covers `contracts/transport.md`:\n * - Structural: `name: string`, `send(event)` exists\n * - T-S1: no `LogEvent` data appears in any `fetch` URL or\n * `navigator.sendBeacon` URL the transport produces\n * - T-S2: cross-origin delivery uses request body (POST/PUT/PATCH JSON\n * or a `sendBeacon` `Blob`) — never URL params\n * - T-S3: any URL with a scheme uses `https:`\n * - T-S4: the transport does NOT mutate the event it receives\n * - T-S5: `flush()` and `shutdown()` are idempotent (safe to call > 1x)\n *\n * The helper temporarily monkey-patches `globalThis.fetch` and\n * `globalThis.navigator.sendBeacon` to capture invocations, then restores\n * the originals when each assertion completes — even on failure. Tests\n * can run multiple consumer transports in series safely.\n *\n * This module is reached only via the package's `./testing` subpath; the\n * runtime entry does NOT re-export it.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\nimport { FIXTURE_VALUES, makeSecretFixture } from './secret-fixtures.js';\n\n// ──────────────────────────────────────────────────────────────────────\n// Public entry point\n// ──────────────────────────────────────────────────────────────────────\n\n/**\n * Run the full Transport contract battery against `transport`. Resolves\n * when all assertions pass; rejects with an `Error` carrying a\n * diagnostic message on the first failure.\n */\nexport async function assertTransportContract(\n transport: Transport,\n): Promise<void> {\n assertStructural(transport);\n await assertSendDoesNotThrow(transport);\n await assertNoEventDataInURLs(transport);\n await assertBodyOnlyDelivery(transport);\n await assertHttpsForAbsoluteUrls(transport);\n await assertEventImmutability(transport);\n await assertFlushIdempotent(transport);\n await assertShutdownIdempotent(transport);\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Individual assertions\n// ──────────────────────────────────────────────────────────────────────\n\nfunction assertStructural(transport: Transport): void {\n if (typeof transport !== 'object' || transport === null) {\n throw fail('transport must be an object');\n }\n if (typeof transport.name !== 'string' || transport.name.length === 0) {\n throw fail('transport.name must be a non-empty string');\n }\n if (typeof transport.send !== 'function') {\n throw fail('transport.send must be a function');\n }\n}\n\nasync function assertSendDoesNotThrow(transport: Transport): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async () => {\n try {\n const result = transport.send(event);\n if (result instanceof Promise) {\n await result;\n }\n } catch (err) {\n throw fail(\n transport,\n 'send() threw to the caller — should fail silently or be wrapped',\n err,\n );\n }\n });\n}\n\nasync function assertNoEventDataInURLs(transport: Transport): Promise<void> {\n const event = makeProbeEvent({ attributes: makeSecretFixture() });\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const url of allUrls(captured)) {\n // 1. Any literal fixture value in the URL is a clear leak.\n const leakedValue = FIXTURE_VALUES.find((v) => url.includes(v));\n if (leakedValue !== undefined) {\n throw fail(\n transport,\n `URL contains a secret fixture value (T-S1 violation): ` +\n `url='${url}', leaked='${leakedValue}'`,\n );\n }\n // 2. The probe event's marker message in the URL also indicates\n // a leak — the consumer encoded event content there.\n if (url.includes(PROBE_MESSAGE)) {\n throw fail(\n transport,\n `URL contains the probe event message (T-S1 violation): ` +\n `url='${url}'`,\n );\n }\n }\n });\n}\n\nasync function assertBodyOnlyDelivery(transport: Transport): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const call of captured.fetchCalls) {\n const method = (call.init?.method ?? 'GET').toUpperCase();\n const allowedMethods = ['POST', 'PUT', 'PATCH'];\n if (!allowedMethods.includes(method)) {\n throw fail(\n transport,\n `fetch used HTTP method '${method}' for delivery — ` +\n `must use POST/PUT/PATCH with body (T-S2): url='${call.url}'`,\n );\n }\n if (call.init?.body === undefined || call.init.body === null) {\n throw fail(\n transport,\n `fetch was called with method='${method}' but no body — ` +\n `events must travel in the request body (T-S2): url='${call.url}'`,\n );\n }\n }\n\n for (const call of captured.beaconCalls) {\n if (call.data === null || call.data === undefined) {\n throw fail(\n transport,\n `navigator.sendBeacon was called without data — ` +\n `events must travel in the body (T-S2): url='${call.url}'`,\n );\n }\n }\n });\n}\n\nasync function assertHttpsForAbsoluteUrls(\n transport: Transport,\n): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const url of allUrls(captured)) {\n // Relative URLs are same-origin by definition and inherit the\n // page's scheme — skip them. Absolute URLs MUST be HTTPS.\n if (!isAbsoluteUrl(url)) continue;\n if (!url.toLowerCase().startsWith('https://')) {\n throw fail(\n transport,\n `cross-origin URL is not HTTPS (T-S3): url='${url}'`,\n );\n }\n }\n });\n}\n\nasync function assertEventImmutability(transport: Transport): Promise<void> {\n const event = makeProbeEvent({ attributes: { mutateMe: 'before' } });\n const before = JSON.stringify(event);\n await withInterceptors(async () => {\n await invokeSendSafely(transport, event);\n });\n const after = JSON.stringify(event);\n if (before !== after) {\n throw fail(\n transport,\n `transport mutated the received event (T-S4 violation):\\n` +\n ` before: ${before}\\n after: ${after}`,\n );\n }\n}\n\nasync function assertFlushIdempotent(transport: Transport): Promise<void> {\n if (typeof transport.flush !== 'function') return; // optional hook\n try {\n await transport.flush();\n await transport.flush();\n await transport.flush();\n } catch (err) {\n throw fail(\n transport,\n 'flush() is not idempotent — repeated calls must each resolve (T-S5)',\n err,\n );\n }\n}\n\nasync function assertShutdownIdempotent(transport: Transport): Promise<void> {\n if (typeof transport.shutdown !== 'function') return; // optional hook\n try {\n await transport.shutdown();\n await transport.shutdown();\n await transport.shutdown();\n } catch (err) {\n throw fail(\n transport,\n 'shutdown() is not idempotent — repeated calls must each resolve (T-S5)',\n err,\n );\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// fetch / sendBeacon interceptor\n// ──────────────────────────────────────────────────────────────────────\n\ninterface FetchCall {\n url: string;\n init: RequestInit | undefined;\n}\n\ninterface BeaconCall {\n url: string;\n data: BodyInit | null | undefined;\n}\n\ninterface CapturedCalls {\n fetchCalls: FetchCall[];\n beaconCalls: BeaconCall[];\n}\n\n/**\n * Run `body` with `globalThis.fetch` and `navigator.sendBeacon` replaced\n * by capturing stubs. Restores originals on success and failure.\n */\nasync function withInterceptors(\n body: (captured: CapturedCalls) => Promise<void>,\n): Promise<void> {\n const captured: CapturedCalls = { fetchCalls: [], beaconCalls: [] };\n\n const g = globalThis as unknown as {\n fetch?: typeof fetch;\n navigator?: { sendBeacon?: Navigator['sendBeacon'] };\n };\n\n const originalFetch = g.fetch;\n const originalBeacon = g.navigator?.sendBeacon?.bind(g.navigator);\n\n g.fetch = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const url = extractFetchUrl(input);\n captured.fetchCalls.push({ url, init });\n return new Response('', { status: 204 });\n };\n\n if (g.navigator !== undefined) {\n g.navigator.sendBeacon = (\n url: string | URL,\n data?: BodyInit | null,\n ): boolean => {\n captured.beaconCalls.push({\n url: typeof url === 'string' ? url : url.toString(),\n data,\n });\n return true;\n };\n }\n\n try {\n await body(captured);\n } finally {\n if (originalFetch === undefined) {\n delete g.fetch;\n } else {\n g.fetch = originalFetch;\n }\n if (g.navigator !== undefined) {\n if (originalBeacon === undefined) {\n delete g.navigator.sendBeacon;\n } else {\n g.navigator.sendBeacon = originalBeacon;\n }\n }\n }\n}\n\nfunction extractFetchUrl(input: RequestInfo | URL): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n // Request\n return input.url;\n}\n\nfunction allUrls(captured: CapturedCalls): string[] {\n return [\n ...captured.fetchCalls.map((c) => c.url),\n ...captured.beaconCalls.map((c) => c.url),\n ];\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Probe event + helpers\n// ──────────────────────────────────────────────────────────────────────\n\nconst PROBE_MESSAGE = 'FLSDK-transport-contract-probe-message';\n\nfunction makeProbeEvent(overrides: Partial<LogEvent> = {}): LogEvent {\n return {\n timestamp: new Date().toISOString(),\n level: 'info',\n message: PROBE_MESSAGE,\n attributes: {},\n context: {\n application: { name: 'transport-contract-probe' },\n environment: 'test',\n },\n ...overrides,\n };\n}\n\nasync function invokeSendSafely(\n transport: Transport,\n event: LogEvent,\n): Promise<void> {\n const result = transport.send(event);\n if (result instanceof Promise) {\n // Some intentionally-bad transports return rejected Promises; allow\n // the rejection to surface only as a contract-failure diagnostic,\n // not as an unhandled rejection in the test runner.\n await result.catch(() => undefined);\n }\n}\n\nfunction isAbsoluteUrl(url: string): boolean {\n // Match an URL scheme followed by `://` (http://, https://, ftp://, etc.)\n return /^[a-z][a-z0-9+.-]*:\\/\\//i.test(url);\n}\n\nfunction fail(...args: unknown[]): Error;\nfunction fail(message: string): Error;\nfunction fail(transport: Transport, message: string, cause?: unknown): Error;\nfunction fail(...args: unknown[]): Error {\n let message: string;\n let cause: unknown;\n if (typeof args[0] === 'string') {\n message = `[assertTransportContract] ${args[0]}`;\n } else {\n const transport = args[0] as Transport;\n const msg = args[1] as string;\n cause = args[2];\n message = `[assertTransportContract] transport '${transport.name}': ${msg}`;\n }\n const err = new Error(message);\n if (cause !== undefined) {\n (err as Error & { cause?: unknown }).cause = cause;\n }\n return err;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/testing/secret-fixtures.ts","../src/testing/assert-transport-contract.ts"],"names":[],"mappings":"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","file":"testing.cjs","sourcesContent":["/**\n * Stable bag of secret-looking values for testing. Every value here is\n * fake but shaped like the real thing — long enough that finding it in\n * a URL, query string, log payload, etc. is meaningful evidence of a\n * leak (no false positives from short substrings).\n *\n * Consumers (and the package's own tests T028, T041, T058) place these\n * values in attributes/context/etc. then assert downstream sinks never\n * see any of them.\n *\n * The keys mirror the documented default redaction denylist in\n * `contracts/redaction.md` so any new key here should track a denylist\n * entry — making the fixture the canonical \"things the package promises\n * to mask\" reference for consumers.\n */\n\n/**\n * Concrete shape of {@link makeSecretFixture}'s return value. Named\n * `string` fields (rather than `Record<string, string>`) so callers get\n * `string` — not `string | undefined` — per-field under the package's\n * `noUncheckedIndexedAccess` setting. Not part of the public `/testing`\n * surface; it only types the helper's return.\n *\n * Declared as a `type` (not an `interface`) on purpose: a type alias\n * whose properties are all `string` gets an *implicit* index signature,\n * so the fixture stays assignable to `Record<string, string>` / OTel\n * `Attributes` — while `keyof SecretFixture` remains the precise named-key\n * union, keeping dynamic `fixture[someKey]` access typed `string`.\n */\ntype SecretFixture = {\n readonly password: string;\n readonly passwd: string;\n readonly token: string;\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly bearerToken: string;\n readonly authorization: string;\n readonly auth: string;\n readonly cookie: string;\n readonly setCookie: string;\n readonly secret: string;\n readonly apiKey: string;\n readonly sessionId: string;\n readonly sid: string;\n readonly ssn: string;\n readonly creditCard: string;\n readonly cardNumber: string;\n readonly cvv: string;\n readonly jwt: string;\n};\n\n/**\n * Return a stable record of secret-looking values keyed by category.\n * Values are deterministic across calls so tests can assert against\n * exact strings. Never mutate the returned object across tests — call\n * `makeSecretFixture()` again to get a fresh copy.\n */\nexport function makeSecretFixture(): SecretFixture {\n return {\n password: 'p4ssw0rd-correct-horse-battery-staple',\n passwd: 'p4ssw0rd-shadow-file-style',\n token: 'tok_AAAABBBBCCCCDDDD1234EEEEFFFFGGGG',\n accessToken: 'access_AAAA1111BBBB2222CCCC3333DDDD4444EEEE5555',\n refreshToken: 'refresh_FFFF6666GGGG7777HHHH8888IIII9999JJJJ0000',\n bearerToken:\n 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.bearerFixtureSignature',\n authorization: 'Basic dXNlcjpwYXNzOmZpeHR1cmUtbm90LXJlYWw',\n auth: 'auth_KKKK1111LLLL2222MMMM3333NNNN4444',\n cookie:\n 'sessionId=fixture-session-id-not-real-abc123; Secure; HttpOnly; SameSite=Strict',\n setCookie:\n 'auth=fixture-auth-cookie-not-real-xyz789; Path=/; Secure; HttpOnly',\n secret: 'sk_test_FIXTURE_4eC39HqLyjWDarjtT1zdp7dc_NOT_REAL',\n apiKey: 'pk_live_FIXTURE_ABCD1234EFGH5678IJKL9012MNOP_NOT_REAL',\n sessionId: 'sess_01HXYZ123ABCDEFGHIJKLMNOPQR',\n sid: 'sid_FIXTURE_QQQQ1111RRRR2222SSSS3333',\n ssn: '123-45-6789',\n creditCard: '4242 4242 4242 4242',\n cardNumber: '5555555555554444',\n cvv: '123',\n jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.fixtureJwtSignatureNotReal',\n };\n}\n\n/**\n * The complete list of fixture VALUES. Useful when scanning an\n * arbitrary string (e.g., a captured URL or POST body) for any leaked\n * fixture: `if (FIXTURE_VALUES.some((v) => url.includes(v))) { leak! }`.\n */\nexport const FIXTURE_VALUES: ReadonlyArray<string> = Object.values(\n makeSecretFixture(),\n);\n","/**\n * `assertTransportContract(transport)` — runs the documented Transport\n * contract battery against a consumer-provided `Transport`. Throws on the\n * first violation with a clear diagnostic message.\n *\n * Covers `contracts/transport.md`:\n * - Structural: `name: string`, `send(event)` exists\n * - T-S1: no `LogEvent` data appears in any `fetch` URL or\n * `navigator.sendBeacon` URL the transport produces\n * - T-S2: cross-origin delivery uses request body (POST/PUT/PATCH JSON\n * or a `sendBeacon` `Blob`) — never URL params\n * - T-S3: any URL with a scheme uses `https:`\n * - T-S4: the transport does NOT mutate the event it receives\n * - T-S5: `flush()` and `shutdown()` are idempotent (safe to call > 1x)\n *\n * The helper temporarily monkey-patches `globalThis.fetch` and\n * `globalThis.navigator.sendBeacon` to capture invocations, then restores\n * the originals when each assertion completes — even on failure. Tests\n * can run multiple consumer transports in series safely.\n *\n * This module is reached only via the package's `./testing` subpath; the\n * runtime entry does NOT re-export it.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\nimport { FIXTURE_VALUES, makeSecretFixture } from './secret-fixtures.js';\n\n// ──────────────────────────────────────────────────────────────────────\n// Public entry point\n// ──────────────────────────────────────────────────────────────────────\n\n/**\n * Run the full Transport contract battery against `transport`. Resolves\n * when all assertions pass; rejects with an `Error` carrying a\n * diagnostic message on the first failure.\n */\nexport async function assertTransportContract(\n transport: Transport,\n): Promise<void> {\n assertStructural(transport);\n await assertSendDoesNotThrow(transport);\n await assertNoEventDataInURLs(transport);\n await assertBodyOnlyDelivery(transport);\n await assertHttpsForAbsoluteUrls(transport);\n await assertEventImmutability(transport);\n await assertFlushIdempotent(transport);\n await assertShutdownIdempotent(transport);\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Individual assertions\n// ──────────────────────────────────────────────────────────────────────\n\nfunction assertStructural(transport: Transport): void {\n if (typeof transport !== 'object' || transport === null) {\n throw fail('transport must be an object');\n }\n if (typeof transport.name !== 'string' || transport.name.length === 0) {\n throw fail('transport.name must be a non-empty string');\n }\n if (typeof transport.send !== 'function') {\n throw fail('transport.send must be a function');\n }\n}\n\nasync function assertSendDoesNotThrow(transport: Transport): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async () => {\n try {\n const result = transport.send(event);\n if (result instanceof Promise) {\n await result;\n }\n } catch (err) {\n throw fail(\n transport,\n 'send() threw to the caller — should fail silently or be wrapped',\n err,\n );\n }\n });\n}\n\nasync function assertNoEventDataInURLs(transport: Transport): Promise<void> {\n const event = makeProbeEvent({ attributes: makeSecretFixture() });\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const url of allUrls(captured)) {\n // 1. Any literal fixture value in the URL is a clear leak.\n const leakedValue = FIXTURE_VALUES.find((v) => url.includes(v));\n if (leakedValue !== undefined) {\n throw fail(\n transport,\n `URL contains a secret fixture value (T-S1 violation): ` +\n `url='${url}', leaked='${leakedValue}'`,\n );\n }\n // 2. The probe event's marker message in the URL also indicates\n // a leak — the consumer encoded event content there.\n if (url.includes(PROBE_MESSAGE)) {\n throw fail(\n transport,\n `URL contains the probe event message (T-S1 violation): ` +\n `url='${url}'`,\n );\n }\n }\n });\n}\n\nasync function assertBodyOnlyDelivery(transport: Transport): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const call of captured.fetchCalls) {\n const method = (call.init?.method ?? 'GET').toUpperCase();\n const allowedMethods = ['POST', 'PUT', 'PATCH'];\n if (!allowedMethods.includes(method)) {\n throw fail(\n transport,\n `fetch used HTTP method '${method}' for delivery — ` +\n `must use POST/PUT/PATCH with body (T-S2): url='${call.url}'`,\n );\n }\n if (call.init?.body === undefined || call.init.body === null) {\n throw fail(\n transport,\n `fetch was called with method='${method}' but no body — ` +\n `events must travel in the request body (T-S2): url='${call.url}'`,\n );\n }\n }\n\n for (const call of captured.beaconCalls) {\n if (call.data === null || call.data === undefined) {\n throw fail(\n transport,\n `navigator.sendBeacon was called without data — ` +\n `events must travel in the body (T-S2): url='${call.url}'`,\n );\n }\n }\n });\n}\n\nasync function assertHttpsForAbsoluteUrls(transport: Transport): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const url of allUrls(captured)) {\n // Relative URLs are same-origin by definition and inherit the\n // page's scheme — skip them. Absolute URLs MUST be HTTPS.\n if (!isAbsoluteUrl(url)) continue;\n if (!url.toLowerCase().startsWith('https://')) {\n throw fail(\n transport,\n `cross-origin URL is not HTTPS (T-S3): url='${url}'`,\n );\n }\n }\n });\n}\n\nasync function assertEventImmutability(transport: Transport): Promise<void> {\n const event = makeProbeEvent({ attributes: { mutateMe: 'before' } });\n const before = JSON.stringify(event);\n await withInterceptors(async () => {\n await invokeSendSafely(transport, event);\n });\n const after = JSON.stringify(event);\n if (before !== after) {\n throw fail(\n transport,\n `transport mutated the received event (T-S4 violation):\\n` +\n ` before: ${before}\\n after: ${after}`,\n );\n }\n}\n\nasync function assertFlushIdempotent(transport: Transport): Promise<void> {\n if (typeof transport.flush !== 'function') return; // optional hook\n try {\n await transport.flush();\n await transport.flush();\n await transport.flush();\n } catch (err) {\n throw fail(\n transport,\n 'flush() is not idempotent — repeated calls must each resolve (T-S5)',\n err,\n );\n }\n}\n\nasync function assertShutdownIdempotent(transport: Transport): Promise<void> {\n if (typeof transport.shutdown !== 'function') return; // optional hook\n try {\n await transport.shutdown();\n await transport.shutdown();\n await transport.shutdown();\n } catch (err) {\n throw fail(\n transport,\n 'shutdown() is not idempotent — repeated calls must each resolve (T-S5)',\n err,\n );\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// fetch / sendBeacon interceptor\n// ──────────────────────────────────────────────────────────────────────\n\ninterface FetchCall {\n url: string;\n init: RequestInit | undefined;\n}\n\ninterface BeaconCall {\n url: string;\n data: BodyInit | null | undefined;\n}\n\ninterface CapturedCalls {\n fetchCalls: FetchCall[];\n beaconCalls: BeaconCall[];\n}\n\n/**\n * Run `body` with `globalThis.fetch` and `navigator.sendBeacon` replaced\n * by capturing stubs. Restores originals on success and failure.\n */\nasync function withInterceptors(\n body: (captured: CapturedCalls) => Promise<void>,\n): Promise<void> {\n const captured: CapturedCalls = { fetchCalls: [], beaconCalls: [] };\n\n const g = globalThis as unknown as {\n fetch?: typeof fetch;\n navigator?: { sendBeacon?: Navigator['sendBeacon'] };\n };\n\n const originalFetch = g.fetch;\n const originalBeacon = g.navigator?.sendBeacon?.bind(g.navigator);\n\n g.fetch = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const url = extractFetchUrl(input);\n captured.fetchCalls.push({ url, init });\n return new Response('', { status: 204 });\n };\n\n if (g.navigator !== undefined) {\n g.navigator.sendBeacon = (\n url: string | URL,\n data?: BodyInit | null,\n ): boolean => {\n captured.beaconCalls.push({\n url: typeof url === 'string' ? url : url.toString(),\n data,\n });\n return true;\n };\n }\n\n try {\n await body(captured);\n } finally {\n if (originalFetch === undefined) {\n delete g.fetch;\n } else {\n g.fetch = originalFetch;\n }\n if (g.navigator !== undefined) {\n if (originalBeacon === undefined) {\n delete g.navigator.sendBeacon;\n } else {\n g.navigator.sendBeacon = originalBeacon;\n }\n }\n }\n}\n\nfunction extractFetchUrl(input: RequestInfo | URL): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n // Request\n return input.url;\n}\n\nfunction allUrls(captured: CapturedCalls): string[] {\n return [\n ...captured.fetchCalls.map((c) => c.url),\n ...captured.beaconCalls.map((c) => c.url),\n ];\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Probe event + helpers\n// ──────────────────────────────────────────────────────────────────────\n\nconst PROBE_MESSAGE = 'FLSDK-transport-contract-probe-message';\n\nfunction makeProbeEvent(overrides: Partial<LogEvent> = {}): LogEvent {\n return {\n timestamp: new Date().toISOString(),\n level: 'info',\n message: PROBE_MESSAGE,\n attributes: {},\n context: {\n application: { name: 'transport-contract-probe' },\n environment: 'test',\n },\n ...overrides,\n };\n}\n\nasync function invokeSendSafely(\n transport: Transport,\n event: LogEvent,\n): Promise<void> {\n const result = transport.send(event);\n if (result instanceof Promise) {\n // Some intentionally-bad transports return rejected Promises; allow\n // the rejection to surface only as a contract-failure diagnostic,\n // not as an unhandled rejection in the test runner.\n await result.catch(() => undefined);\n }\n}\n\nfunction isAbsoluteUrl(url: string): boolean {\n // Match an URL scheme followed by `://` (http://, https://, ftp://, etc.)\n return /^[a-z][a-z0-9+.-]*:\\/\\//i.test(url);\n}\n\nfunction fail(...args: unknown[]): Error;\nfunction fail(message: string): Error;\nfunction fail(transport: Transport, message: string, cause?: unknown): Error;\nfunction fail(...args: unknown[]): Error {\n let message: string;\n let cause: unknown;\n if (typeof args[0] === 'string') {\n message = `[assertTransportContract] ${args[0]}`;\n } else {\n const transport = args[0] as Transport;\n const msg = args[1] as string;\n cause = args[2];\n message = `[assertTransportContract] transport '${transport.name}': ${msg}`;\n }\n const err = new Error(message);\n if (cause !== undefined) {\n (err as Error & { cause?: unknown }).cause = cause;\n }\n return err;\n}\n"]}
|
package/dist/testing.d.cts
CHANGED
package/dist/testing.d.ts
CHANGED
package/dist/testing.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing/secret-fixtures.ts","../src/testing/assert-transport-contract.ts"],"names":[],"mappings":"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","file":"testing.mjs","sourcesContent":["/**\n * Stable bag of secret-looking values for testing. Every value here is\n * fake but shaped like the real thing — long enough that finding it in\n * a URL, query string, log payload, etc. is meaningful evidence of a\n * leak (no false positives from short substrings).\n *\n * Consumers (and the package's own tests T028, T041, T058) place these\n * values in attributes/context/etc. then assert downstream sinks never\n * see any of them.\n *\n * The keys mirror the documented default redaction denylist in\n * `contracts/redaction.md` so any new key here should track a denylist\n * entry — making the fixture the canonical \"things the package promises\n * to mask\" reference for consumers.\n */\n\n/**\n * Concrete shape of {@link makeSecretFixture}'s return value. Named\n * `string` fields (rather than `Record<string, string>`) so callers get\n * `string` — not `string | undefined` — per-field under the package's\n * `noUncheckedIndexedAccess` setting. Not part of the public `/testing`\n * surface; it only types the helper's return.\n *\n * Declared as a `type` (not an `interface`) on purpose: a type alias\n * whose properties are all `string` gets an *implicit* index signature,\n * so the fixture stays assignable to `Record<string, string>` / OTel\n * `Attributes` — while `keyof SecretFixture` remains the precise named-key\n * union, keeping dynamic `fixture[someKey]` access typed `string`.\n */\ntype SecretFixture = {\n readonly password: string;\n readonly passwd: string;\n readonly token: string;\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly bearerToken: string;\n readonly authorization: string;\n readonly auth: string;\n readonly cookie: string;\n readonly setCookie: string;\n readonly secret: string;\n readonly apiKey: string;\n readonly sessionId: string;\n readonly sid: string;\n readonly ssn: string;\n readonly creditCard: string;\n readonly cardNumber: string;\n readonly cvv: string;\n readonly jwt: string;\n};\n\n/**\n * Return a stable record of secret-looking values keyed by category.\n * Values are deterministic across calls so tests can assert against\n * exact strings. Never mutate the returned object across tests — call\n * `makeSecretFixture()` again to get a fresh copy.\n */\nexport function makeSecretFixture(): SecretFixture {\n return {\n password: 'p4ssw0rd-correct-horse-battery-staple',\n passwd: 'p4ssw0rd-shadow-file-style',\n token: 'tok_AAAABBBBCCCCDDDD1234EEEEFFFFGGGG',\n accessToken: 'access_AAAA1111BBBB2222CCCC3333DDDD4444EEEE5555',\n refreshToken: 'refresh_FFFF6666GGGG7777HHHH8888IIII9999JJJJ0000',\n bearerToken:\n 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.bearerFixtureSignature',\n authorization: 'Basic dXNlcjpwYXNzOmZpeHR1cmUtbm90LXJlYWw',\n auth: 'auth_KKKK1111LLLL2222MMMM3333NNNN4444',\n cookie:\n 'sessionId=fixture-session-id-not-real-abc123; Secure; HttpOnly; SameSite=Strict',\n setCookie:\n 'auth=fixture-auth-cookie-not-real-xyz789; Path=/; Secure; HttpOnly',\n secret: 'sk_test_FIXTURE_4eC39HqLyjWDarjtT1zdp7dc_NOT_REAL',\n apiKey: 'pk_live_FIXTURE_ABCD1234EFGH5678IJKL9012MNOP_NOT_REAL',\n sessionId: 'sess_01HXYZ123ABCDEFGHIJKLMNOPQR',\n sid: 'sid_FIXTURE_QQQQ1111RRRR2222SSSS3333',\n ssn: '123-45-6789',\n creditCard: '4242 4242 4242 4242',\n cardNumber: '5555555555554444',\n cvv: '123',\n jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.fixtureJwtSignatureNotReal',\n };\n}\n\n/**\n * The complete list of fixture VALUES. Useful when scanning an\n * arbitrary string (e.g., a captured URL or POST body) for any leaked\n * fixture: `if (FIXTURE_VALUES.some((v) => url.includes(v))) { leak! }`.\n */\nexport const FIXTURE_VALUES: ReadonlyArray<string> = Object.values(\n makeSecretFixture(),\n);\n","/**\n * `assertTransportContract(transport)` — runs the documented Transport\n * contract battery against a consumer-provided `Transport`. Throws on the\n * first violation with a clear diagnostic message.\n *\n * Covers `contracts/transport.md`:\n * - Structural: `name: string`, `send(event)` exists\n * - T-S1: no `LogEvent` data appears in any `fetch` URL or\n * `navigator.sendBeacon` URL the transport produces\n * - T-S2: cross-origin delivery uses request body (POST/PUT/PATCH JSON\n * or a `sendBeacon` `Blob`) — never URL params\n * - T-S3: any URL with a scheme uses `https:`\n * - T-S4: the transport does NOT mutate the event it receives\n * - T-S5: `flush()` and `shutdown()` are idempotent (safe to call > 1x)\n *\n * The helper temporarily monkey-patches `globalThis.fetch` and\n * `globalThis.navigator.sendBeacon` to capture invocations, then restores\n * the originals when each assertion completes — even on failure. Tests\n * can run multiple consumer transports in series safely.\n *\n * This module is reached only via the package's `./testing` subpath; the\n * runtime entry does NOT re-export it.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\nimport { FIXTURE_VALUES, makeSecretFixture } from './secret-fixtures.js';\n\n// ──────────────────────────────────────────────────────────────────────\n// Public entry point\n// ──────────────────────────────────────────────────────────────────────\n\n/**\n * Run the full Transport contract battery against `transport`. Resolves\n * when all assertions pass; rejects with an `Error` carrying a\n * diagnostic message on the first failure.\n */\nexport async function assertTransportContract(\n transport: Transport,\n): Promise<void> {\n assertStructural(transport);\n await assertSendDoesNotThrow(transport);\n await assertNoEventDataInURLs(transport);\n await assertBodyOnlyDelivery(transport);\n await assertHttpsForAbsoluteUrls(transport);\n await assertEventImmutability(transport);\n await assertFlushIdempotent(transport);\n await assertShutdownIdempotent(transport);\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Individual assertions\n// ──────────────────────────────────────────────────────────────────────\n\nfunction assertStructural(transport: Transport): void {\n if (typeof transport !== 'object' || transport === null) {\n throw fail('transport must be an object');\n }\n if (typeof transport.name !== 'string' || transport.name.length === 0) {\n throw fail('transport.name must be a non-empty string');\n }\n if (typeof transport.send !== 'function') {\n throw fail('transport.send must be a function');\n }\n}\n\nasync function assertSendDoesNotThrow(transport: Transport): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async () => {\n try {\n const result = transport.send(event);\n if (result instanceof Promise) {\n await result;\n }\n } catch (err) {\n throw fail(\n transport,\n 'send() threw to the caller — should fail silently or be wrapped',\n err,\n );\n }\n });\n}\n\nasync function assertNoEventDataInURLs(transport: Transport): Promise<void> {\n const event = makeProbeEvent({ attributes: makeSecretFixture() });\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const url of allUrls(captured)) {\n // 1. Any literal fixture value in the URL is a clear leak.\n const leakedValue = FIXTURE_VALUES.find((v) => url.includes(v));\n if (leakedValue !== undefined) {\n throw fail(\n transport,\n `URL contains a secret fixture value (T-S1 violation): ` +\n `url='${url}', leaked='${leakedValue}'`,\n );\n }\n // 2. The probe event's marker message in the URL also indicates\n // a leak — the consumer encoded event content there.\n if (url.includes(PROBE_MESSAGE)) {\n throw fail(\n transport,\n `URL contains the probe event message (T-S1 violation): ` +\n `url='${url}'`,\n );\n }\n }\n });\n}\n\nasync function assertBodyOnlyDelivery(transport: Transport): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const call of captured.fetchCalls) {\n const method = (call.init?.method ?? 'GET').toUpperCase();\n const allowedMethods = ['POST', 'PUT', 'PATCH'];\n if (!allowedMethods.includes(method)) {\n throw fail(\n transport,\n `fetch used HTTP method '${method}' for delivery — ` +\n `must use POST/PUT/PATCH with body (T-S2): url='${call.url}'`,\n );\n }\n if (call.init?.body === undefined || call.init.body === null) {\n throw fail(\n transport,\n `fetch was called with method='${method}' but no body — ` +\n `events must travel in the request body (T-S2): url='${call.url}'`,\n );\n }\n }\n\n for (const call of captured.beaconCalls) {\n if (call.data === null || call.data === undefined) {\n throw fail(\n transport,\n `navigator.sendBeacon was called without data — ` +\n `events must travel in the body (T-S2): url='${call.url}'`,\n );\n }\n }\n });\n}\n\nasync function assertHttpsForAbsoluteUrls(\n transport: Transport,\n): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const url of allUrls(captured)) {\n // Relative URLs are same-origin by definition and inherit the\n // page's scheme — skip them. Absolute URLs MUST be HTTPS.\n if (!isAbsoluteUrl(url)) continue;\n if (!url.toLowerCase().startsWith('https://')) {\n throw fail(\n transport,\n `cross-origin URL is not HTTPS (T-S3): url='${url}'`,\n );\n }\n }\n });\n}\n\nasync function assertEventImmutability(transport: Transport): Promise<void> {\n const event = makeProbeEvent({ attributes: { mutateMe: 'before' } });\n const before = JSON.stringify(event);\n await withInterceptors(async () => {\n await invokeSendSafely(transport, event);\n });\n const after = JSON.stringify(event);\n if (before !== after) {\n throw fail(\n transport,\n `transport mutated the received event (T-S4 violation):\\n` +\n ` before: ${before}\\n after: ${after}`,\n );\n }\n}\n\nasync function assertFlushIdempotent(transport: Transport): Promise<void> {\n if (typeof transport.flush !== 'function') return; // optional hook\n try {\n await transport.flush();\n await transport.flush();\n await transport.flush();\n } catch (err) {\n throw fail(\n transport,\n 'flush() is not idempotent — repeated calls must each resolve (T-S5)',\n err,\n );\n }\n}\n\nasync function assertShutdownIdempotent(transport: Transport): Promise<void> {\n if (typeof transport.shutdown !== 'function') return; // optional hook\n try {\n await transport.shutdown();\n await transport.shutdown();\n await transport.shutdown();\n } catch (err) {\n throw fail(\n transport,\n 'shutdown() is not idempotent — repeated calls must each resolve (T-S5)',\n err,\n );\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// fetch / sendBeacon interceptor\n// ──────────────────────────────────────────────────────────────────────\n\ninterface FetchCall {\n url: string;\n init: RequestInit | undefined;\n}\n\ninterface BeaconCall {\n url: string;\n data: BodyInit | null | undefined;\n}\n\ninterface CapturedCalls {\n fetchCalls: FetchCall[];\n beaconCalls: BeaconCall[];\n}\n\n/**\n * Run `body` with `globalThis.fetch` and `navigator.sendBeacon` replaced\n * by capturing stubs. Restores originals on success and failure.\n */\nasync function withInterceptors(\n body: (captured: CapturedCalls) => Promise<void>,\n): Promise<void> {\n const captured: CapturedCalls = { fetchCalls: [], beaconCalls: [] };\n\n const g = globalThis as unknown as {\n fetch?: typeof fetch;\n navigator?: { sendBeacon?: Navigator['sendBeacon'] };\n };\n\n const originalFetch = g.fetch;\n const originalBeacon = g.navigator?.sendBeacon?.bind(g.navigator);\n\n g.fetch = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const url = extractFetchUrl(input);\n captured.fetchCalls.push({ url, init });\n return new Response('', { status: 204 });\n };\n\n if (g.navigator !== undefined) {\n g.navigator.sendBeacon = (\n url: string | URL,\n data?: BodyInit | null,\n ): boolean => {\n captured.beaconCalls.push({\n url: typeof url === 'string' ? url : url.toString(),\n data,\n });\n return true;\n };\n }\n\n try {\n await body(captured);\n } finally {\n if (originalFetch === undefined) {\n delete g.fetch;\n } else {\n g.fetch = originalFetch;\n }\n if (g.navigator !== undefined) {\n if (originalBeacon === undefined) {\n delete g.navigator.sendBeacon;\n } else {\n g.navigator.sendBeacon = originalBeacon;\n }\n }\n }\n}\n\nfunction extractFetchUrl(input: RequestInfo | URL): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n // Request\n return input.url;\n}\n\nfunction allUrls(captured: CapturedCalls): string[] {\n return [\n ...captured.fetchCalls.map((c) => c.url),\n ...captured.beaconCalls.map((c) => c.url),\n ];\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Probe event + helpers\n// ──────────────────────────────────────────────────────────────────────\n\nconst PROBE_MESSAGE = 'FLSDK-transport-contract-probe-message';\n\nfunction makeProbeEvent(overrides: Partial<LogEvent> = {}): LogEvent {\n return {\n timestamp: new Date().toISOString(),\n level: 'info',\n message: PROBE_MESSAGE,\n attributes: {},\n context: {\n application: { name: 'transport-contract-probe' },\n environment: 'test',\n },\n ...overrides,\n };\n}\n\nasync function invokeSendSafely(\n transport: Transport,\n event: LogEvent,\n): Promise<void> {\n const result = transport.send(event);\n if (result instanceof Promise) {\n // Some intentionally-bad transports return rejected Promises; allow\n // the rejection to surface only as a contract-failure diagnostic,\n // not as an unhandled rejection in the test runner.\n await result.catch(() => undefined);\n }\n}\n\nfunction isAbsoluteUrl(url: string): boolean {\n // Match an URL scheme followed by `://` (http://, https://, ftp://, etc.)\n return /^[a-z][a-z0-9+.-]*:\\/\\//i.test(url);\n}\n\nfunction fail(...args: unknown[]): Error;\nfunction fail(message: string): Error;\nfunction fail(transport: Transport, message: string, cause?: unknown): Error;\nfunction fail(...args: unknown[]): Error {\n let message: string;\n let cause: unknown;\n if (typeof args[0] === 'string') {\n message = `[assertTransportContract] ${args[0]}`;\n } else {\n const transport = args[0] as Transport;\n const msg = args[1] as string;\n cause = args[2];\n message = `[assertTransportContract] transport '${transport.name}': ${msg}`;\n }\n const err = new Error(message);\n if (cause !== undefined) {\n (err as Error & { cause?: unknown }).cause = cause;\n }\n return err;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/testing/secret-fixtures.ts","../src/testing/assert-transport-contract.ts"],"names":[],"mappings":"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","file":"testing.mjs","sourcesContent":["/**\n * Stable bag of secret-looking values for testing. Every value here is\n * fake but shaped like the real thing — long enough that finding it in\n * a URL, query string, log payload, etc. is meaningful evidence of a\n * leak (no false positives from short substrings).\n *\n * Consumers (and the package's own tests T028, T041, T058) place these\n * values in attributes/context/etc. then assert downstream sinks never\n * see any of them.\n *\n * The keys mirror the documented default redaction denylist in\n * `contracts/redaction.md` so any new key here should track a denylist\n * entry — making the fixture the canonical \"things the package promises\n * to mask\" reference for consumers.\n */\n\n/**\n * Concrete shape of {@link makeSecretFixture}'s return value. Named\n * `string` fields (rather than `Record<string, string>`) so callers get\n * `string` — not `string | undefined` — per-field under the package's\n * `noUncheckedIndexedAccess` setting. Not part of the public `/testing`\n * surface; it only types the helper's return.\n *\n * Declared as a `type` (not an `interface`) on purpose: a type alias\n * whose properties are all `string` gets an *implicit* index signature,\n * so the fixture stays assignable to `Record<string, string>` / OTel\n * `Attributes` — while `keyof SecretFixture` remains the precise named-key\n * union, keeping dynamic `fixture[someKey]` access typed `string`.\n */\ntype SecretFixture = {\n readonly password: string;\n readonly passwd: string;\n readonly token: string;\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly bearerToken: string;\n readonly authorization: string;\n readonly auth: string;\n readonly cookie: string;\n readonly setCookie: string;\n readonly secret: string;\n readonly apiKey: string;\n readonly sessionId: string;\n readonly sid: string;\n readonly ssn: string;\n readonly creditCard: string;\n readonly cardNumber: string;\n readonly cvv: string;\n readonly jwt: string;\n};\n\n/**\n * Return a stable record of secret-looking values keyed by category.\n * Values are deterministic across calls so tests can assert against\n * exact strings. Never mutate the returned object across tests — call\n * `makeSecretFixture()` again to get a fresh copy.\n */\nexport function makeSecretFixture(): SecretFixture {\n return {\n password: 'p4ssw0rd-correct-horse-battery-staple',\n passwd: 'p4ssw0rd-shadow-file-style',\n token: 'tok_AAAABBBBCCCCDDDD1234EEEEFFFFGGGG',\n accessToken: 'access_AAAA1111BBBB2222CCCC3333DDDD4444EEEE5555',\n refreshToken: 'refresh_FFFF6666GGGG7777HHHH8888IIII9999JJJJ0000',\n bearerToken:\n 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.bearerFixtureSignature',\n authorization: 'Basic dXNlcjpwYXNzOmZpeHR1cmUtbm90LXJlYWw',\n auth: 'auth_KKKK1111LLLL2222MMMM3333NNNN4444',\n cookie:\n 'sessionId=fixture-session-id-not-real-abc123; Secure; HttpOnly; SameSite=Strict',\n setCookie:\n 'auth=fixture-auth-cookie-not-real-xyz789; Path=/; Secure; HttpOnly',\n secret: 'sk_test_FIXTURE_4eC39HqLyjWDarjtT1zdp7dc_NOT_REAL',\n apiKey: 'pk_live_FIXTURE_ABCD1234EFGH5678IJKL9012MNOP_NOT_REAL',\n sessionId: 'sess_01HXYZ123ABCDEFGHIJKLMNOPQR',\n sid: 'sid_FIXTURE_QQQQ1111RRRR2222SSSS3333',\n ssn: '123-45-6789',\n creditCard: '4242 4242 4242 4242',\n cardNumber: '5555555555554444',\n cvv: '123',\n jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.fixtureJwtSignatureNotReal',\n };\n}\n\n/**\n * The complete list of fixture VALUES. Useful when scanning an\n * arbitrary string (e.g., a captured URL or POST body) for any leaked\n * fixture: `if (FIXTURE_VALUES.some((v) => url.includes(v))) { leak! }`.\n */\nexport const FIXTURE_VALUES: ReadonlyArray<string> = Object.values(\n makeSecretFixture(),\n);\n","/**\n * `assertTransportContract(transport)` — runs the documented Transport\n * contract battery against a consumer-provided `Transport`. Throws on the\n * first violation with a clear diagnostic message.\n *\n * Covers `contracts/transport.md`:\n * - Structural: `name: string`, `send(event)` exists\n * - T-S1: no `LogEvent` data appears in any `fetch` URL or\n * `navigator.sendBeacon` URL the transport produces\n * - T-S2: cross-origin delivery uses request body (POST/PUT/PATCH JSON\n * or a `sendBeacon` `Blob`) — never URL params\n * - T-S3: any URL with a scheme uses `https:`\n * - T-S4: the transport does NOT mutate the event it receives\n * - T-S5: `flush()` and `shutdown()` are idempotent (safe to call > 1x)\n *\n * The helper temporarily monkey-patches `globalThis.fetch` and\n * `globalThis.navigator.sendBeacon` to capture invocations, then restores\n * the originals when each assertion completes — even on failure. Tests\n * can run multiple consumer transports in series safely.\n *\n * This module is reached only via the package's `./testing` subpath; the\n * runtime entry does NOT re-export it.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\nimport { FIXTURE_VALUES, makeSecretFixture } from './secret-fixtures.js';\n\n// ──────────────────────────────────────────────────────────────────────\n// Public entry point\n// ──────────────────────────────────────────────────────────────────────\n\n/**\n * Run the full Transport contract battery against `transport`. Resolves\n * when all assertions pass; rejects with an `Error` carrying a\n * diagnostic message on the first failure.\n */\nexport async function assertTransportContract(\n transport: Transport,\n): Promise<void> {\n assertStructural(transport);\n await assertSendDoesNotThrow(transport);\n await assertNoEventDataInURLs(transport);\n await assertBodyOnlyDelivery(transport);\n await assertHttpsForAbsoluteUrls(transport);\n await assertEventImmutability(transport);\n await assertFlushIdempotent(transport);\n await assertShutdownIdempotent(transport);\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Individual assertions\n// ──────────────────────────────────────────────────────────────────────\n\nfunction assertStructural(transport: Transport): void {\n if (typeof transport !== 'object' || transport === null) {\n throw fail('transport must be an object');\n }\n if (typeof transport.name !== 'string' || transport.name.length === 0) {\n throw fail('transport.name must be a non-empty string');\n }\n if (typeof transport.send !== 'function') {\n throw fail('transport.send must be a function');\n }\n}\n\nasync function assertSendDoesNotThrow(transport: Transport): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async () => {\n try {\n const result = transport.send(event);\n if (result instanceof Promise) {\n await result;\n }\n } catch (err) {\n throw fail(\n transport,\n 'send() threw to the caller — should fail silently or be wrapped',\n err,\n );\n }\n });\n}\n\nasync function assertNoEventDataInURLs(transport: Transport): Promise<void> {\n const event = makeProbeEvent({ attributes: makeSecretFixture() });\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const url of allUrls(captured)) {\n // 1. Any literal fixture value in the URL is a clear leak.\n const leakedValue = FIXTURE_VALUES.find((v) => url.includes(v));\n if (leakedValue !== undefined) {\n throw fail(\n transport,\n `URL contains a secret fixture value (T-S1 violation): ` +\n `url='${url}', leaked='${leakedValue}'`,\n );\n }\n // 2. The probe event's marker message in the URL also indicates\n // a leak — the consumer encoded event content there.\n if (url.includes(PROBE_MESSAGE)) {\n throw fail(\n transport,\n `URL contains the probe event message (T-S1 violation): ` +\n `url='${url}'`,\n );\n }\n }\n });\n}\n\nasync function assertBodyOnlyDelivery(transport: Transport): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const call of captured.fetchCalls) {\n const method = (call.init?.method ?? 'GET').toUpperCase();\n const allowedMethods = ['POST', 'PUT', 'PATCH'];\n if (!allowedMethods.includes(method)) {\n throw fail(\n transport,\n `fetch used HTTP method '${method}' for delivery — ` +\n `must use POST/PUT/PATCH with body (T-S2): url='${call.url}'`,\n );\n }\n if (call.init?.body === undefined || call.init.body === null) {\n throw fail(\n transport,\n `fetch was called with method='${method}' but no body — ` +\n `events must travel in the request body (T-S2): url='${call.url}'`,\n );\n }\n }\n\n for (const call of captured.beaconCalls) {\n if (call.data === null || call.data === undefined) {\n throw fail(\n transport,\n `navigator.sendBeacon was called without data — ` +\n `events must travel in the body (T-S2): url='${call.url}'`,\n );\n }\n }\n });\n}\n\nasync function assertHttpsForAbsoluteUrls(transport: Transport): Promise<void> {\n const event = makeProbeEvent();\n await withInterceptors(async (captured) => {\n await invokeSendSafely(transport, event);\n\n for (const url of allUrls(captured)) {\n // Relative URLs are same-origin by definition and inherit the\n // page's scheme — skip them. Absolute URLs MUST be HTTPS.\n if (!isAbsoluteUrl(url)) continue;\n if (!url.toLowerCase().startsWith('https://')) {\n throw fail(\n transport,\n `cross-origin URL is not HTTPS (T-S3): url='${url}'`,\n );\n }\n }\n });\n}\n\nasync function assertEventImmutability(transport: Transport): Promise<void> {\n const event = makeProbeEvent({ attributes: { mutateMe: 'before' } });\n const before = JSON.stringify(event);\n await withInterceptors(async () => {\n await invokeSendSafely(transport, event);\n });\n const after = JSON.stringify(event);\n if (before !== after) {\n throw fail(\n transport,\n `transport mutated the received event (T-S4 violation):\\n` +\n ` before: ${before}\\n after: ${after}`,\n );\n }\n}\n\nasync function assertFlushIdempotent(transport: Transport): Promise<void> {\n if (typeof transport.flush !== 'function') return; // optional hook\n try {\n await transport.flush();\n await transport.flush();\n await transport.flush();\n } catch (err) {\n throw fail(\n transport,\n 'flush() is not idempotent — repeated calls must each resolve (T-S5)',\n err,\n );\n }\n}\n\nasync function assertShutdownIdempotent(transport: Transport): Promise<void> {\n if (typeof transport.shutdown !== 'function') return; // optional hook\n try {\n await transport.shutdown();\n await transport.shutdown();\n await transport.shutdown();\n } catch (err) {\n throw fail(\n transport,\n 'shutdown() is not idempotent — repeated calls must each resolve (T-S5)',\n err,\n );\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// fetch / sendBeacon interceptor\n// ──────────────────────────────────────────────────────────────────────\n\ninterface FetchCall {\n url: string;\n init: RequestInit | undefined;\n}\n\ninterface BeaconCall {\n url: string;\n data: BodyInit | null | undefined;\n}\n\ninterface CapturedCalls {\n fetchCalls: FetchCall[];\n beaconCalls: BeaconCall[];\n}\n\n/**\n * Run `body` with `globalThis.fetch` and `navigator.sendBeacon` replaced\n * by capturing stubs. Restores originals on success and failure.\n */\nasync function withInterceptors(\n body: (captured: CapturedCalls) => Promise<void>,\n): Promise<void> {\n const captured: CapturedCalls = { fetchCalls: [], beaconCalls: [] };\n\n const g = globalThis as unknown as {\n fetch?: typeof fetch;\n navigator?: { sendBeacon?: Navigator['sendBeacon'] };\n };\n\n const originalFetch = g.fetch;\n const originalBeacon = g.navigator?.sendBeacon?.bind(g.navigator);\n\n g.fetch = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const url = extractFetchUrl(input);\n captured.fetchCalls.push({ url, init });\n return new Response('', { status: 204 });\n };\n\n if (g.navigator !== undefined) {\n g.navigator.sendBeacon = (\n url: string | URL,\n data?: BodyInit | null,\n ): boolean => {\n captured.beaconCalls.push({\n url: typeof url === 'string' ? url : url.toString(),\n data,\n });\n return true;\n };\n }\n\n try {\n await body(captured);\n } finally {\n if (originalFetch === undefined) {\n delete g.fetch;\n } else {\n g.fetch = originalFetch;\n }\n if (g.navigator !== undefined) {\n if (originalBeacon === undefined) {\n delete g.navigator.sendBeacon;\n } else {\n g.navigator.sendBeacon = originalBeacon;\n }\n }\n }\n}\n\nfunction extractFetchUrl(input: RequestInfo | URL): string {\n if (typeof input === 'string') return input;\n if (input instanceof URL) return input.toString();\n // Request\n return input.url;\n}\n\nfunction allUrls(captured: CapturedCalls): string[] {\n return [\n ...captured.fetchCalls.map((c) => c.url),\n ...captured.beaconCalls.map((c) => c.url),\n ];\n}\n\n// ──────────────────────────────────────────────────────────────────────\n// Probe event + helpers\n// ──────────────────────────────────────────────────────────────────────\n\nconst PROBE_MESSAGE = 'FLSDK-transport-contract-probe-message';\n\nfunction makeProbeEvent(overrides: Partial<LogEvent> = {}): LogEvent {\n return {\n timestamp: new Date().toISOString(),\n level: 'info',\n message: PROBE_MESSAGE,\n attributes: {},\n context: {\n application: { name: 'transport-contract-probe' },\n environment: 'test',\n },\n ...overrides,\n };\n}\n\nasync function invokeSendSafely(\n transport: Transport,\n event: LogEvent,\n): Promise<void> {\n const result = transport.send(event);\n if (result instanceof Promise) {\n // Some intentionally-bad transports return rejected Promises; allow\n // the rejection to surface only as a contract-failure diagnostic,\n // not as an unhandled rejection in the test runner.\n await result.catch(() => undefined);\n }\n}\n\nfunction isAbsoluteUrl(url: string): boolean {\n // Match an URL scheme followed by `://` (http://, https://, ftp://, etc.)\n return /^[a-z][a-z0-9+.-]*:\\/\\//i.test(url);\n}\n\nfunction fail(...args: unknown[]): Error;\nfunction fail(message: string): Error;\nfunction fail(transport: Transport, message: string, cause?: unknown): Error;\nfunction fail(...args: unknown[]): Error {\n let message: string;\n let cause: unknown;\n if (typeof args[0] === 'string') {\n message = `[assertTransportContract] ${args[0]}`;\n } else {\n const transport = args[0] as Transport;\n const msg = args[1] as string;\n cause = args[2];\n message = `[assertTransportContract] transport '${transport.name}': ${msg}`;\n }\n const err = new Error(message);\n if (cause !== undefined) {\n (err as Error & { cause?: unknown }).cause = cause;\n }\n return err;\n}\n"]}
|
|
@@ -55,23 +55,37 @@ function createBatcher(opts) {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// src/transport-beacon/
|
|
59
|
-
var
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
value: cause,
|
|
68
|
-
enumerable: true,
|
|
69
|
-
writable: false,
|
|
70
|
-
configurable: false
|
|
71
|
-
});
|
|
72
|
-
}
|
|
58
|
+
// src/transport-beacon/delivery.ts
|
|
59
|
+
var BEACON_SIZE_LIMIT_BYTES = 65536;
|
|
60
|
+
function getPayloadByteLength(payload) {
|
|
61
|
+
return new TextEncoder().encode(payload).length;
|
|
62
|
+
}
|
|
63
|
+
function tryBeacon(endpoint, payload) {
|
|
64
|
+
const nav = globalThis.navigator;
|
|
65
|
+
if (nav === void 0 || typeof nav.sendBeacon !== "function") {
|
|
66
|
+
return false;
|
|
73
67
|
}
|
|
74
|
-
|
|
68
|
+
try {
|
|
69
|
+
const blob = new Blob([payload], { type: "application/json" });
|
|
70
|
+
return nav.sendBeacon(endpoint, blob);
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function tryFetchKeepalive(endpoint, payload) {
|
|
76
|
+
const fetchFn = globalThis.fetch;
|
|
77
|
+
if (typeof fetchFn !== "function") {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const response = await fetchFn(endpoint, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
body: payload,
|
|
83
|
+
headers: { "content-type": "application/json" },
|
|
84
|
+
keepalive: true,
|
|
85
|
+
credentials: "same-origin"
|
|
86
|
+
});
|
|
87
|
+
return response.ok;
|
|
88
|
+
}
|
|
75
89
|
|
|
76
90
|
// src/transport-beacon/endpoint-validation.ts
|
|
77
91
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
@@ -118,37 +132,23 @@ function typeName(value) {
|
|
|
118
132
|
return typeof value;
|
|
119
133
|
}
|
|
120
134
|
|
|
121
|
-
// src/transport-beacon/
|
|
122
|
-
var
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
async function tryFetchKeepalive(endpoint, payload) {
|
|
139
|
-
const fetchFn = globalThis.fetch;
|
|
140
|
-
if (typeof fetchFn !== "function") {
|
|
141
|
-
return false;
|
|
135
|
+
// src/transport-beacon/errors.ts
|
|
136
|
+
var BeaconError = class extends Error {
|
|
137
|
+
constructor(code, transportName, message, cause) {
|
|
138
|
+
super(message);
|
|
139
|
+
this.name = "BeaconError";
|
|
140
|
+
this.code = code;
|
|
141
|
+
this.transportName = transportName;
|
|
142
|
+
if (cause !== void 0) {
|
|
143
|
+
Object.defineProperty(this, "cause", {
|
|
144
|
+
value: cause,
|
|
145
|
+
enumerable: true,
|
|
146
|
+
writable: false,
|
|
147
|
+
configurable: false
|
|
148
|
+
});
|
|
149
|
+
}
|
|
142
150
|
}
|
|
143
|
-
|
|
144
|
-
method: "POST",
|
|
145
|
-
body: payload,
|
|
146
|
-
headers: { "content-type": "application/json" },
|
|
147
|
-
keepalive: true,
|
|
148
|
-
credentials: "same-origin"
|
|
149
|
-
});
|
|
150
|
-
return response.ok;
|
|
151
|
-
}
|
|
151
|
+
};
|
|
152
152
|
|
|
153
153
|
// src/transport-beacon/lifecycle.ts
|
|
154
154
|
function installPagehideHandler(handler) {
|
|
@@ -173,7 +173,9 @@ function validateOptions(options) {
|
|
|
173
173
|
}
|
|
174
174
|
if (options.batching !== void 0) {
|
|
175
175
|
if (typeof options.batching !== "object" || options.batching === null) {
|
|
176
|
-
throw new TypeError(
|
|
176
|
+
throw new TypeError(
|
|
177
|
+
"beacon transport: options.batching must be an object"
|
|
178
|
+
);
|
|
177
179
|
}
|
|
178
180
|
const { maxBatchSize, maxBatchAgeMs } = options.batching;
|
|
179
181
|
if (!Number.isInteger(maxBatchSize) || maxBatchSize < 1 || maxBatchSize > 1e3) {
|
|
@@ -241,7 +243,10 @@ function notifyBatchDrop(state, droppedCount, reason, cause) {
|
|
|
241
243
|
function createBeaconTransport(options) {
|
|
242
244
|
validateOptions(options);
|
|
243
245
|
const allowInsecureLoopback = options.allowInsecureLoopback ?? false;
|
|
244
|
-
validateEndpoint(
|
|
246
|
+
validateEndpoint(
|
|
247
|
+
options.endpoint,
|
|
248
|
+
allowInsecureLoopback
|
|
249
|
+
);
|
|
245
250
|
const state = {
|
|
246
251
|
endpoint: options.endpoint,
|
|
247
252
|
name: options.name ?? "beacon",
|
|
@@ -384,7 +389,12 @@ function createBeaconTransport(options) {
|
|
|
384
389
|
try {
|
|
385
390
|
envelope = JSON.stringify({ events });
|
|
386
391
|
} catch (cause) {
|
|
387
|
-
notifyBatchDrop(
|
|
392
|
+
notifyBatchDrop(
|
|
393
|
+
state2,
|
|
394
|
+
events.length,
|
|
395
|
+
"JSON.stringify threw on the envelope",
|
|
396
|
+
cause
|
|
397
|
+
);
|
|
388
398
|
return;
|
|
389
399
|
}
|
|
390
400
|
const bytes = getPayloadByteLength(envelope);
|
|
@@ -420,7 +430,12 @@ function createBeaconTransport(options) {
|
|
|
420
430
|
}
|
|
421
431
|
},
|
|
422
432
|
(cause) => {
|
|
423
|
-
notifyBatchDrop(
|
|
433
|
+
notifyBatchDrop(
|
|
434
|
+
state2,
|
|
435
|
+
events.length,
|
|
436
|
+
`fetch fallback rejected`,
|
|
437
|
+
cause
|
|
438
|
+
);
|
|
424
439
|
}
|
|
425
440
|
);
|
|
426
441
|
return;
|