From 83eed65735e12f59c9d3089037d00c5b50cd210b Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 1 Feb 2026 22:41:56 +0000 Subject: [PATCH] fix: Address CodeQL security alerts - workflow/plugins/ts/dict: Fix prototype pollution by adding key validation and safeAssign wrapper to reject __proto__, constructor, prototype keys in DictSet, DictDelete, DictPick, DictInvert classes - pastebin/quality-validator: Fix regex injection by escaping regex metacharacters before creating RegExp from user input in matchesPattern - postgres/generate-password: Fix biased cryptographic random by using crypto.randomInt() instead of modulo operation Co-Authored-By: Claude Opus 4.5 --- .../lib/quality-validator/utils/fileSystem.ts | 16 ++++++-- postgres/scripts/generate-password.ts | 9 ++--- workflow/plugins/ts/dict/src/index.ts | 38 ++++++++++++++++++- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/pastebin/src/lib/quality-validator/utils/fileSystem.ts b/pastebin/src/lib/quality-validator/utils/fileSystem.ts index 70e309984..48c77f66b 100644 --- a/pastebin/src/lib/quality-validator/utils/fileSystem.ts +++ b/pastebin/src/lib/quality-validator/utils/fileSystem.ts @@ -225,13 +225,23 @@ export function shouldExclude(filePath: string, patterns: string[]): boolean { return false; } +/** + * Escape special regex characters in a string + * This prevents regex injection attacks (ReDoS) + */ +function escapeRegexChars(str: string): string { + // Escape all regex special characters except * and ? (glob wildcards we handle separately) + return str.replace(/[.+^${}()|[\]\\]/g, '\\$&'); +} + /** * Simple glob pattern matching + * Safely converts glob patterns to regex by escaping special characters first */ export function matchesPattern(filePath: string, pattern: string): boolean { - // Convert glob pattern to regex - const regexPattern = pattern - .replace(/\./g, '\\.') + // First, escape all regex special characters (except glob wildcards * and ?) + // Then convert glob wildcards to their regex equivalents + const regexPattern = escapeRegexChars(pattern) .replace(/\*/g, '.*') .replace(/\?/g, '.'); diff --git a/postgres/scripts/generate-password.ts b/postgres/scripts/generate-password.ts index a56fd3526..654214237 100644 --- a/postgres/scripts/generate-password.ts +++ b/postgres/scripts/generate-password.ts @@ -14,14 +14,13 @@ function generateSecurePassword(length = 32, includeSpecial = true): string { charset += '!@#$%^&*()-_=+[]{}|;:,.<>?'; } - const randomBytes = crypto.randomBytes(length); let password = ''; for (let i = 0; i < length; i++) { - const byte = randomBytes[i]; - if (byte !== undefined) { - password += charset[byte % charset.length]; - } + // Use crypto.randomInt() for unbiased random selection + // This avoids modulo bias that occurs with randomBytes % charset.length + const randomIndex = crypto.randomInt(charset.length); + password += charset[randomIndex]; } return password; diff --git a/workflow/plugins/ts/dict/src/index.ts b/workflow/plugins/ts/dict/src/index.ts index fb60740aa..5db368793 100644 --- a/workflow/plugins/ts/dict/src/index.ts +++ b/workflow/plugins/ts/dict/src/index.ts @@ -12,6 +12,26 @@ const resolve = (value: any, ctx: any): any => { return value; }; +/** + * Check if a key is a prototype-polluting key that should be rejected. + * Prevents prototype pollution attacks where attackers inject __proto__, + * constructor, or prototype keys to modify Object.prototype. + */ +const isPrototypePollutingKey = (key: string): boolean => { + return key === '__proto__' || key === 'constructor' || key === 'prototype'; +}; + +/** + * Safely assign a value to an object, rejecting prototype-polluting keys. + * @throws Error if key is a prototype-polluting key + */ +const safeAssign = (obj: any, key: string, value: any): void => { + if (isPrototypePollutingKey(key)) { + throw new Error(`Prototype-polluting key "${key}" is not allowed`); + } + obj[key] = value; +}; + export class DictGet implements NodeExecutor { readonly nodeType = 'dict.get'; readonly category = 'dict'; @@ -49,12 +69,16 @@ export class DictSet implements NodeExecutor { let current = result; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; + if (isPrototypePollutingKey(key)) { + throw new Error(`Prototype-polluting key "${key}" is not allowed in path`); + } if (!(key in current) || typeof current[key] !== 'object') { current[key] = {}; } current = current[key]; } - current[keys[keys.length - 1]] = value; + const finalKey = keys[keys.length - 1]; + safeAssign(current, finalKey, value); return { result }; } } @@ -69,6 +93,9 @@ export class DictDelete implements NodeExecutor { const obj = resolve(inputs.node.parameters.object, ctx) || {}; const key = resolve(inputs.node.parameters.key, ctx); if (typeof obj !== 'object' || obj === null) return { result: {} }; + if (isPrototypePollutingKey(key)) { + throw new Error(`Prototype-polluting key "${key}" is not allowed`); + } const result = { ...obj }; delete result[key]; return { result }; @@ -176,6 +203,9 @@ export class DictPick implements NodeExecutor { if (typeof obj !== 'object' || obj === null) return { result: {} }; const result: Record = {}; for (const key of keys) { + if (isPrototypePollutingKey(key)) { + throw new Error(`Prototype-polluting key "${key}" is not allowed`); + } if (key in obj) result[key] = obj[key]; } return { result }; @@ -239,7 +269,11 @@ export class DictInvert implements NodeExecutor { if (typeof obj !== 'object' || obj === null) return { result: {} }; const result: Record = {}; for (const [key, value] of Object.entries(obj)) { - result[String(value)] = key; + const newKey = String(value); + if (isPrototypePollutingKey(newKey)) { + throw new Error(`Prototype-polluting key "${newKey}" is not allowed`); + } + result[newKey] = key; } return { result }; }