greed.js
Version:
Run Python libraries in the browser with WebGPU acceleration - PyTorch, NumPy, and more. Modular architecture with full backward compatibility.
711 lines (619 loc) • 20.5 kB
JavaScript
/**
* SecurityValidator - Comprehensive security validation for Python code and tensor operations
* Prevents code injection, validates inputs, and enforces security policies
*/
import EventEmitter from '../core/event-emitter.js';
class SecurityValidator extends EventEmitter {
constructor(config = {}) {
super();
this.config = {
// Security levels
strictMode: config.strictMode !== false,
allowEval: config.allowEval === true,
allowFileSystem: config.allowFileSystem === true,
allowNetwork: config.allowNetwork === true,
allowSubprocess: config.allowSubprocess === true,
// Input validation limits
maxCodeLength: config.maxCodeLength || 100000, // 100KB
maxTensorSize: config.maxTensorSize || 100_000_000, // 100M elements
maxTensorCount: config.maxTensorCount || 100,
maxStringLength: config.maxStringLength || 10000,
// Resource limits
maxMemoryMB: config.maxMemoryMB || 1024,
maxExecutionTimeMs: config.maxExecutionTimeMs || 30000,
// Package whitelist
allowedPackages: new Set(config.allowedPackages || [
'numpy', 'math', 'random', 'json', 'datetime', 'collections',
'itertools', 'functools', 'operator', 're'
]),
// Custom validation patterns
customDangerousPatterns: config.customDangerousPatterns || [],
customAllowedPatterns: config.customAllowedPatterns || [],
...config
};
// Security pattern definitions
this.dangerousPatterns = this._initializeDangerousPatterns();
this.suspiciousPatterns = this._initializeSuspiciousPatterns();
this.fileSystemPatterns = this._initializeFileSystemPatterns();
this.networkPatterns = this._initializeNetworkPatterns();
// Validation statistics
this.stats = {
totalValidations: 0,
blockedOperations: 0,
warningsIssued: 0,
lastValidation: null,
threatCategories: {}
};
}
/**
* Validate Python code for security threats
*/
validatePythonCode(code, options = {}) {
const startTime = performance.now();
this.stats.totalValidations++;
try {
this.emit('validation:start', {
codeLength: code.length,
strict: this.config.strictMode
});
// Basic input validation
this._validateCodeInput(code);
// Security pattern analysis
const threats = this._analyzeSecurityThreats(code);
// Risk assessment
const riskLevel = this._assessRiskLevel(threats);
// Policy enforcement
const result = this._enforceSecurityPolicy(code, threats, riskLevel, options);
// Update statistics
this._updateValidationStats(threats, riskLevel, result);
const validationTime = performance.now() - startTime;
this.emit('validation:complete', {
threats: threats.length,
riskLevel,
allowed: result.allowed,
validationTime
});
return result;
} catch (error) {
this.emit('validation:error', { error, codePreview: code.substring(0, 100) });
throw error;
}
}
/**
* Validate tensor data for security and resource constraints
*/
validateTensorData(tensors, options = {}) {
const tensorArray = Array.isArray(tensors) ? tensors : [tensors];
this.emit('tensor:validation:start', { tensorCount: tensorArray.length });
try {
// Count and size validation
if (tensorArray.length > this.config.maxTensorCount) {
throw new SecurityError(`Too many tensors: ${tensorArray.length} > ${this.config.maxTensorCount}`);
}
let totalElements = 0;
let totalMemoryMB = 0;
for (let i = 0; i < tensorArray.length; i++) {
const tensor = tensorArray[i];
// Type validation
if (!this._isValidTensorType(tensor)) {
throw new SecurityError(`Invalid tensor type at index ${i}: ${typeof tensor}`);
}
// Size validation
const elementCount = this._getTensorElementCount(tensor);
if (elementCount > this.config.maxTensorSize) {
throw new SecurityError(`Tensor too large at index ${i}: ${elementCount} > ${this.config.maxTensorSize}`);
}
// Memory estimation
const memoryMB = this._estimateTensorMemoryMB(tensor);
totalMemoryMB += memoryMB;
totalElements += elementCount;
this.emit('tensor:validated', {
index: i,
elements: elementCount,
memoryMB: Math.round(memoryMB * 100) / 100
});
}
// Total resource validation
if (totalMemoryMB > this.config.maxMemoryMB) {
throw new SecurityError(`Total tensor memory too large: ${Math.round(totalMemoryMB)}MB > ${this.config.maxMemoryMB}MB`);
}
this.emit('tensor:validation:complete', {
tensorCount: tensorArray.length,
totalElements,
totalMemoryMB: Math.round(totalMemoryMB * 100) / 100
});
return {
valid: true,
tensorCount: tensorArray.length,
totalElements,
totalMemoryMB,
warnings: []
};
} catch (error) {
this.emit('tensor:validation:error', { error });
throw error;
}
}
/**
* Validate operation parameters and options
*/
validateOperation(operation, params, options = {}) {
this.emit('operation:validation:start', { operation });
try {
// Operation name validation
if (!operation || typeof operation !== 'string') {
throw new SecurityError('Operation must be a non-empty string');
}
if (operation.length > 100) {
throw new SecurityError(`Operation name too long: ${operation.length} > 100`);
}
// Check for dangerous operation patterns
const dangerousOps = ['eval', 'exec', 'compile', '__import__', 'subprocess'];
if (dangerousOps.some(danger => operation.toLowerCase().includes(danger))) {
throw new SecurityError(`Dangerous operation detected: ${operation}`);
}
// Validate parameters
const validatedParams = this._validateOperationParams(params);
// Validate options
const validatedOptions = this._validateOperationOptions(options);
this.emit('operation:validation:complete', {
operation,
paramCount: Object.keys(validatedParams).length
});
return {
valid: true,
operation,
params: validatedParams,
options: validatedOptions
};
} catch (error) {
this.emit('operation:validation:error', { operation, error });
throw error;
}
}
/**
* Validate URL for safe external requests
*/
validateURL(url, options = {}) {
const { allowedDomains = [], blockedDomains = [], requireHTTPS = true } = options;
try {
const urlObj = new URL(url);
// Protocol validation
if (requireHTTPS && urlObj.protocol !== 'https:') {
throw new SecurityError(`HTTPS required for URL: ${url}`);
}
// Domain validation
if (blockedDomains.includes(urlObj.hostname)) {
throw new SecurityError(`Blocked domain: ${urlObj.hostname}`);
}
if (allowedDomains.length > 0 && !allowedDomains.includes(urlObj.hostname)) {
throw new SecurityError(`Domain not in allowlist: ${urlObj.hostname}`);
}
// Suspicious pattern detection
const suspiciousPatterns = [
/localhost/i, /127\.0\.0\.1/, /0\.0\.0\.0/, /\[::\]/,
/internal/i, /private/i, /admin/i, /\.local$/i
];
if (suspiciousPatterns.some(pattern => pattern.test(url))) {
throw new SecurityError(`Suspicious URL pattern detected: ${url}`);
}
return { valid: true, url: urlObj.href, domain: urlObj.hostname };
} catch (error) {
if (error instanceof SecurityError) {
throw error;
}
throw new SecurityError(`Invalid URL: ${url} - ${error.message}`);
}
}
/**
* Get security statistics
*/
getStats() {
return {
...this.stats,
config: {
strictMode: this.config.strictMode,
maxTensorSize: this.config.maxTensorSize,
maxMemoryMB: this.config.maxMemoryMB,
allowedPackages: Array.from(this.config.allowedPackages)
}
};
}
/**
* Update security configuration
*/
updateConfig(newConfig) {
const oldConfig = { ...this.config };
this.config = { ...this.config, ...newConfig };
// Update allowed packages set
if (newConfig.allowedPackages) {
this.config.allowedPackages = new Set(newConfig.allowedPackages);
}
this.emit('config:updated', { oldConfig, newConfig: this.config });
}
/**
* Reset security statistics
*/
resetStats() {
this.stats = {
totalValidations: 0,
blockedOperations: 0,
warningsIssued: 0,
lastValidation: null,
threatCategories: {}
};
this.emit('stats:reset');
}
// Private methods
_validateCodeInput(code) {
if (typeof code !== 'string') {
throw new SecurityError('Code must be a string');
}
if (code.length === 0) {
throw new SecurityError('Code cannot be empty');
}
if (code.length > this.config.maxCodeLength) {
throw new SecurityError(`Code too long: ${code.length} > ${this.config.maxCodeLength}`);
}
// Check for null bytes and control characters
if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/.test(code)) {
throw new SecurityError('Code contains invalid control characters');
}
}
_analyzeSecurityThreats(code) {
const threats = [];
// Check dangerous patterns
for (const [category, patterns] of Object.entries(this.dangerousPatterns)) {
for (const pattern of patterns) {
const matches = code.match(pattern.regex);
if (matches) {
threats.push({
category,
type: 'dangerous',
pattern: pattern.name,
severity: pattern.severity,
matches: matches.length,
description: pattern.description,
firstMatch: matches[0]
});
}
}
}
// Check suspicious patterns
for (const [category, patterns] of Object.entries(this.suspiciousPatterns)) {
for (const pattern of patterns) {
const matches = code.match(pattern.regex);
if (matches) {
threats.push({
category,
type: 'suspicious',
pattern: pattern.name,
severity: pattern.severity,
matches: matches.length,
description: pattern.description,
firstMatch: matches[0]
});
}
}
}
// Check file system access
if (!this.config.allowFileSystem) {
for (const pattern of this.fileSystemPatterns) {
const matches = code.match(pattern.regex);
if (matches) {
threats.push({
category: 'filesystem',
type: 'blocked',
pattern: pattern.name,
severity: 'high',
matches: matches.length,
description: 'File system access not allowed',
firstMatch: matches[0]
});
}
}
}
// Check network access
if (!this.config.allowNetwork) {
for (const pattern of this.networkPatterns) {
const matches = code.match(pattern.regex);
if (matches) {
threats.push({
category: 'network',
type: 'blocked',
pattern: pattern.name,
severity: 'high',
matches: matches.length,
description: 'Network access not allowed',
firstMatch: matches[0]
});
}
}
}
return threats;
}
_assessRiskLevel(threats) {
if (threats.length === 0) {
return 'low';
}
const criticalThreats = threats.filter(t => t.severity === 'critical').length;
const highThreats = threats.filter(t => t.severity === 'high').length;
const mediumThreats = threats.filter(t => t.severity === 'medium').length;
if (criticalThreats > 0) {
return 'critical';
} else if (highThreats > 2) {
return 'critical';
} else if (highThreats > 0) {
return 'high';
} else if (mediumThreats > 3) {
return 'high';
} else if (mediumThreats > 0) {
return 'medium';
}
return 'low';
}
_enforceSecurityPolicy(code, threats, riskLevel, options = {}) {
const { allowWarnings = false, bypassValidation = false } = options;
if (bypassValidation && !this.config.strictMode) {
this.emit('policy:bypassed', { riskLevel, threats: threats.length });
return {
allowed: true,
bypassed: true,
riskLevel,
threats,
warnings: ['Security validation was bypassed']
};
}
// Critical and high-risk operations are always blocked
if (riskLevel === 'critical' || (riskLevel === 'high' && this.config.strictMode)) {
this.stats.blockedOperations++;
this.emit('policy:blocked', { riskLevel, threats: threats.length });
throw new SecurityError(`Security policy violation: ${riskLevel} risk detected with ${threats.length} threats`);
}
// Medium-risk operations may be allowed with warnings
if (riskLevel === 'medium') {
if (allowWarnings) {
this.stats.warningsIssued++;
this.emit('policy:warning', { riskLevel, threats: threats.length });
return {
allowed: true,
riskLevel,
threats,
warnings: threats.map(t => `${t.category}: ${t.description}`)
};
} else if (this.config.strictMode) {
this.stats.blockedOperations++;
throw new SecurityError(`Security policy violation: ${riskLevel} risk detected (strict mode)`);
}
}
// Low-risk operations are allowed
return {
allowed: true,
riskLevel,
threats,
warnings: riskLevel === 'low' ? [] : threats.map(t => `${t.category}: ${t.description}`)
};
}
_isValidTensorType(tensor) {
return ArrayBuffer.isView(tensor) ||
tensor instanceof ArrayBuffer ||
Array.isArray(tensor) ||
(tensor && typeof tensor === 'object' && tensor.constructor && tensor.constructor.name.includes('Array'));
}
_getTensorElementCount(tensor) {
if (ArrayBuffer.isView(tensor)) {
return tensor.length;
} else if (tensor instanceof ArrayBuffer) {
return tensor.byteLength / 4; // Assume 32-bit elements
} else if (Array.isArray(tensor)) {
return tensor.length;
}
return 0;
}
_estimateTensorMemoryMB(tensor) {
const elementCount = this._getTensorElementCount(tensor);
const bytesPerElement = ArrayBuffer.isView(tensor) ? tensor.BYTES_PER_ELEMENT : 4;
return (elementCount * bytesPerElement) / (1024 * 1024);
}
_validateOperationParams(params) {
const validated = {};
for (const [key, value] of Object.entries(params || {})) {
if (typeof key !== 'string' || key.length > 100) {
throw new SecurityError(`Invalid parameter key: ${key}`);
}
if (typeof value === 'string' && value.length > this.config.maxStringLength) {
throw new SecurityError(`Parameter value too long: ${key}`);
}
validated[key] = value;
}
return validated;
}
_validateOperationOptions(options) {
const validated = {};
const allowedOptions = [
'timeout', 'strategy', 'workgroupSize', 'dataType',
'precision', 'optimization', 'caching', 'parallel'
];
for (const [key, value] of Object.entries(options || {})) {
if (!allowedOptions.includes(key)) {
this.emit('option:unknown', { key, value });
continue; // Skip unknown options rather than throwing
}
validated[key] = value;
}
return validated;
}
_updateValidationStats(threats, riskLevel, result) {
this.stats.lastValidation = {
timestamp: Date.now(),
threats: threats.length,
riskLevel,
allowed: result.allowed
};
// Update threat category statistics
for (const threat of threats) {
if (!this.stats.threatCategories[threat.category]) {
this.stats.threatCategories[threat.category] = 0;
}
this.stats.threatCategories[threat.category]++;
}
}
_initializeDangerousPatterns() {
return {
codeExecution: [
{
name: 'eval',
regex: /\beval\s*\(/g,
severity: 'critical',
description: 'Dynamic code execution with eval()'
},
{
name: 'exec',
regex: /\bexec\s*\(/g,
severity: 'critical',
description: 'Dynamic code execution with exec()'
},
{
name: 'compile',
regex: /\bcompile\s*\(/g,
severity: 'high',
description: 'Code compilation detected'
}
],
imports: [
{
name: 'dynamic_import',
regex: /\b__import__\s*\(/g,
severity: 'high',
description: 'Dynamic import detected'
},
{
name: 'importlib',
regex: /\bimportlib\./g,
severity: 'medium',
description: 'Import library usage'
}
],
subprocess: [
{
name: 'subprocess',
regex: /\bsubprocess\./g,
severity: 'critical',
description: 'Subprocess execution'
},
{
name: 'os_system',
regex: /\bos\.system\s*\(/g,
severity: 'critical',
description: 'OS system command execution'
},
{
name: 'popen',
regex: /\bos\.popen\s*\(/g,
severity: 'critical',
description: 'Process execution with popen'
}
]
};
}
_initializeSuspiciousPatterns() {
return {
reflection: [
{
name: 'getattr',
regex: /\bgetattr\s*\(/g,
severity: 'medium',
description: 'Attribute access via getattr'
},
{
name: 'setattr',
regex: /\bsetattr\s*\(/g,
severity: 'medium',
description: 'Attribute modification via setattr'
},
{
name: 'hasattr',
regex: /\bhasattr\s*\(/g,
severity: 'low',
description: 'Attribute existence check'
}
],
globals: [
{
name: 'globals',
regex: /\bglobals\s*\(\s*\)/g,
severity: 'medium',
description: 'Access to global namespace'
},
{
name: 'locals',
regex: /\blocals\s*\(\s*\)/g,
severity: 'medium',
description: 'Access to local namespace'
},
{
name: 'vars',
regex: /\bvars\s*\(/g,
severity: 'low',
description: 'Variable inspection'
}
]
};
}
_initializeFileSystemPatterns() {
return [
{
name: 'open',
regex: /\bopen\s*\(/g,
severity: 'high',
description: 'File opening operation'
},
{
name: 'file',
regex: /\bfile\s*\(/g,
severity: 'high',
description: 'File object creation'
},
{
name: 'pathlib',
regex: /\bpathlib\./g,
severity: 'medium',
description: 'Path manipulation'
}
];
}
_initializeNetworkPatterns() {
return [
{
name: 'urllib',
regex: /\burllib\./g,
severity: 'high',
description: 'URL library usage'
},
{
name: 'requests',
regex: /\brequests\./g,
severity: 'high',
description: 'HTTP requests library'
},
{
name: 'socket',
regex: /\bsocket\./g,
severity: 'high',
description: 'Socket networking'
}
];
}
}
// Custom security error class
class SecurityError extends Error {
constructor(message, category = 'security', severity = 'high') {
super(message);
this.name = 'SecurityError';
this.category = category;
this.severity = severity;
this.timestamp = new Date().toISOString();
}
}
export default SecurityValidator;
export { SecurityError };