@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,314 @@
1
+ // INT-008: Kill Switch and Recovery
2
+ // ATLAS: AML.TA0006 (ML Attack Lifecycle)
3
+ // OWASP: A04 (Excessive Agency)
4
+ // Scenario: Critical threat triggers kill, verify process stops, then recovery
5
+ //
6
+ // This test spawns a real child process, uses ARP's enforcement engine
7
+ // to kill it, and verifies:
8
+ // 1. The kill action succeeds and the process terminates
9
+ // 2. ARP itself remains running and can process new events after the kill
10
+ // 3. The enforcement result contains correct metadata
11
+
12
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
13
+ import { spawn, type ChildProcess } from 'child_process';
14
+ import { ArpWrapper } from '../harness/arp-wrapper';
15
+ import type { ARPEvent, AlertRule } from '@opena2a/arp';
16
+
17
+ describe('INT-008: Kill Switch and Recovery', () => {
18
+ let arp: ArpWrapper;
19
+ let childProcess: ChildProcess | null = null;
20
+
21
+ const killRules: AlertRule[] = [
22
+ {
23
+ name: 'critical-threat-kill',
24
+ condition: { category: 'threat', minSeverity: 'critical' },
25
+ action: 'kill',
26
+ },
27
+ {
28
+ name: 'high-violation-alert',
29
+ condition: { category: 'violation', minSeverity: 'high' },
30
+ action: 'alert',
31
+ },
32
+ ];
33
+
34
+ beforeEach(async () => {
35
+ arp = new ArpWrapper({
36
+ monitors: { process: false, network: false, filesystem: false },
37
+ rules: killRules,
38
+ });
39
+ await arp.start();
40
+ });
41
+
42
+ afterEach(async () => {
43
+ // Clean up child process if still running
44
+ if (childProcess && childProcess.pid) {
45
+ try {
46
+ process.kill(childProcess.pid, 0); // Check if alive
47
+ childProcess.kill('SIGKILL');
48
+ } catch {
49
+ // Already dead
50
+ }
51
+ }
52
+ childProcess = null;
53
+ await arp.stop();
54
+ });
55
+
56
+ /**
57
+ * Spawn a simple long-running child process for testing.
58
+ * Uses 'sleep' on unix which blocks indefinitely.
59
+ */
60
+ function spawnTarget(): ChildProcess {
61
+ const child = spawn('sleep', ['300'], {
62
+ stdio: 'ignore',
63
+ detached: false,
64
+ });
65
+ childProcess = child;
66
+ return child;
67
+ }
68
+
69
+ /**
70
+ * Check if a process with the given PID is alive.
71
+ */
72
+ function isProcessAlive(pid: number): boolean {
73
+ try {
74
+ process.kill(pid, 0);
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Wait for a process to terminate (with timeout).
83
+ */
84
+ function waitForProcessExit(pid: number, timeoutMs: number = 5000): Promise<boolean> {
85
+ return new Promise((resolve) => {
86
+ const deadline = Date.now() + timeoutMs;
87
+ const check = () => {
88
+ if (!isProcessAlive(pid)) {
89
+ resolve(true);
90
+ return;
91
+ }
92
+ if (Date.now() > deadline) {
93
+ resolve(false);
94
+ return;
95
+ }
96
+ setTimeout(check, 100);
97
+ };
98
+ check();
99
+ });
100
+ }
101
+
102
+ it('should kill a real child process via enforcement engine', async () => {
103
+ const child = spawnTarget();
104
+ const pid = child.pid!;
105
+
106
+ // Verify child is alive
107
+ expect(isProcessAlive(pid)).toBe(true);
108
+
109
+ // Create a mock event referencing the child PID
110
+ const mockEvent: ARPEvent = {
111
+ id: 'kill-test-001',
112
+ timestamp: new Date().toISOString(),
113
+ source: 'process',
114
+ category: 'threat',
115
+ severity: 'critical',
116
+ description: 'Critical threat: malicious process detected',
117
+ data: { pid },
118
+ classifiedBy: 'L0-rules',
119
+ };
120
+
121
+ // Execute kill action via enforcement engine
122
+ const enforcement = arp.getEnforcement();
123
+ const result = await enforcement.execute('kill', mockEvent, pid);
124
+
125
+ expect(result.action).toBe('kill');
126
+ expect(result.success).toBe(true);
127
+ expect(result.targetPid).toBe(pid);
128
+ expect(result.reason).toContain(`Killed PID ${pid}`);
129
+
130
+ // Wait for process to actually terminate
131
+ const exited = await waitForProcessExit(pid);
132
+ expect(exited).toBe(true);
133
+
134
+ // Process should be dead now
135
+ expect(isProcessAlive(pid)).toBe(false);
136
+ });
137
+
138
+ it('should report failure when trying to kill a non-existent PID', async () => {
139
+ // Use a PID that almost certainly does not exist
140
+ const fakePid = 999999;
141
+
142
+ const mockEvent: ARPEvent = {
143
+ id: 'kill-test-002',
144
+ timestamp: new Date().toISOString(),
145
+ source: 'process',
146
+ category: 'threat',
147
+ severity: 'critical',
148
+ description: 'Kill attempt on non-existent process',
149
+ data: { pid: fakePid },
150
+ classifiedBy: 'L0-rules',
151
+ };
152
+
153
+ const enforcement = arp.getEnforcement();
154
+ const result = await enforcement.execute('kill', mockEvent, fakePid);
155
+
156
+ expect(result.action).toBe('kill');
157
+ expect(result.success).toBe(false);
158
+ expect(result.targetPid).toBe(fakePid);
159
+ expect(result.reason).toContain('Failed to kill');
160
+ });
161
+
162
+ it('should report failure when no PID is provided for kill', async () => {
163
+ const mockEvent: ARPEvent = {
164
+ id: 'kill-test-003',
165
+ timestamp: new Date().toISOString(),
166
+ source: 'process',
167
+ category: 'threat',
168
+ severity: 'critical',
169
+ description: 'Kill attempt without PID',
170
+ data: {},
171
+ classifiedBy: 'L0-rules',
172
+ };
173
+
174
+ const enforcement = arp.getEnforcement();
175
+ const result = await enforcement.execute('kill', mockEvent);
176
+
177
+ expect(result.action).toBe('kill');
178
+ expect(result.success).toBe(false);
179
+ expect(result.reason).toContain('No PID');
180
+ });
181
+
182
+ it('should continue processing events after killing a process', async () => {
183
+ const child = spawnTarget();
184
+ const pid = child.pid!;
185
+
186
+ // Kill the child
187
+ const mockEvent: ARPEvent = {
188
+ id: 'kill-test-004',
189
+ timestamp: new Date().toISOString(),
190
+ source: 'process',
191
+ category: 'threat',
192
+ severity: 'critical',
193
+ description: 'Kill target process',
194
+ data: { pid },
195
+ classifiedBy: 'L0-rules',
196
+ };
197
+
198
+ const enforcement = arp.getEnforcement();
199
+ const killResult = await enforcement.execute('kill', mockEvent, pid);
200
+ expect(killResult.success).toBe(true);
201
+
202
+ // Wait for termination
203
+ await waitForProcessExit(pid);
204
+
205
+ // ARP should still be operational — inject new events
206
+ await arp.injectEvent({
207
+ source: 'network',
208
+ category: 'normal',
209
+ severity: 'info',
210
+ description: 'Post-kill normal event: ARP still running',
211
+ data: { phase: 'recovery', eventAfterKill: true },
212
+ });
213
+
214
+ await arp.injectEvent({
215
+ source: 'process',
216
+ category: 'violation',
217
+ severity: 'high',
218
+ description: 'Post-kill violation: new threat detected',
219
+ data: { phase: 'recovery', newThreat: true },
220
+ });
221
+
222
+ // Verify events were captured after the kill
223
+ const allEvents = arp.collector.getEvents();
224
+ expect(allEvents.length).toBe(2);
225
+
226
+ const normalEvents = arp.collector.eventsByCategory('normal');
227
+ expect(normalEvents.length).toBe(1);
228
+ expect(normalEvents[0].data.eventAfterKill).toBe(true);
229
+
230
+ const violations = arp.collector.eventsByCategory('violation');
231
+ expect(violations.length).toBe(1);
232
+ expect(violations[0].data.newThreat).toBe(true);
233
+
234
+ // Enforcement still works after kill
235
+ const alertActions = arp.collector.enforcementsByAction('alert');
236
+ expect(alertActions.length).toBe(1);
237
+ });
238
+
239
+ it('should handle kill triggered by L0 rule via event injection', async () => {
240
+ const child = spawnTarget();
241
+ const pid = child.pid!;
242
+
243
+ expect(isProcessAlive(pid)).toBe(true);
244
+
245
+ // Inject a critical threat event with the child PID in data
246
+ // The kill rule should fire, but note: L0 rule enforcement
247
+ // calls execute() which looks for data.pid
248
+ await arp.injectEvent({
249
+ source: 'process',
250
+ category: 'threat',
251
+ severity: 'critical',
252
+ description: 'Critical process threat detected',
253
+ data: { pid },
254
+ });
255
+
256
+ // Verify the threat was captured
257
+ const threats = arp.collector.eventsByCategory('threat');
258
+ expect(threats.length).toBe(1);
259
+
260
+ // Verify kill enforcement was triggered by the rule
261
+ const killActions = arp.collector.enforcementsByAction('kill');
262
+ expect(killActions.length).toBe(1);
263
+ expect(killActions[0].reason).toContain('critical-threat-kill');
264
+
265
+ // Wait for the process to terminate (enforcement engine sends SIGTERM)
266
+ const exited = await waitForProcessExit(pid, 8000);
267
+ expect(exited).toBe(true);
268
+ });
269
+
270
+ it('should verify ARP instance itself survives process kill', async () => {
271
+ const child = spawnTarget();
272
+ const pid = child.pid!;
273
+
274
+ // Kill the child via enforcement
275
+ const mockEvent: ARPEvent = {
276
+ id: 'kill-test-006',
277
+ timestamp: new Date().toISOString(),
278
+ source: 'process',
279
+ category: 'threat',
280
+ severity: 'critical',
281
+ description: 'Verify ARP survives kill',
282
+ data: { pid },
283
+ classifiedBy: 'L0-rules',
284
+ };
285
+
286
+ const enforcement = arp.getEnforcement();
287
+ await enforcement.execute('kill', mockEvent, pid);
288
+ await waitForProcessExit(pid);
289
+
290
+ // ARP's underlying instance should still be accessible
291
+ const instance = arp.getInstance();
292
+ expect(instance).toBeDefined();
293
+ expect(instance.isRunning()).toBe(true);
294
+
295
+ // Engine should still accept events
296
+ const engine = arp.getEngine();
297
+ const newEvent = await engine.emit({
298
+ source: 'heartbeat',
299
+ category: 'normal',
300
+ severity: 'info',
301
+ description: 'ARP heartbeat after kill — system operational',
302
+ data: { systemCheck: true },
303
+ });
304
+
305
+ expect(newEvent.id).toBeDefined();
306
+ expect(newEvent.timestamp).toBeDefined();
307
+ expect(newEvent.classifiedBy).toBe('L0-rules');
308
+
309
+ // Collector should capture the heartbeat
310
+ const heartbeats = arp.collector.eventsBySource('heartbeat');
311
+ expect(heartbeats.length).toBe(1);
312
+ expect(heartbeats[0].data.systemCheck).toBe(true);
313
+ });
314
+ });