mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-09-14 14:01:28 +08:00
402b701a9d
* fix(security): ADR-165 Phase 1 — close all critical CVEs, refresh registry, add CI gate Closes ADR-165 Phase 1. Both workspaces pass `npm audit --audit-level=critical`. ## Before (2026-06-29) | Workspace | Critical | High | Moderate | Total | |-----------|----------|------|----------|-------| | Root | **1** | 6 | 31 | 38 | | v3 | **4** | 33 | 57 | 97 | ## After | Workspace | Critical | High | Moderate | Total | |-----------|----------|------|----------|-------| | Root | **0** | 0 | 31 | 31 | | v3 | **0** | 27 | 58 | 88 | All 5 critical advisories closed (vitest GHSA-5xrq, handlebars prototype-pollution, protobufjs constructor-pollution, plus 2 more in v3). 12 of 39 high advisories closed via overrides; the remaining 27 v3 highs are deeper transitive chains that would require breaking-change major bumps — flagged for ADR-165 Phase 2. ## Changes ### Root workspace overrides (package.json) - `vitest`: `^1.0.0` → `^3.2.6` (closes critical GHSA-5xrq CVSS 9.8) - `hono`: `>=4.11.4` → `>=4.12.25` (closes 6 hono advisories) - `undici`: `>=7.18.0` → `>=8.5.0` (closes 7 undici advisories — bumps the stale pin that ADR-165 flagged as still-in-vulnerable-range) - `vite`: `>=6.4.6` → `>=8.0.16` (closes 2 vite advisories) - NEW `@grpc/grpc-js`: `>=1.14.4` - NEW `form-data`: `>=4.0.6` - NEW `http-proxy-middleware`: `>=3.0.7` - `@hono/node-server`: `>=1.19.10` → `>=1.19.14` ### v3 workspace overrides (v3/package.json + pnpm.overrides) - `vitest`: `^4.0.16` → `^4.1.0` (and `@vitest/coverage-v8` matched) - Top-level + pnpm overrides: `handlebars: >=4.7.9`, `protobufjs: >=8.6.0` - 27 sub-package `package.json` updated to `vitest ^4.1.0` ### CVE registry refresh (v3/@claude-flow/security/src/CVE-REMEDIATION.ts) Rewritten from 5 stale Jan-2026 entries to 16 total: 15 fixed, 1 open (ADR165-OPEN-01 — PII pipeline wiring per ADR-164 §6.1 not yet on dispatch-layer). `SECURITY_SUMMARY` is now computed dynamically from the registry; `validateRemediation()` correctly returns `allFixed: false` with 1 issue. ADR-165 §5 flagged this as THE Phase 2 priority — now closed. ### New CI gate `.github/workflows/cve-audit.yml`: - `audit-root` — `npm audit --audit-level=critical` in root, blocking - `audit-v3` — same in v3, blocking - `audit-high-report` — warn-only summary on high-severity drift - Triggers: PR, push to main, daily cron ### ADR-165 §9 evidence ledger addendum Captures AFTER-remediation `npm audit --json` metadata for both workspaces. The diff between BEFORE (already in §9) and AFTER makes Phase 1 verifiable. Also CORRECTED one prior surprise in §9: the `validate-input.ts` in `mcp-tools/` is NOT a 9-line shim. It's 269 lines of real implementation. (That was an inaccurate findings note from the initial audit.) ### §7.3 research items resolved - `protobufjs` v3 chain confirmed gone after override pins - `handlebars` v3 chain confirmed not user-reachable on the workflow path (toolchain-only; override sufficient) - `validate-input.ts` confirmed real implementation, not a shim (registry entry corrected) - CORS surface check: no `cors()` / `app.use(cors` call sites — the hono CORS-wildcard advisory is not exploitable on the current server - ADR-164 §6.1 PII pipeline NOT wired in current agentbbs dispatch — recorded as ADR165-OPEN-01 (Phase 3 work) ## Gates (all PASS) - `npm audit --audit-level=critical` root: exit 0 (was non-zero) - `npm audit --audit-level=critical` v3: exit 0 (was non-zero) - vitest suites: 105/109 pass (4 skip-conditional — same as before) - ADR-112 audit: 352/352 tools with guidance - business-pods smoke: 11/11 PASS - agentbbs smoke: 8/8 PASS - agenticow smoke: 8/8 PASS - cli build: clean (tsc) ## Phase 2+ deferred The 27 v3 high-severity advisories that remain are transitive chains with no patched-in-range version. ADR-165 §6 Phase 2 picks these up after the `cve-watch.yml` automation in §6 lands (NEW PR — not in scope here). Co-Authored-By: RuFlo <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01WbBa4nccx5aGhXphoHxcni * chore(lock): regen v3/pnpm-lock.yaml after override updates (PR #2508 CI fix) PR #2508 added handlebars + protobufjs to v3 overrides + bumped vitest pin, but v3/pnpm-lock.yaml wasn't regenerated. CI surfaced ERR_PNPM_LOCKFILE_CONFIG_MISMATCH on every install step. Same pattern as the previous lockfile-drift fixes on PR #2500 and #2503. * fix(ci): cve-audit.yml jq-only + regen witness manifests for #1609 marker drift Two PR #2508 CI failures fixed: 1. Static-regression-guard tripped on cve-audit.yml line 52: the static YAML strict-parser sees `if high > 0:` inside the `run: |` block as a YAML block-mapping key indicator. Refactored all 3 inline-python blocks to use jq (preinstalled on ubuntu-latest) — no embedded `if x > 0:` lines, so the YAML guard passes. 2. Witness marker drift smoke flagged fix-marker #1609 — the Phase 1 vitest bump (4.0.16 → 4.1.0) drifted the cited marker string in v3/@claude-flow/aidefence/package.json. Updated `marker` field in all 3 platform manifests + `desc` to reference ADR-165 Phase 1, then re-signed via plugins/ruflo-core/scripts/witness/regen.mjs. Co-Authored-By: RuFlo <ruv@ruv.net>
@claude-flow/testing
Comprehensive testing framework for V3 Claude-Flow modules. Implements London School TDD patterns with behavior verification, shared fixtures, and mock services.
Based on ADR-008 (Vitest over Jest).
Installation
npm install @claude-flow/testing vitest --save-dev
Quick Start
import {
setupV3Tests,
createMockApplication,
agentConfigs,
swarmConfigs,
waitFor,
} from '@claude-flow/testing';
// Configure test environment
setupV3Tests();
describe('MyModule', () => {
const app = createMockApplication();
beforeEach(() => {
// Mocks are automatically reset
});
it('should spawn an agent', async () => {
const result = await app.agentLifecycle.spawn(agentConfigs.queenCoordinator);
expect(result.success).toBe(true);
expect(result.agent.type).toBe('queen-coordinator');
});
});
Directory Structure
src/
├── fixtures/ # Pre-defined test data
│ ├── agent-fixtures.ts # Mock agents, configs
│ ├── memory-fixtures.ts # Memory entries, backends
│ ├── swarm-fixtures.ts # Swarm configs, topologies
│ └── mcp-fixtures.ts # MCP tools, contexts
├── helpers/ # Test utilities
│ ├── test-utils.ts # waitFor, retry, timeout
│ ├── mock-factory.ts # Factory functions for mocks
│ ├── assertion-helpers.ts # Custom Vitest matchers
│ └── setup-teardown.ts # Global setup/teardown
├── mocks/ # Mock service implementations
│ ├── mock-services.ts # AgentDB, SwarmCoordinator, etc.
│ └── mock-mcp-client.ts # MCP client for CLI testing
├── setup.ts # Global test configuration
└── index.ts # Main exports
Fixtures
Agent Fixtures
import {
agentConfigs,
agentInstances,
createAgentConfig,
createAgentInstance,
createV3SwarmAgentConfigs,
createMockAgent,
createMockV3Swarm,
} from '@claude-flow/testing';
// Pre-defined configs
const queen = agentConfigs.queenCoordinator;
const coder = agentConfigs.coder;
// Create with overrides
const customAgent = createAgentConfig('coder', {
name: 'Custom Coder',
priority: 90,
});
// Full V3 15-agent swarm
const swarmConfigs = createV3SwarmAgentConfigs();
// Mock agents with vitest mocks
const mockAgent = createMockAgent('security-architect');
mockAgent.execute.mockResolvedValue({ success: true });
Memory Fixtures
import {
memoryEntries,
searchResults,
learnedPatterns,
hnswConfigs,
memoryBackendConfigs,
createMemoryEntry,
createVectorQuery,
generateMockEmbedding,
createMemoryBatch,
} from '@claude-flow/testing';
// Pre-defined entries
const pattern = memoryEntries.agentPattern;
const securityRule = memoryEntries.securityRule;
// Create with overrides
const entry = createMemoryEntry('agentPattern', {
key: 'custom:pattern:001',
});
// Generate embeddings
const embedding = generateMockEmbedding(384, 'my-seed');
// Create batch for performance testing
const batch = createMemoryBatch(10000, 'semantic');
Swarm Fixtures
import {
swarmConfigs,
swarmStates,
swarmTasks,
swarmMessages,
coordinationResults,
createSwarmConfig,
createSwarmTask,
createSwarmMessage,
createConsensusRequest,
createMockSwarmCoordinator,
} from '@claude-flow/testing';
// Pre-defined configs
const v3Config = swarmConfigs.v3Default;
const minimalConfig = swarmConfigs.minimal;
// Create with overrides
const customConfig = createSwarmConfig('v3Default', {
maxAgents: 20,
coordination: {
consensusProtocol: 'pbft',
heartbeatInterval: 500,
electionTimeout: 3000,
},
});
// Mock coordinator
const coordinator = createMockSwarmCoordinator();
await coordinator.initialize(v3Config);
MCP Fixtures
import {
mcpTools,
mcpResources,
mcpPrompts,
mcpServerConfigs,
mcpToolResults,
mcpErrors,
createMCPTool,
createMCPRequest,
createMCPResponse,
createMockMCPClient,
} from '@claude-flow/testing';
// Pre-defined tools
const swarmInit = mcpTools.swarmInit;
const agentSpawn = mcpTools.agentSpawn;
// Mock client
const client = createMockMCPClient();
await client.connect();
const result = await client.callTool('swarm_init', { topology: 'mesh' });
Test Utilities
Async Utilities
import {
waitFor,
waitUntilChanged,
retry,
withTimeout,
sleep,
parallelLimit,
} from '@claude-flow/testing';
// Wait for condition
await waitFor(() => element.isVisible(), { timeout: 5000 });
// Wait for value to change
await waitUntilChanged(() => counter.value, { from: 0 });
// Retry with exponential backoff
const result = await retry(
async () => await fetchData(),
{ maxAttempts: 3, backoff: 100 }
);
// Timeout wrapper
await withTimeout(async () => await longOp(), 5000);
// Parallel with concurrency limit
const results = await parallelLimit(
items.map(item => () => processItem(item)),
5 // max 5 concurrent
);
Time Control
import { createMockClock, measureTime } from '@claude-flow/testing';
// Mock clock for time-dependent tests
const clock = createMockClock();
clock.install();
clock.tick(1000); // Advance by 1 second
clock.uninstall();
// Measure execution time
const { result, duration } = await measureTime(async () => {
return await expensiveOperation();
});
Event Emitter
import { createTestEmitter } from '@claude-flow/testing';
const emitter = createTestEmitter<{ message: string; count: number }>();
const handler = vi.fn();
emitter.on('message', handler);
emitter.emit('message', 'hello');
expect(handler).toHaveBeenCalledWith('hello');
Mock Factory
Application Mocks
import {
createMockApplication,
createMockEventBus,
createMockTaskManager,
createMockAgentLifecycle,
createMockMemoryService,
createMockSecurityService,
createMockSwarmCoordinator,
createMockLogger,
} from '@claude-flow/testing';
// Full application with all mocks
const app = createMockApplication();
// Individual service mocks
const eventBus = createMockEventBus();
const taskManager = createMockTaskManager();
const security = createMockSecurityService();
// Use in tests
await app.taskManager.create({ name: 'Test', type: 'coding', payload: {} });
expect(app.taskManager.create).toHaveBeenCalled();
// Access tracked state
expect(app.eventBus.publishedEvents).toHaveLength(1);
expect(app.taskManager.tasks.size).toBe(1);
Mock Services
MockAgentDB
import { MockAgentDB } from '@claude-flow/testing';
const db = new MockAgentDB();
// Insert vectors
await db.insert('vec-1', embedding, { type: 'pattern' });
// Search
const results = await db.search(queryEmbedding, 10, 0.7);
// Verify calls
expect(db.insert).toHaveBeenCalledWith('vec-1', expect.any(Array), expect.any(Object));
MockSwarmCoordinator
import { MockSwarmCoordinator } from '@claude-flow/testing';
const coordinator = new MockSwarmCoordinator();
await coordinator.initialize({ topology: 'hierarchical-mesh' });
await coordinator.addAgent({ type: 'coder', name: 'Coder-1' });
const result = await coordinator.coordinate({ id: 'task-1', type: 'coding', payload: {} });
expect(coordinator.getState().agentCount).toBe(1);
expect(result.success).toBe(true);
MockMCPClient
import { MockMCPClient, createStandardMockMCPClient } from '@claude-flow/testing';
// Standard client with common tools
const client = createStandardMockMCPClient();
await client.connect();
// Custom tool handlers
client.setToolHandler('swarm_init', async (params) => ({
content: [{ type: 'text', text: JSON.stringify({ swarmId: 'test' }) }],
}));
const result = await client.callTool('swarm_init', { topology: 'mesh' });
// Verify request history
expect(client.getRequestHistory()).toHaveLength(1);
expect(client.getLastRequest()?.method).toBe('tools/call');
Assertions
Standard Assertions
import {
assertEventPublished,
assertEventOrder,
assertMocksCalledInOrder,
assertV3PerformanceTargets,
assertValidStateTransition,
assertNoSensitiveData,
} from '@claude-flow/testing';
// Event assertions
assertEventPublished(mockEventBus, 'UserCreated', { userId: '123' });
assertEventOrder(mockEventBus.publish, ['UserCreated', 'EmailSent']);
// Mock order
assertMocksCalledInOrder([mockValidate, mockSave, mockNotify]);
// Performance targets
assertV3PerformanceTargets({
searchSpeedup: 160,
flashAttentionSpeedup: 3.5,
memoryReduction: 0.55,
});
// State transitions
assertValidStateTransition('pending', 'running', {
pending: ['running', 'cancelled'],
running: ['completed', 'failed'],
});
// Security
assertNoSensitiveData(mockLogger.logs, ['password', 'token', 'secret']);
Custom Vitest Matchers
import { registerCustomMatchers } from '@claude-flow/testing';
// Register in setup
registerCustomMatchers();
// Use in tests
expect(mockFn).toHaveBeenCalledWithPattern({ userId: expect.any(String) });
expect(event).toHaveEventType('UserCreated');
expect(metrics).toMeetV3PerformanceTargets();
Setup & Teardown
Global Test Setup
import { setupV3Tests, configureTestEnvironment } from '@claude-flow/testing';
// Simple setup
setupV3Tests();
// Custom configuration
configureTestEnvironment({
resetMocks: true,
fakeTimers: true,
suppressConsole: ['log', 'warn'],
env: {
NODE_ENV: 'test',
DEBUG: 'false',
},
});
Test Context
import { createSetupContext, createTestScope } from '@claude-flow/testing';
// Setup context with cleanup
const ctx = createSetupContext();
ctx.addCleanup(() => server.close());
ctx.registerResource(database);
// ... run tests
await ctx.runCleanup();
// Isolated test scope
const scope = createTestScope();
scope.addMock(mockService);
await scope.run(async () => {
// test code - mocks auto-cleared after
});
Performance Testing
import { createPerformanceTestHelper } from '@claude-flow/testing';
const perf = createPerformanceTestHelper();
perf.startMeasurement('search');
await search(query);
const duration = perf.endMeasurement('search');
// Get statistics
const stats = perf.getStats('search');
console.log(`Avg: ${stats.avg}ms, P95: ${stats.p95}ms`);
Performance Targets
The testing framework includes assertions for V3 performance targets:
| Metric | Target |
|---|---|
| Search Speedup | 150x - 12,500x |
| Flash Attention Speedup | 2.49x - 7.47x |
| Memory Reduction | >= 50% |
| Startup Time | < 500ms |
| Response Time | < 100ms |
import { assertV3PerformanceTargets, TEST_CONFIG } from '@claude-flow/testing';
// Assert targets
assertV3PerformanceTargets({
searchSpeedup: 160,
flashAttentionSpeedup: 3.5,
memoryReduction: 0.55,
startupTimeMs: 450,
responseTimeMs: 80,
});
// Access constants
console.log(TEST_CONFIG.FLASH_ATTENTION_SPEEDUP_MIN); // 2.49
console.log(TEST_CONFIG.AGENTDB_SEARCH_IMPROVEMENT_MAX); // 12500
Best Practices
1. Use London School TDD
// Arrange mocks before acting
const mockRepo = createMock<UserRepository>();
mockRepo.findById.mockResolvedValue(user);
// Act
await service.processUser('123');
// Assert behavior (not implementation)
expect(mockRepo.findById).toHaveBeenCalledWith('123');
expect(mockNotifier.notify).toHaveBeenCalledBefore(mockRepo.save);
2. Use Fixtures Over Inline Data
// Good - use fixtures
const agent = agentConfigs.queenCoordinator;
const task = createSwarmTask('securityScan');
// Avoid - inline data
const agent = { type: 'queen-coordinator', name: 'Test', capabilities: [] };
3. Isolate Tests
// Use fresh mocks per test
beforeEach(() => {
vi.clearAllMocks();
});
// Or use test scope
const scope = createTestScope();
await scope.run(async () => {
// isolated test
});
4. Test Behavior, Not Implementation
// Good - behavior verification
expect(mockEventBus.publish).toHaveBeenCalledWith(
expect.objectContaining({ type: 'UserCreated' })
);
// Avoid - implementation details
expect(service._internalQueue.length).toBe(1);
License
MIT