From 7bd7a54db8cd871133326e87ae6710ee45cbcf58 Mon Sep 17 00:00:00 2001 From: ruv Date: Sat, 23 May 2026 05:29:54 -0400 Subject: [PATCH] fix(security): replace insecure PRNG, harden JSON.parse, fix ESLint errors Security fixes (CWE-338): - src/memory/agentdb/client.ts: replace Math.random() with crypto.randomBytes for ID generation; add top-level crypto import - src/memory/reasoning-bank/bank.ts: replace Math.random() with crypto.randomBytes for knowledge-unit ID generation; add top-level crypto import - Remove unused DEFAULT_SAFLA_CONFIG import from reasoning-bank/bank.ts Input hardening: - src/optimize/bootstrap.ts: wrap JSON.parse(readFileSync(...)) in try/catch with descriptive error message in load() - src/optimize/gepa.ts: same JSON.parse hardening in load() - src/optimize/miprov2.ts: same JSON.parse hardening in load() ESLint error fixes (0 errors, was 11): - src/core/factory.ts: remove phantom TResult from ModuleOptions interface; keep it on defineModule function signature - src/core/module.ts: remove unused FieldDefinition import - src/lm/onnx.ts: prefix unused 'tokens' parameter as '_tokens' - src/lm/torch.ts: prefix unused 'options' parameter as '_options' - src/modules/chain-of-thought.ts: let -> const for jsonMatch; drop unused error binding in catch clause - src/modules/react.ts: rename 'steps' param to '_steps'; let -> const for value (prefer-const) - src/types/js-pytorch.d.ts: fix no-misused-new by replacing interface new() signatures with separate LinearConstructor/ReLUConstructor interfaces All 177 tests pass; TypeScript compilation clean. Co-Authored-By: claude-flow --- src/core/factory.ts | 10 +++++----- src/core/module.ts | 2 +- src/lm/onnx.ts | 2 +- src/lm/torch.ts | 2 +- src/memory/agentdb/client.ts | 3 ++- src/memory/reasoning-bank/bank.ts | 5 +++-- src/modules/chain-of-thought.ts | 4 ++-- src/modules/react.ts | 4 ++-- src/optimize/bootstrap.ts | 7 ++++++- src/optimize/gepa.ts | 7 ++++++- src/optimize/miprov2.ts | 7 ++++++- src/types/js-pytorch.d.ts | 13 ++++++++----- 12 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/core/factory.ts b/src/core/factory.ts index 4018730..e23b03d 100644 --- a/src/core/factory.ts +++ b/src/core/factory.ts @@ -5,7 +5,7 @@ import { PredictModule } from '../modules/predict'; /** * Options for creating a module */ -export interface ModuleOptions, TOutput extends Record> { +export interface ModuleOptions> { name: string; signature: Signature; promptTemplate: (input: TInput) => string; @@ -15,14 +15,14 @@ export interface ModuleOptions, TOutput exten /** * Factory function to create modules based on strategy */ -export function defineModule, TOutput extends Record>( - options: ModuleOptions -): Module { +export function defineModule, TResult extends Record>( + options: ModuleOptions +): Module { const strategy = options.strategy || 'Predict'; switch (strategy) { case 'Predict': - return new PredictModule(options); + return new PredictModule(options); case 'ChainOfThought': case 'ReAct': diff --git a/src/core/module.ts b/src/core/module.ts index 1e147b4..75c8f15 100644 --- a/src/core/module.ts +++ b/src/core/module.ts @@ -1,4 +1,4 @@ -import { Signature, FieldDefinition } from './signature'; +import { Signature } from './signature'; /** * Base class for DSPy.ts modules. diff --git a/src/lm/onnx.ts b/src/lm/onnx.ts index 5195b2a..5bb6384 100644 --- a/src/lm/onnx.ts +++ b/src/lm/onnx.ts @@ -107,7 +107,7 @@ export class ONNXModel implements LMDriver { // This will be expanded in future phases to handle actual tokenization this.tokenizer = { encode: (text: string) => new Float32Array([text.length]), // Dummy implementation - decode: (tokens: Float32Array) => 'Decoded text' // Dummy implementation + decode: (_tokens: Float32Array) => 'Decoded text' // Dummy implementation }; } diff --git a/src/lm/torch.ts b/src/lm/torch.ts index 995e27a..088af7c 100644 --- a/src/lm/torch.ts +++ b/src/lm/torch.ts @@ -215,7 +215,7 @@ export class TorchModel implements LMDriver { /** * Process output tensor to text */ - private processOutput(output: torch.Tensor, options?: GenerationOptions): string { + private processOutput(output: torch.Tensor, _options?: GenerationOptions): string { // For MVP, return a simple string based on the output tensor // This will be replaced with actual detokenization in future phases const shape = output.shape.join('x'); diff --git a/src/memory/agentdb/client.ts b/src/memory/agentdb/client.ts index 1a2edcf..ce18bcb 100644 --- a/src/memory/agentdb/client.ts +++ b/src/memory/agentdb/client.ts @@ -8,6 +8,7 @@ * environments without native deps. */ +import { randomBytes } from 'crypto'; import pino from 'pino'; import retry from 'async-retry'; import { AgentDBConfig, mergeConfig } from './config'; @@ -461,7 +462,7 @@ export class AgentDBClient { } private generateId(): string { - return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; + return `${Date.now()}-${randomBytes(5).toString('hex')}`; } private invalidateCache(): void { diff --git a/src/memory/reasoning-bank/bank.ts b/src/memory/reasoning-bank/bank.ts index 73e8f91..48977f1 100644 --- a/src/memory/reasoning-bank/bank.ts +++ b/src/memory/reasoning-bank/bank.ts @@ -4,9 +4,10 @@ * Persistent memory system for AI agents with self-learning capabilities */ +import { randomBytes } from 'crypto'; import pino from 'pino'; import { AgentDBClient } from '../agentdb/client'; -import { SAFLA, DEFAULT_SAFLA_CONFIG } from './safla'; +import { SAFLA } from './safla'; import { KnowledgeUnit, Experience, @@ -513,7 +514,7 @@ export class ReasoningBank { * Generate unique ID */ private generateId(): string { - return `ku-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + return `ku-${Date.now()}-${randomBytes(5).toString('hex')}`; } /** diff --git a/src/modules/chain-of-thought.ts b/src/modules/chain-of-thought.ts index cadf22c..4864c96 100644 --- a/src/modules/chain-of-thought.ts +++ b/src/modules/chain-of-thought.ts @@ -169,7 +169,7 @@ export class ChainOfThought< */ private parseCoTResponse(response: string): Record { // Try to extract JSON from response - let jsonMatch = response.match(/\{[\s\S]*\}/); + const jsonMatch = response.match(/\{[\s\S]*\}/); if (jsonMatch) { try { @@ -181,7 +181,7 @@ export class ChainOfThought< } return parsed; - } catch (error) { + } catch { // JSON parsing failed, fallback } } diff --git a/src/modules/react.ts b/src/modules/react.ts index 74f606c..a384ffb 100644 --- a/src/modules/react.ts +++ b/src/modules/react.ts @@ -422,7 +422,7 @@ export class ReAct< */ private parseAnswer( answerText: string, - steps: ReActStep[] + _steps: ReActStep[] ): Record { const result: Record = {}; @@ -435,7 +435,7 @@ export class ReAct< const match = answerText.match(pattern); if (match) { - let value = match[1].trim().replace(/^["']|["']$/g, ''); + const value = match[1].trim().replace(/^["']|["']$/g, ''); // Type conversion if (field.type === 'number') { diff --git a/src/optimize/bootstrap.ts b/src/optimize/bootstrap.ts index b0b2118..985c6c6 100644 --- a/src/optimize/bootstrap.ts +++ b/src/optimize/bootstrap.ts @@ -202,7 +202,12 @@ export class BootstrapFewShot< load(filePath: string): void { const safePath = safeResolvePath(filePath); - const data = JSON.parse(fs.readFileSync(safePath, 'utf8')); + let data: Record; + try { + data = JSON.parse(fs.readFileSync(safePath, 'utf8')); + } catch (err) { + throw new Error(`Failed to parse saved state from ${safePath}: ${err instanceof Error ? err.message : String(err)}`); + } // Reconstructs with the fixed demo set (dynamic selection needs a live store, which isn't serialized). this.optimizedProgram = new BootstrapOptimizedModule(data.program.name, data.program.signature, data.program.demos ?? []); if (data.config) this.config = data.config; diff --git a/src/optimize/gepa.ts b/src/optimize/gepa.ts index ae15908..f1efcdc 100644 --- a/src/optimize/gepa.ts +++ b/src/optimize/gepa.ts @@ -293,7 +293,12 @@ export class GEPA, TOutput extends Record; + try { + data = JSON.parse(fs.readFileSync(safePath, 'utf8')); + } catch (err) { + throw new Error(`Failed to parse saved state from ${safePath}: ${err instanceof Error ? err.message : String(err)}`); + } this.optimizedProgram = new OptimizedModule(data.program.name, data.program.signature, data.program.instruction, []); this.lastResult = data.result ?? null; } diff --git a/src/optimize/miprov2.ts b/src/optimize/miprov2.ts index 2eaca41..7ccfdaf 100644 --- a/src/optimize/miprov2.ts +++ b/src/optimize/miprov2.ts @@ -387,7 +387,12 @@ export class MIPROv2, TOutput extends Record< load(filePath: string): void { const safePath = safeResolvePath(filePath); - const data = JSON.parse(fs.readFileSync(safePath, 'utf8')); + let data: Record; + try { + data = JSON.parse(fs.readFileSync(safePath, 'utf8')); + } catch (err) { + throw new Error(`Failed to parse saved state from ${safePath}: ${err instanceof Error ? err.message : String(err)}`); + } this.optimizedProgram = new OptimizedModule(data.program.name, data.program.signature, data.program.instruction, data.program.demos ?? []); this.lastResult = data.result ?? null; if (data.config) this.config = data.config; diff --git a/src/types/js-pytorch.d.ts b/src/types/js-pytorch.d.ts index 3be5610..a6ee38d 100644 --- a/src/types/js-pytorch.d.ts +++ b/src/types/js-pytorch.d.ts @@ -21,18 +21,21 @@ declare module 'js-pytorch' { } interface Linear extends Module { - new(inputSize: number, outputSize: number): Linear; copy_: (value: any) => void; } - interface ReLU extends Module { - new(): ReLU; + interface LinearConstructor { + new(inputSize: number, outputSize: number): Linear; + } + + interface ReLUConstructor { + new(): Module; } interface NN { Module: typeof Module; - Linear: Linear; - ReLU: ReLU; + Linear: LinearConstructor; + ReLU: ReLUConstructor; } // Mock support for testing