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.
- package/.claude/settings.local.json +42 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +47 -0
- package/.github/ISSUE_TEMPLATE/false_positive.md +43 -0
- package/.github/ISSUE_TEMPLATE/new_pattern.md +49 -0
- package/.github/ISSUE_TEMPLATE/security_report.md +31 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +39 -0
- package/.mcpregistry_github_token +1 -0
- package/.mcpregistry_registry_token +1 -0
- package/CLAUDE.md +197 -0
- package/CONTRIBUTING.md +329 -0
- package/README.md +111 -45
- package/STATUS.md +167 -29
- package/SUBMISSION.md +66 -0
- package/TROUBLESHOOT-COGNITO-AUTH-20260324-2029.md +415 -0
- package/TROUBLESHOOT-COGNITO-JWT-20260324.md +592 -0
- package/dist/browser/playwright-renderer.d.ts.map +1 -1
- package/dist/browser/playwright-renderer.js +71 -51
- package/dist/browser/playwright-renderer.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -6
- package/dist/index.js.map +1 -1
- package/dist/sanitizer/elicit-runner.d.ts +48 -0
- package/dist/sanitizer/elicit-runner.d.ts.map +1 -0
- package/dist/sanitizer/elicit-runner.js +100 -0
- package/dist/sanitizer/elicit-runner.js.map +1 -0
- package/dist/sanitizer/framework-mapper.d.ts +2 -0
- package/dist/sanitizer/framework-mapper.d.ts.map +1 -1
- package/dist/sanitizer/framework-mapper.js +91 -45
- package/dist/sanitizer/framework-mapper.js.map +1 -1
- package/dist/sanitizer/hitl-gate.d.ts +69 -0
- package/dist/sanitizer/hitl-gate.d.ts.map +1 -0
- package/dist/sanitizer/hitl-gate.js +101 -0
- package/dist/sanitizer/hitl-gate.js.map +1 -0
- package/dist/sanitizer/threat-reporter.d.ts +1 -0
- package/dist/sanitizer/threat-reporter.d.ts.map +1 -1
- package/dist/sanitizer/threat-reporter.js +10 -7
- package/dist/sanitizer/threat-reporter.js.map +1 -1
- package/infrastructure/stack.ts +1 -0
- package/lambda-deploy/index.js +81512 -0
- package/lambda-deploy/index.js.map +7 -0
- package/lambda-package/browser/__mocks__/playwright-renderer.d.ts +25 -0
- package/lambda-package/browser/__mocks__/playwright-renderer.d.ts.map +1 -0
- package/lambda-package/browser/__mocks__/playwright-renderer.js +119 -0
- package/lambda-package/browser/__mocks__/playwright-renderer.js.map +1 -0
- package/lambda-package/browser/playwright-renderer.d.ts +40 -0
- package/lambda-package/browser/playwright-renderer.d.ts.map +1 -0
- package/lambda-package/browser/playwright-renderer.js +214 -0
- package/lambda-package/browser/playwright-renderer.js.map +1 -0
- package/lambda-package/browser/reader.d.ts +31 -0
- package/lambda-package/browser/reader.d.ts.map +1 -0
- package/lambda-package/browser/reader.js +98 -0
- package/lambda-package/browser/reader.js.map +1 -0
- package/lambda-package/index.d.ts +18 -0
- package/lambda-package/index.d.ts.map +1 -0
- package/lambda-package/index.js +238 -0
- package/lambda-package/index.js.map +1 -0
- package/lambda-package/lambda-handler.d.ts +28 -0
- package/lambda-package/lambda-handler.d.ts.map +1 -0
- package/lambda-package/lambda-handler.js +257 -0
- package/lambda-package/lambda-handler.js.map +1 -0
- package/lambda-package/package-lock.json +7435 -0
- package/lambda-package/package.json +74 -0
- package/lambda-package/runtime.d.ts +50 -0
- package/lambda-package/runtime.d.ts.map +1 -0
- package/lambda-package/runtime.js +86 -0
- package/lambda-package/runtime.js.map +1 -0
- package/lambda-package/sanitizer/elicit-runner.d.ts +48 -0
- package/lambda-package/sanitizer/elicit-runner.d.ts.map +1 -0
- package/lambda-package/sanitizer/elicit-runner.js +100 -0
- package/lambda-package/sanitizer/elicit-runner.js.map +1 -0
- package/lambda-package/sanitizer/framework-mapper.d.ts +24 -0
- package/lambda-package/sanitizer/framework-mapper.d.ts.map +1 -0
- package/lambda-package/sanitizer/framework-mapper.js +342 -0
- package/lambda-package/sanitizer/framework-mapper.js.map +1 -0
- package/lambda-package/sanitizer/hitl-gate.d.ts +69 -0
- package/lambda-package/sanitizer/hitl-gate.d.ts.map +1 -0
- package/lambda-package/sanitizer/hitl-gate.js +101 -0
- package/lambda-package/sanitizer/hitl-gate.js.map +1 -0
- package/lambda-package/sanitizer/index.d.ts +63 -0
- package/lambda-package/sanitizer/index.d.ts.map +1 -0
- package/lambda-package/sanitizer/index.js +105 -0
- package/lambda-package/sanitizer/index.js.map +1 -0
- package/lambda-package/sanitizer/injection-detector.d.ts +34 -0
- package/lambda-package/sanitizer/injection-detector.d.ts.map +1 -0
- package/lambda-package/sanitizer/injection-detector.js +89 -0
- package/lambda-package/sanitizer/injection-detector.js.map +1 -0
- package/lambda-package/sanitizer/patterns.d.ts +30 -0
- package/lambda-package/sanitizer/patterns.d.ts.map +1 -0
- package/lambda-package/sanitizer/patterns.js +372 -0
- package/lambda-package/sanitizer/patterns.js.map +1 -0
- package/lambda-package/sanitizer/pii-allowlist.d.ts +49 -0
- package/lambda-package/sanitizer/pii-allowlist.d.ts.map +1 -0
- package/lambda-package/sanitizer/pii-allowlist.js +231 -0
- package/lambda-package/sanitizer/pii-allowlist.js.map +1 -0
- package/lambda-package/sanitizer/pii-redactor.d.ts +41 -0
- package/lambda-package/sanitizer/pii-redactor.d.ts.map +1 -0
- package/lambda-package/sanitizer/pii-redactor.js +213 -0
- package/lambda-package/sanitizer/pii-redactor.js.map +1 -0
- package/lambda-package/sanitizer/severity-classifier.d.ts +33 -0
- package/lambda-package/sanitizer/severity-classifier.d.ts.map +1 -0
- package/lambda-package/sanitizer/severity-classifier.js +113 -0
- package/lambda-package/sanitizer/severity-classifier.js.map +1 -0
- package/lambda-package/sanitizer/threat-reporter.d.ts +66 -0
- package/lambda-package/sanitizer/threat-reporter.d.ts.map +1 -0
- package/lambda-package/sanitizer/threat-reporter.js +163 -0
- package/lambda-package/sanitizer/threat-reporter.js.map +1 -0
- package/lambda-package/tools/fetch-structured.d.ts +51 -0
- package/lambda-package/tools/fetch-structured.d.ts.map +1 -0
- package/lambda-package/tools/fetch-structured.js +237 -0
- package/lambda-package/tools/fetch-structured.js.map +1 -0
- package/lambda-package/tools/fetch.d.ts +49 -0
- package/lambda-package/tools/fetch.d.ts.map +1 -0
- package/lambda-package/tools/fetch.js +131 -0
- package/lambda-package/tools/fetch.js.map +1 -0
- package/lambda-package/tools/read.d.ts +51 -0
- package/lambda-package/tools/read.d.ts.map +1 -0
- package/lambda-package/tools/read.js +127 -0
- package/lambda-package/tools/read.js.map +1 -0
- package/lambda-package/tools/search.d.ts +45 -0
- package/lambda-package/tools/search.d.ts.map +1 -0
- package/lambda-package/tools/search.js +220 -0
- package/lambda-package/tools/search.js.map +1 -0
- package/lambda-package/types.d.ts +167 -0
- package/lambda-package/types.d.ts.map +1 -0
- package/lambda-package/types.js +16 -0
- package/lambda-package/types.js.map +1 -0
- package/lambda-package/utils/format-converter.d.ts +39 -0
- package/lambda-package/utils/format-converter.d.ts.map +1 -0
- package/lambda-package/utils/format-converter.js +191 -0
- package/lambda-package/utils/format-converter.js.map +1 -0
- package/lambda-package/utils/truncate.d.ts +26 -0
- package/lambda-package/utils/truncate.d.ts.map +1 -0
- package/lambda-package/utils/truncate.js +54 -0
- package/lambda-package/utils/truncate.js.map +1 -0
- package/lambda.zip +0 -0
- package/mcp.json +44 -0
- package/package.json +9 -8
- package/server.json +43 -0
- package/src/browser/playwright-renderer.ts +74 -51
- package/src/index.ts +78 -6
- package/src/sanitizer/elicit-runner.ts +125 -0
- package/src/sanitizer/framework-mapper.ts +92 -45
- package/src/sanitizer/hitl-gate.ts +111 -0
- package/src/sanitizer/threat-reporter.ts +11 -7
- package/tests/elicit-runner.test.ts +232 -0
- package/tests/hitl-gate.test.ts +267 -0
- 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
|
|
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
|
});
|