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 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 22:41:56 +00:00
parent 8fcc71d530
commit 83eed65735
3 changed files with 53 additions and 10 deletions

View File

@@ -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, '.');

View File

@@ -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;

View File

@@ -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<string, any> = {};
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<string, any> = {};
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 };
}