@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,121 @@
1
+ // AT-INT-005: Baseline Learning
2
+ // ATLAS: AML.T0015 (Evasion)
3
+ // OWASP: A04 (Excessive Agency)
4
+ //
5
+ // Verifies that the AnomalyDetector learns a statistical baseline from
6
+ // observed events and can distinguish normal frequency patterns from
7
+ // anomalous ones. Also verifies that reset() clears learned baselines
8
+ // so the detector returns to its initial state.
9
+
10
+ import { describe, it, expect } from 'vitest';
11
+ import { AnomalyDetector } from '@opena2a/arp';
12
+ import type { ARPEvent } from '@opena2a/arp';
13
+
14
+ /** Create a minimal ARPEvent for the given source. */
15
+ function makeEvent(source: ARPEvent['source']): ARPEvent {
16
+ return {
17
+ id: crypto.randomUUID(),
18
+ timestamp: new Date().toISOString(),
19
+ source,
20
+ category: 'normal',
21
+ severity: 'info',
22
+ description: 'Baseline test event',
23
+ data: {},
24
+ classifiedBy: 'L0-rules',
25
+ };
26
+ }
27
+
28
+ describe('AT-INT-005: Baseline Learning', () => {
29
+ it('should establish a baseline after recording many events', () => {
30
+ const detector = new AnomalyDetector();
31
+
32
+ // Feed 50 observations to build a solid baseline for the 'network' source
33
+ for (let i = 0; i < 50; i++) {
34
+ detector.record(makeEvent('network'));
35
+ }
36
+
37
+ const baseline = detector.getBaseline('network');
38
+ expect(baseline).not.toBeNull();
39
+ expect(baseline!.count).toBeGreaterThan(0);
40
+ expect(baseline!.mean).toBeGreaterThan(0);
41
+ });
42
+
43
+ it('should return low anomaly score for events matching the baseline', () => {
44
+ const detector = new AnomalyDetector();
45
+
46
+ // Build baseline with consistent frequency
47
+ for (let i = 0; i < 50; i++) {
48
+ detector.record(makeEvent('network'));
49
+ }
50
+
51
+ // Score a new event from the same source -- should be low
52
+ const score = detector.score(makeEvent('network'));
53
+ expect(score).toBeLessThan(2.0);
54
+ });
55
+
56
+ it('should track baselines independently per source', () => {
57
+ const detector = new AnomalyDetector();
58
+
59
+ // Build baseline for 'network' only
60
+ for (let i = 0; i < 50; i++) {
61
+ detector.record(makeEvent('network'));
62
+ }
63
+
64
+ // 'process' has no baseline
65
+ expect(detector.getBaseline('process')).toBeNull();
66
+ expect(detector.getBaseline('network')).not.toBeNull();
67
+
68
+ // Score for 'process' should be 0 (no data)
69
+ const processScore = detector.score(makeEvent('process'));
70
+ expect(processScore).toBe(0);
71
+ });
72
+
73
+ it('should clear all baselines on reset and return score 0', () => {
74
+ const detector = new AnomalyDetector();
75
+
76
+ // Build baselines for two sources
77
+ for (let i = 0; i < 50; i++) {
78
+ detector.record(makeEvent('network'));
79
+ detector.record(makeEvent('process'));
80
+ }
81
+
82
+ expect(detector.getBaseline('network')).not.toBeNull();
83
+ expect(detector.getBaseline('process')).not.toBeNull();
84
+
85
+ // Reset clears everything
86
+ detector.reset();
87
+
88
+ expect(detector.getBaseline('network')).toBeNull();
89
+ expect(detector.getBaseline('process')).toBeNull();
90
+
91
+ // Scores should return 0 after reset (insufficient data)
92
+ expect(detector.score(makeEvent('network'))).toBe(0);
93
+ expect(detector.score(makeEvent('process'))).toBe(0);
94
+ });
95
+
96
+ it('should differentiate between sources with different baselines', () => {
97
+ const detector = new AnomalyDetector();
98
+
99
+ // Build baseline for 'network' with many events
100
+ for (let i = 0; i < 50; i++) {
101
+ detector.record(makeEvent('network'));
102
+ }
103
+
104
+ // Build baseline for 'filesystem' with fewer events
105
+ for (let i = 0; i < 35; i++) {
106
+ detector.record(makeEvent('filesystem'));
107
+ }
108
+
109
+ const networkBaseline = detector.getBaseline('network');
110
+ const fsBaseline = detector.getBaseline('filesystem');
111
+
112
+ expect(networkBaseline).not.toBeNull();
113
+ expect(fsBaseline).not.toBeNull();
114
+
115
+ // Both sources should have baselines but with different event counts
116
+ // (the mean tracks events-per-minute, which will be similar since all
117
+ // events are in the same minute, but the raw time series differs)
118
+ expect(networkBaseline!.mean).toBeGreaterThan(0);
119
+ expect(fsBaseline!.mean).toBeGreaterThan(0);
120
+ });
121
+ });
@@ -0,0 +1,103 @@
1
+ // AT-NET-001: New Outbound Connection Detection
2
+ // ATLAS: AML.T0024 (Exfiltration via ML Model)
3
+ // OWASP: A07 (System Prompt Leakage)
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { ArpWrapper } from '../../harness/arp-wrapper';
7
+
8
+ describe('AT-NET-001: New Outbound Connection Detection', () => {
9
+ let arp: ArpWrapper;
10
+
11
+ beforeEach(async () => {
12
+ arp = new ArpWrapper({
13
+ monitors: { network: false },
14
+ });
15
+ await arp.start();
16
+ });
17
+
18
+ afterEach(async () => {
19
+ await arp.stop();
20
+ });
21
+
22
+ it('should capture normal outbound connections without flagging them', async () => {
23
+ await arp.injectEvent({
24
+ source: 'network-monitor',
25
+ category: 'connection',
26
+ severity: 'info',
27
+ description: 'New outbound connection to localhost:443',
28
+ data: {
29
+ remoteAddr: 'localhost',
30
+ remotePort: 443,
31
+ protocol: 'tcp',
32
+ direction: 'outbound',
33
+ },
34
+ });
35
+
36
+ const events = arp.collector.eventsByCategory('connection');
37
+ expect(events.length).toBe(1);
38
+ expect(events[0].severity).toBe('info');
39
+ expect(events[0].data.remoteAddr).toBe('localhost');
40
+ });
41
+
42
+ it('should capture anomalous outbound connections to unknown hosts', async () => {
43
+ await arp.injectEvent({
44
+ source: 'network-monitor',
45
+ category: 'anomaly',
46
+ severity: 'warning',
47
+ description: 'New outbound connection to unknown-host:8080',
48
+ data: {
49
+ remoteAddr: 'unknown-host',
50
+ remotePort: 8080,
51
+ protocol: 'tcp',
52
+ direction: 'outbound',
53
+ },
54
+ });
55
+
56
+ const anomalies = arp.collector.eventsByCategory('anomaly');
57
+ expect(anomalies.length).toBe(1);
58
+ expect(anomalies[0].severity).toBe('warning');
59
+ expect(anomalies[0].data.remoteAddr).toBe('unknown-host');
60
+ expect(anomalies[0].data.remotePort).toBe(8080);
61
+ });
62
+
63
+ it('should distinguish normal connections from anomalous ones', async () => {
64
+ await arp.injectEvent({
65
+ source: 'network-monitor',
66
+ category: 'connection',
67
+ severity: 'info',
68
+ description: 'New outbound connection to localhost:443',
69
+ data: {
70
+ remoteAddr: 'localhost',
71
+ remotePort: 443,
72
+ protocol: 'tcp',
73
+ direction: 'outbound',
74
+ },
75
+ });
76
+
77
+ await arp.injectEvent({
78
+ source: 'network-monitor',
79
+ category: 'anomaly',
80
+ severity: 'warning',
81
+ description: 'New outbound connection to unknown-host:8080',
82
+ data: {
83
+ remoteAddr: 'unknown-host',
84
+ remotePort: 8080,
85
+ protocol: 'tcp',
86
+ direction: 'outbound',
87
+ },
88
+ });
89
+
90
+ const allEvents = arp.collector.getEvents();
91
+ expect(allEvents.length).toBe(2);
92
+
93
+ const normalEvents = arp.collector.eventsByCategory('connection');
94
+ expect(normalEvents.length).toBe(1);
95
+
96
+ const anomalyEvents = arp.collector.eventsByCategory('anomaly');
97
+ expect(anomalyEvents.length).toBe(1);
98
+
99
+ expect(
100
+ arp.collector.hasEvent((e) => e.data.remoteAddr === 'unknown-host')
101
+ ).toBe(true);
102
+ });
103
+ });
@@ -0,0 +1,82 @@
1
+ // AT-NET-002: Suspicious Host Detection
2
+ // ATLAS: AML.T0057 (Data Leakage)
3
+ // OWASP: A07 (System Prompt Leakage)
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { ArpWrapper } from '../../harness/arp-wrapper';
7
+
8
+ describe('AT-NET-002: Suspicious Host Detection', () => {
9
+ let arp: ArpWrapper;
10
+
11
+ const suspiciousHosts = [
12
+ { host: 'webhook.site', description: 'webhook exfiltration endpoint' },
13
+ { host: 'pastebin.com', description: 'public paste service' },
14
+ { host: 'ngrok.io', description: 'tunnel service' },
15
+ ];
16
+
17
+ beforeEach(async () => {
18
+ arp = new ArpWrapper({
19
+ monitors: { network: false },
20
+ });
21
+ await arp.start();
22
+ });
23
+
24
+ afterEach(async () => {
25
+ await arp.stop();
26
+ });
27
+
28
+ it.each(suspiciousHosts)(
29
+ 'should flag connection to $host as critical threat',
30
+ async ({ host, description }) => {
31
+ await arp.injectEvent({
32
+ source: 'network-monitor',
33
+ category: 'threat',
34
+ severity: 'critical',
35
+ description: `Connection to suspicious host: ${description}`,
36
+ data: {
37
+ remoteAddr: host,
38
+ remotePort: 443,
39
+ protocol: 'tcp',
40
+ direction: 'outbound',
41
+ },
42
+ });
43
+
44
+ const threats = arp.collector.eventsByCategory('threat');
45
+ expect(threats.length).toBeGreaterThanOrEqual(1);
46
+
47
+ const hostThreat = threats.find((e) => e.data.remoteAddr === host);
48
+ expect(hostThreat).toBeDefined();
49
+ expect(hostThreat!.severity).toBe('critical');
50
+ expect(hostThreat!.category).toBe('threat');
51
+ }
52
+ );
53
+
54
+ it('should capture all suspicious host connections', async () => {
55
+ for (const { host, description } of suspiciousHosts) {
56
+ await arp.injectEvent({
57
+ source: 'network-monitor',
58
+ category: 'threat',
59
+ severity: 'critical',
60
+ description: `Connection to suspicious host: ${description}`,
61
+ data: {
62
+ remoteAddr: host,
63
+ remotePort: 443,
64
+ protocol: 'tcp',
65
+ direction: 'outbound',
66
+ },
67
+ });
68
+ }
69
+
70
+ const threats = arp.collector.eventsByCategory('threat');
71
+ expect(threats.length).toBe(suspiciousHosts.length);
72
+
73
+ const criticalEvents = arp.collector.eventsBySeverity('critical');
74
+ expect(criticalEvents.length).toBe(suspiciousHosts.length);
75
+
76
+ for (const { host } of suspiciousHosts) {
77
+ expect(
78
+ arp.collector.hasEvent((e) => e.data.remoteAddr === host)
79
+ ).toBe(true);
80
+ }
81
+ });
82
+ });
@@ -0,0 +1,91 @@
1
+ // AT-NET-003: Connection Burst Detection
2
+ // ATLAS: AML.T0029 (Denial of Service)
3
+ // OWASP: A06 (Excessive Consumption)
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { ArpWrapper } from '../../harness/arp-wrapper';
7
+
8
+ describe('AT-NET-003: Connection Burst Detection', () => {
9
+ let arp: ArpWrapper;
10
+
11
+ const BURST_COUNT = 15;
12
+
13
+ beforeEach(async () => {
14
+ arp = new ArpWrapper({
15
+ monitors: { network: false },
16
+ });
17
+ await arp.start();
18
+ });
19
+
20
+ afterEach(async () => {
21
+ await arp.stop();
22
+ });
23
+
24
+ it('should capture all events from a rapid connection burst', async () => {
25
+ for (let i = 0; i < BURST_COUNT; i++) {
26
+ await arp.injectEvent({
27
+ source: 'network-monitor',
28
+ category: 'anomaly',
29
+ severity: 'warning',
30
+ description: `Rapid connection burst event ${i + 1}/${BURST_COUNT}`,
31
+ data: {
32
+ remoteAddr: `target-${i}.example.com`,
33
+ remotePort: 443,
34
+ protocol: 'tcp',
35
+ direction: 'outbound',
36
+ burstIndex: i,
37
+ },
38
+ });
39
+ }
40
+
41
+ const allEvents = arp.collector.getEvents();
42
+ expect(allEvents.length).toBe(BURST_COUNT);
43
+ });
44
+
45
+ it('should categorize all burst events as anomalies', async () => {
46
+ for (let i = 0; i < BURST_COUNT; i++) {
47
+ await arp.injectEvent({
48
+ source: 'network-monitor',
49
+ category: 'anomaly',
50
+ severity: 'warning',
51
+ description: `Rapid connection burst event ${i + 1}/${BURST_COUNT}`,
52
+ data: {
53
+ remoteAddr: `target-${i}.example.com`,
54
+ remotePort: 443,
55
+ protocol: 'tcp',
56
+ direction: 'outbound',
57
+ burstIndex: i,
58
+ },
59
+ });
60
+ }
61
+
62
+ const anomalies = arp.collector.eventsByCategory('anomaly');
63
+ expect(anomalies.length).toBe(BURST_COUNT);
64
+
65
+ const warnings = arp.collector.eventsBySeverity('warning');
66
+ expect(warnings.length).toBe(BURST_COUNT);
67
+ });
68
+
69
+ it('should preserve event ordering within a burst', async () => {
70
+ for (let i = 0; i < BURST_COUNT; i++) {
71
+ await arp.injectEvent({
72
+ source: 'network-monitor',
73
+ category: 'anomaly',
74
+ severity: 'warning',
75
+ description: `Rapid connection burst event ${i + 1}/${BURST_COUNT}`,
76
+ data: {
77
+ remoteAddr: `target-${i}.example.com`,
78
+ remotePort: 443,
79
+ protocol: 'tcp',
80
+ direction: 'outbound',
81
+ burstIndex: i,
82
+ },
83
+ });
84
+ }
85
+
86
+ const events = arp.collector.getEvents();
87
+ for (let i = 0; i < BURST_COUNT; i++) {
88
+ expect(events[i].data.burstIndex).toBe(i);
89
+ }
90
+ });
91
+ });
@@ -0,0 +1,129 @@
1
+ // AT-NET-004: Allowed Host Bypass (subdomain attack)
2
+ // ATLAS: AML.T0024
3
+ // OWASP: A04 (Excessive Agency)
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { ArpWrapper } from '../../harness/arp-wrapper';
7
+
8
+ describe('AT-NET-004: Allowed Host Bypass (subdomain attack)', () => {
9
+ let arp: ArpWrapper;
10
+
11
+ beforeEach(async () => {
12
+ arp = new ArpWrapper({
13
+ monitors: { network: false },
14
+ });
15
+ await arp.start();
16
+ });
17
+
18
+ afterEach(async () => {
19
+ await arp.stop();
20
+ });
21
+
22
+ it('should flag evil-api.example.com as anomaly (not a subdomain of api.example.com)', async () => {
23
+ // evil-api.example.com ends with "api.example.com" as a string suffix,
24
+ // but it is NOT a proper subdomain. A naive suffix check would miss this.
25
+ await arp.injectEvent({
26
+ source: 'network-monitor',
27
+ category: 'anomaly',
28
+ severity: 'warning',
29
+ description: 'Connection to host mimicking allowed host via suffix match',
30
+ data: {
31
+ remoteAddr: 'evil-api.example.com',
32
+ remotePort: 443,
33
+ protocol: 'tcp',
34
+ direction: 'outbound',
35
+ allowedHost: 'api.example.com',
36
+ matchResult: 'rejected',
37
+ },
38
+ });
39
+
40
+ const anomalies = arp.collector.eventsByCategory('anomaly');
41
+ expect(anomalies.length).toBe(1);
42
+ expect(anomalies[0].data.remoteAddr).toBe('evil-api.example.com');
43
+ expect(anomalies[0].data.matchResult).toBe('rejected');
44
+
45
+ // Verify it was NOT treated as a normal connection
46
+ const normalConnections = arp.collector.eventsByCategory('connection');
47
+ expect(normalConnections.length).toBe(0);
48
+ });
49
+
50
+ it('should allow sub.api.example.com as a proper subdomain of api.example.com', async () => {
51
+ // sub.api.example.com IS a proper subdomain of api.example.com
52
+ // (preceded by a dot separator)
53
+ await arp.injectEvent({
54
+ source: 'network-monitor',
55
+ category: 'connection',
56
+ severity: 'info',
57
+ description: 'Connection to proper subdomain of allowed host',
58
+ data: {
59
+ remoteAddr: 'sub.api.example.com',
60
+ remotePort: 443,
61
+ protocol: 'tcp',
62
+ direction: 'outbound',
63
+ allowedHost: 'api.example.com',
64
+ matchResult: 'accepted',
65
+ },
66
+ });
67
+
68
+ const connections = arp.collector.eventsByCategory('connection');
69
+ expect(connections.length).toBe(1);
70
+ expect(connections[0].data.remoteAddr).toBe('sub.api.example.com');
71
+ expect(connections[0].data.matchResult).toBe('accepted');
72
+ expect(connections[0].severity).toBe('info');
73
+ });
74
+
75
+ it('should correctly differentiate bypass attempts from legitimate subdomains', async () => {
76
+ // Inject the bypass attempt
77
+ await arp.injectEvent({
78
+ source: 'network-monitor',
79
+ category: 'anomaly',
80
+ severity: 'warning',
81
+ description: 'Suffix-match bypass: evil-api.example.com',
82
+ data: {
83
+ remoteAddr: 'evil-api.example.com',
84
+ remotePort: 443,
85
+ protocol: 'tcp',
86
+ direction: 'outbound',
87
+ allowedHost: 'api.example.com',
88
+ matchResult: 'rejected',
89
+ },
90
+ });
91
+
92
+ // Inject the legitimate subdomain
93
+ await arp.injectEvent({
94
+ source: 'network-monitor',
95
+ category: 'connection',
96
+ severity: 'info',
97
+ description: 'Proper subdomain: sub.api.example.com',
98
+ data: {
99
+ remoteAddr: 'sub.api.example.com',
100
+ remotePort: 443,
101
+ protocol: 'tcp',
102
+ direction: 'outbound',
103
+ allowedHost: 'api.example.com',
104
+ matchResult: 'accepted',
105
+ },
106
+ });
107
+
108
+ const allEvents = arp.collector.getEvents();
109
+ expect(allEvents.length).toBe(2);
110
+
111
+ // The bypass attempt should be an anomaly
112
+ expect(
113
+ arp.collector.hasEvent(
114
+ (e) =>
115
+ e.data.remoteAddr === 'evil-api.example.com' &&
116
+ e.category === 'anomaly'
117
+ )
118
+ ).toBe(true);
119
+
120
+ // The legitimate subdomain should be a normal connection
121
+ expect(
122
+ arp.collector.hasEvent(
123
+ (e) =>
124
+ e.data.remoteAddr === 'sub.api.example.com' &&
125
+ e.category === 'connection'
126
+ )
127
+ ).toBe(true);
128
+ });
129
+ });
@@ -0,0 +1,117 @@
1
+ // AT-NET-005: Exfiltration Destination Detection
2
+ // ATLAS: AML.T0057 (Data Leakage)
3
+ // OWASP: A07 (System Prompt Leakage)
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { ArpWrapper } from '../../harness/arp-wrapper';
7
+
8
+ describe('AT-NET-005: Exfiltration Destination Detection', () => {
9
+ let arp: ArpWrapper;
10
+
11
+ const exfilDestinations = [
12
+ { host: 'transfer.sh', description: 'file transfer service' },
13
+ { host: 'requestbin.com', description: 'HTTP request capture service' },
14
+ { host: 'interact.sh', description: 'OOB interaction service' },
15
+ ];
16
+
17
+ beforeEach(async () => {
18
+ arp = new ArpWrapper({
19
+ monitors: { network: false },
20
+ });
21
+ await arp.start();
22
+ });
23
+
24
+ afterEach(async () => {
25
+ await arp.stop();
26
+ });
27
+
28
+ it.each(exfilDestinations)(
29
+ 'should flag connection to $host as critical exfiltration threat',
30
+ async ({ host, description }) => {
31
+ await arp.injectEvent({
32
+ source: 'network-monitor',
33
+ category: 'threat',
34
+ severity: 'critical',
35
+ description: `Exfiltration destination detected: ${host} (${description})`,
36
+ data: {
37
+ remoteAddr: host,
38
+ remotePort: 443,
39
+ protocol: 'tcp',
40
+ direction: 'outbound',
41
+ threatType: 'exfiltration',
42
+ },
43
+ });
44
+
45
+ const threats = arp.collector.eventsByCategory('threat');
46
+ expect(threats.length).toBeGreaterThanOrEqual(1);
47
+
48
+ const exfilThreat = threats.find((e) => e.data.remoteAddr === host);
49
+ expect(exfilThreat).toBeDefined();
50
+ expect(exfilThreat!.severity).toBe('critical');
51
+ expect(exfilThreat!.category).toBe('threat');
52
+ expect(exfilThreat!.data.threatType).toBe('exfiltration');
53
+ }
54
+ );
55
+
56
+ it('should capture all exfiltration destinations as critical threats', async () => {
57
+ for (const { host, description } of exfilDestinations) {
58
+ await arp.injectEvent({
59
+ source: 'network-monitor',
60
+ category: 'threat',
61
+ severity: 'critical',
62
+ description: `Exfiltration destination detected: ${host} (${description})`,
63
+ data: {
64
+ remoteAddr: host,
65
+ remotePort: 443,
66
+ protocol: 'tcp',
67
+ direction: 'outbound',
68
+ threatType: 'exfiltration',
69
+ },
70
+ });
71
+ }
72
+
73
+ const threats = arp.collector.eventsByCategory('threat');
74
+ expect(threats.length).toBe(exfilDestinations.length);
75
+
76
+ const criticalEvents = arp.collector.eventsBySeverity('critical');
77
+ expect(criticalEvents.length).toBe(exfilDestinations.length);
78
+
79
+ for (const { host } of exfilDestinations) {
80
+ expect(
81
+ arp.collector.hasEvent(
82
+ (e) =>
83
+ e.data.remoteAddr === host && e.data.threatType === 'exfiltration'
84
+ )
85
+ ).toBe(true);
86
+ }
87
+ });
88
+
89
+ it('should tag all exfiltration events with the correct threat type', async () => {
90
+ for (const { host, description } of exfilDestinations) {
91
+ await arp.injectEvent({
92
+ source: 'network-monitor',
93
+ category: 'threat',
94
+ severity: 'critical',
95
+ description: `Exfiltration destination detected: ${host} (${description})`,
96
+ data: {
97
+ remoteAddr: host,
98
+ remotePort: 443,
99
+ protocol: 'tcp',
100
+ direction: 'outbound',
101
+ threatType: 'exfiltration',
102
+ },
103
+ });
104
+ }
105
+
106
+ const allEvents = arp.collector.getEvents();
107
+ const exfilEvents = allEvents.filter(
108
+ (e) => e.data.threatType === 'exfiltration'
109
+ );
110
+ expect(exfilEvents.length).toBe(exfilDestinations.length);
111
+
112
+ for (const event of exfilEvents) {
113
+ expect(event.severity).toBe('critical');
114
+ expect(event.category).toBe('threat');
115
+ }
116
+ });
117
+ });