@opena2a/oasb 0.2.0 → 0.3.1

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 (52) hide show
  1. package/README.md +61 -18
  2. package/dist/harness/adapter.d.ts +205 -0
  3. package/dist/harness/adapter.js +18 -0
  4. package/dist/harness/arp-wrapper.d.ts +25 -20
  5. package/dist/harness/arp-wrapper.js +137 -28
  6. package/dist/harness/capabilities.d.ts +26 -0
  7. package/dist/harness/capabilities.js +76 -0
  8. package/dist/harness/create-adapter.d.ts +16 -0
  9. package/dist/harness/create-adapter.js +40 -0
  10. package/dist/harness/event-collector.d.ts +1 -1
  11. package/dist/harness/llm-guard-wrapper.d.ts +32 -0
  12. package/dist/harness/llm-guard-wrapper.js +325 -0
  13. package/dist/harness/mock-llm-adapter.d.ts +2 -2
  14. package/dist/harness/mock-llm-adapter.js +6 -5
  15. package/dist/harness/rebuff-wrapper.d.ts +32 -0
  16. package/dist/harness/rebuff-wrapper.js +325 -0
  17. package/dist/harness/types.d.ts +4 -38
  18. package/package.json +15 -7
  19. package/src/atomic/ai-layer/AT-AI-001.prompt-input-scan.test.ts +18 -42
  20. package/src/atomic/ai-layer/AT-AI-002.prompt-output-scan.test.ts +13 -32
  21. package/src/atomic/ai-layer/AT-AI-003.mcp-tool-scan.test.ts +18 -42
  22. package/src/atomic/ai-layer/AT-AI-004.a2a-message-scan.test.ts +14 -36
  23. package/src/atomic/ai-layer/AT-AI-005.pattern-coverage.test.ts +11 -5
  24. package/src/atomic/enforcement/AT-ENF-001.log-action.test.ts +4 -4
  25. package/src/atomic/enforcement/AT-ENF-002.alert-callback.test.ts +5 -5
  26. package/src/atomic/enforcement/AT-ENF-003.pause-sigstop.test.ts +4 -4
  27. package/src/atomic/enforcement/AT-ENF-004.kill-sigterm.test.ts +5 -5
  28. package/src/atomic/enforcement/AT-ENF-005.resume-sigcont.test.ts +4 -4
  29. package/src/atomic/intelligence/AT-INT-001.l0-rule-match.test.ts +1 -1
  30. package/src/atomic/intelligence/AT-INT-002.l1-anomaly-score.test.ts +10 -8
  31. package/src/atomic/intelligence/AT-INT-003.l2-escalation.test.ts +1 -1
  32. package/src/atomic/intelligence/AT-INT-004.budget-exhaustion.test.ts +8 -6
  33. package/src/atomic/intelligence/AT-INT-005.baseline-learning.test.ts +9 -9
  34. package/src/baseline/BL-002.anomaly-injection.test.ts +6 -6
  35. package/src/baseline/BL-003.baseline-persistence.test.ts +9 -9
  36. package/src/harness/adapter.ts +261 -0
  37. package/src/harness/arp-wrapper.ts +175 -42
  38. package/src/harness/capabilities.ts +79 -0
  39. package/src/harness/create-adapter.ts +53 -0
  40. package/src/harness/event-collector.ts +1 -1
  41. package/src/harness/llm-guard-wrapper.ts +345 -0
  42. package/src/harness/mock-llm-adapter.ts +7 -6
  43. package/src/harness/rebuff-wrapper.ts +343 -0
  44. package/src/harness/types.ts +33 -39
  45. package/src/integration/INT-001.data-exfil-detection.test.ts +1 -1
  46. package/src/integration/INT-002.mcp-tool-abuse.test.ts +1 -1
  47. package/src/integration/INT-003.prompt-injection-response.test.ts +1 -1
  48. package/src/integration/INT-004.a2a-trust-exploitation.test.ts +1 -1
  49. package/src/integration/INT-005.baseline-then-attack.test.ts +1 -1
  50. package/src/integration/INT-006.multi-monitor-correlation.test.ts +1 -1
  51. package/src/integration/INT-007.budget-exhaustion-attack.test.ts +8 -8
  52. package/src/integration/INT-008.kill-switch-recovery.test.ts +6 -6
@@ -8,11 +8,11 @@
8
8
  // so the detector returns to its initial state.
9
9
 
10
10
  import { describe, it, expect } from 'vitest';
11
- import { AnomalyDetector } from '@opena2a/arp';
12
- import type { ARPEvent } from '@opena2a/arp';
11
+ import { createAdapter } from '../../harness/create-adapter';
12
+ import type { SecurityEvent, AnomalyScorer } from '../../harness/adapter';
13
13
 
14
- /** Create a minimal ARPEvent for the given source. */
15
- function makeEvent(source: ARPEvent['source']): ARPEvent {
14
+ /** Create a minimal SecurityEvent for the given source. */
15
+ function makeEvent(source: SecurityEvent['source']): SecurityEvent {
16
16
  return {
17
17
  id: crypto.randomUUID(),
18
18
  timestamp: new Date().toISOString(),
@@ -27,7 +27,7 @@ function makeEvent(source: ARPEvent['source']): ARPEvent {
27
27
 
28
28
  describe('AT-INT-005: Baseline Learning', () => {
29
29
  it('should establish a baseline after recording many events', () => {
30
- const detector = new AnomalyDetector();
30
+ const detector = createAdapter().createAnomalyScorer();
31
31
 
32
32
  // Feed 50 observations to build a solid baseline for the 'network' source
33
33
  for (let i = 0; i < 50; i++) {
@@ -41,7 +41,7 @@ describe('AT-INT-005: Baseline Learning', () => {
41
41
  });
42
42
 
43
43
  it('should return low anomaly score for events matching the baseline', () => {
44
- const detector = new AnomalyDetector();
44
+ const detector = createAdapter().createAnomalyScorer();
45
45
 
46
46
  // Build baseline with consistent frequency
47
47
  for (let i = 0; i < 50; i++) {
@@ -54,7 +54,7 @@ describe('AT-INT-005: Baseline Learning', () => {
54
54
  });
55
55
 
56
56
  it('should track baselines independently per source', () => {
57
- const detector = new AnomalyDetector();
57
+ const detector = createAdapter().createAnomalyScorer();
58
58
 
59
59
  // Build baseline for 'network' only
60
60
  for (let i = 0; i < 50; i++) {
@@ -71,7 +71,7 @@ describe('AT-INT-005: Baseline Learning', () => {
71
71
  });
72
72
 
73
73
  it('should clear all baselines on reset and return score 0', () => {
74
- const detector = new AnomalyDetector();
74
+ const detector = createAdapter().createAnomalyScorer();
75
75
 
76
76
  // Build baselines for two sources
77
77
  for (let i = 0; i < 50; i++) {
@@ -94,7 +94,7 @@ describe('AT-INT-005: Baseline Learning', () => {
94
94
  });
95
95
 
96
96
  it('should differentiate between sources with different baselines', () => {
97
- const detector = new AnomalyDetector();
97
+ const detector = createAdapter().createAnomalyScorer();
98
98
 
99
99
  // Build baseline for 'network' with many events
100
100
  for (let i = 0; i < 50; i++) {
@@ -7,11 +7,11 @@
7
7
  // the detection threshold.
8
8
 
9
9
  import { describe, it, expect, beforeEach } from 'vitest';
10
- import { AnomalyDetector } from '@opena2a/arp';
11
- import type { ARPEvent } from '@opena2a/arp';
10
+ import { createAdapter } from '../harness/create-adapter';
11
+ import type { SecurityEvent, AnomalyScorer } from '../harness/adapter';
12
12
 
13
- /** Helper: create a minimal ARPEvent for a given source */
14
- function makeEvent(source: 'process' | 'network' | 'filesystem', index: number): ARPEvent {
13
+ /** Helper: create a minimal SecurityEvent for a given source */
14
+ function makeEvent(source: 'process' | 'network' | 'filesystem', index: number): SecurityEvent {
15
15
  return {
16
16
  id: `bl002-${source}-${index}`,
17
17
  timestamp: new Date().toISOString(),
@@ -25,10 +25,10 @@ function makeEvent(source: 'process' | 'network' | 'filesystem', index: number):
25
25
  }
26
26
 
27
27
  describe('BL-002: Controlled Anomaly Injection', () => {
28
- let detector: AnomalyDetector;
28
+ let detector: AnomalyScorer;
29
29
 
30
30
  beforeEach(() => {
31
- detector = new AnomalyDetector();
31
+ detector = createAdapter().createAnomalyScorer();
32
32
  });
33
33
 
34
34
  it('should return z-score 0 before baseline is established', () => {
@@ -7,11 +7,11 @@
7
7
  // would need to serialize baselines to disk or a database to survive restarts.
8
8
 
9
9
  import { describe, it, expect, beforeEach } from 'vitest';
10
- import { AnomalyDetector } from '@opena2a/arp';
11
- import type { ARPEvent } from '@opena2a/arp';
10
+ import { createAdapter } from '../harness/create-adapter';
11
+ import type { SecurityEvent, AnomalyScorer } from '../harness/adapter';
12
12
 
13
- /** Helper: create a minimal ARPEvent for a given source */
14
- function makeEvent(source: 'process' | 'network' | 'filesystem', index: number): ARPEvent {
13
+ /** Helper: create a minimal SecurityEvent for a given source */
14
+ function makeEvent(source: 'process' | 'network' | 'filesystem', index: number): SecurityEvent {
15
15
  return {
16
16
  id: `bl003-${source}-${index}`,
17
17
  timestamp: new Date().toISOString(),
@@ -25,10 +25,10 @@ function makeEvent(source: 'process' | 'network' | 'filesystem', index: number):
25
25
  }
26
26
 
27
27
  describe('BL-003: Baseline Persistence Across Restarts', () => {
28
- let detector: AnomalyDetector;
28
+ let detector: AnomalyScorer;
29
29
 
30
30
  beforeEach(() => {
31
- detector = new AnomalyDetector();
31
+ detector = createAdapter().createAnomalyScorer();
32
32
  });
33
33
 
34
34
  it('should accumulate baseline data during a session', () => {
@@ -53,7 +53,7 @@ describe('BL-003: Baseline Persistence Across Restarts', () => {
53
53
  expect(baselineBefore).not.toBeNull();
54
54
 
55
55
  // Simulate restart: create a new AnomalyDetector instance
56
- const restartedDetector = new AnomalyDetector();
56
+ const restartedDetector = createAdapter().createAnomalyScorer();
57
57
 
58
58
  // KNOWN GAP: baseline is lost after restart
59
59
  const baselineAfter = restartedDetector.getBaseline('process');
@@ -88,7 +88,7 @@ describe('BL-003: Baseline Persistence Across Restarts', () => {
88
88
  }
89
89
 
90
90
  // Simulate restart
91
- const restartedDetector = new AnomalyDetector();
91
+ const restartedDetector = createAdapter().createAnomalyScorer();
92
92
 
93
93
  // KNOWN GAP: all baselines lost
94
94
  for (const source of sources) {
@@ -107,7 +107,7 @@ describe('BL-003: Baseline Persistence Across Restarts', () => {
107
107
  const originalMean = originalBaseline!.mean;
108
108
 
109
109
  // Simulate restart
110
- const restartedDetector = new AnomalyDetector();
110
+ const restartedDetector = createAdapter().createAnomalyScorer();
111
111
 
112
112
  // Feed the same number of events to the restarted detector
113
113
  for (let i = 0; i < 50; i++) {
@@ -0,0 +1,261 @@
1
+ /**
2
+ * OASB Security Product Adapter Interface
3
+ *
4
+ * Implement this interface to evaluate your security product against OASB.
5
+ * The reference implementation (ARP adapter) is in arp-wrapper.ts.
6
+ *
7
+ * @example
8
+ * // Vendor implements the adapter for their product:
9
+ * class MyProductAdapter implements SecurityProductAdapter { ... }
10
+ *
11
+ * // OASB tests use the adapter, not your product directly:
12
+ * const adapter = createAdapter(); // returns configured adapter
13
+ * await adapter.start();
14
+ * await adapter.injectEvent({ ... });
15
+ * const threats = adapter.getEventsByCategory('threat');
16
+ */
17
+
18
+ // ─── Core Event Types ───────────────────────────────────────────────
19
+
20
+ export type EventCategory = 'normal' | 'activity' | 'threat' | 'violation';
21
+ export type EventSeverity = 'info' | 'low' | 'medium' | 'high' | 'critical';
22
+ export type MonitorSource = 'process' | 'network' | 'filesystem' | 'prompt' | 'mcp-protocol' | 'a2a-protocol' | string;
23
+ export type EnforcementAction = 'log' | 'alert' | 'pause' | 'kill' | 'resume';
24
+
25
+ export interface SecurityEvent {
26
+ id?: string;
27
+ timestamp?: string;
28
+ source: MonitorSource;
29
+ category: EventCategory;
30
+ severity: EventSeverity;
31
+ description: string;
32
+ data?: Record<string, unknown>;
33
+ classifiedBy?: string;
34
+ }
35
+
36
+ export interface EnforcementResult {
37
+ action: EnforcementAction;
38
+ success: boolean;
39
+ reason: string;
40
+ event: SecurityEvent;
41
+ pid?: number;
42
+ }
43
+
44
+ export interface AlertRule {
45
+ name: string;
46
+ condition: AlertCondition;
47
+ action: EnforcementAction;
48
+ }
49
+
50
+ export interface AlertCondition {
51
+ source?: MonitorSource;
52
+ category?: EventCategory;
53
+ minSeverity?: EventSeverity;
54
+ descriptionContains?: string;
55
+ }
56
+
57
+ // ─── Scanner Types ──────────────────────────────────────────────────
58
+
59
+ export interface ScanResult {
60
+ detected: boolean;
61
+ matches: ScanMatch[];
62
+ truncated?: boolean;
63
+ }
64
+
65
+ export interface ScanMatch {
66
+ pattern: ThreatPattern;
67
+ matchedText: string;
68
+ }
69
+
70
+ export interface ThreatPattern {
71
+ id: string;
72
+ category: string;
73
+ description: string;
74
+ pattern: RegExp;
75
+ severity: 'medium' | 'high' | 'critical';
76
+ }
77
+
78
+ // ─── Scanner Interfaces ─────────────────────────────────────────────
79
+
80
+ export interface PromptScanner {
81
+ start(): Promise<void>;
82
+ stop(): Promise<void>;
83
+ scanInput(text: string): ScanResult;
84
+ scanOutput(text: string): ScanResult;
85
+ }
86
+
87
+ export interface MCPScanner {
88
+ start(): Promise<void>;
89
+ stop(): Promise<void>;
90
+ scanToolCall(toolName: string, params: Record<string, unknown>): ScanResult;
91
+ }
92
+
93
+ export interface A2AScanner {
94
+ start(): Promise<void>;
95
+ stop(): Promise<void>;
96
+ scanMessage(from: string, to: string, content: string): ScanResult;
97
+ }
98
+
99
+ export interface PatternScanner {
100
+ scanText(text: string, patterns: readonly ThreatPattern[]): ScanResult;
101
+ getAllPatterns(): readonly ThreatPattern[];
102
+ getPatternSets(): Record<string, readonly ThreatPattern[]>;
103
+ }
104
+
105
+ // ─── Intelligence Interfaces ────────────────────────────────────────
106
+
107
+ export interface BudgetStatus {
108
+ spent: number;
109
+ budget: number;
110
+ remaining: number;
111
+ percentUsed: number;
112
+ callsThisHour: number;
113
+ maxCallsPerHour: number;
114
+ totalCalls: number;
115
+ }
116
+
117
+ export interface BudgetManager {
118
+ canAfford(estimatedCostUsd: number): boolean;
119
+ record(costUsd: number, tokens: number): void;
120
+ getStatus(): BudgetStatus;
121
+ reset(): void;
122
+ }
123
+
124
+ export interface AnomalyScorer {
125
+ score(event: SecurityEvent): number;
126
+ record(event: SecurityEvent): void;
127
+ getBaseline(source: string): { mean: number; stddev: number; count: number } | null;
128
+ reset(): void;
129
+ }
130
+
131
+ // ─── LLM Adapter (for mock testing) ────────────────────────────────
132
+
133
+ export interface LLMAdapter {
134
+ name: string;
135
+ assess(prompt: string): Promise<LLMResponse>;
136
+ }
137
+
138
+ export interface LLMResponse {
139
+ content: string;
140
+ usage?: { inputTokens: number; outputTokens: number };
141
+ }
142
+
143
+ // ─── Event Engine Interface ─────────────────────────────────────────
144
+
145
+ export interface EventEngine {
146
+ emit(event: Omit<SecurityEvent, 'id' | 'timestamp' | 'classifiedBy'>): SecurityEvent;
147
+ onEvent(handler: (event: SecurityEvent) => void | Promise<void>): void;
148
+ }
149
+
150
+ // ─── Enforcement Interface ──────────────────────────────────────────
151
+
152
+ export interface EnforcementEngine {
153
+ execute(action: EnforcementAction, event: SecurityEvent): Promise<EnforcementResult>;
154
+ pause(pid: number): boolean;
155
+ resume(pid: number): boolean;
156
+ kill(pid: number, signal?: string): boolean;
157
+ getPausedPids(): number[];
158
+ setAlertCallback(callback: (event: SecurityEvent, rule: AlertRule) => void): void;
159
+ }
160
+
161
+ // ─── Capability Declaration ─────────────────────────────────────────
162
+
163
+ /**
164
+ * Capabilities that a security product may or may not support.
165
+ * Adapters declare their capabilities via getCapabilities().
166
+ * Tests check capabilities before running — unsupported tests are
167
+ * marked N/A instead of FAIL, producing an honest scorecard.
168
+ */
169
+ export type Capability =
170
+ | 'process-monitoring'
171
+ | 'network-monitoring'
172
+ | 'filesystem-monitoring'
173
+ | 'prompt-input-scanning'
174
+ | 'prompt-output-scanning'
175
+ | 'mcp-scanning'
176
+ | 'a2a-scanning'
177
+ | 'anomaly-detection'
178
+ | 'budget-management'
179
+ | 'enforcement-log'
180
+ | 'enforcement-alert'
181
+ | 'enforcement-pause'
182
+ | 'enforcement-kill'
183
+ | 'enforcement-resume'
184
+ | 'pattern-scanning'
185
+ | 'event-correlation';
186
+
187
+ /** Full capability declaration for a product */
188
+ export interface CapabilityMatrix {
189
+ /** Product name */
190
+ product: string;
191
+ /** Product version */
192
+ version: string;
193
+ /** Set of supported capabilities */
194
+ capabilities: Set<Capability>;
195
+ }
196
+
197
+ // ─── Main Adapter Interface ─────────────────────────────────────────
198
+
199
+ export interface SecurityProductAdapter {
200
+ /** Declare which capabilities this product supports */
201
+ getCapabilities(): CapabilityMatrix;
202
+
203
+ /** Start the security product */
204
+ start(): Promise<void>;
205
+ /** Stop the security product */
206
+ stop(): Promise<void>;
207
+
208
+ /** Inject a synthetic event for testing */
209
+ injectEvent(event: Omit<SecurityEvent, 'id' | 'timestamp' | 'classifiedBy'>): Promise<SecurityEvent>;
210
+
211
+ /** Wait for an event matching a predicate */
212
+ waitForEvent(predicate: (event: SecurityEvent) => boolean, timeoutMs?: number): Promise<SecurityEvent>;
213
+
214
+ /** Get collected events */
215
+ getEvents(): SecurityEvent[];
216
+ getEventsByCategory(category: EventCategory): SecurityEvent[];
217
+ getEnforcements(): EnforcementResult[];
218
+ getEnforcementsByAction(action: EnforcementAction): EnforcementResult[];
219
+
220
+ /** Reset collected events */
221
+ resetCollector(): void;
222
+
223
+ /** Access sub-components (for tests that need direct access) */
224
+ getEventEngine(): EventEngine;
225
+ getEnforcementEngine(): EnforcementEngine;
226
+
227
+ /** Factory methods for component-level testing */
228
+ createPromptScanner(): PromptScanner;
229
+ createMCPScanner(allowedTools?: string[]): MCPScanner;
230
+ createA2AScanner(trustedAgents?: string[]): A2AScanner;
231
+ createPatternScanner(): PatternScanner;
232
+ createBudgetManager(dataDir: string, config?: { budgetUsd?: number; maxCallsPerHour?: number }): BudgetManager;
233
+ createAnomalyScorer(): AnomalyScorer;
234
+ }
235
+
236
+ // ─── Lab Config ─────────────────────────────────────────────────────
237
+
238
+ export interface LabConfig {
239
+ monitors?: {
240
+ process?: boolean;
241
+ network?: boolean;
242
+ filesystem?: boolean;
243
+ };
244
+ rules?: AlertRule[];
245
+ intelligence?: {
246
+ enabled?: boolean;
247
+ };
248
+ dataDir?: string;
249
+ filesystemWatchPaths?: string[];
250
+ filesystemAllowedPaths?: string[];
251
+ networkAllowedHosts?: string[];
252
+ processIntervalMs?: number;
253
+ networkIntervalMs?: number;
254
+ interceptors?: {
255
+ process?: boolean;
256
+ network?: boolean;
257
+ filesystem?: boolean;
258
+ };
259
+ interceptorNetworkAllowedHosts?: string[];
260
+ interceptorFilesystemAllowedPaths?: string[];
261
+ }
@@ -1,30 +1,55 @@
1
+ /**
2
+ * ARP Adapter — Reference implementation of SecurityProductAdapter
3
+ *
4
+ * Wraps HackMyAgent's ARP (Agent Runtime Protection) for OASB evaluation.
5
+ * Other vendors implement their own adapter against the same interface.
6
+ *
7
+ * Uses lazy require() for arp-guard so the module is only loaded when
8
+ * this adapter is actually selected. Tests that use a different adapter
9
+ * never trigger the arp-guard import.
10
+ */
1
11
  import * as fs from 'fs';
2
12
  import * as os from 'os';
3
13
  import * as path from 'path';
4
- import {
5
- AgentRuntimeProtection,
6
- EventEngine,
7
- EnforcementEngine,
8
- type ARPConfig,
9
- type ARPEvent,
10
- } from '@opena2a/arp';
11
14
  import { EventCollector } from './event-collector';
12
- import type { LabConfig } from './types';
15
+ import type {
16
+ SecurityProductAdapter,
17
+ SecurityEvent,
18
+ EnforcementResult,
19
+ LabConfig,
20
+ PromptScanner,
21
+ MCPScanner,
22
+ A2AScanner,
23
+ PatternScanner,
24
+ BudgetManager,
25
+ AnomalyScorer,
26
+ EventEngine,
27
+ EnforcementEngine as EnforcementEngineInterface,
28
+ ScanResult,
29
+ ThreatPattern,
30
+ CapabilityMatrix,
31
+ } from './adapter';
13
32
 
14
- /**
15
- * Wraps AgentRuntimeProtection for controlled testing.
16
- * Creates temp dataDir per test, registers EventCollector,
17
- * and provides injection + assertion helpers.
18
- */
19
- export class ArpWrapper {
20
- private arp: AgentRuntimeProtection;
33
+ // Lazy-loaded arp-guard module
34
+ let _arp: any;
35
+ function arp(): any {
36
+ if (!_arp) {
37
+ _arp = require('arp-guard');
38
+ }
39
+ return _arp;
40
+ }
41
+
42
+ export class ArpWrapper implements SecurityProductAdapter {
43
+ private _arpInstance: any;
21
44
  private _dataDir: string;
22
45
  readonly collector: EventCollector;
23
46
 
24
47
  constructor(labConfig?: LabConfig) {
25
48
  this._dataDir = labConfig?.dataDir ?? fs.mkdtempSync(path.join(os.tmpdir(), 'arp-lab-'));
26
49
 
27
- const config: ARPConfig = {
50
+ const { AgentRuntimeProtection } = arp();
51
+
52
+ const config = {
28
53
  agentName: 'arp-lab-target',
29
54
  agentDescription: 'Test target for ARP security lab',
30
55
  declaredCapabilities: ['file read/write', 'HTTP requests'],
@@ -63,22 +88,44 @@ export class ArpWrapper {
63
88
  },
64
89
  };
65
90
 
66
- this.arp = new AgentRuntimeProtection(config);
91
+ this._arpInstance = new AgentRuntimeProtection(config);
67
92
  this.collector = new EventCollector();
68
93
 
69
- // Register event and enforcement collectors
70
- this.arp.onEvent(this.collector.eventHandler);
71
- this.arp.onEnforcement(this.collector.enforcementHandler);
94
+ this._arpInstance.onEvent(this.collector.eventHandler);
95
+ this._arpInstance.onEnforcement(this.collector.enforcementHandler);
96
+ }
97
+
98
+ getCapabilities(): CapabilityMatrix {
99
+ return {
100
+ product: 'arp-guard',
101
+ version: arp().VERSION || '0.3.0',
102
+ capabilities: new Set([
103
+ 'process-monitoring',
104
+ 'network-monitoring',
105
+ 'filesystem-monitoring',
106
+ 'prompt-input-scanning',
107
+ 'prompt-output-scanning',
108
+ 'mcp-scanning',
109
+ 'a2a-scanning',
110
+ 'anomaly-detection',
111
+ 'budget-management',
112
+ 'enforcement-log',
113
+ 'enforcement-alert',
114
+ 'enforcement-pause',
115
+ 'enforcement-kill',
116
+ 'enforcement-resume',
117
+ 'pattern-scanning',
118
+ ]),
119
+ };
72
120
  }
73
121
 
74
122
  async start(): Promise<void> {
75
- await this.arp.start();
123
+ await this._arpInstance.start();
76
124
  }
77
125
 
78
126
  async stop(): Promise<void> {
79
- await this.arp.stop();
127
+ await this._arpInstance.stop();
80
128
  this.collector.reset();
81
- // Clean up temp dir
82
129
  try {
83
130
  fs.rmSync(this._dataDir, { recursive: true, force: true });
84
131
  } catch {
@@ -86,36 +133,122 @@ export class ArpWrapper {
86
133
  }
87
134
  }
88
135
 
89
- /** Get the underlying ARP instance */
90
- getInstance(): AgentRuntimeProtection {
91
- return this.arp;
136
+ async injectEvent(event: Omit<SecurityEvent, 'id' | 'timestamp' | 'classifiedBy'>): Promise<SecurityEvent> {
137
+ return this.getEngine().emit(event);
92
138
  }
93
139
 
94
- /** Get the event engine for direct event injection */
95
- getEngine(): EventEngine {
96
- return this.arp.getEngine();
140
+ waitForEvent(predicate: (event: SecurityEvent) => boolean, timeoutMs: number = 10000): Promise<SecurityEvent> {
141
+ return this.collector.waitForEvent(predicate, timeoutMs);
97
142
  }
98
143
 
99
- /** Get the enforcement engine */
100
- getEnforcement(): EnforcementEngine {
101
- return this.arp.getEnforcement();
144
+ getEvents(): SecurityEvent[] {
145
+ return this.collector.getEvents();
102
146
  }
103
147
 
104
- /** Inject a synthetic event into the ARP engine (for testing without real OS activity) */
105
- async injectEvent(event: Omit<ARPEvent, 'id' | 'timestamp' | 'classifiedBy'>): Promise<ARPEvent> {
106
- return this.getEngine().emit(event);
148
+ getEventsByCategory(category: string): SecurityEvent[] {
149
+ return this.collector.eventsByCategory(category);
107
150
  }
108
151
 
109
- /** Wait for an event matching a predicate */
110
- waitForEvent(
111
- predicate: (event: ARPEvent) => boolean,
112
- timeoutMs: number = 10000,
113
- ): Promise<ARPEvent> {
114
- return this.collector.waitForEvent(predicate, timeoutMs);
152
+ getEnforcements(): EnforcementResult[] {
153
+ return this.collector.getEnforcements() as EnforcementResult[];
154
+ }
155
+
156
+ getEnforcementsByAction(action: string): EnforcementResult[] {
157
+ return this.collector.enforcementsByAction(action) as EnforcementResult[];
158
+ }
159
+
160
+ resetCollector(): void {
161
+ this.collector.reset();
162
+ }
163
+
164
+ getInstance(): any {
165
+ return this._arpInstance;
166
+ }
167
+
168
+ getEventEngine(): EventEngine {
169
+ return this._arpInstance.getEngine() as unknown as EventEngine;
170
+ }
171
+
172
+ getEnforcementEngine(): EnforcementEngineInterface {
173
+ return this._arpInstance.getEnforcement() as unknown as EnforcementEngineInterface;
174
+ }
175
+
176
+ getEngine(): any {
177
+ return this._arpInstance.getEngine();
178
+ }
179
+
180
+ getEnforcement(): any {
181
+ return this._arpInstance.getEnforcement();
115
182
  }
116
183
 
117
- /** Get the data directory */
118
184
  get dataDir(): string {
119
185
  return this._dataDir;
120
186
  }
187
+
188
+ // ─── Factory Methods ────────────────────────────────────────────
189
+
190
+ createPromptScanner(): PromptScanner {
191
+ const { EventEngine, PromptInterceptor } = arp();
192
+ const engine = new EventEngine({ agentName: 'oasb-prompt-test' });
193
+ const interceptor = new PromptInterceptor(engine);
194
+ return {
195
+ start: () => interceptor.start(),
196
+ stop: () => interceptor.stop(),
197
+ scanInput: (text: string) => interceptor.scanInput(text),
198
+ scanOutput: (text: string) => interceptor.scanOutput(text),
199
+ };
200
+ }
201
+
202
+ createMCPScanner(allowedTools?: string[]): MCPScanner {
203
+ const { EventEngine, MCPProtocolInterceptor } = arp();
204
+ const engine = new EventEngine({ agentName: 'oasb-mcp-test' });
205
+ const interceptor = new MCPProtocolInterceptor(engine, allowedTools);
206
+ return {
207
+ start: () => interceptor.start(),
208
+ stop: () => interceptor.stop(),
209
+ scanToolCall: (toolName: string, params: Record<string, unknown>) => interceptor.scanToolCall(toolName, params),
210
+ };
211
+ }
212
+
213
+ createA2AScanner(trustedAgents?: string[]): A2AScanner {
214
+ const { EventEngine, A2AProtocolInterceptor } = arp();
215
+ const engine = new EventEngine({ agentName: 'oasb-a2a-test' });
216
+ const interceptor = new A2AProtocolInterceptor(engine, trustedAgents);
217
+ return {
218
+ start: () => interceptor.start(),
219
+ stop: () => interceptor.stop(),
220
+ scanMessage: (from: string, to: string, content: string) => interceptor.scanMessage(from, to, content),
221
+ };
222
+ }
223
+
224
+ createPatternScanner(): PatternScanner {
225
+ const { scanText: _scanText, ALL_PATTERNS: _allPatterns, PATTERN_SETS: _patternSets } = arp();
226
+ return {
227
+ scanText: (text: string, patterns: readonly ThreatPattern[]) => _scanText(text, patterns) as ScanResult,
228
+ getAllPatterns: () => _allPatterns as unknown as readonly ThreatPattern[],
229
+ getPatternSets: () => _patternSets as unknown as Record<string, readonly ThreatPattern[]>,
230
+ };
231
+ }
232
+
233
+ createBudgetManager(dataDir: string, config?: { budgetUsd?: number; maxCallsPerHour?: number }): BudgetManager {
234
+ const { BudgetController } = arp();
235
+ const controller = new BudgetController(dataDir, config);
236
+ return {
237
+ canAfford: (cost: number) => controller.canAfford(cost),
238
+ record: (cost: number, tokens: number) => controller.record(cost, tokens),
239
+ getStatus: () => controller.getStatus(),
240
+ reset: () => controller.reset(),
241
+ };
242
+ }
243
+
244
+ createAnomalyScorer(): AnomalyScorer {
245
+ const { AnomalyDetector } = arp();
246
+ const detector = new AnomalyDetector();
247
+ return {
248
+ score: (event: SecurityEvent) => detector.score(event),
249
+ record: (event: SecurityEvent) => detector.record(event),
250
+ getBaseline: (source: string) => detector.getBaseline(source),
251
+ reset: () => detector.reset(),
252
+ };
253
+ }
121
254
  }