mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-19 07:36:52 +00:00
fix(core): Apply PII scrubber on telemetry (no-changelog) (#32438)
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user