visus-mcp 0.3.0 → 0.6.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 (96) hide show
  1. package/.claude/settings.local.json +22 -0
  2. package/LINKEDIN-STRATEGY.md +367 -0
  3. package/README.md +491 -16
  4. package/ROADMAP.md +167 -30
  5. package/SECURITY-AUDIT-v1.md +277 -0
  6. package/STATUS.md +801 -42
  7. package/TROUBLESHOOT-AUTH-20260322-2019.md +291 -0
  8. package/TROUBLESHOOT-JEST-20260323-1357.md +139 -0
  9. package/TROUBLESHOOT-LAMBDA-20260322-1945.md +183 -0
  10. package/VISUS-CLAUDE-CODE-PROMPT.md +1 -1
  11. package/VISUS-PROJECT-PLAN.md +7 -0
  12. package/dist/browser/playwright-renderer.d.ts.map +1 -1
  13. package/dist/browser/playwright-renderer.js +7 -0
  14. package/dist/browser/playwright-renderer.js.map +1 -1
  15. package/dist/browser/reader.d.ts +31 -0
  16. package/dist/browser/reader.d.ts.map +1 -0
  17. package/dist/browser/reader.js +98 -0
  18. package/dist/browser/reader.js.map +1 -0
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +37 -5
  22. package/dist/index.js.map +1 -1
  23. package/dist/lambda-handler.d.ts +0 -6
  24. package/dist/lambda-handler.d.ts.map +1 -1
  25. package/dist/lambda-handler.js +97 -25
  26. package/dist/lambda-handler.js.map +1 -1
  27. package/dist/sanitizer/framework-mapper.d.ts +22 -0
  28. package/dist/sanitizer/framework-mapper.d.ts.map +1 -0
  29. package/dist/sanitizer/framework-mapper.js +296 -0
  30. package/dist/sanitizer/framework-mapper.js.map +1 -0
  31. package/dist/sanitizer/index.d.ts +2 -0
  32. package/dist/sanitizer/index.d.ts.map +1 -1
  33. package/dist/sanitizer/index.js +14 -1
  34. package/dist/sanitizer/index.js.map +1 -1
  35. package/dist/sanitizer/patterns.js +1 -1
  36. package/dist/sanitizer/patterns.js.map +1 -1
  37. package/dist/sanitizer/severity-classifier.d.ts +33 -0
  38. package/dist/sanitizer/severity-classifier.d.ts.map +1 -0
  39. package/dist/sanitizer/severity-classifier.js +113 -0
  40. package/dist/sanitizer/severity-classifier.js.map +1 -0
  41. package/dist/sanitizer/threat-reporter.d.ts +65 -0
  42. package/dist/sanitizer/threat-reporter.d.ts.map +1 -0
  43. package/dist/sanitizer/threat-reporter.js +160 -0
  44. package/dist/sanitizer/threat-reporter.js.map +1 -0
  45. package/dist/tools/fetch-structured.d.ts +5 -0
  46. package/dist/tools/fetch-structured.d.ts.map +1 -1
  47. package/dist/tools/fetch-structured.js +54 -6
  48. package/dist/tools/fetch-structured.js.map +1 -1
  49. package/dist/tools/fetch.d.ts +5 -0
  50. package/dist/tools/fetch.d.ts.map +1 -1
  51. package/dist/tools/fetch.js +42 -9
  52. package/dist/tools/fetch.js.map +1 -1
  53. package/dist/tools/read.d.ts +51 -0
  54. package/dist/tools/read.d.ts.map +1 -0
  55. package/dist/tools/read.js +127 -0
  56. package/dist/tools/read.js.map +1 -0
  57. package/dist/tools/search.d.ts +45 -0
  58. package/dist/tools/search.d.ts.map +1 -0
  59. package/dist/tools/search.js +220 -0
  60. package/dist/tools/search.js.map +1 -0
  61. package/dist/types.d.ts +64 -0
  62. package/dist/types.d.ts.map +1 -1
  63. package/dist/types.js.map +1 -1
  64. package/dist/utils/format-converter.d.ts +39 -0
  65. package/dist/utils/format-converter.d.ts.map +1 -0
  66. package/dist/utils/format-converter.js +191 -0
  67. package/dist/utils/format-converter.js.map +1 -0
  68. package/dist/utils/truncate.d.ts +26 -0
  69. package/dist/utils/truncate.d.ts.map +1 -0
  70. package/dist/utils/truncate.js +54 -0
  71. package/dist/utils/truncate.js.map +1 -0
  72. package/infrastructure/stack.ts +55 -6
  73. package/jest.config.js +3 -0
  74. package/package.json +9 -2
  75. package/src/browser/playwright-renderer.ts +8 -0
  76. package/src/browser/reader.ts +129 -0
  77. package/src/index.ts +49 -5
  78. package/src/lambda-handler.ts +131 -26
  79. package/src/sanitizer/framework-mapper.ts +347 -0
  80. package/src/sanitizer/index.ts +18 -1
  81. package/src/sanitizer/patterns.ts +1 -1
  82. package/src/sanitizer/severity-classifier.ts +132 -0
  83. package/src/sanitizer/threat-reporter.ts +261 -0
  84. package/src/tools/fetch-structured.ts +58 -6
  85. package/src/tools/fetch.ts +44 -9
  86. package/src/tools/read.ts +143 -0
  87. package/src/tools/search.ts +263 -0
  88. package/src/types.ts +69 -0
  89. package/src/utils/format-converter.ts +236 -0
  90. package/src/utils/truncate.ts +64 -0
  91. package/tests/auth-smoke.test.ts +480 -0
  92. package/tests/fetch-tool.test.ts +595 -2
  93. package/tests/reader.test.ts +353 -0
  94. package/tests/sanitizer.test.ts +52 -0
  95. package/tests/search.test.ts +456 -0
  96. package/tests/threat-reporter.test.ts +266 -0
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Threat Reporter Test Suite
3
+ *
4
+ * Tests TOON encoding, Markdown generation, and framework mappings
5
+ */
6
+
7
+ import { generateThreatReport } from '../src/sanitizer/threat-reporter.js';
8
+ import { classifySeverity, aggregateSeverity, countBySeverity, getSeverityEmoji } from '../src/sanitizer/severity-classifier.js';
9
+ import { getFrameworkMappings } from '../src/sanitizer/framework-mapper.js';
10
+
11
+ describe('Threat Reporter', () => {
12
+ describe('generateThreatReport()', () => {
13
+ it('should return null for clean page (no findings)', () => {
14
+ const result = generateThreatReport({
15
+ patterns_detected: [],
16
+ pii_redacted: 0,
17
+ source_url: 'https://example.com'
18
+ });
19
+
20
+ expect(result).toBeNull();
21
+ });
22
+
23
+ it('should generate report for single HIGH injection', () => {
24
+ const result = generateThreatReport({
25
+ patterns_detected: ['role_hijacking'],
26
+ pii_redacted: 0,
27
+ source_url: 'https://malicious.example.com'
28
+ });
29
+
30
+ expect(result).not.toBeNull();
31
+ if (result) {
32
+ expect(result.overall_severity).toBe('CRITICAL'); // role_hijacking is CRITICAL
33
+ expect(result.total_findings).toBe(1);
34
+ expect(result.by_severity.CRITICAL).toBe(1);
35
+ expect(result.by_severity.HIGH).toBe(0);
36
+ }
37
+ });
38
+
39
+ it('should classify CRITICAL + MEDIUM as overall CRITICAL', () => {
40
+ const result = generateThreatReport({
41
+ patterns_detected: ['data_exfiltration', 'comment_injection'],
42
+ pii_redacted: 0,
43
+ source_url: 'https://test.example.com'
44
+ });
45
+
46
+ expect(result).not.toBeNull();
47
+ if (result) {
48
+ expect(result.overall_severity).toBe('CRITICAL');
49
+ expect(result.total_findings).toBe(2);
50
+ expect(result.by_severity.CRITICAL).toBe(1);
51
+ expect(result.by_severity.MEDIUM).toBe(1);
52
+ }
53
+ });
54
+
55
+ it('should include PII redacted count in report', () => {
56
+ const result = generateThreatReport({
57
+ patterns_detected: ['role_hijacking'],
58
+ pii_redacted: 3,
59
+ source_url: 'https://test.example.com'
60
+ });
61
+
62
+ expect(result).not.toBeNull();
63
+ if (result) {
64
+ expect(result.pii_redacted).toBe(3);
65
+ expect(result.report_markdown).toContain('Items Redacted:** 3');
66
+ }
67
+ });
68
+
69
+ it('should have non-empty TOON findings string when findings exist', () => {
70
+ const result = generateThreatReport({
71
+ patterns_detected: ['role_hijacking'],
72
+ pii_redacted: 0,
73
+ source_url: 'https://test.example.com'
74
+ });
75
+
76
+ expect(result).not.toBeNull();
77
+ if (result) {
78
+ expect(result.findings_toon).toBeTruthy();
79
+ expect(result.findings_toon.length).toBeGreaterThan(0);
80
+ }
81
+ });
82
+
83
+ it('should include all required sections in Markdown report', () => {
84
+ const result = generateThreatReport({
85
+ patterns_detected: ['role_hijacking', 'data_exfiltration'],
86
+ pii_redacted: 2,
87
+ source_url: 'https://test.example.com'
88
+ });
89
+
90
+ expect(result).not.toBeNull();
91
+ if (result) {
92
+ const md = result.report_markdown;
93
+ expect(md).toContain('Visus Threat Report');
94
+ expect(md).toContain('Findings Summary');
95
+ expect(md).toContain('Findings Detail');
96
+ expect(md).toContain('PII Redaction');
97
+ expect(md).toContain('Remediation Status');
98
+ expect(md).toContain('Generated:');
99
+ expect(md).toContain('Source:');
100
+ expect(md).toContain('Overall Severity:');
101
+ }
102
+ });
103
+
104
+ it('should contain valid TOON format with correct field count', () => {
105
+ const result = generateThreatReport({
106
+ patterns_detected: ['role_hijacking'],
107
+ pii_redacted: 0,
108
+ source_url: 'https://test.example.com'
109
+ });
110
+
111
+ expect(result).not.toBeNull();
112
+ if (result) {
113
+ const toon = result.findings_toon;
114
+ // TOON should contain findings array with expected fields
115
+ expect(toon).toContain('findings');
116
+ }
117
+ });
118
+
119
+ it('should use all four severity emojis in Markdown', () => {
120
+ const result = generateThreatReport({
121
+ patterns_detected: ['role_hijacking'], // CRITICAL
122
+ pii_redacted: 0,
123
+ source_url: 'https://test.example.com'
124
+ });
125
+
126
+ expect(result).not.toBeNull();
127
+ if (result) {
128
+ const md = result.report_markdown;
129
+ // Should have severity emojis in the table
130
+ expect(md).toContain('🔴'); // CRITICAL
131
+ expect(md).toContain('🟠'); // HIGH
132
+ expect(md).toContain('🟡'); // MEDIUM
133
+ expect(md).toContain('🟢'); // LOW
134
+ }
135
+ });
136
+
137
+ it('should include all three frameworks', () => {
138
+ const result = generateThreatReport({
139
+ patterns_detected: ['role_hijacking'],
140
+ pii_redacted: 0,
141
+ source_url: 'https://test.example.com'
142
+ });
143
+
144
+ expect(result).not.toBeNull();
145
+ if (result) {
146
+ expect(result.frameworks).toContain('OWASP LLM Top 10');
147
+ expect(result.frameworks).toContain('NIST AI 600-1');
148
+ expect(result.frameworks).toContain('MITRE ATLAS');
149
+ }
150
+ });
151
+
152
+ it('should mark sanitization_applied as true', () => {
153
+ const result = generateThreatReport({
154
+ patterns_detected: ['role_hijacking'],
155
+ pii_redacted: 0,
156
+ source_url: 'https://test.example.com'
157
+ });
158
+
159
+ expect(result).not.toBeNull();
160
+ if (result) {
161
+ expect(result.sanitization_applied).toBe(true);
162
+ }
163
+ });
164
+
165
+ it('should include timestamp in ISO format', () => {
166
+ const result = generateThreatReport({
167
+ patterns_detected: ['role_hijacking'],
168
+ pii_redacted: 0,
169
+ source_url: 'https://test.example.com'
170
+ });
171
+
172
+ expect(result).not.toBeNull();
173
+ if (result) {
174
+ expect(result.generated).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
175
+ }
176
+ });
177
+ });
178
+
179
+ describe('Severity Classifier', () => {
180
+ it('should classify role_hijacking as CRITICAL', () => {
181
+ expect(classifySeverity('role_hijacking')).toBe('CRITICAL');
182
+ });
183
+
184
+ it('should classify data_exfiltration as CRITICAL', () => {
185
+ expect(classifySeverity('data_exfiltration')).toBe('CRITICAL');
186
+ });
187
+
188
+ it('should classify context_poisoning as HIGH', () => {
189
+ expect(classifySeverity('context_poisoning')).toBe('HIGH');
190
+ });
191
+
192
+ it('should classify comment_injection as MEDIUM', () => {
193
+ expect(classifySeverity('comment_injection')).toBe('MEDIUM');
194
+ });
195
+
196
+ it('should classify leetspeak_obfuscation as LOW', () => {
197
+ expect(classifySeverity('leetspeak_obfuscation')).toBe('LOW');
198
+ });
199
+
200
+ it('should aggregate to CLEAN when no findings', () => {
201
+ expect(aggregateSeverity([])).toBe('CLEAN');
202
+ });
203
+
204
+ it('should aggregate to CRITICAL when CRITICAL finding present', () => {
205
+ const findings = [
206
+ { pattern_category: 'role_hijacking', severity: 'CRITICAL' as const },
207
+ { pattern_category: 'comment_injection', severity: 'MEDIUM' as const }
208
+ ];
209
+ expect(aggregateSeverity(findings)).toBe('CRITICAL');
210
+ });
211
+
212
+ it('should aggregate to HIGH when no CRITICAL but HIGH present', () => {
213
+ const findings = [
214
+ { pattern_category: 'context_poisoning', severity: 'HIGH' as const },
215
+ { pattern_category: 'comment_injection', severity: 'MEDIUM' as const }
216
+ ];
217
+ expect(aggregateSeverity(findings)).toBe('HIGH');
218
+ });
219
+
220
+ it('should count findings by severity correctly', () => {
221
+ const findings = [
222
+ { pattern_category: 'role_hijacking', severity: 'CRITICAL' as const },
223
+ { pattern_category: 'data_exfiltration', severity: 'CRITICAL' as const },
224
+ { pattern_category: 'context_poisoning', severity: 'HIGH' as const },
225
+ { pattern_category: 'comment_injection', severity: 'MEDIUM' as const }
226
+ ];
227
+
228
+ const counts = countBySeverity(findings);
229
+ expect(counts.CRITICAL).toBe(2);
230
+ expect(counts.HIGH).toBe(1);
231
+ expect(counts.MEDIUM).toBe(1);
232
+ expect(counts.LOW).toBe(0);
233
+ });
234
+
235
+ it('should return correct emojis for all severity levels', () => {
236
+ expect(getSeverityEmoji('CRITICAL')).toBe('🔴');
237
+ expect(getSeverityEmoji('HIGH')).toBe('🟠');
238
+ expect(getSeverityEmoji('MEDIUM')).toBe('🟡');
239
+ expect(getSeverityEmoji('LOW')).toBe('🟢');
240
+ expect(getSeverityEmoji('CLEAN')).toBe('✅');
241
+ });
242
+ });
243
+
244
+ describe('Framework Mapper', () => {
245
+ it('should map role_hijacking to correct frameworks', () => {
246
+ const mappings = getFrameworkMappings('role_hijacking');
247
+ expect(mappings.owasp_llm).toContain('LLM01:2025');
248
+ expect(mappings.nist_ai_600_1).toContain('MS-2.5');
249
+ expect(mappings.mitre_atlas).toContain('AML.T0051');
250
+ });
251
+
252
+ it('should map data_exfiltration to correct frameworks', () => {
253
+ const mappings = getFrameworkMappings('data_exfiltration');
254
+ expect(mappings.owasp_llm).toContain('LLM02:2025');
255
+ expect(mappings.nist_ai_600_1).toContain('MS-2.6');
256
+ expect(mappings.mitre_atlas).toContain('AML.T0048');
257
+ });
258
+
259
+ it('should return default mappings for unknown pattern', () => {
260
+ const mappings = getFrameworkMappings('unknown_pattern_xyz');
261
+ expect(mappings.owasp_llm).toContain('LLM01:2025');
262
+ expect(mappings.nist_ai_600_1).toContain('MS-2.5');
263
+ expect(mappings.mitre_atlas).toContain('AML.T0051');
264
+ });
265
+ });
266
+ });