visus-mcp 0.6.0 → 0.6.2

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 (147) hide show
  1. package/.claude/settings.local.json +42 -1
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +47 -0
  3. package/.github/ISSUE_TEMPLATE/false_positive.md +43 -0
  4. package/.github/ISSUE_TEMPLATE/new_pattern.md +49 -0
  5. package/.github/ISSUE_TEMPLATE/security_report.md +31 -0
  6. package/.github/PULL_REQUEST_TEMPLATE.md +39 -0
  7. package/.mcpregistry_github_token +1 -0
  8. package/.mcpregistry_registry_token +1 -0
  9. package/CLAUDE.md +197 -0
  10. package/CONTRIBUTING.md +329 -0
  11. package/README.md +111 -45
  12. package/STATUS.md +167 -29
  13. package/SUBMISSION.md +66 -0
  14. package/TROUBLESHOOT-COGNITO-AUTH-20260324-2029.md +415 -0
  15. package/TROUBLESHOOT-COGNITO-JWT-20260324.md +592 -0
  16. package/dist/browser/playwright-renderer.d.ts.map +1 -1
  17. package/dist/browser/playwright-renderer.js +71 -51
  18. package/dist/browser/playwright-renderer.js.map +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +49 -6
  21. package/dist/index.js.map +1 -1
  22. package/dist/sanitizer/elicit-runner.d.ts +48 -0
  23. package/dist/sanitizer/elicit-runner.d.ts.map +1 -0
  24. package/dist/sanitizer/elicit-runner.js +100 -0
  25. package/dist/sanitizer/elicit-runner.js.map +1 -0
  26. package/dist/sanitizer/framework-mapper.d.ts +2 -0
  27. package/dist/sanitizer/framework-mapper.d.ts.map +1 -1
  28. package/dist/sanitizer/framework-mapper.js +91 -45
  29. package/dist/sanitizer/framework-mapper.js.map +1 -1
  30. package/dist/sanitizer/hitl-gate.d.ts +69 -0
  31. package/dist/sanitizer/hitl-gate.d.ts.map +1 -0
  32. package/dist/sanitizer/hitl-gate.js +101 -0
  33. package/dist/sanitizer/hitl-gate.js.map +1 -0
  34. package/dist/sanitizer/threat-reporter.d.ts +1 -0
  35. package/dist/sanitizer/threat-reporter.d.ts.map +1 -1
  36. package/dist/sanitizer/threat-reporter.js +10 -7
  37. package/dist/sanitizer/threat-reporter.js.map +1 -1
  38. package/infrastructure/stack.ts +1 -0
  39. package/lambda-deploy/index.js +81512 -0
  40. package/lambda-deploy/index.js.map +7 -0
  41. package/lambda-package/browser/__mocks__/playwright-renderer.d.ts +25 -0
  42. package/lambda-package/browser/__mocks__/playwright-renderer.d.ts.map +1 -0
  43. package/lambda-package/browser/__mocks__/playwright-renderer.js +119 -0
  44. package/lambda-package/browser/__mocks__/playwright-renderer.js.map +1 -0
  45. package/lambda-package/browser/playwright-renderer.d.ts +40 -0
  46. package/lambda-package/browser/playwright-renderer.d.ts.map +1 -0
  47. package/lambda-package/browser/playwright-renderer.js +214 -0
  48. package/lambda-package/browser/playwright-renderer.js.map +1 -0
  49. package/lambda-package/browser/reader.d.ts +31 -0
  50. package/lambda-package/browser/reader.d.ts.map +1 -0
  51. package/lambda-package/browser/reader.js +98 -0
  52. package/lambda-package/browser/reader.js.map +1 -0
  53. package/lambda-package/index.d.ts +18 -0
  54. package/lambda-package/index.d.ts.map +1 -0
  55. package/lambda-package/index.js +238 -0
  56. package/lambda-package/index.js.map +1 -0
  57. package/lambda-package/lambda-handler.d.ts +28 -0
  58. package/lambda-package/lambda-handler.d.ts.map +1 -0
  59. package/lambda-package/lambda-handler.js +257 -0
  60. package/lambda-package/lambda-handler.js.map +1 -0
  61. package/lambda-package/package-lock.json +7435 -0
  62. package/lambda-package/package.json +74 -0
  63. package/lambda-package/runtime.d.ts +50 -0
  64. package/lambda-package/runtime.d.ts.map +1 -0
  65. package/lambda-package/runtime.js +86 -0
  66. package/lambda-package/runtime.js.map +1 -0
  67. package/lambda-package/sanitizer/elicit-runner.d.ts +48 -0
  68. package/lambda-package/sanitizer/elicit-runner.d.ts.map +1 -0
  69. package/lambda-package/sanitizer/elicit-runner.js +100 -0
  70. package/lambda-package/sanitizer/elicit-runner.js.map +1 -0
  71. package/lambda-package/sanitizer/framework-mapper.d.ts +24 -0
  72. package/lambda-package/sanitizer/framework-mapper.d.ts.map +1 -0
  73. package/lambda-package/sanitizer/framework-mapper.js +342 -0
  74. package/lambda-package/sanitizer/framework-mapper.js.map +1 -0
  75. package/lambda-package/sanitizer/hitl-gate.d.ts +69 -0
  76. package/lambda-package/sanitizer/hitl-gate.d.ts.map +1 -0
  77. package/lambda-package/sanitizer/hitl-gate.js +101 -0
  78. package/lambda-package/sanitizer/hitl-gate.js.map +1 -0
  79. package/lambda-package/sanitizer/index.d.ts +63 -0
  80. package/lambda-package/sanitizer/index.d.ts.map +1 -0
  81. package/lambda-package/sanitizer/index.js +105 -0
  82. package/lambda-package/sanitizer/index.js.map +1 -0
  83. package/lambda-package/sanitizer/injection-detector.d.ts +34 -0
  84. package/lambda-package/sanitizer/injection-detector.d.ts.map +1 -0
  85. package/lambda-package/sanitizer/injection-detector.js +89 -0
  86. package/lambda-package/sanitizer/injection-detector.js.map +1 -0
  87. package/lambda-package/sanitizer/patterns.d.ts +30 -0
  88. package/lambda-package/sanitizer/patterns.d.ts.map +1 -0
  89. package/lambda-package/sanitizer/patterns.js +372 -0
  90. package/lambda-package/sanitizer/patterns.js.map +1 -0
  91. package/lambda-package/sanitizer/pii-allowlist.d.ts +49 -0
  92. package/lambda-package/sanitizer/pii-allowlist.d.ts.map +1 -0
  93. package/lambda-package/sanitizer/pii-allowlist.js +231 -0
  94. package/lambda-package/sanitizer/pii-allowlist.js.map +1 -0
  95. package/lambda-package/sanitizer/pii-redactor.d.ts +41 -0
  96. package/lambda-package/sanitizer/pii-redactor.d.ts.map +1 -0
  97. package/lambda-package/sanitizer/pii-redactor.js +213 -0
  98. package/lambda-package/sanitizer/pii-redactor.js.map +1 -0
  99. package/lambda-package/sanitizer/severity-classifier.d.ts +33 -0
  100. package/lambda-package/sanitizer/severity-classifier.d.ts.map +1 -0
  101. package/lambda-package/sanitizer/severity-classifier.js +113 -0
  102. package/lambda-package/sanitizer/severity-classifier.js.map +1 -0
  103. package/lambda-package/sanitizer/threat-reporter.d.ts +66 -0
  104. package/lambda-package/sanitizer/threat-reporter.d.ts.map +1 -0
  105. package/lambda-package/sanitizer/threat-reporter.js +163 -0
  106. package/lambda-package/sanitizer/threat-reporter.js.map +1 -0
  107. package/lambda-package/tools/fetch-structured.d.ts +51 -0
  108. package/lambda-package/tools/fetch-structured.d.ts.map +1 -0
  109. package/lambda-package/tools/fetch-structured.js +237 -0
  110. package/lambda-package/tools/fetch-structured.js.map +1 -0
  111. package/lambda-package/tools/fetch.d.ts +49 -0
  112. package/lambda-package/tools/fetch.d.ts.map +1 -0
  113. package/lambda-package/tools/fetch.js +131 -0
  114. package/lambda-package/tools/fetch.js.map +1 -0
  115. package/lambda-package/tools/read.d.ts +51 -0
  116. package/lambda-package/tools/read.d.ts.map +1 -0
  117. package/lambda-package/tools/read.js +127 -0
  118. package/lambda-package/tools/read.js.map +1 -0
  119. package/lambda-package/tools/search.d.ts +45 -0
  120. package/lambda-package/tools/search.d.ts.map +1 -0
  121. package/lambda-package/tools/search.js +220 -0
  122. package/lambda-package/tools/search.js.map +1 -0
  123. package/lambda-package/types.d.ts +167 -0
  124. package/lambda-package/types.d.ts.map +1 -0
  125. package/lambda-package/types.js +16 -0
  126. package/lambda-package/types.js.map +1 -0
  127. package/lambda-package/utils/format-converter.d.ts +39 -0
  128. package/lambda-package/utils/format-converter.d.ts.map +1 -0
  129. package/lambda-package/utils/format-converter.js +191 -0
  130. package/lambda-package/utils/format-converter.js.map +1 -0
  131. package/lambda-package/utils/truncate.d.ts +26 -0
  132. package/lambda-package/utils/truncate.d.ts.map +1 -0
  133. package/lambda-package/utils/truncate.js +54 -0
  134. package/lambda-package/utils/truncate.js.map +1 -0
  135. package/lambda.zip +0 -0
  136. package/mcp.json +44 -0
  137. package/package.json +9 -8
  138. package/server.json +43 -0
  139. package/src/browser/playwright-renderer.ts +74 -51
  140. package/src/index.ts +78 -6
  141. package/src/sanitizer/elicit-runner.ts +125 -0
  142. package/src/sanitizer/framework-mapper.ts +92 -45
  143. package/src/sanitizer/hitl-gate.ts +111 -0
  144. package/src/sanitizer/threat-reporter.ts +11 -7
  145. package/tests/elicit-runner.test.ts +232 -0
  146. package/tests/hitl-gate.test.ts +267 -0
  147. package/tests/threat-reporter.test.ts +69 -1
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Tests for Elicitation Runner
3
+ *
4
+ * Validates:
5
+ * - User accept/decline/cancel handling
6
+ * - Fail-safe behavior on errors
7
+ * - Threat report inclusion logic
8
+ */
9
+
10
+ import { runElicitation } from '../src/sanitizer/elicit-runner.js';
11
+ import type { ThreatReport } from '../src/sanitizer/threat-reporter.js';
12
+ import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
13
+
14
+ describe('Elicitation Runner', () => {
15
+ let mockServer: jest.Mocked<Server>;
16
+
17
+ beforeEach(() => {
18
+ // Create a mock server with elicitInput method
19
+ mockServer = {
20
+ elicitInput: jest.fn()
21
+ } as any;
22
+ });
23
+
24
+ const createMockThreatReport = (): ThreatReport => ({
25
+ generated: new Date().toISOString(),
26
+ source_url: 'https://malicious.example.com',
27
+ overall_severity: 'CRITICAL',
28
+ total_findings: 5,
29
+ by_severity: { CRITICAL: 5, HIGH: 0, MEDIUM: 0, LOW: 0 },
30
+ pii_redacted: 0,
31
+ sanitization_applied: true,
32
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
33
+ findings_toon: 'findings[5]{id,pattern_id,category,severity,confidence,owasp_llm,nist_ai_600_1,mitre_atlas,remediation}:\n1,PI-001,role_hijacking,CRITICAL,0.95,LLM01:2025 - Prompt Injection,MS-2.3,AML.T0051.000 - LLM Prompt Injection,Content sanitized',
34
+ report_markdown: '# Report'
35
+ });
36
+
37
+ describe('User response handling', () => {
38
+ it('returns proceed:true when user accepts with proceed:true', async () => {
39
+ const threatReport = createMockThreatReport();
40
+
41
+ mockServer.elicitInput.mockResolvedValue({
42
+ action: 'accept',
43
+ content: {
44
+ proceed: true,
45
+ view_report: true
46
+ }
47
+ });
48
+
49
+ const result = await runElicitation(
50
+ mockServer,
51
+ threatReport,
52
+ 'https://malicious.example.com'
53
+ );
54
+
55
+ expect(result.proceed).toBe(true);
56
+ expect(result.includeReport).toBe(true);
57
+ });
58
+
59
+ it('returns proceed:false when user accepts with proceed:false', async () => {
60
+ const threatReport = createMockThreatReport();
61
+
62
+ mockServer.elicitInput.mockResolvedValue({
63
+ action: 'accept',
64
+ content: {
65
+ proceed: false,
66
+ view_report: true
67
+ }
68
+ });
69
+
70
+ const result = await runElicitation(
71
+ mockServer,
72
+ threatReport,
73
+ 'https://malicious.example.com'
74
+ );
75
+
76
+ expect(result.proceed).toBe(false);
77
+ expect(result.includeReport).toBe(false); // Report not included when not proceeding
78
+ });
79
+
80
+ it('returns proceed:false on decline action', async () => {
81
+ const threatReport = createMockThreatReport();
82
+
83
+ mockServer.elicitInput.mockResolvedValue({
84
+ action: 'decline',
85
+ content: undefined
86
+ });
87
+
88
+ const result = await runElicitation(
89
+ mockServer,
90
+ threatReport,
91
+ 'https://malicious.example.com'
92
+ );
93
+
94
+ expect(result.proceed).toBe(false);
95
+ expect(result.includeReport).toBe(false);
96
+ });
97
+
98
+ it('returns proceed:false on cancel action', async () => {
99
+ const threatReport = createMockThreatReport();
100
+
101
+ mockServer.elicitInput.mockResolvedValue({
102
+ action: 'cancel',
103
+ content: undefined
104
+ });
105
+
106
+ const result = await runElicitation(
107
+ mockServer,
108
+ threatReport,
109
+ 'https://malicious.example.com'
110
+ );
111
+
112
+ expect(result.proceed).toBe(false);
113
+ expect(result.includeReport).toBe(false);
114
+ });
115
+
116
+ it('includes report when user checks view_report', async () => {
117
+ const threatReport = createMockThreatReport();
118
+
119
+ mockServer.elicitInput.mockResolvedValue({
120
+ action: 'accept',
121
+ content: {
122
+ proceed: true,
123
+ view_report: true
124
+ }
125
+ });
126
+
127
+ const result = await runElicitation(
128
+ mockServer,
129
+ threatReport,
130
+ 'https://malicious.example.com'
131
+ );
132
+
133
+ expect(result.proceed).toBe(true);
134
+ expect(result.includeReport).toBe(true);
135
+ });
136
+
137
+ it('excludes report when user unchecks view_report', async () => {
138
+ const threatReport = createMockThreatReport();
139
+
140
+ mockServer.elicitInput.mockResolvedValue({
141
+ action: 'accept',
142
+ content: {
143
+ proceed: true,
144
+ view_report: false
145
+ }
146
+ });
147
+
148
+ const result = await runElicitation(
149
+ mockServer,
150
+ threatReport,
151
+ 'https://malicious.example.com'
152
+ );
153
+
154
+ expect(result.proceed).toBe(true);
155
+ expect(result.includeReport).toBe(false);
156
+ });
157
+
158
+ it('defaults to including report when view_report is undefined', async () => {
159
+ const threatReport = createMockThreatReport();
160
+
161
+ mockServer.elicitInput.mockResolvedValue({
162
+ action: 'accept',
163
+ content: {
164
+ proceed: true
165
+ }
166
+ });
167
+
168
+ const result = await runElicitation(
169
+ mockServer,
170
+ threatReport,
171
+ 'https://malicious.example.com'
172
+ );
173
+
174
+ expect(result.proceed).toBe(true);
175
+ expect(result.includeReport).toBe(true);
176
+ });
177
+ });
178
+
179
+ describe('Fail-safe behavior', () => {
180
+ it('proceeds with sanitized content on elicitation error (fail-safe)', async () => {
181
+ const threatReport = createMockThreatReport();
182
+
183
+ mockServer.elicitInput.mockRejectedValue(
184
+ new Error('Elicitation not supported')
185
+ );
186
+
187
+ const result = await runElicitation(
188
+ mockServer,
189
+ threatReport,
190
+ 'https://malicious.example.com'
191
+ );
192
+
193
+ expect(result.proceed).toBe(true);
194
+ expect(result.includeReport).toBe(true);
195
+ });
196
+
197
+ it('proceeds with sanitized content on timeout (fail-safe)', async () => {
198
+ const threatReport = createMockThreatReport();
199
+
200
+ mockServer.elicitInput.mockRejectedValue(
201
+ new Error('Request timeout')
202
+ );
203
+
204
+ const result = await runElicitation(
205
+ mockServer,
206
+ threatReport,
207
+ 'https://malicious.example.com'
208
+ );
209
+
210
+ expect(result.proceed).toBe(true);
211
+ expect(result.includeReport).toBe(true);
212
+ });
213
+
214
+ it('proceeds with sanitized content on unknown action (fail-safe)', async () => {
215
+ const threatReport = createMockThreatReport();
216
+
217
+ mockServer.elicitInput.mockResolvedValue({
218
+ action: 'unknown_action' as any,
219
+ content: undefined
220
+ });
221
+
222
+ const result = await runElicitation(
223
+ mockServer,
224
+ threatReport,
225
+ 'https://malicious.example.com'
226
+ );
227
+
228
+ expect(result.proceed).toBe(true);
229
+ expect(result.includeReport).toBe(true);
230
+ });
231
+ });
232
+ });
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Tests for HITL (Human-in-the-Loop) Gate
3
+ *
4
+ * Validates:
5
+ * - shouldElicit decision logic
6
+ * - buildElicitMessage formatting
7
+ * - ElicitSchema structure compliance
8
+ */
9
+
10
+ import { shouldElicit, buildElicitMessage, ElicitSchema } from '../src/sanitizer/hitl-gate.js';
11
+ import type { ThreatReport } from '../src/sanitizer/threat-reporter.js';
12
+
13
+ describe('HITL Gate', () => {
14
+ describe('shouldElicit', () => {
15
+ it('returns true for CRITICAL severity report with findings', () => {
16
+ const report: ThreatReport = {
17
+ generated: new Date().toISOString(),
18
+ source_url: 'https://malicious.example.com',
19
+ overall_severity: 'CRITICAL',
20
+ total_findings: 5,
21
+ by_severity: { CRITICAL: 5, HIGH: 0, MEDIUM: 0, LOW: 0 },
22
+ pii_redacted: 0,
23
+ sanitization_applied: true,
24
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
25
+ findings_toon: 'findings[5]{id,pattern_id,category,severity,confidence,owasp_llm,nist_ai_600_1,mitre_atlas,remediation}:\n1,PI-001,role_hijacking,CRITICAL,0.95,LLM01:2025,MS-2.3,AML.T0051.000,Content sanitized',
26
+ report_markdown: '# Report'
27
+ };
28
+
29
+ expect(shouldElicit(report)).toBe(true);
30
+ });
31
+
32
+ it('returns false for HIGH severity report', () => {
33
+ const report: ThreatReport = {
34
+ generated: new Date().toISOString(),
35
+ source_url: 'https://example.com',
36
+ overall_severity: 'HIGH',
37
+ total_findings: 3,
38
+ by_severity: { CRITICAL: 0, HIGH: 3, MEDIUM: 0, LOW: 0 },
39
+ pii_redacted: 0,
40
+ sanitization_applied: true,
41
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
42
+ findings_toon: '',
43
+ report_markdown: ''
44
+ };
45
+
46
+ expect(shouldElicit(report)).toBe(false);
47
+ });
48
+
49
+ it('returns false for MEDIUM severity report', () => {
50
+ const report: ThreatReport = {
51
+ generated: new Date().toISOString(),
52
+ source_url: 'https://example.com',
53
+ overall_severity: 'MEDIUM',
54
+ total_findings: 2,
55
+ by_severity: { CRITICAL: 0, HIGH: 0, MEDIUM: 2, LOW: 0 },
56
+ pii_redacted: 0,
57
+ sanitization_applied: true,
58
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
59
+ findings_toon: '',
60
+ report_markdown: ''
61
+ };
62
+
63
+ expect(shouldElicit(report)).toBe(false);
64
+ });
65
+
66
+ it('returns false for LOW severity report', () => {
67
+ const report: ThreatReport = {
68
+ generated: new Date().toISOString(),
69
+ source_url: 'https://example.com',
70
+ overall_severity: 'LOW',
71
+ total_findings: 1,
72
+ by_severity: { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 1 },
73
+ pii_redacted: 0,
74
+ sanitization_applied: true,
75
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
76
+ findings_toon: '',
77
+ report_markdown: ''
78
+ };
79
+
80
+ expect(shouldElicit(report)).toBe(false);
81
+ });
82
+
83
+ it('returns false for CLEAN report', () => {
84
+ const report: ThreatReport = {
85
+ generated: new Date().toISOString(),
86
+ source_url: 'https://example.com',
87
+ overall_severity: 'CLEAN',
88
+ total_findings: 0,
89
+ by_severity: { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 },
90
+ pii_redacted: 0,
91
+ sanitization_applied: false,
92
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
93
+ findings_toon: '',
94
+ report_markdown: ''
95
+ };
96
+
97
+ expect(shouldElicit(report)).toBe(false);
98
+ });
99
+
100
+ it('returns false for null report', () => {
101
+ expect(shouldElicit(null)).toBe(false);
102
+ });
103
+
104
+ it('returns false for CRITICAL severity with zero findings', () => {
105
+ const report: ThreatReport = {
106
+ generated: new Date().toISOString(),
107
+ source_url: 'https://example.com',
108
+ overall_severity: 'CRITICAL',
109
+ total_findings: 0,
110
+ by_severity: { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 },
111
+ pii_redacted: 0,
112
+ sanitization_applied: false,
113
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
114
+ findings_toon: '',
115
+ report_markdown: ''
116
+ };
117
+
118
+ expect(shouldElicit(report)).toBe(false);
119
+ });
120
+ });
121
+
122
+ describe('buildElicitMessage', () => {
123
+ it('contains URL in output', () => {
124
+ const report: ThreatReport = {
125
+ generated: new Date().toISOString(),
126
+ source_url: 'https://malicious.example.com',
127
+ overall_severity: 'CRITICAL',
128
+ total_findings: 2,
129
+ by_severity: { CRITICAL: 2, HIGH: 0, MEDIUM: 0, LOW: 0 },
130
+ pii_redacted: 0,
131
+ sanitization_applied: true,
132
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
133
+ findings_toon: 'findings[2]{id,pattern_id,category,severity,confidence,owasp_llm,nist_ai_600_1,mitre_atlas,remediation}:\n1,PI-001,role_hijacking,CRITICAL,0.95,LLM01:2025 - Prompt Injection,MS-2.3,AML.T0051.000 - LLM Prompt Injection,Content sanitized\n2,PI-002,system_prompt_leak,CRITICAL,0.90,LLM01:2025 - Prompt Injection,MS-2.3,AML.T0051.001 - LLM Jailbreak,Content sanitized',
134
+ report_markdown: ''
135
+ };
136
+
137
+ const url = 'https://malicious.example.com';
138
+ const message = buildElicitMessage(report, url);
139
+
140
+ expect(message).toContain(url);
141
+ });
142
+
143
+ it('contains finding count', () => {
144
+ const report: ThreatReport = {
145
+ generated: new Date().toISOString(),
146
+ source_url: 'https://malicious.example.com',
147
+ overall_severity: 'CRITICAL',
148
+ total_findings: 3,
149
+ by_severity: { CRITICAL: 3, HIGH: 0, MEDIUM: 0, LOW: 0 },
150
+ pii_redacted: 0,
151
+ sanitization_applied: true,
152
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
153
+ findings_toon: 'findings[3]{id,pattern_id,category,severity,confidence,owasp_llm,nist_ai_600_1,mitre_atlas,remediation}:\n1,PI-001,role_hijacking,CRITICAL,0.95,LLM01:2025 - Prompt Injection,MS-2.3,AML.T0051.000 - LLM Prompt Injection,Content sanitized',
154
+ report_markdown: ''
155
+ };
156
+
157
+ const message = buildElicitMessage(report, 'https://example.com');
158
+
159
+ expect(message).toContain('3');
160
+ expect(message).toContain('injection attempt(s)');
161
+ });
162
+
163
+ it('is under 300 characters', () => {
164
+ const report: ThreatReport = {
165
+ generated: new Date().toISOString(),
166
+ source_url: 'https://malicious.example.com',
167
+ overall_severity: 'CRITICAL',
168
+ total_findings: 5,
169
+ by_severity: { CRITICAL: 5, HIGH: 0, MEDIUM: 0, LOW: 0 },
170
+ pii_redacted: 0,
171
+ sanitization_applied: true,
172
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
173
+ findings_toon: 'findings[5]{id,pattern_id,category,severity,confidence,owasp_llm,nist_ai_600_1,mitre_atlas,remediation}:\n1,PI-001,role_hijacking,CRITICAL,0.95,LLM01:2025 - Prompt Injection,MS-2.3,AML.T0051.000 - LLM Prompt Injection,Content sanitized',
174
+ report_markdown: ''
175
+ };
176
+
177
+ const message = buildElicitMessage(report, 'https://example.com');
178
+
179
+ expect(message.length).toBeLessThan(300);
180
+ });
181
+
182
+ it('contains top category', () => {
183
+ const report: ThreatReport = {
184
+ generated: new Date().toISOString(),
185
+ source_url: 'https://malicious.example.com',
186
+ overall_severity: 'CRITICAL',
187
+ total_findings: 1,
188
+ by_severity: { CRITICAL: 1, HIGH: 0, MEDIUM: 0, LOW: 0 },
189
+ pii_redacted: 0,
190
+ sanitization_applied: true,
191
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
192
+ findings_toon: 'findings[1]{id,pattern_id,category,severity,confidence,owasp_llm,nist_ai_600_1,mitre_atlas,remediation}:\n1,PI-001,role_hijacking,CRITICAL,0.95,LLM01:2025 - Prompt Injection,MS-2.3,AML.T0051.000 - LLM Prompt Injection,Content sanitized',
193
+ report_markdown: ''
194
+ };
195
+
196
+ const message = buildElicitMessage(report, 'https://example.com');
197
+
198
+ expect(message).toContain('role_hijacking');
199
+ });
200
+
201
+ it('contains OWASP and MITRE identifiers', () => {
202
+ const report: ThreatReport = {
203
+ generated: new Date().toISOString(),
204
+ source_url: 'https://malicious.example.com',
205
+ overall_severity: 'CRITICAL',
206
+ total_findings: 1,
207
+ by_severity: { CRITICAL: 1, HIGH: 0, MEDIUM: 0, LOW: 0 },
208
+ pii_redacted: 0,
209
+ sanitization_applied: true,
210
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
211
+ findings_toon: 'findings[1]{id,pattern_id,category,severity,confidence,owasp_llm,nist_ai_600_1,mitre_atlas,remediation}:\n1,PI-001,role_hijacking,CRITICAL,0.95,LLM01:2025 - Prompt Injection,MS-2.3,AML.T0051.000 - LLM Prompt Injection,Content sanitized',
212
+ report_markdown: ''
213
+ };
214
+
215
+ const message = buildElicitMessage(report, 'https://example.com');
216
+
217
+ expect(message).toContain('LLM01:2025');
218
+ expect(message).toContain('AML.T0051.000');
219
+ });
220
+
221
+ it('handles empty findings gracefully', () => {
222
+ const report: ThreatReport = {
223
+ generated: new Date().toISOString(),
224
+ source_url: 'https://malicious.example.com',
225
+ overall_severity: 'CRITICAL',
226
+ total_findings: 0,
227
+ by_severity: { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 },
228
+ pii_redacted: 0,
229
+ sanitization_applied: true,
230
+ frameworks: ['OWASP LLM Top 10', 'NIST AI 600-1', 'MITRE ATLAS'],
231
+ findings_toon: 'findings[0]{id,pattern_id,category,severity,confidence,owasp_llm,nist_ai_600_1,mitre_atlas,remediation}:',
232
+ report_markdown: ''
233
+ };
234
+
235
+ const message = buildElicitMessage(report, 'https://example.com');
236
+
237
+ expect(message).toContain('unknown');
238
+ expect(message).toContain('N/A');
239
+ });
240
+ });
241
+
242
+ describe('ElicitSchema', () => {
243
+ it('has flat primitive properties only', () => {
244
+ expect(ElicitSchema.type).toBe('object');
245
+ expect(ElicitSchema.properties.proceed.type).toBe('boolean');
246
+ expect(ElicitSchema.properties.view_report.type).toBe('boolean');
247
+
248
+ // Verify no nested objects
249
+ const proceedProp = ElicitSchema.properties.proceed;
250
+ const viewReportProp = ElicitSchema.properties.view_report;
251
+
252
+ expect(typeof proceedProp.type).toBe('string');
253
+ expect(typeof viewReportProp.type).toBe('string');
254
+ });
255
+
256
+ it('has required array containing proceed', () => {
257
+ expect(ElicitSchema.required).toContain('proceed');
258
+ });
259
+
260
+ it('has descriptive titles and descriptions', () => {
261
+ expect(ElicitSchema.properties.proceed.title).toBeTruthy();
262
+ expect(ElicitSchema.properties.proceed.description).toBeTruthy();
263
+ expect(ElicitSchema.properties.view_report.title).toBeTruthy();
264
+ expect(ElicitSchema.properties.view_report.description).toBeTruthy();
265
+ });
266
+ });
267
+ });
@@ -134,7 +134,7 @@ describe('Threat Reporter', () => {
134
134
  }
135
135
  });
136
136
 
137
- it('should include all three frameworks', () => {
137
+ it('should include all four frameworks', () => {
138
138
  const result = generateThreatReport({
139
139
  patterns_detected: ['role_hijacking'],
140
140
  pii_redacted: 0,
@@ -146,6 +146,7 @@ describe('Threat Reporter', () => {
146
146
  expect(result.frameworks).toContain('OWASP LLM Top 10');
147
147
  expect(result.frameworks).toContain('NIST AI 600-1');
148
148
  expect(result.frameworks).toContain('MITRE ATLAS');
149
+ expect(result.frameworks).toContain('ISO/IEC 42001');
149
150
  }
150
151
  });
151
152
 
@@ -247,6 +248,7 @@ describe('Threat Reporter', () => {
247
248
  expect(mappings.owasp_llm).toContain('LLM01:2025');
248
249
  expect(mappings.nist_ai_600_1).toContain('MS-2.5');
249
250
  expect(mappings.mitre_atlas).toContain('AML.T0051');
251
+ expect(mappings.iso_42001).toBe('A.6.1.5 - AI System Security (Adversarial Input)');
250
252
  });
251
253
 
252
254
  it('should map data_exfiltration to correct frameworks', () => {
@@ -254,6 +256,7 @@ describe('Threat Reporter', () => {
254
256
  expect(mappings.owasp_llm).toContain('LLM02:2025');
255
257
  expect(mappings.nist_ai_600_1).toContain('MS-2.6');
256
258
  expect(mappings.mitre_atlas).toContain('AML.T0048');
259
+ expect(mappings.iso_42001).toContain('A.7.5');
257
260
  });
258
261
 
259
262
  it('should return default mappings for unknown pattern', () => {
@@ -261,6 +264,71 @@ describe('Threat Reporter', () => {
261
264
  expect(mappings.owasp_llm).toContain('LLM01:2025');
262
265
  expect(mappings.nist_ai_600_1).toContain('MS-2.5');
263
266
  expect(mappings.mitre_atlas).toContain('AML.T0051');
267
+ expect(mappings.iso_42001).toBe('A.6.1.5 - AI System Security');
268
+ });
269
+
270
+ it('should have ISO 42001 mapping for all 43 patterns', () => {
271
+ // List of all 43 patterns from injection corpus
272
+ const allPatterns = [
273
+ 'direct_instruction_injection', 'role_hijacking', 'system_prompt_extraction',
274
+ 'privilege_escalation', 'context_poisoning', 'data_exfiltration',
275
+ 'base64_obfuscation', 'unicode_lookalikes', 'zero_width_characters',
276
+ 'html_script_injection', 'data_uri_injection', 'markdown_link_injection',
277
+ 'url_fragment_hashjack', 'social_engineering_urgency', 'instruction_delimiter_injection',
278
+ 'multi_language_obfuscation', 'reverse_text_obfuscation', 'leetspeak_obfuscation',
279
+ 'jailbreak_keywords', 'token_smuggling', 'system_message_injection',
280
+ 'conversation_reset', 'memory_manipulation', 'capability_probing',
281
+ 'chain_of_thought_manipulation', 'hypothetical_scenario_injection', 'ethical_override',
282
+ 'output_format_manipulation', 'negative_instruction', 'credential_harvesting',
283
+ 'time_based_triggers', 'code_execution_requests', 'file_system_access',
284
+ 'training_data_extraction', 'simulator_mode', 'nested_encoding',
285
+ 'payload_splitting', 'css_hiding', 'authority_impersonation',
286
+ 'testing_debugging_claims', 'callback_url_injection', 'whitespace_steganography',
287
+ 'comment_injection'
288
+ ];
289
+
290
+ for (const pattern of allPatterns) {
291
+ const mappings = getFrameworkMappings(pattern);
292
+ expect(mappings.iso_42001).toBeTruthy();
293
+ expect(mappings.iso_42001.length).toBeGreaterThan(0);
294
+ expect(mappings.iso_42001).toMatch(/^A\.\d+/); // Should start with A.X (Annex A format)
295
+ }
296
+ });
297
+
298
+ it('should include ISO 42001 column in Markdown report', () => {
299
+ const result = generateThreatReport({
300
+ patterns_detected: ['role_hijacking'],
301
+ pii_redacted: 0,
302
+ source_url: 'https://test.example.com'
303
+ });
304
+
305
+ expect(result).not.toBeNull();
306
+ if (result) {
307
+ const md = result.report_markdown;
308
+ expect(md).toContain('ISO 42001');
309
+ expect(md).toContain('ISO/IEC 42001');
310
+ }
311
+ });
312
+
313
+ it('should have 10 fields in TOON header', () => {
314
+ const result = generateThreatReport({
315
+ patterns_detected: ['role_hijacking'],
316
+ pii_redacted: 0,
317
+ source_url: 'https://test.example.com'
318
+ });
319
+
320
+ expect(result).not.toBeNull();
321
+ if (result) {
322
+ const toon = result.findings_toon;
323
+ // TOON header should have 10 fields: id, pattern_id, category, severity, confidence, owasp_llm, nist_ai_600_1, mitre_atlas, iso_42001, remediation
324
+ expect(toon).toMatch(/findings\[\d+\]\{[^}]+\}/);
325
+ const headerMatch = toon.match(/findings\[\d+\]\{([^}]+)\}/);
326
+ if (headerMatch) {
327
+ const fields = headerMatch[1].split(',');
328
+ expect(fields.length).toBe(10);
329
+ expect(fields).toContain('iso_42001');
330
+ }
331
+ }
264
332
  });
265
333
  });
266
334
  });