fix(core): Apply PII scrubber on telemetry (no-changelog) (#32438)

This commit is contained in:
Jaakko Husso
2026-06-17 10:47:05 +02:00
committed by GitHub
parent b60bc923d4
commit 8ceeab547d
2 changed files with 44 additions and 5 deletions
@@ -615,6 +615,26 @@ describe('createInstanceAiTraceContext', () => {
expect(serialized).toContain('[REDACTED]');
});
it('redacts PII (email, credit-card, SSN) from telemetry strings', () => {
const span = {
attributes: {
'ai.operationId': 'ai.streamText.doStream',
'ai.response.text': 'reach me at jane.doe@example.com about card 4111 1111 1111 1111',
'ai.prompt.messages': JSON.stringify([{ role: 'user', content: 'my ssn is 123-45-6789' }]),
},
};
const redacted = redactLangSmithTelemetrySpan(span) as {
attributes: Record<string, unknown>;
};
const serialized = JSON.stringify(redacted.attributes);
expect(serialized).not.toContain('jane.doe@example.com');
expect(serialized).not.toContain('4111 1111 1111 1111');
expect(serialized).not.toContain('123-45-6789');
expect(serialized).toContain('[REDACTED]');
});
it('uses cache-only Anthropic input tokens for LangSmith prompt totals', () => {
const span = {
attributes: {
@@ -1,5 +1,10 @@
import type { AttributeValue, RuntimeSkillRegistry } from '@n8n/agents';
import { scrubSecretsInText } from '@n8n/utils';
import {
redactText,
SUPPORTED_PII_CATEGORIES,
type AttributeValue,
type RedactionOptions,
type RuntimeSkillRegistry,
} from '@n8n/agents';
import { createHash } from 'node:crypto';
import {
@@ -22,6 +27,20 @@ const MAX_TRACE_OBJECT_KEYS = 30;
const SENSITIVE_TELEMETRY_KEY_PATTERN =
/(api[_-]?key|authorization|bearer|cookie|credentials?|password|secret|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token|(?:^|[._-])token$)/i;
/**
* Telemetry/tracing redaction policy. Deliberately stricter than the
* user-facing output policy `DEFAULT_OUTPUT_REDACTION_OPTIONS`.
*/
export const DEFAULT_TELEMETRY_REDACTION_OPTIONS: RedactionOptions = {
secrets: true,
detect: SUPPORTED_PII_CATEGORIES,
};
/** Redact secrets + all PII from a free-text telemetry value before it egresses. */
function scrubTelemetryText(value: string): string {
return redactText(value, DEFAULT_TELEMETRY_REDACTION_OPTIONS).text;
}
const LANGSMITH_TRACE_NAME = 'langsmith.trace.name';
const LANGSMITH_SPAN_KIND = 'langsmith.span.kind';
const LANGSMITH_USAGE_METADATA = 'langsmith.usage_metadata';
@@ -133,7 +152,7 @@ function redactTelemetryJsonValue(
}
if (typeof value === 'string') {
return scrubSecretsInText(value);
return scrubTelemetryText(value);
}
if (typeof value === 'number' || typeof value === 'boolean' || value === null) {
@@ -187,11 +206,11 @@ function redactTelemetryAttribute(key: string, value: unknown): unknown {
const parsed: unknown = JSON.parse(trimmed);
return JSON.stringify(redactTelemetryJsonValue(parsed, key, 0, maxDepth));
} catch {
return scrubSecretsInText(value);
return scrubTelemetryText(value);
}
}
return scrubSecretsInText(value);
return scrubTelemetryText(value);
}
function parseTelemetryJson(value: unknown): unknown {