fix(core): Run Guardrails custom regex checks on the user regex engine

PII and secret-key custom patterns are user-authored, so they must run
on safeUserRegex like the rest of a user's patterns, not on
safeInternalRegex.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Emilia 2026-09-23 15:19:40 +01:00
parent 94253e8c19
commit 3294b22da3
No known key found for this signature in database
GPG Key ID: 837988349B71DD1A
2 changed files with 11 additions and 6 deletions

View File

@ -7,7 +7,8 @@
* schema for entity selection, output/result structures, and the async guardrail
* check_fn for runtime enforcement.
*/
import { safeRegex } from 'n8n-workflow';
import { safeInternalRegex, safeUserRegex } from 'n8n-workflow';
import type { RegexEngine } from 'n8n-workflow';
import { parseRegex } from '../../helpers/common';
import type { CreateCheckFn, CustomRegex } from '../types';
@ -223,10 +224,14 @@ function detectPii(text: string, config: PIIConfig): PiiDetectionResult {
const grouped: Record<string, string[]> = {};
const analyzerResults: PiiAnalyzerResult[] = [];
const matchAgainstPattern = (name: string, literal: { source: string; flags: string }) => {
const matchAgainstPattern = (
name: string,
literal: { source: string; flags: string },
engine: RegexEngine = safeInternalRegex,
) => {
// make sure to add the global flag to the regex, otherwise while() will never end
const flags = literal.flags.includes('g') ? literal.flags : literal.flags + 'g';
const matches = safeRegex.matchAll(literal.source, text, flags);
const matches = engine.matchAll(literal.source, text, flags);
for (const match of matches) {
if (match.index === undefined) continue;
@ -256,7 +261,7 @@ function detectPii(text: string, config: PIIConfig): PiiDetectionResult {
}
if (config.customRegex?.length) {
for (const regex of config.customRegex) {
matchAgainstPattern(regex.name, parseRegex(regex.value));
matchAgainstPattern(regex.name, parseRegex(regex.value), safeUserRegex);
}
}

View File

@ -6,7 +6,7 @@
* recognition, and a guardrail check_fn for runtime enforcement.
*/
import { safeInternalRegex } from 'n8n-workflow';
import { safeUserRegex } from 'n8n-workflow';
import type { CreateCheckFn, GuardrailResult } from '../types';
@ -187,7 +187,7 @@ function isSecretCandidate(
if (customRegex) {
for (const pattern of customRegex) {
try {
if (safeInternalRegex.test(pattern, s)) {
if (safeUserRegex.test(pattern, s)) {
return true;
}
} catch {