@opena2a/oasb 0.2.0 → 0.3.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 (46) hide show
  1. package/README.md +57 -16
  2. package/dist/harness/adapter.d.ts +187 -0
  3. package/dist/harness/adapter.js +18 -0
  4. package/dist/harness/arp-wrapper.d.ts +24 -20
  5. package/dist/harness/arp-wrapper.js +114 -28
  6. package/dist/harness/create-adapter.d.ts +16 -0
  7. package/dist/harness/create-adapter.js +36 -0
  8. package/dist/harness/event-collector.d.ts +1 -1
  9. package/dist/harness/llm-guard-wrapper.d.ts +31 -0
  10. package/dist/harness/llm-guard-wrapper.js +315 -0
  11. package/dist/harness/mock-llm-adapter.d.ts +2 -2
  12. package/dist/harness/mock-llm-adapter.js +6 -5
  13. package/dist/harness/types.d.ts +4 -38
  14. package/package.json +15 -7
  15. package/src/atomic/ai-layer/AT-AI-001.prompt-input-scan.test.ts +18 -42
  16. package/src/atomic/ai-layer/AT-AI-002.prompt-output-scan.test.ts +13 -32
  17. package/src/atomic/ai-layer/AT-AI-003.mcp-tool-scan.test.ts +18 -42
  18. package/src/atomic/ai-layer/AT-AI-004.a2a-message-scan.test.ts +14 -36
  19. package/src/atomic/ai-layer/AT-AI-005.pattern-coverage.test.ts +11 -5
  20. package/src/atomic/enforcement/AT-ENF-001.log-action.test.ts +4 -4
  21. package/src/atomic/enforcement/AT-ENF-002.alert-callback.test.ts +5 -5
  22. package/src/atomic/enforcement/AT-ENF-003.pause-sigstop.test.ts +4 -4
  23. package/src/atomic/enforcement/AT-ENF-004.kill-sigterm.test.ts +5 -5
  24. package/src/atomic/enforcement/AT-ENF-005.resume-sigcont.test.ts +4 -4
  25. package/src/atomic/intelligence/AT-INT-001.l0-rule-match.test.ts +1 -1
  26. package/src/atomic/intelligence/AT-INT-002.l1-anomaly-score.test.ts +10 -8
  27. package/src/atomic/intelligence/AT-INT-003.l2-escalation.test.ts +1 -1
  28. package/src/atomic/intelligence/AT-INT-004.budget-exhaustion.test.ts +8 -6
  29. package/src/atomic/intelligence/AT-INT-005.baseline-learning.test.ts +9 -9
  30. package/src/baseline/BL-002.anomaly-injection.test.ts +6 -6
  31. package/src/baseline/BL-003.baseline-persistence.test.ts +9 -9
  32. package/src/harness/adapter.ts +222 -0
  33. package/src/harness/arp-wrapper.ts +150 -42
  34. package/src/harness/create-adapter.ts +49 -0
  35. package/src/harness/event-collector.ts +1 -1
  36. package/src/harness/llm-guard-wrapper.ts +333 -0
  37. package/src/harness/mock-llm-adapter.ts +7 -6
  38. package/src/harness/types.ts +31 -39
  39. package/src/integration/INT-001.data-exfil-detection.test.ts +1 -1
  40. package/src/integration/INT-002.mcp-tool-abuse.test.ts +1 -1
  41. package/src/integration/INT-003.prompt-injection-response.test.ts +1 -1
  42. package/src/integration/INT-004.a2a-trust-exploitation.test.ts +1 -1
  43. package/src/integration/INT-005.baseline-then-attack.test.ts +1 -1
  44. package/src/integration/INT-006.multi-monitor-correlation.test.ts +1 -1
  45. package/src/integration/INT-007.budget-exhaustion-attack.test.ts +8 -8
  46. package/src/integration/INT-008.kill-switch-recovery.test.ts +6 -6
@@ -8,7 +8,7 @@
8
8
  // marks the event for LLM review rather than executing immediate enforcement.
9
9
 
10
10
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
11
- import type { AlertRule } from '@opena2a/arp';
11
+ import type { AlertRule } from '../../harness/adapter';
12
12
  import { ArpWrapper } from '../../harness/arp-wrapper';
13
13
 
14
14
  describe('AT-INT-003: L2 LLM Escalation', () => {
@@ -10,10 +10,12 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
10
10
  import * as fs from 'fs';
11
11
  import * as os from 'os';
12
12
  import * as path from 'path';
13
- import { BudgetController } from '@opena2a/arp';
13
+ import { createAdapter } from '../../harness/create-adapter';
14
+ import type { BudgetManager } from '../../harness/adapter';
14
15
 
15
16
  describe('AT-INT-004: Budget Exhaustion', () => {
16
17
  let dataDir: string;
18
+ const adapter = createAdapter();
17
19
 
18
20
  beforeEach(() => {
19
21
  dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'arp-budget-test-'));
@@ -28,7 +30,7 @@ describe('AT-INT-004: Budget Exhaustion', () => {
28
30
  });
29
31
 
30
32
  it('should allow spending when budget is available', () => {
31
- const budget = new BudgetController(dataDir, {
33
+ const budget = adapter.createBudgetManager(dataDir, {
32
34
  budgetUsd: 0.01,
33
35
  maxCallsPerHour: 5,
34
36
  });
@@ -37,7 +39,7 @@ describe('AT-INT-004: Budget Exhaustion', () => {
37
39
  });
38
40
 
39
41
  it('should deny spending after budget is exhausted', () => {
40
- const budget = new BudgetController(dataDir, {
42
+ const budget = adapter.createBudgetManager(dataDir, {
41
43
  budgetUsd: 0.01,
42
44
  maxCallsPerHour: 100, // High limit so we hit budget cap first
43
45
  });
@@ -53,7 +55,7 @@ describe('AT-INT-004: Budget Exhaustion', () => {
53
55
  });
54
56
 
55
57
  it('should deny spending after hourly call limit is reached', () => {
56
- const budget = new BudgetController(dataDir, {
58
+ const budget = adapter.createBudgetManager(dataDir, {
57
59
  budgetUsd: 100, // Large budget so we hit call cap first
58
60
  maxCallsPerHour: 5,
59
61
  });
@@ -68,7 +70,7 @@ describe('AT-INT-004: Budget Exhaustion', () => {
68
70
  });
69
71
 
70
72
  it('should report correct totals in getStatus()', () => {
71
- const budget = new BudgetController(dataDir, {
73
+ const budget = adapter.createBudgetManager(dataDir, {
72
74
  budgetUsd: 1.0,
73
75
  maxCallsPerHour: 20,
74
76
  });
@@ -88,7 +90,7 @@ describe('AT-INT-004: Budget Exhaustion', () => {
88
90
  });
89
91
 
90
92
  it('should allow spending again after reset', () => {
91
- const budget = new BudgetController(dataDir, {
93
+ const budget = adapter.createBudgetManager(dataDir, {
92
94
  budgetUsd: 0.01,
93
95
  maxCallsPerHour: 5,
94
96
  });
@@ -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,222 @@
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
+ // ─── Main Adapter Interface ─────────────────────────────────────────
162
+
163
+ export interface SecurityProductAdapter {
164
+ /** Start the security product */
165
+ start(): Promise<void>;
166
+ /** Stop the security product */
167
+ stop(): Promise<void>;
168
+
169
+ /** Inject a synthetic event for testing */
170
+ injectEvent(event: Omit<SecurityEvent, 'id' | 'timestamp' | 'classifiedBy'>): Promise<SecurityEvent>;
171
+
172
+ /** Wait for an event matching a predicate */
173
+ waitForEvent(predicate: (event: SecurityEvent) => boolean, timeoutMs?: number): Promise<SecurityEvent>;
174
+
175
+ /** Get collected events */
176
+ getEvents(): SecurityEvent[];
177
+ getEventsByCategory(category: EventCategory): SecurityEvent[];
178
+ getEnforcements(): EnforcementResult[];
179
+ getEnforcementsByAction(action: EnforcementAction): EnforcementResult[];
180
+
181
+ /** Reset collected events */
182
+ resetCollector(): void;
183
+
184
+ /** Access sub-components (for tests that need direct access) */
185
+ getEventEngine(): EventEngine;
186
+ getEnforcementEngine(): EnforcementEngine;
187
+
188
+ /** Factory methods for component-level testing */
189
+ createPromptScanner(): PromptScanner;
190
+ createMCPScanner(allowedTools?: string[]): MCPScanner;
191
+ createA2AScanner(trustedAgents?: string[]): A2AScanner;
192
+ createPatternScanner(): PatternScanner;
193
+ createBudgetManager(dataDir: string, config?: { budgetUsd?: number; maxCallsPerHour?: number }): BudgetManager;
194
+ createAnomalyScorer(): AnomalyScorer;
195
+ }
196
+
197
+ // ─── Lab Config ─────────────────────────────────────────────────────
198
+
199
+ export interface LabConfig {
200
+ monitors?: {
201
+ process?: boolean;
202
+ network?: boolean;
203
+ filesystem?: boolean;
204
+ };
205
+ rules?: AlertRule[];
206
+ intelligence?: {
207
+ enabled?: boolean;
208
+ };
209
+ dataDir?: string;
210
+ filesystemWatchPaths?: string[];
211
+ filesystemAllowedPaths?: string[];
212
+ networkAllowedHosts?: string[];
213
+ processIntervalMs?: number;
214
+ networkIntervalMs?: number;
215
+ interceptors?: {
216
+ process?: boolean;
217
+ network?: boolean;
218
+ filesystem?: boolean;
219
+ };
220
+ interceptorNetworkAllowedHosts?: string[];
221
+ interceptorFilesystemAllowedPaths?: string[];
222
+ }
@@ -1,30 +1,54 @@
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
+ } from './adapter';
13
31
 
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;
32
+ // Lazy-loaded arp-guard module
33
+ let _arp: any;
34
+ function arp(): any {
35
+ if (!_arp) {
36
+ _arp = require('arp-guard');
37
+ }
38
+ return _arp;
39
+ }
40
+
41
+ export class ArpWrapper implements SecurityProductAdapter {
42
+ private _arpInstance: any;
21
43
  private _dataDir: string;
22
44
  readonly collector: EventCollector;
23
45
 
24
46
  constructor(labConfig?: LabConfig) {
25
47
  this._dataDir = labConfig?.dataDir ?? fs.mkdtempSync(path.join(os.tmpdir(), 'arp-lab-'));
26
48
 
27
- const config: ARPConfig = {
49
+ const { AgentRuntimeProtection } = arp();
50
+
51
+ const config = {
28
52
  agentName: 'arp-lab-target',
29
53
  agentDescription: 'Test target for ARP security lab',
30
54
  declaredCapabilities: ['file read/write', 'HTTP requests'],
@@ -63,22 +87,20 @@ export class ArpWrapper {
63
87
  },
64
88
  };
65
89
 
66
- this.arp = new AgentRuntimeProtection(config);
90
+ this._arpInstance = new AgentRuntimeProtection(config);
67
91
  this.collector = new EventCollector();
68
92
 
69
- // Register event and enforcement collectors
70
- this.arp.onEvent(this.collector.eventHandler);
71
- this.arp.onEnforcement(this.collector.enforcementHandler);
93
+ this._arpInstance.onEvent(this.collector.eventHandler);
94
+ this._arpInstance.onEnforcement(this.collector.enforcementHandler);
72
95
  }
73
96
 
74
97
  async start(): Promise<void> {
75
- await this.arp.start();
98
+ await this._arpInstance.start();
76
99
  }
77
100
 
78
101
  async stop(): Promise<void> {
79
- await this.arp.stop();
102
+ await this._arpInstance.stop();
80
103
  this.collector.reset();
81
- // Clean up temp dir
82
104
  try {
83
105
  fs.rmSync(this._dataDir, { recursive: true, force: true });
84
106
  } catch {
@@ -86,36 +108,122 @@ export class ArpWrapper {
86
108
  }
87
109
  }
88
110
 
89
- /** Get the underlying ARP instance */
90
- getInstance(): AgentRuntimeProtection {
91
- return this.arp;
111
+ async injectEvent(event: Omit<SecurityEvent, 'id' | 'timestamp' | 'classifiedBy'>): Promise<SecurityEvent> {
112
+ return this.getEngine().emit(event);
113
+ }
114
+
115
+ waitForEvent(predicate: (event: SecurityEvent) => boolean, timeoutMs: number = 10000): Promise<SecurityEvent> {
116
+ return this.collector.waitForEvent(predicate, timeoutMs);
92
117
  }
93
118
 
94
- /** Get the event engine for direct event injection */
95
- getEngine(): EventEngine {
96
- return this.arp.getEngine();
119
+ getEvents(): SecurityEvent[] {
120
+ return this.collector.getEvents();
97
121
  }
98
122
 
99
- /** Get the enforcement engine */
100
- getEnforcement(): EnforcementEngine {
101
- return this.arp.getEnforcement();
123
+ getEventsByCategory(category: string): SecurityEvent[] {
124
+ return this.collector.eventsByCategory(category);
102
125
  }
103
126
 
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);
127
+ getEnforcements(): EnforcementResult[] {
128
+ return this.collector.getEnforcements() as EnforcementResult[];
107
129
  }
108
130
 
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);
131
+ getEnforcementsByAction(action: string): EnforcementResult[] {
132
+ return this.collector.enforcementsByAction(action) as EnforcementResult[];
133
+ }
134
+
135
+ resetCollector(): void {
136
+ this.collector.reset();
137
+ }
138
+
139
+ getInstance(): any {
140
+ return this._arpInstance;
141
+ }
142
+
143
+ getEventEngine(): EventEngine {
144
+ return this._arpInstance.getEngine() as unknown as EventEngine;
145
+ }
146
+
147
+ getEnforcementEngine(): EnforcementEngineInterface {
148
+ return this._arpInstance.getEnforcement() as unknown as EnforcementEngineInterface;
149
+ }
150
+
151
+ getEngine(): any {
152
+ return this._arpInstance.getEngine();
153
+ }
154
+
155
+ getEnforcement(): any {
156
+ return this._arpInstance.getEnforcement();
115
157
  }
116
158
 
117
- /** Get the data directory */
118
159
  get dataDir(): string {
119
160
  return this._dataDir;
120
161
  }
162
+
163
+ // ─── Factory Methods ────────────────────────────────────────────
164
+
165
+ createPromptScanner(): PromptScanner {
166
+ const { EventEngine, PromptInterceptor } = arp();
167
+ const engine = new EventEngine({ agentName: 'oasb-prompt-test' });
168
+ const interceptor = new PromptInterceptor(engine);
169
+ return {
170
+ start: () => interceptor.start(),
171
+ stop: () => interceptor.stop(),
172
+ scanInput: (text: string) => interceptor.scanInput(text),
173
+ scanOutput: (text: string) => interceptor.scanOutput(text),
174
+ };
175
+ }
176
+
177
+ createMCPScanner(allowedTools?: string[]): MCPScanner {
178
+ const { EventEngine, MCPProtocolInterceptor } = arp();
179
+ const engine = new EventEngine({ agentName: 'oasb-mcp-test' });
180
+ const interceptor = new MCPProtocolInterceptor(engine, allowedTools);
181
+ return {
182
+ start: () => interceptor.start(),
183
+ stop: () => interceptor.stop(),
184
+ scanToolCall: (toolName: string, params: Record<string, unknown>) => interceptor.scanToolCall(toolName, params),
185
+ };
186
+ }
187
+
188
+ createA2AScanner(trustedAgents?: string[]): A2AScanner {
189
+ const { EventEngine, A2AProtocolInterceptor } = arp();
190
+ const engine = new EventEngine({ agentName: 'oasb-a2a-test' });
191
+ const interceptor = new A2AProtocolInterceptor(engine, trustedAgents);
192
+ return {
193
+ start: () => interceptor.start(),
194
+ stop: () => interceptor.stop(),
195
+ scanMessage: (from: string, to: string, content: string) => interceptor.scanMessage(from, to, content),
196
+ };
197
+ }
198
+
199
+ createPatternScanner(): PatternScanner {
200
+ const { scanText: _scanText, ALL_PATTERNS: _allPatterns, PATTERN_SETS: _patternSets } = arp();
201
+ return {
202
+ scanText: (text: string, patterns: readonly ThreatPattern[]) => _scanText(text, patterns) as ScanResult,
203
+ getAllPatterns: () => _allPatterns as unknown as readonly ThreatPattern[],
204
+ getPatternSets: () => _patternSets as unknown as Record<string, readonly ThreatPattern[]>,
205
+ };
206
+ }
207
+
208
+ createBudgetManager(dataDir: string, config?: { budgetUsd?: number; maxCallsPerHour?: number }): BudgetManager {
209
+ const { BudgetController } = arp();
210
+ const controller = new BudgetController(dataDir, config);
211
+ return {
212
+ canAfford: (cost: number) => controller.canAfford(cost),
213
+ record: (cost: number, tokens: number) => controller.record(cost, tokens),
214
+ getStatus: () => controller.getStatus(),
215
+ reset: () => controller.reset(),
216
+ };
217
+ }
218
+
219
+ createAnomalyScorer(): AnomalyScorer {
220
+ const { AnomalyDetector } = arp();
221
+ const detector = new AnomalyDetector();
222
+ return {
223
+ score: (event: SecurityEvent) => detector.score(event),
224
+ record: (event: SecurityEvent) => detector.record(event),
225
+ getBaseline: (source: string) => detector.getBaseline(source),
226
+ reset: () => detector.reset(),
227
+ };
228
+ }
121
229
  }