mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
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:
@@ -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, '.');
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user