@opena2a/oasb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +287 -0
  3. package/config/arp-lab-default.yaml +54 -0
  4. package/config/dvaa-targets.ts +97 -0
  5. package/dist/harness/arp-wrapper.d.ts +28 -0
  6. package/dist/harness/arp-wrapper.js +133 -0
  7. package/dist/harness/dvaa-client.d.ts +45 -0
  8. package/dist/harness/dvaa-client.js +97 -0
  9. package/dist/harness/dvaa-manager.d.ts +16 -0
  10. package/dist/harness/dvaa-manager.js +131 -0
  11. package/dist/harness/event-collector.d.ts +32 -0
  12. package/dist/harness/event-collector.js +85 -0
  13. package/dist/harness/metrics.d.ts +13 -0
  14. package/dist/harness/metrics.js +55 -0
  15. package/dist/harness/mock-llm-adapter.d.ts +33 -0
  16. package/dist/harness/mock-llm-adapter.js +68 -0
  17. package/dist/harness/types.d.ts +73 -0
  18. package/dist/harness/types.js +2 -0
  19. package/package.json +39 -0
  20. package/src/atomic/enforcement/AT-ENF-001.log-action.test.ts +89 -0
  21. package/src/atomic/enforcement/AT-ENF-002.alert-callback.test.ts +120 -0
  22. package/src/atomic/enforcement/AT-ENF-003.pause-sigstop.test.ts +104 -0
  23. package/src/atomic/enforcement/AT-ENF-004.kill-sigterm.test.ts +153 -0
  24. package/src/atomic/enforcement/AT-ENF-005.resume-sigcont.test.ts +164 -0
  25. package/src/atomic/filesystem/AT-FS-001.sensitive-path.test.ts +118 -0
  26. package/src/atomic/filesystem/AT-FS-002.outside-allowed.test.ts +122 -0
  27. package/src/atomic/filesystem/AT-FS-003.credential-file.test.ts +115 -0
  28. package/src/atomic/filesystem/AT-FS-004.mass-file-creation.test.ts +137 -0
  29. package/src/atomic/filesystem/AT-FS-005.dotfile-write.test.ts +154 -0
  30. package/src/atomic/intelligence/AT-INT-001.l0-rule-match.test.ts +107 -0
  31. package/src/atomic/intelligence/AT-INT-002.l1-anomaly-score.test.ts +94 -0
  32. package/src/atomic/intelligence/AT-INT-003.l2-escalation.test.ts +124 -0
  33. package/src/atomic/intelligence/AT-INT-004.budget-exhaustion.test.ts +108 -0
  34. package/src/atomic/intelligence/AT-INT-005.baseline-learning.test.ts +121 -0
  35. package/src/atomic/network/AT-NET-001.new-outbound.test.ts +103 -0
  36. package/src/atomic/network/AT-NET-002.suspicious-host.test.ts +82 -0
  37. package/src/atomic/network/AT-NET-003.connection-burst.test.ts +91 -0
  38. package/src/atomic/network/AT-NET-004.allowed-host-bypass.test.ts +129 -0
  39. package/src/atomic/network/AT-NET-005.exfil-destination.test.ts +117 -0
  40. package/src/atomic/process/AT-PROC-001.spawn-child.test.ts +148 -0
  41. package/src/atomic/process/AT-PROC-002.suspicious-binary.test.ts +123 -0
  42. package/src/atomic/process/AT-PROC-003.high-cpu.test.ts +120 -0
  43. package/src/atomic/process/AT-PROC-004.privilege-escalation.test.ts +114 -0
  44. package/src/atomic/process/AT-PROC-005.process-terminated.test.ts +150 -0
  45. package/src/baseline/BL-001.normal-agent-profile.test.ts +140 -0
  46. package/src/baseline/BL-002.anomaly-injection.test.ts +134 -0
  47. package/src/baseline/BL-003.baseline-persistence.test.ts +130 -0
  48. package/src/e2e/E2E-001.live-filesystem-detection.test.ts +129 -0
  49. package/src/e2e/E2E-002.live-process-detection.test.ts +106 -0
  50. package/src/e2e/E2E-003.live-network-detection.test.ts +114 -0
  51. package/src/e2e/E2E-004.interceptor-process.test.ts +125 -0
  52. package/src/e2e/E2E-005.interceptor-network.test.ts +134 -0
  53. package/src/e2e/E2E-006.interceptor-filesystem.test.ts +140 -0
  54. package/src/harness/arp-wrapper.ts +121 -0
  55. package/src/harness/dvaa-client.ts +130 -0
  56. package/src/harness/dvaa-manager.ts +106 -0
  57. package/src/harness/event-collector.ts +100 -0
  58. package/src/harness/metrics.ts +64 -0
  59. package/src/harness/mock-llm-adapter.ts +90 -0
  60. package/src/harness/types.ts +77 -0
  61. package/src/integration/INT-001.data-exfil-detection.test.ts +228 -0
  62. package/src/integration/INT-002.mcp-tool-abuse.test.ts +236 -0
  63. package/src/integration/INT-003.prompt-injection-response.test.ts +238 -0
  64. package/src/integration/INT-004.a2a-trust-exploitation.test.ts +280 -0
  65. package/src/integration/INT-005.baseline-then-attack.test.ts +239 -0
  66. package/src/integration/INT-006.multi-monitor-correlation.test.ts +265 -0
  67. package/src/integration/INT-007.budget-exhaustion-attack.test.ts +249 -0
  68. package/src/integration/INT-008.kill-switch-recovery.test.ts +314 -0
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ArpWrapper = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const arp_1 = require("@opena2a/arp");
41
+ const event_collector_1 = require("./event-collector");
42
+ /**
43
+ * Wraps AgentRuntimeProtection for controlled testing.
44
+ * Creates temp dataDir per test, registers EventCollector,
45
+ * and provides injection + assertion helpers.
46
+ */
47
+ class ArpWrapper {
48
+ constructor(labConfig) {
49
+ this._dataDir = labConfig?.dataDir ?? fs.mkdtempSync(path.join(os.tmpdir(), 'arp-lab-'));
50
+ const config = {
51
+ agentName: 'arp-lab-target',
52
+ agentDescription: 'Test target for ARP security lab',
53
+ declaredCapabilities: ['file read/write', 'HTTP requests'],
54
+ dataDir: this._dataDir,
55
+ monitors: {
56
+ process: {
57
+ enabled: labConfig?.monitors?.process ?? false,
58
+ intervalMs: labConfig?.processIntervalMs,
59
+ },
60
+ network: {
61
+ enabled: labConfig?.monitors?.network ?? false,
62
+ intervalMs: labConfig?.networkIntervalMs,
63
+ allowedHosts: labConfig?.networkAllowedHosts,
64
+ },
65
+ filesystem: {
66
+ enabled: labConfig?.monitors?.filesystem ?? false,
67
+ watchPaths: labConfig?.filesystemWatchPaths,
68
+ allowedPaths: labConfig?.filesystemAllowedPaths,
69
+ },
70
+ },
71
+ rules: labConfig?.rules,
72
+ intelligence: {
73
+ enabled: labConfig?.intelligence?.enabled ?? false,
74
+ budgetUsd: 0,
75
+ },
76
+ interceptors: {
77
+ process: { enabled: labConfig?.interceptors?.process ?? false },
78
+ network: {
79
+ enabled: labConfig?.interceptors?.network ?? false,
80
+ allowedHosts: labConfig?.interceptorNetworkAllowedHosts,
81
+ },
82
+ filesystem: {
83
+ enabled: labConfig?.interceptors?.filesystem ?? false,
84
+ allowedPaths: labConfig?.interceptorFilesystemAllowedPaths,
85
+ },
86
+ },
87
+ };
88
+ this.arp = new arp_1.AgentRuntimeProtection(config);
89
+ this.collector = new event_collector_1.EventCollector();
90
+ // Register event and enforcement collectors
91
+ this.arp.onEvent(this.collector.eventHandler);
92
+ this.arp.onEnforcement(this.collector.enforcementHandler);
93
+ }
94
+ async start() {
95
+ await this.arp.start();
96
+ }
97
+ async stop() {
98
+ await this.arp.stop();
99
+ this.collector.reset();
100
+ // Clean up temp dir
101
+ try {
102
+ fs.rmSync(this._dataDir, { recursive: true, force: true });
103
+ }
104
+ catch {
105
+ // Best effort cleanup
106
+ }
107
+ }
108
+ /** Get the underlying ARP instance */
109
+ getInstance() {
110
+ return this.arp;
111
+ }
112
+ /** Get the event engine for direct event injection */
113
+ getEngine() {
114
+ return this.arp.getEngine();
115
+ }
116
+ /** Get the enforcement engine */
117
+ getEnforcement() {
118
+ return this.arp.getEnforcement();
119
+ }
120
+ /** Inject a synthetic event into the ARP engine (for testing without real OS activity) */
121
+ async injectEvent(event) {
122
+ return this.getEngine().emit(event);
123
+ }
124
+ /** Wait for an event matching a predicate */
125
+ waitForEvent(predicate, timeoutMs = 10000) {
126
+ return this.collector.waitForEvent(predicate, timeoutMs);
127
+ }
128
+ /** Get the data directory */
129
+ get dataDir() {
130
+ return this._dataDir;
131
+ }
132
+ }
133
+ exports.ArpWrapper = ArpWrapper;
@@ -0,0 +1,45 @@
1
+ interface ChatResponse {
2
+ id: string;
3
+ choices: Array<{
4
+ message: {
5
+ role: string;
6
+ content: string;
7
+ };
8
+ finish_reason: string;
9
+ }>;
10
+ }
11
+ interface MCPToolResponse {
12
+ success: boolean;
13
+ content?: string;
14
+ output?: string;
15
+ results?: unknown[];
16
+ note?: string;
17
+ }
18
+ interface HealthResponse {
19
+ status: string;
20
+ agent: string;
21
+ port: number;
22
+ }
23
+ interface StatsResponse {
24
+ totalRequests: number;
25
+ attacksDetected: number;
26
+ attacksSuccessful: number;
27
+ }
28
+ /**
29
+ * HTTP client for DVAA agent endpoints.
30
+ */
31
+ export declare class DVAAClient {
32
+ /** Send a chat message to an API agent */
33
+ chat(port: number, message: string): Promise<ChatResponse>;
34
+ /** Execute an MCP tool on an MCP agent */
35
+ mcpExecute(port: number, tool: string, args: Record<string, unknown>): Promise<MCPToolResponse>;
36
+ /** Send an A2A message */
37
+ a2aMessage(port: number, from: string, message: string): Promise<ChatResponse>;
38
+ /** Health check */
39
+ health(port: number): Promise<HealthResponse>;
40
+ /** Get stats */
41
+ stats(port: number): Promise<StatsResponse>;
42
+ private get;
43
+ private post;
44
+ }
45
+ export {};
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DVAAClient = void 0;
7
+ const http_1 = __importDefault(require("http"));
8
+ /**
9
+ * HTTP client for DVAA agent endpoints.
10
+ */
11
+ class DVAAClient {
12
+ /** Send a chat message to an API agent */
13
+ async chat(port, message) {
14
+ return this.post(port, '/v1/chat/completions', {
15
+ messages: [{ role: 'user', content: message }],
16
+ });
17
+ }
18
+ /** Execute an MCP tool on an MCP agent */
19
+ async mcpExecute(port, tool, args) {
20
+ return this.post(port, '/mcp/execute', {
21
+ tool,
22
+ arguments: args,
23
+ });
24
+ }
25
+ /** Send an A2A message */
26
+ async a2aMessage(port, from, message) {
27
+ return this.post(port, '/v1/chat/completions', {
28
+ messages: [
29
+ { role: 'system', content: `Message from agent: ${from}` },
30
+ { role: 'user', content: message },
31
+ ],
32
+ });
33
+ }
34
+ /** Health check */
35
+ async health(port) {
36
+ return this.get(port, '/health');
37
+ }
38
+ /** Get stats */
39
+ async stats(port) {
40
+ return this.get(port, '/stats');
41
+ }
42
+ get(port, path) {
43
+ return new Promise((resolve, reject) => {
44
+ const req = http_1.default.get(`http://localhost:${port}${path}`, (res) => {
45
+ let body = '';
46
+ res.on('data', (chunk) => { body += chunk; });
47
+ res.on('end', () => {
48
+ try {
49
+ resolve(JSON.parse(body));
50
+ }
51
+ catch {
52
+ reject(new Error(`Invalid JSON from port ${port}${path}: ${body.slice(0, 200)}`));
53
+ }
54
+ });
55
+ });
56
+ req.on('error', reject);
57
+ req.setTimeout(10000, () => {
58
+ req.destroy();
59
+ reject(new Error(`Request to port ${port}${path} timed out`));
60
+ });
61
+ });
62
+ }
63
+ post(port, path, body) {
64
+ const payload = JSON.stringify(body);
65
+ return new Promise((resolve, reject) => {
66
+ const req = http_1.default.request({
67
+ hostname: 'localhost',
68
+ port,
69
+ path,
70
+ method: 'POST',
71
+ headers: {
72
+ 'Content-Type': 'application/json',
73
+ 'Content-Length': Buffer.byteLength(payload),
74
+ },
75
+ }, (res) => {
76
+ let data = '';
77
+ res.on('data', (chunk) => { data += chunk; });
78
+ res.on('end', () => {
79
+ try {
80
+ resolve(JSON.parse(data));
81
+ }
82
+ catch {
83
+ reject(new Error(`Invalid JSON from port ${port}${path}: ${data.slice(0, 200)}`));
84
+ }
85
+ });
86
+ });
87
+ req.on('error', reject);
88
+ req.setTimeout(10000, () => {
89
+ req.destroy();
90
+ reject(new Error(`POST to port ${port}${path} timed out`));
91
+ });
92
+ req.write(payload);
93
+ req.end();
94
+ });
95
+ }
96
+ }
97
+ exports.DVAAClient = DVAAClient;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Manages the DVAA (Damn Vulnerable AI Agent) process lifecycle for integration tests.
3
+ */
4
+ export declare class DVAAManager {
5
+ private process;
6
+ private started;
7
+ /** Start DVAA with all agents */
8
+ start(): Promise<void>;
9
+ /** Stop DVAA gracefully */
10
+ stop(): Promise<void>;
11
+ /** Get the DVAA process PID (for ARP to monitor) */
12
+ getPid(): number | undefined;
13
+ /** Check if DVAA is running */
14
+ isRunning(): boolean;
15
+ private waitForHealth;
16
+ }
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.DVAAManager = void 0;
40
+ const child_process_1 = require("child_process");
41
+ const path = __importStar(require("path"));
42
+ const http_1 = __importDefault(require("http"));
43
+ const DVAA_PATH = path.resolve(__dirname, '../../../damn-vulnerable-ai-agent');
44
+ const HEALTH_CHECK_TIMEOUT = 30000;
45
+ const HEALTH_CHECK_INTERVAL = 500;
46
+ /**
47
+ * Manages the DVAA (Damn Vulnerable AI Agent) process lifecycle for integration tests.
48
+ */
49
+ class DVAAManager {
50
+ constructor() {
51
+ this.process = null;
52
+ this.started = false;
53
+ }
54
+ /** Start DVAA with all agents */
55
+ async start() {
56
+ if (this.started)
57
+ return;
58
+ const entryPoint = path.join(DVAA_PATH, 'src', 'index.js');
59
+ this.process = (0, child_process_1.fork)(entryPoint, [], {
60
+ cwd: DVAA_PATH,
61
+ stdio: 'pipe',
62
+ env: { ...process.env, NODE_ENV: 'test' },
63
+ });
64
+ this.process.on('error', (err) => {
65
+ console.error('DVAA process error:', err.message);
66
+ });
67
+ // Wait for health checks on key ports
68
+ await this.waitForHealth(3000); // Dashboard
69
+ await this.waitForHealth(3001); // SecureBot
70
+ await this.waitForHealth(3003); // LegacyBot
71
+ this.started = true;
72
+ }
73
+ /** Stop DVAA gracefully */
74
+ async stop() {
75
+ if (!this.process || !this.started)
76
+ return;
77
+ return new Promise((resolve) => {
78
+ const timeout = setTimeout(() => {
79
+ if (this.process) {
80
+ this.process.kill('SIGKILL');
81
+ }
82
+ resolve();
83
+ }, 5000);
84
+ this.process.once('exit', () => {
85
+ clearTimeout(timeout);
86
+ resolve();
87
+ });
88
+ this.process.kill('SIGTERM');
89
+ this.started = false;
90
+ this.process = null;
91
+ });
92
+ }
93
+ /** Get the DVAA process PID (for ARP to monitor) */
94
+ getPid() {
95
+ return this.process?.pid;
96
+ }
97
+ /** Check if DVAA is running */
98
+ isRunning() {
99
+ return this.started && this.process !== null;
100
+ }
101
+ waitForHealth(port) {
102
+ const deadline = Date.now() + HEALTH_CHECK_TIMEOUT;
103
+ return new Promise((resolve, reject) => {
104
+ const check = () => {
105
+ if (Date.now() > deadline) {
106
+ reject(new Error(`DVAA health check timed out on port ${port}`));
107
+ return;
108
+ }
109
+ const req = http_1.default.get(`http://localhost:${port}/health`, (res) => {
110
+ if (res.statusCode === 200) {
111
+ res.resume();
112
+ resolve();
113
+ }
114
+ else {
115
+ res.resume();
116
+ setTimeout(check, HEALTH_CHECK_INTERVAL);
117
+ }
118
+ });
119
+ req.on('error', () => {
120
+ setTimeout(check, HEALTH_CHECK_INTERVAL);
121
+ });
122
+ req.setTimeout(2000, () => {
123
+ req.destroy();
124
+ setTimeout(check, HEALTH_CHECK_INTERVAL);
125
+ });
126
+ };
127
+ check();
128
+ });
129
+ }
130
+ }
131
+ exports.DVAAManager = DVAAManager;
@@ -0,0 +1,32 @@
1
+ import type { ARPEvent, EnforcementResult } from '@opena2a/arp';
2
+ /**
3
+ * Collects ARP events and enforcement results for test assertions.
4
+ * Supports async waiting for specific events with timeout.
5
+ */
6
+ export declare class EventCollector {
7
+ private events;
8
+ private enforcements;
9
+ private waiters;
10
+ /** Handler to register on ARP's onEvent */
11
+ readonly eventHandler: (event: ARPEvent) => void;
12
+ /** Handler to register on ARP's onEnforcement */
13
+ readonly enforcementHandler: (result: EnforcementResult) => void;
14
+ /** Wait for an event matching a predicate, with timeout */
15
+ waitForEvent(predicate: (event: ARPEvent) => boolean, timeoutMs?: number): Promise<ARPEvent>;
16
+ /** Check if any event matches a predicate */
17
+ hasEvent(predicate: (event: ARPEvent) => boolean): boolean;
18
+ /** Get all events */
19
+ getEvents(): ARPEvent[];
20
+ /** Get events by category */
21
+ eventsByCategory(category: string): ARPEvent[];
22
+ /** Get events by severity */
23
+ eventsBySeverity(severity: string): ARPEvent[];
24
+ /** Get events by source */
25
+ eventsBySource(source: string): ARPEvent[];
26
+ /** Get all enforcement results */
27
+ getEnforcements(): EnforcementResult[];
28
+ /** Get enforcement results by action */
29
+ enforcementsByAction(action: string): EnforcementResult[];
30
+ /** Reset all collected data */
31
+ reset(): void;
32
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventCollector = void 0;
4
+ /**
5
+ * Collects ARP events and enforcement results for test assertions.
6
+ * Supports async waiting for specific events with timeout.
7
+ */
8
+ class EventCollector {
9
+ constructor() {
10
+ this.events = [];
11
+ this.enforcements = [];
12
+ this.waiters = [];
13
+ /** Handler to register on ARP's onEvent */
14
+ this.eventHandler = (event) => {
15
+ this.events.push(event);
16
+ // Check if any waiters match
17
+ for (let i = this.waiters.length - 1; i >= 0; i--) {
18
+ const waiter = this.waiters[i];
19
+ if (waiter.predicate(event)) {
20
+ clearTimeout(waiter.timer);
21
+ waiter.resolve(event);
22
+ this.waiters.splice(i, 1);
23
+ }
24
+ }
25
+ };
26
+ /** Handler to register on ARP's onEnforcement */
27
+ this.enforcementHandler = (result) => {
28
+ this.enforcements.push(result);
29
+ };
30
+ }
31
+ /** Wait for an event matching a predicate, with timeout */
32
+ waitForEvent(predicate, timeoutMs = 10000) {
33
+ // Check existing events first
34
+ const existing = this.events.find(predicate);
35
+ if (existing)
36
+ return Promise.resolve(existing);
37
+ return new Promise((resolve, reject) => {
38
+ const timer = setTimeout(() => {
39
+ const idx = this.waiters.findIndex((w) => w.resolve === resolve);
40
+ if (idx >= 0)
41
+ this.waiters.splice(idx, 1);
42
+ reject(new Error(`Timed out after ${timeoutMs}ms waiting for event`));
43
+ }, timeoutMs);
44
+ this.waiters.push({ predicate, resolve, timer });
45
+ });
46
+ }
47
+ /** Check if any event matches a predicate */
48
+ hasEvent(predicate) {
49
+ return this.events.some(predicate);
50
+ }
51
+ /** Get all events */
52
+ getEvents() {
53
+ return [...this.events];
54
+ }
55
+ /** Get events by category */
56
+ eventsByCategory(category) {
57
+ return this.events.filter((e) => e.category === category);
58
+ }
59
+ /** Get events by severity */
60
+ eventsBySeverity(severity) {
61
+ return this.events.filter((e) => e.severity === severity);
62
+ }
63
+ /** Get events by source */
64
+ eventsBySource(source) {
65
+ return this.events.filter((e) => e.source === source);
66
+ }
67
+ /** Get all enforcement results */
68
+ getEnforcements() {
69
+ return [...this.enforcements];
70
+ }
71
+ /** Get enforcement results by action */
72
+ enforcementsByAction(action) {
73
+ return this.enforcements.filter((e) => e.action === action);
74
+ }
75
+ /** Reset all collected data */
76
+ reset() {
77
+ this.events = [];
78
+ this.enforcements = [];
79
+ for (const waiter of this.waiters) {
80
+ clearTimeout(waiter.timer);
81
+ }
82
+ this.waiters = [];
83
+ }
84
+ }
85
+ exports.EventCollector = EventCollector;
@@ -0,0 +1,13 @@
1
+ import type { TestResult, TestAnnotation, SuiteMetrics } from './types';
2
+ /**
3
+ * Computes detection effectiveness metrics from test results.
4
+ */
5
+ export declare function computeMetrics(results: TestResult[]): SuiteMetrics;
6
+ /** Create a test annotation for attack scenarios */
7
+ export declare function attackAnnotation(opts: {
8
+ atlasId?: string;
9
+ owaspId?: string;
10
+ expectedSeverity?: 'info' | 'low' | 'medium' | 'high' | 'critical';
11
+ }): TestAnnotation;
12
+ /** Create a test annotation for benign scenarios */
13
+ export declare function benignAnnotation(): TestAnnotation;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeMetrics = computeMetrics;
4
+ exports.attackAnnotation = attackAnnotation;
5
+ exports.benignAnnotation = benignAnnotation;
6
+ /**
7
+ * Computes detection effectiveness metrics from test results.
8
+ */
9
+ function computeMetrics(results) {
10
+ const attacks = results.filter((r) => r.annotation.isAttack);
11
+ const benign = results.filter((r) => !r.annotation.isAttack);
12
+ const truePositives = attacks.filter((r) => r.detected).length;
13
+ const falseNegatives = attacks.filter((r) => !r.detected).length;
14
+ const trueNegatives = benign.filter((r) => !r.detected).length;
15
+ const falsePositives = benign.filter((r) => r.detected).length;
16
+ const detectionTimes = attacks
17
+ .filter((r) => r.detected && r.detectionTimeMs !== undefined)
18
+ .map((r) => r.detectionTimeMs);
19
+ detectionTimes.sort((a, b) => a - b);
20
+ const meanDetectionTimeMs = detectionTimes.length > 0
21
+ ? detectionTimes.reduce((sum, t) => sum + t, 0) / detectionTimes.length
22
+ : 0;
23
+ const p95Index = Math.ceil(detectionTimes.length * 0.95) - 1;
24
+ const p95DetectionTimeMs = detectionTimes.length > 0
25
+ ? detectionTimes[Math.max(0, p95Index)]
26
+ : 0;
27
+ return {
28
+ totalTests: results.length,
29
+ attacks: attacks.length,
30
+ benign: benign.length,
31
+ truePositives,
32
+ falsePositives,
33
+ trueNegatives,
34
+ falseNegatives,
35
+ detectionRate: attacks.length > 0 ? truePositives / attacks.length : 1,
36
+ falsePositiveRate: benign.length > 0 ? falsePositives / benign.length : 0,
37
+ meanDetectionTimeMs,
38
+ p95DetectionTimeMs,
39
+ };
40
+ }
41
+ /** Create a test annotation for attack scenarios */
42
+ function attackAnnotation(opts) {
43
+ return {
44
+ isAttack: true,
45
+ expectedDetection: true,
46
+ ...opts,
47
+ };
48
+ }
49
+ /** Create a test annotation for benign scenarios */
50
+ function benignAnnotation() {
51
+ return {
52
+ isAttack: false,
53
+ expectedDetection: false,
54
+ };
55
+ }
@@ -0,0 +1,33 @@
1
+ import type { LLMAdapter, LLMResponse } from '@opena2a/arp';
2
+ interface MockCall {
3
+ prompt: string;
4
+ maxTokens: number;
5
+ timestamp: number;
6
+ }
7
+ /**
8
+ * Deterministic LLM adapter for testing L2 intelligence layer.
9
+ * Returns structured responses based on input patterns.
10
+ */
11
+ export declare class MockLLMAdapter implements LLMAdapter {
12
+ readonly name = "mock";
13
+ private calls;
14
+ private latencyMs;
15
+ private costPerCall;
16
+ constructor(options?: {
17
+ latencyMs?: number;
18
+ costPerCall?: number;
19
+ });
20
+ assess(prompt: string, maxTokens: number): Promise<LLMResponse>;
21
+ estimateCost(inputTokens: number, outputTokens: number): number;
22
+ healthCheck(): Promise<boolean>;
23
+ /** Get number of calls made */
24
+ getCallCount(): number;
25
+ /** Get all calls for assertions */
26
+ getCalls(): MockCall[];
27
+ /** Get the most recent call */
28
+ getLastCall(): MockCall | undefined;
29
+ /** Reset call history */
30
+ reset(): void;
31
+ private generateResponse;
32
+ }
33
+ export {};