v3/implementation/security/SECURITY_FIXES_CHECKLIST.md
This checklist provides actionable steps to address all security vulnerabilities identified in the security audit.
Time Estimate: 30 minutes
Files: package.json
# Update critical vulnerabilities
npm update @anthropic-ai/claude-code@^2.0.31
npm update @modelcontextprotocol/sdk@^1.24.0
npm update body-parser@^2.2.1
# Run full audit fix
npm audit fix --force
# Verify fixes
npm audit
Verification:
npm audit | grep -E "(critical|high)"
# Should show 0 critical and 0 high vulnerabilities
Time Estimate: 2 hours
Files: src/api/auth-service.ts
Step 1: Install bcrypt
npm install bcrypt
npm install --save-dev @types/bcrypt
Step 2: Replace password hashing functions (Lines 580-588)
// BEFORE (INSECURE)
private async hashPassword(password: string): Promise<string> {
return createHash('sha256').update(password + 'salt').digest('hex');
}
// AFTER (SECURE)
import bcrypt from 'bcrypt';
private async hashPassword(password: string): Promise<string> {
const rounds = this.config.bcryptRounds || 12;
return await bcrypt.hash(password, rounds);
}
private async verifyPassword(password: string, hash: string): Promise<boolean> {
return await bcrypt.compare(password, hash);
}
Step 3: Update AuthConfig interface
export interface AuthConfig {
jwtSecret: string;
jwtExpiresIn?: string;
apiKeyLength?: number;
bcryptRounds?: number; // Add this line
sessionTimeout?: number;
// ...
}
Verification:
npm run test -- auth-service.test
Time Estimate: 3 hours
Files: src/api/auth-service.ts, src/cli/commands/init.ts
Step 1: Remove hardcoded passwords (Lines 602-643)
// BEFORE (INSECURE)
private initializeDefaultUsers(): void {
const adminUser: User = {
id: 'admin_default',
email: '[email protected]',
passwordHash: createHash('sha256').update('admin123' + 'salt').digest('hex'),
// ...
};
}
// AFTER (SECURE)
private async initializeDefaultUsers(): Promise<void> {
// Check if admin already exists
const existingAdmin = Array.from(this.users.values())
.find(u => u.role === 'admin');
if (!existingAdmin) {
// Generate random password
const randomPassword = this.generateSecurePassword();
console.log('═══════════════════════════════════════════');
console.log('IMPORTANT: SAVE THESE CREDENTIALS NOW');
console.log('═══════════════════════════════════════════');
console.log('Default Admin Credentials:');
console.log(`Email: [email protected]`);
console.log(`Password: ${randomPassword}`);
console.log('═══════════════════════════════════════════');
console.log('You will NOT see this password again!');
console.log('Please change it immediately after first login.');
console.log('═══════════════════════════════════════════\n');
const adminUser: User = {
id: 'admin_default',
email: '[email protected]',
passwordHash: await this.hashPassword(randomPassword),
role: 'admin',
permissions: ROLE_PERMISSIONS.admin,
apiKeys: [],
isActive: true,
loginAttempts: 0,
mfaEnabled: false,
mustChangePassword: true, // Add this field
createdAt: new Date(),
updatedAt: new Date(),
};
this.users.set(adminUser.id, adminUser);
}
}
private generateSecurePassword(): string {
const length = 24;
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
const randomBytes = require('crypto').randomBytes(length);
let password = '';
for (let i = 0; i < length; i++) {
password += charset[randomBytes[i] % charset.length];
}
return password;
}
Step 2: Add password change enforcement
async authenticateUser(email: string, password: string, ...): Promise<...> {
// ... existing authentication code ...
// After successful authentication
if (user.mustChangePassword) {
return {
user,
token,
session,
requirePasswordChange: true // Signal to frontend
};
}
return { user, token, session };
}
Verification:
Time Estimate: 4 hours
Files: src/cli/commands/hook.ts, src/utils/error-recovery.ts
Fix 1: Remove shell:true from spawn calls
// File: src/cli/commands/hook.ts:184
// BEFORE (VULNERABLE)
const child = spawn('npx', ['ruv-swarm', 'hook', ...args], {
stdio: 'inherit',
shell: true, // DANGEROUS
});
// AFTER (SECURE)
const child = spawn('npx', ['ruv-swarm', 'hook', ...args], {
stdio: 'inherit',
shell: false, // SAFE
});
Fix 2: Add argument validation
// File: src/cli/commands/hook.ts (add at top)
function validateHookArgs(args: string[]): void {
const dangerousPatterns = [
/[;&|`$()]/, // Shell metacharacters
/\.\.\//, // Path traversal
/~\//, // Home directory
/^-/, // Options that could be exploited
];
for (const arg of args) {
for (const pattern of dangerousPatterns) {
if (pattern.test(arg)) {
throw new Error(`Potentially dangerous argument detected: ${arg}`);
}
}
}
}
// Use before spawning
async function executeHook(hookType: string, options: Record<string, any>): Promise<void> {
const args = buildArgs(hookType, options);
validateHookArgs(args); // Add this line
// ... rest of function
}
Verification:
# Should fail with error
claude-flow hook pre-task --description "test; whoami"
claude-flow hook pre-task --description "test && ls"
# Should succeed
claude-flow hook pre-task --description "legitimate task description"
Time Estimate: 3 hours
Files: src/utils/path-validator.ts (new), src/cli/commands/task.ts
Step 1: Create path validation utility
// File: src/utils/path-validator.ts (NEW FILE)
import { resolve, join, normalize, isAbsolute } from 'path';
import { access, constants } from 'fs/promises';
export class PathValidator {
private allowedDirectories: Set<string>;
constructor(allowedDirs: string[] = []) {
this.allowedDirectories = new Set(
allowedDirs.map(dir => resolve(normalize(dir)))
);
}
/**
* Validate that a file path is within allowed directories
*/
async validatePath(userPath: string, baseDir?: string): Promise<string> {
// Normalize and resolve path
const normalizedPath = normalize(userPath);
const resolvedPath = baseDir
? resolve(baseDir, normalizedPath)
: resolve(normalizedPath);
// Check for path traversal attempts
if (normalizedPath.includes('..')) {
throw new Error('Path traversal detected: .. not allowed');
}
// If no allowed directories specified, use current working directory
if (this.allowedDirectories.size === 0) {
const cwd = resolve(process.cwd());
if (!resolvedPath.startsWith(cwd)) {
throw new Error(`Path must be within current directory: ${cwd}`);
}
} else {
// Check if path is within any allowed directory
const isAllowed = Array.from(this.allowedDirectories)
.some(allowedDir => resolvedPath.startsWith(allowedDir));
if (!isAllowed) {
throw new Error(`Path not within allowed directories`);
}
}
// Verify file exists and is accessible
try {
await access(resolvedPath, constants.R_OK);
} catch (error) {
throw new Error(`File not accessible: ${resolvedPath}`);
}
return resolvedPath;
}
/**
* Add an allowed directory
*/
addAllowedDirectory(dir: string): void {
this.allowedDirectories.add(resolve(normalize(dir)));
}
}
export const defaultPathValidator = new PathValidator([
process.cwd(),
join(process.cwd(), '.claude-flow'),
join(process.cwd(), 'workflows'),
]);
Step 2: Use in task command
// File: src/cli/commands/task.ts:66
import { defaultPathValidator } from '../../utils/path-validator.js';
.action(async (workflowFile: string, options: any) => {
try {
// Validate path before reading
const safePath = await defaultPathValidator.validatePath(workflowFile);
const content = await fs.readFile(safePath, 'utf-8');
const workflow = JSON.parse(content);
// ... rest of function
} catch (error) {
console.error(chalk.red('Failed to load workflow:'), getErrorMessage(error));
process.exit(1);
}
});
Verification:
# Should fail
claude-flow task workflow ../../../etc/passwd
claude-flow task workflow ~/.ssh/id_rsa
claude-flow task workflow /etc/hosts
# Should succeed
claude-flow task workflow ./workflows/my-workflow.json
claude-flow task workflow workflows/test.json
Time Estimate: 2 hours
Files: src/cli/commands/config.ts
Step 1: Install Zod for schema validation
npm install zod
Step 2: Define config schemas
// File: src/cli/commands/config.ts (add at top)
import { z } from 'zod';
// Define allowed config keys and their schemas
const CONFIG_SCHEMAS = {
'theme': z.enum(['light', 'dark', 'auto']),
'timeout': z.number().int().min(1000).max(300000),
'logLevel': z.enum(['debug', 'info', 'warn', 'error']),
'maxRetries': z.number().int().min(0).max(10),
'cacheSizeMB': z.number().int().min(10).max(1000),
} as const;
const ALLOWED_CONFIG_KEYS = Object.keys(CONFIG_SCHEMAS);
// Prevent modification of sensitive keys
const PROTECTED_KEYS = [
'authConfig',
'jwtSecret',
'apiKeys',
'database',
'credentials',
];
Step 3: Update set command
// File: src/cli/commands/config.ts:28-50
.action(async (key: string, value: string) => {
try {
// Check if key is protected
if (PROTECTED_KEYS.some(pk => key.startsWith(pk))) {
console.error(chalk.red(`Cannot modify protected config key: ${key}`));
process.exit(1);
}
// Check if key is allowed
if (!ALLOWED_CONFIG_KEYS.includes(key)) {
console.error(chalk.red(`Unknown config key: ${key}`));
console.log(chalk.yellow(`Allowed keys: ${ALLOWED_CONFIG_KEYS.join(', ')}`));
process.exit(1);
}
// Parse and validate value
let parsedValue: any;
try {
parsedValue = JSON.parse(value);
} catch {
parsedValue = value; // Keep as string if not valid JSON
}
// Validate against schema
const schema = CONFIG_SCHEMAS[key as keyof typeof CONFIG_SCHEMAS];
const validatedValue = schema.parse(parsedValue);
await configManager.set(key, validatedValue);
console.log(
chalk.green('✓'),
`Configuration updated: ${key} = ${JSON.stringify(validatedValue)}`
);
} catch (error) {
if (error instanceof z.ZodError) {
console.error(chalk.red('Validation error:'), error.errors[0].message);
} else {
console.error(chalk.red('Failed to set configuration:'), (error as Error).message);
}
process.exit(1);
}
});
Verification:
# Should fail
claude-flow config set "authConfig.jwtSecret" "hacked"
claude-flow config set "__proto__.isAdmin" "true"
claude-flow config set "timeout" "999999999"
# Should succeed
claude-flow config set "theme" "dark"
claude-flow config set "timeout" "30000"
Time Estimate: 1 hour
Files: src/mcp/auth.ts
Replace Math.random() with crypto.randomBytes()
// File: src/mcp/auth.ts:375-385
// BEFORE (INSECURE)
private createSecureToken(): string {
const timestamp = Date.now().toString(36);
const random1 = Math.random().toString(36).substring(2, 15);
const random2 = Math.random().toString(36).substring(2, 15);
const hash = createHash('sha256')
.update(`${timestamp}${random1}${random2}`)
.digest('hex')
.substring(0, 32);
return `mcp_${timestamp}_${hash}`;
}
// AFTER (SECURE)
import { randomBytes } from 'crypto';
private createSecureToken(): string {
// Generate 32 bytes (256 bits) of cryptographically secure random data
const tokenBytes = randomBytes(32);
const token = tokenBytes.toString('hex');
return `mcp_${token}`;
}
Verification:
// Add test
describe('Token Generation', () => {
it('should generate unique tokens', () => {
const authManager = new AuthManager(config, logger);
const token1 = authManager.generateToken('user1', []);
const token2 = authManager.generateToken('user1', []);
expect(token1).not.toBe(token2);
expect(token1).toMatch(/^mcp_[a-f0-9]{64}$/);
});
});
Time Estimate: 8 hours
Files: src/audit/audit-logger.ts (new)
// File: src/audit/audit-logger.ts (NEW FILE)
import { ILogger } from '../core/logger.js';
import { promises as fs } from 'fs';
import { join } from 'path';
export interface AuditEvent {
id: string;
timestamp: Date;
userId?: string;
sessionId?: string;
action: string;
resource: string;
result: 'success' | 'failure' | 'denied';
metadata?: Record<string, any>;
ipAddress?: string;
userAgent?: string;
}
export class AuditLogger {
private events: AuditEvent[] = [];
private logPath: string;
constructor(
private logger: ILogger,
logPath: string = './logs/audit.jsonl'
) {
this.logPath = logPath;
}
async log(event: Omit<AuditEvent, 'id' | 'timestamp'>): Promise<void> {
const auditEvent: AuditEvent = {
id: `audit_${Date.now()}_${Math.random().toString(36).slice(2)}`,
timestamp: new Date(),
...event
};
this.events.push(auditEvent);
// Write to file (JSONL format)
const logLine = JSON.stringify(auditEvent) + '\n';
await fs.appendFile(this.logPath, logLine, 'utf-8');
// Log critical events
if (event.result === 'failure' || event.result === 'denied') {
this.logger.warn('Audit event', auditEvent);
}
}
async query(filter: Partial<AuditEvent>): Promise<AuditEvent[]> {
return this.events.filter(event => {
return Object.entries(filter).every(([key, value]) => {
return event[key as keyof AuditEvent] === value;
});
});
}
}
Integration:
// Use in auth-service.ts, permission-manager.ts, etc.
await auditLogger.log({
userId: user.id,
action: 'user.login',
resource: 'authentication',
result: 'success',
metadata: { method: 'password' }
});
Time Estimate: 6 hours
Files: src/secrets/secret-manager.ts (new)
// File: src/secrets/secret-manager.ts (NEW FILE)
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto';
export class SecretManager {
private masterKey: Buffer;
constructor(masterPassword: string) {
// Derive master key from password using scrypt
this.masterKey = scryptSync(masterPassword, 'salt', 32);
}
/**
* Encrypt a secret
*/
encrypt(plaintext: string): string {
const iv = randomBytes(16);
const cipher = createCipheriv('aes-256-gcm', this.masterKey, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
// Return: iv + authTag + encrypted
return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted;
}
/**
* Decrypt a secret
*/
decrypt(ciphertext: string): string {
const parts = ciphertext.split(':');
const iv = Buffer.from(parts[0], 'hex');
const authTag = Buffer.from(parts[1], 'hex');
const encrypted = parts[2];
const decipher = createDecipheriv('aes-256-gcm', this.masterKey, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
/**
* Store secret in environment
*/
async storeSecret(key: string, value: string): Promise<void> {
const encrypted = this.encrypt(value);
// Store in secure storage (file, database, vault, etc.)
// For now, just log warning
console.warn(`Secret ${key} should be stored in secure vault`);
}
}
After implementing each fix:
# Run all tests
npm test
# Run security-specific tests
npm run test:security
# Check for vulnerabilities
npm audit
# Lint code
npm run lint
# Type check
npm run typecheck
| Week | Focus | Tasks |
|---|---|---|
| 1 | Critical Fixes | Tasks 1-3 |
| 2-3 | High Priority | Tasks 4-7 |
| 4-5 | Medium Priority | Tasks 8-9 |
| 6+ | Low Priority | Tasks 10-13 |
Last Updated: 2026-01-03 Document Version: 1.0