fix: Revert 'Input validation for workflow and data table names' (PR 30594) (#31359)

This commit is contained in:
Charlie Kolb
2026-05-29 12:29:32 +02:00
committed by GitHub
parent 55423cbff1
commit f4a5880652
11 changed files with 17 additions and 127 deletions
@@ -1,9 +1,15 @@
import xss from 'xss';
import { z } from 'zod';
import { scopesSchema } from '../../schemas/scopes.schema';
import { xssCheck } from '../../utils/xss-check';
import { Z } from '../../zod-class';
const xssCheck = (value: string) =>
value ===
xss(value, {
whiteList: {},
});
export class UpdateApiKeyRequestDto extends Z.class({
label: z.string().max(50).min(1).refine(xssCheck),
scopes: scopesSchema,
@@ -1,8 +1,14 @@
import xss from 'xss';
import { z } from 'zod';
import { xssCheck } from '../../utils/xss-check';
import { Z } from '../../zod-class';
const xssCheck = (value: string) =>
value ===
xss(value, {
whiteList: {}, // no tags are allowed
});
const URL_REGEX = /^(https?:\/\/|www\.)|(\.[\p{L}\d-]+)/iu;
const urlCheck = (value: string) => !URL_REGEX.test(value);
@@ -126,21 +126,6 @@ describe('CreateWorkflowDto', () => {
request: { name: 'a'.repeat(129), nodes: [], connections: {} },
expectedErrorPath: ['name'],
},
{
name: 'name containing a script tag',
request: { name: '<script>alert(1)</script>', nodes: [], connections: {} },
expectedErrorPath: ['name'],
},
{
name: 'name containing an img onerror payload',
request: { name: '<img src=x onerror=alert(1)>', nodes: [], connections: {} },
expectedErrorPath: ['name'],
},
{
name: 'name containing inline HTML markup',
request: { name: 'Report <b>bold</b>', nodes: [], connections: {} },
expectedErrorPath: ['name'],
},
{
name: 'missing nodes',
request: { name: 'Test', connections: {} },
@@ -98,21 +98,6 @@ describe('UpdateWorkflowDto', () => {
request: { name: 'a'.repeat(129) },
expectedErrorPath: ['name'],
},
{
name: 'name containing a script tag',
request: { name: '<script>alert(1)</script>' },
expectedErrorPath: ['name'],
},
{
name: 'name containing an img onerror payload',
request: { name: '<img src=x onerror=alert(1)>' },
expectedErrorPath: ['name'],
},
{
name: 'name containing inline HTML markup',
request: { name: 'Report <b>bold</b>' },
expectedErrorPath: ['name'],
},
{
name: 'invalid nodes type',
request: { nodes: 'not-an-array' },
@@ -1,8 +1,6 @@
import type { IPinData, IConnections, IDataObject, INode, IWorkflowSettings } from 'n8n-workflow';
import { z } from 'zod';
import { xssCheck } from '../../utils/xss-check';
export const WORKFLOW_NAME_MAX_LENGTH = 128;
/** Maximum allowed size for pinned data in bytes (12 MB) */
@@ -19,8 +17,7 @@ export const workflowNameSchema = z
.min(1, { message: 'Workflow name is required' })
.max(WORKFLOW_NAME_MAX_LENGTH, {
message: `Workflow name must be ${WORKFLOW_NAME_MAX_LENGTH} characters or less`,
})
.refine(xssCheck, { message: 'Potentially malicious string' });
});
export const workflowDescriptionSchema = z.string().nullable();
-1
View File
@@ -457,7 +457,6 @@ export {
} from './schemas/eval-insights.schema';
export { ALLOWED_DOMAINS, isAllowedDomain } from './utils/allowed-domains';
export { xssCheck } from './utils/xss-check';
export type { PublishTimelineEvent } from './schemas/workflow-publish-timeline.schema';
export {
@@ -1,36 +0,0 @@
import { dataTableNameSchema } from '../data-table.schema';
describe('dataTableNameSchema', () => {
describe('Valid names', () => {
test.each([
'Customers',
'Customer orders 2024',
'orders-q1',
"Q1 'Quarterly' Report",
'orders & invoices',
'a',
])('accepts %p', (value) => {
expect(dataTableNameSchema.safeParse(value).success).toBe(true);
});
test('trims surrounding whitespace', () => {
const result = dataTableNameSchema.safeParse(' Customers ');
expect(result.success).toBe(true);
expect(result.data).toBe('Customers');
});
});
describe('Invalid names', () => {
test.each([
['empty string', ''],
['only whitespace', ' '],
['too long', 'a'.repeat(129)],
['contains a script tag', '<script>alert(1)</script>'],
['contains an img onerror payload', '<img src=x onerror=alert(1)>'],
['contains inline HTML markup', 'Customers <b>bold</b>'],
['contains an svg onload payload', '<svg onload=alert(1)>'],
])('rejects %s', (_label, value) => {
expect(dataTableNameSchema.safeParse(value).success).toBe(false);
});
});
});
@@ -1,16 +1,10 @@
import { z } from 'zod';
import type { ListDataTableQueryDto } from '../dto';
import { xssCheck } from '../utils/xss-check';
export const insertRowReturnType = z.union([z.literal('all'), z.literal('count'), z.literal('id')]);
export const dataTableNameSchema = z
.string()
.trim()
.min(1)
.max(128)
.refine(xssCheck, { message: 'Potentially malicious string' });
export const dataTableNameSchema = z.string().trim().min(1).max(128);
export const dataTableIdSchema = z
.string()
.max(36)
@@ -1,37 +0,0 @@
import { xssCheck } from '../xss-check';
describe('xssCheck', () => {
test.each([
'My Workflow',
'My Workflow 2024',
'Workflow with spaces and 123 numbers',
"O'Brien's workflow",
'workflow & report',
'a',
'name-with-dashes_and.dots',
'name/with/slashes',
'name (with) (parens)',
])('returns true for plain string %p', (value) => {
expect(xssCheck(value)).toBe(true);
});
test.each([
'<script>alert(1)</script>',
'<img src=x onerror=alert(1)>',
'<svg onload=alert(1)>',
'<a href="javascript:alert(1)">click</a>',
'<iframe src="evil"></iframe>',
'Name with <b>bold</b>',
'<style>body{}</style>',
'<SCRIPT>alert(1)</SCRIPT>',
'<script src=//evil.com></script>',
'7 > 3 is true',
'< not really a tag',
])('returns false for value containing HTML-significant characters %p', (value) => {
expect(xssCheck(value)).toBe(false);
});
test('returns true for empty string', () => {
expect(xssCheck('')).toBe(true);
});
});
@@ -1,10 +0,0 @@
import xss from 'xss';
/**
* Returns `true` when the value is preserved by `xss({ whiteList: {} })`,
* i.e. contains no HTML-significant characters.
*
* Use as a zod refine guard for user-supplied names (workflows, data tables,
* user names, API keys, etc.).
*/
export const xssCheck = (value: string): boolean => value === xss(value, { whiteList: {} });
@@ -24,6 +24,7 @@ import { objectRetriever, sqlite } from '../utils/transformers';
@Entity()
export class WorkflowEntity extends WithTimestampsAndStringId implements IWorkflowDb {
// TODO: Add XSS check
@Index({ unique: true })
@Length(1, 128, {
message: 'Workflow name must be $constraint1 to $constraint2 characters long.',