fix(otel): address lint and test regressions from C1/C2 fixes
- Prefix unused opts param with _opts in bindOtelInstrumentation (ESLint) - Extend IPv6 regex to cover prefix::suffix form (e.g. 2001:0db8::1) so the IPv6 scrub test passes correctly - Delete orphaned sentry-pii-scrubber.test.ts files from apps/cms and apps/web-tanstack that imported the deleted sentry/scrub module (broke typecheck; the OTel-layer replacement tests are in pii-scrub-processor.test.ts) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,56 +0,0 @@
|
|||||||
import { describe, it, expect } from "vitest";
|
|
||||||
import {
|
|
||||||
beforeSend,
|
|
||||||
beforeSendTransaction,
|
|
||||||
} from "@repo/core-shared/instrumentation/sentry/scrub";
|
|
||||||
|
|
||||||
describe("R38 — apps/cms PII scrubber", () => {
|
|
||||||
it("strips email/password/cookie/auth/IP from event payload", () => {
|
|
||||||
const event = {
|
|
||||||
extra: {
|
|
||||||
userEmail: "alice@example.com",
|
|
||||||
password: "p4$$w0rd",
|
|
||||||
ipAddress: "192.168.1.10",
|
|
||||||
note: "request from 10.0.0.1",
|
|
||||||
},
|
|
||||||
request: {
|
|
||||||
headers: {
|
|
||||||
Authorization: "Bearer secret",
|
|
||||||
"Set-Cookie": "session=abc",
|
|
||||||
"User-Agent": "Mozilla",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as Parameters<typeof beforeSend>[0];
|
|
||||||
const result = beforeSend(event, {}) as {
|
|
||||||
extra: Record<string, string>;
|
|
||||||
request: { headers: Record<string, string> };
|
|
||||||
};
|
|
||||||
expect(result.extra["userEmail"]).toBe("[redacted]");
|
|
||||||
expect(result.extra["password"]).toBe("[redacted]");
|
|
||||||
expect(result.extra["ipAddress"]).toBe("[redacted]");
|
|
||||||
expect(result.extra["note"]).toContain("[redacted-ip]");
|
|
||||||
expect(result.request.headers["Authorization"]).toBe("[redacted]");
|
|
||||||
expect(result.request.headers["Set-Cookie"]).toBe("[redacted]");
|
|
||||||
expect(result.request.headers["User-Agent"]).toBe("Mozilla");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("strips ?token / ?email / ?password / ?secret / ?signature from URLs", () => {
|
|
||||||
const event = {
|
|
||||||
request: {
|
|
||||||
url: "https://app/api/x?token=abc&email=a@b.c&password=p&secret=z&signature=s&safe=1",
|
|
||||||
},
|
|
||||||
transaction: "/foo?accessToken=t",
|
|
||||||
} as Parameters<typeof beforeSendTransaction>[0];
|
|
||||||
const result = beforeSendTransaction(event, {}) as {
|
|
||||||
request: { url: string };
|
|
||||||
transaction: string;
|
|
||||||
};
|
|
||||||
const url = decodeURIComponent(result.request.url);
|
|
||||||
const txn = decodeURIComponent(result.transaction);
|
|
||||||
for (const key of ["token", "email", "password", "secret", "signature"]) {
|
|
||||||
expect(url).toContain(`${key}=[redacted]`);
|
|
||||||
}
|
|
||||||
expect(url).toContain("safe=1");
|
|
||||||
expect(txn).toContain("accessToken=[redacted]");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import { describe, it, expect } from "vitest";
|
|
||||||
import {
|
|
||||||
beforeSend,
|
|
||||||
beforeSendTransaction,
|
|
||||||
} from "@repo/core-shared/instrumentation/sentry/scrub";
|
|
||||||
|
|
||||||
describe("R38 — apps/web-tanstack PII scrubber", () => {
|
|
||||||
it("strips email/password/cookie/auth/IP from event payload", () => {
|
|
||||||
const event = {
|
|
||||||
extra: {
|
|
||||||
userEmail: "alice@example.com",
|
|
||||||
password: "p4$$w0rd",
|
|
||||||
ipAddress: "192.168.1.10",
|
|
||||||
note: "request from 10.0.0.1",
|
|
||||||
},
|
|
||||||
request: {
|
|
||||||
headers: {
|
|
||||||
Authorization: "Bearer secret",
|
|
||||||
"Set-Cookie": "session=abc",
|
|
||||||
"User-Agent": "Mozilla",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as Parameters<typeof beforeSend>[0];
|
|
||||||
const result = beforeSend(event, {}) as {
|
|
||||||
extra: Record<string, string>;
|
|
||||||
request: { headers: Record<string, string> };
|
|
||||||
};
|
|
||||||
expect(result.extra["userEmail"]).toBe("[redacted]");
|
|
||||||
expect(result.extra["password"]).toBe("[redacted]");
|
|
||||||
expect(result.extra["ipAddress"]).toBe("[redacted]");
|
|
||||||
expect(result.extra["note"]).toContain("[redacted-ip]");
|
|
||||||
expect(result.request.headers["Authorization"]).toBe("[redacted]");
|
|
||||||
expect(result.request.headers["Set-Cookie"]).toBe("[redacted]");
|
|
||||||
expect(result.request.headers["User-Agent"]).toBe("Mozilla");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("strips ?token / ?email / ?password / ?secret / ?signature from URLs", () => {
|
|
||||||
const event = {
|
|
||||||
request: {
|
|
||||||
url: "https://app/api/x?token=abc&email=a@b.c&password=p&secret=z&signature=s&safe=1",
|
|
||||||
},
|
|
||||||
transaction: "/foo?accessToken=t",
|
|
||||||
} as Parameters<typeof beforeSendTransaction>[0];
|
|
||||||
const result = beforeSendTransaction(event, {}) as {
|
|
||||||
request: { url: string };
|
|
||||||
transaction: string;
|
|
||||||
};
|
|
||||||
const url = decodeURIComponent(result.request.url);
|
|
||||||
const txn = decodeURIComponent(result.transaction);
|
|
||||||
for (const key of ["token", "email", "password", "secret", "signature"]) {
|
|
||||||
expect(url).toContain(`${key}=[redacted]`);
|
|
||||||
}
|
|
||||||
expect(url).toContain("safe=1");
|
|
||||||
expect(txn).toContain("accessToken=[redacted]");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -24,7 +24,9 @@ export type BindOtelOpts = {
|
|||||||
*/
|
*/
|
||||||
export function bindOtelInstrumentation(
|
export function bindOtelInstrumentation(
|
||||||
container: Container,
|
container: Container,
|
||||||
opts: BindOtelOpts,
|
// opts is accepted for API compatibility with call sites that still pass dsn + app.
|
||||||
|
// The SDK is initialized by instrumentation.ts register() — no fields are used here.
|
||||||
|
_opts: BindOtelOpts,
|
||||||
): { tracer: ITracer; logger: ILogger; metrics: IMetrics } {
|
): { tracer: ITracer; logger: ILogger; metrics: IMetrics } {
|
||||||
const tracer = new OtelTracer();
|
const tracer = new OtelTracer();
|
||||||
const logger = new OtelLogger();
|
const logger = new OtelLogger();
|
||||||
|
|||||||
@@ -44,10 +44,17 @@ export const PII_QUERY_PARAM_SUBSTRINGS = [
|
|||||||
export const REDACTED_VALUE = "[redacted]" as const;
|
export const REDACTED_VALUE = "[redacted]" as const;
|
||||||
export const REDACTED_IP = "[redacted-ip]" as const;
|
export const REDACTED_IP = "[redacted-ip]" as const;
|
||||||
|
|
||||||
// IPv4: simple dotted-quad; IPv6: any colon-separated hex with at least one ::
|
// IPv4: simple dotted-quad.
|
||||||
export const IPV4_REGEX = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g;
|
export const IPV4_REGEX = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g;
|
||||||
|
|
||||||
|
// IPv6: covers the three common forms:
|
||||||
|
// 1. Full / no-:: form: 2001:0db8:0000:0000:0000:0000:0000:0001
|
||||||
|
// 2. Compressed prefix::rest: 2001:0db8::1 (one or more groups before ::)
|
||||||
|
// 3. Leading ::suffix: ::1 or ::ffff:192.0.2.1
|
||||||
|
// The alternation order puts the longer prefix:: pattern first so it captures
|
||||||
|
// the full address rather than leaving the prefix unmatched.
|
||||||
export const IPV6_REGEX =
|
export const IPV6_REGEX =
|
||||||
/\b(?:[0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}\b|::(?:[0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}/g;
|
/\b(?:[0-9a-fA-F]{1,4}:){1,7}:[0-9a-fA-F]{0,4}\b|\b(?:[0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}\b|::(?:[0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{0,4}/g;
|
||||||
|
|
||||||
export function keyContainsPii(key: string): boolean {
|
export function keyContainsPii(key: string): boolean {
|
||||||
const lower = key.toLowerCase();
|
const lower = key.toLowerCase();
|
||||||
|
|||||||
Reference in New Issue
Block a user