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
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# Contributing to Visus
|
|
2
|
+
|
|
3
|
+
Thank you for considering contributing to Visus! This project is security-first — all contributions must maintain the sanitization guarantees that protect users. Visus is engineered, not vibe-coded. We expect rigorous testing, clear documentation, and adherence to security best practices.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What We're Looking For
|
|
8
|
+
|
|
9
|
+
The most valuable contributions to Visus are:
|
|
10
|
+
|
|
11
|
+
- **New injection pattern categories** (most wanted) — Validated detection patterns for emerging prompt injection techniques
|
|
12
|
+
- **False positive reports** — Cases where Visus incorrectly flags or redacts legitimate content
|
|
13
|
+
- **New PII redaction types** — Additional personally identifiable information patterns (passports, driver's licenses, medical IDs, etc.)
|
|
14
|
+
- **Performance improvements** — Optimizations to the sanitizer pipeline that maintain coverage
|
|
15
|
+
- **Documentation improvements** — Clearer explanations, better examples, tutorial content
|
|
16
|
+
- **Bug reports with reproduction steps** — Detailed reports that help us quickly identify and fix issues
|
|
17
|
+
|
|
18
|
+
### What is OUT OF SCOPE
|
|
19
|
+
|
|
20
|
+
To avoid wasted effort, please **do not submit PRs** for:
|
|
21
|
+
|
|
22
|
+
- Changes that reduce sanitization coverage or allow bypassing the pipeline
|
|
23
|
+
- New tools that don't run content through the sanitizer
|
|
24
|
+
- Dependencies that require Python runtime (Visus is TypeScript-only)
|
|
25
|
+
- Modifications to the security rules defined in CLAUDE.md
|
|
26
|
+
- Changes that introduce `any` types or violate TypeScript strict mode
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## How to Add a New Injection Pattern
|
|
31
|
+
|
|
32
|
+
This is the most important contribution type. Follow these steps carefully:
|
|
33
|
+
|
|
34
|
+
### Step 1: Add the pattern definition
|
|
35
|
+
|
|
36
|
+
Open `src/sanitizer/patterns.ts` and add your pattern to the `INJECTION_PATTERNS` array. Each pattern requires:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
{
|
|
40
|
+
name: 'your_pattern_name', // snake_case identifier
|
|
41
|
+
description: 'What this detects', // Brief explanation
|
|
42
|
+
regex: /pattern_here/gi, // Detection regex (case-insensitive)
|
|
43
|
+
severity: 'critical', // critical | high | medium | low
|
|
44
|
+
action: 'redact' // strip | redact | escape
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Example pattern:**
|
|
49
|
+
```typescript
|
|
50
|
+
{
|
|
51
|
+
name: 'unicode_normalization_attack',
|
|
52
|
+
description: 'Uses Unicode normalization to hide instructions',
|
|
53
|
+
regex: /\u0041\u0301.*\b(ignore|admin)\b/gi, // Á (decomposed) hiding text
|
|
54
|
+
severity: 'high',
|
|
55
|
+
action: 'strip'
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Step 2: Add severity classification
|
|
60
|
+
|
|
61
|
+
Open `src/sanitizer/severity-classifier.ts` and add your pattern category to the correct severity level:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
case 'your_pattern_name':
|
|
65
|
+
return 'CRITICAL'; // or HIGH, MEDIUM, LOW
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Step 3: Add framework mappings
|
|
69
|
+
|
|
70
|
+
Open `src/sanitizer/framework-mapper.ts` and add mappings for all four compliance frameworks:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
your_pattern_name: {
|
|
74
|
+
owasp_llm: 'LLM01:2025 - Prompt Injection',
|
|
75
|
+
nist_ai_600_1: 'MS-2.5 - Prompt Injection',
|
|
76
|
+
mitre_atlas: 'AML.T0051.000 - LLM Prompt Injection',
|
|
77
|
+
iso_42001: 'A.6.1.5 - AI System Security (Adversarial Input)'
|
|
78
|
+
},
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**How to choose mappings:**
|
|
82
|
+
- **OWASP LLM Top 10**: See [OWASP LLM Top 10 (2025)](https://owasp.org/www-project-top-10-for-large-language-model-applications/)
|
|
83
|
+
- **NIST AI 600-1**: See [NIST AI 600-1 Controls](https://csrc.nist.gov/pubs/ai/600/1/final)
|
|
84
|
+
- **MITRE ATLAS**: See [MITRE ATLAS Tactics](https://atlas.mitre.org/)
|
|
85
|
+
- **ISO/IEC 42001**: Use Annex A controls (A.X.X format)
|
|
86
|
+
|
|
87
|
+
### Step 4: Add test cases
|
|
88
|
+
|
|
89
|
+
Open `tests/sanitizer.test.ts` and add at least two test cases:
|
|
90
|
+
|
|
91
|
+
**Positive case** (content that SHOULD be caught):
|
|
92
|
+
```typescript
|
|
93
|
+
it('should detect your_pattern_name', () => {
|
|
94
|
+
const result = sanitize('Malicious content here that triggers pattern');
|
|
95
|
+
expect(result.patterns_detected).toContain('your_pattern_name');
|
|
96
|
+
expect(result.content_modified).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Negative case** (legitimate content that should NOT be caught):
|
|
101
|
+
```typescript
|
|
102
|
+
it('should NOT flag legitimate content as your_pattern_name', () => {
|
|
103
|
+
const result = sanitize('Legitimate content that looks similar but is safe');
|
|
104
|
+
expect(result.patterns_detected).not.toContain('your_pattern_name');
|
|
105
|
+
expect(result.content_modified).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Why negative cases matter:** False positives erode trust. Always test that your pattern doesn't fire on legitimate content.
|
|
110
|
+
|
|
111
|
+
### Step 5: Run tests
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm test
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
All tests must pass (100% pass rate). If any tests fail, fix them before submitting.
|
|
118
|
+
|
|
119
|
+
### Step 6: Update SECURITY.md
|
|
120
|
+
|
|
121
|
+
Add your pattern to the appropriate severity section in `SECURITY.md` with an example:
|
|
122
|
+
|
|
123
|
+
```markdown
|
|
124
|
+
**XX. Your Pattern Name**
|
|
125
|
+
- **Example**: "Text that triggers the pattern"
|
|
126
|
+
- **Action**: Redact/Strip/Escape
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## How to Report a False Positive
|
|
132
|
+
|
|
133
|
+
A **false positive** occurs when Visus incorrectly flags or redacts legitimate, non-malicious content. These are **high priority bugs** because they impact usability.
|
|
134
|
+
|
|
135
|
+
**To report a false positive:**
|
|
136
|
+
|
|
137
|
+
1. Open a **"False Positive Report"** issue using the GitHub issue template
|
|
138
|
+
2. Include:
|
|
139
|
+
- The URL or content that triggered the false positive (sanitize if sensitive)
|
|
140
|
+
- Which pattern category fired (visible in `patterns_detected` field)
|
|
141
|
+
- What the expected behavior should be
|
|
142
|
+
- Domain context (news site, documentation, health info, government, etc.)
|
|
143
|
+
3. **Do NOT include:**
|
|
144
|
+
- Sensitive URLs or private content in public issues
|
|
145
|
+
- Personally identifiable information
|
|
146
|
+
|
|
147
|
+
We take false positives seriously and will prioritize fixes.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Development Setup
|
|
152
|
+
|
|
153
|
+
### Prerequisites
|
|
154
|
+
|
|
155
|
+
- **Node.js** 18+ and npm
|
|
156
|
+
- **Git** for version control
|
|
157
|
+
- **macOS / Windows**: No additional setup required
|
|
158
|
+
- **Linux**: Playwright requires system libraries (see README.md)
|
|
159
|
+
|
|
160
|
+
### Clone and Install
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
git clone https://github.com/visus-mcp/visus-mcp.git
|
|
164
|
+
cd visus-mcp
|
|
165
|
+
npm install
|
|
166
|
+
npm run build
|
|
167
|
+
npm test
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Note about Playwright:** The first run will download Chromium (~170MB). This is normal.
|
|
171
|
+
|
|
172
|
+
**Note about macOS iCloud:** If you use iCloud Drive, develop in `~/Projects`, NOT `~/Documents`. iCloud sync can interfere with node_modules.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Running Tests
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm test # Full test suite (all 274+ tests)
|
|
180
|
+
npm test -- --watch # Watch mode for active development
|
|
181
|
+
npm test sanitizer # Run sanitizer tests only
|
|
182
|
+
npm test -- --coverage # Generate coverage report
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Test requirements:**
|
|
186
|
+
- All PRs must pass 100% of existing tests
|
|
187
|
+
- New functionality must include new tests
|
|
188
|
+
- Test count should never decrease
|
|
189
|
+
- Minimum 80% code coverage
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Security Vulnerability Reporting
|
|
194
|
+
|
|
195
|
+
**DO NOT open public issues for security vulnerabilities.**
|
|
196
|
+
|
|
197
|
+
If you discover a security vulnerability in Visus (e.g., a way to bypass the sanitizer, extract PII, or compromise the system):
|
|
198
|
+
|
|
199
|
+
📧 **Email:** security@lateos.ai
|
|
200
|
+
|
|
201
|
+
Include in your report:
|
|
202
|
+
- Description of the vulnerability
|
|
203
|
+
- Steps to reproduce
|
|
204
|
+
- Potential impact assessment
|
|
205
|
+
- Suggested fix (optional)
|
|
206
|
+
|
|
207
|
+
We aim to respond within 48 hours and will work with you on a coordinated disclosure timeline (typically 90 days).
|
|
208
|
+
|
|
209
|
+
See [SECURITY.md](./SECURITY.md) for the full disclosure policy.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Pull Request Process
|
|
214
|
+
|
|
215
|
+
### Before Opening a PR
|
|
216
|
+
|
|
217
|
+
1. **Fork the repo** and create a feature branch:
|
|
218
|
+
```bash
|
|
219
|
+
git checkout -b feature/your-feature-name
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
2. **Make your changes** with tests:
|
|
223
|
+
- Write code following TypeScript strict mode
|
|
224
|
+
- Add test cases for new functionality
|
|
225
|
+
- Update documentation if needed
|
|
226
|
+
|
|
227
|
+
3. **Run the test suite**:
|
|
228
|
+
```bash
|
|
229
|
+
npm test
|
|
230
|
+
```
|
|
231
|
+
All tests must pass (100% success rate).
|
|
232
|
+
|
|
233
|
+
4. **Run the build**:
|
|
234
|
+
```bash
|
|
235
|
+
npm run build
|
|
236
|
+
```
|
|
237
|
+
TypeScript must compile cleanly with zero errors.
|
|
238
|
+
|
|
239
|
+
5. **Update STATUS.md** if adding a new feature:
|
|
240
|
+
- Add your feature to the current version section
|
|
241
|
+
- Use consistent formatting with existing entries
|
|
242
|
+
|
|
243
|
+
### Opening the PR
|
|
244
|
+
|
|
245
|
+
1. Push your branch to your fork
|
|
246
|
+
2. Open a PR against the `main` branch
|
|
247
|
+
3. Use the PR template and fill out all sections
|
|
248
|
+
4. Include a clear description of **what** changed and **why**
|
|
249
|
+
5. Reference any related issues (e.g., "Closes #123")
|
|
250
|
+
|
|
251
|
+
### PR Review Criteria
|
|
252
|
+
|
|
253
|
+
Your PR will be reviewed for:
|
|
254
|
+
|
|
255
|
+
- ✅ **Test coverage** — All existing tests pass, new tests added
|
|
256
|
+
- ✅ **TypeScript compliance** — No `any` types, strict mode passes
|
|
257
|
+
- ✅ **Security** — Sanitizer pipeline not bypassed
|
|
258
|
+
- ✅ **Documentation** — Code is well-commented and clear
|
|
259
|
+
- ✅ **Performance** — No significant latency regressions
|
|
260
|
+
|
|
261
|
+
**PRs that will NOT be merged:**
|
|
262
|
+
- ❌ Reduce test count or coverage
|
|
263
|
+
- ❌ Bypass the sanitizer pipeline
|
|
264
|
+
- ❌ Introduce `any` types or disable strict mode
|
|
265
|
+
- ❌ Break existing functionality
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Code Style
|
|
270
|
+
|
|
271
|
+
### TypeScript Conventions
|
|
272
|
+
|
|
273
|
+
- **TypeScript strict mode** — No `any` types allowed (use `unknown` if necessary)
|
|
274
|
+
- **Explicit return types** — All functions must declare return types
|
|
275
|
+
- **JSDoc comments** — All public functions must have JSDoc documentation
|
|
276
|
+
- **Error handling** — Never throw raw errors; return typed Result objects
|
|
277
|
+
|
|
278
|
+
### MCP Tool Registration
|
|
279
|
+
|
|
280
|
+
All new tools must register with proper MCP annotations:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
{
|
|
284
|
+
name: 'tool_name',
|
|
285
|
+
description: 'What this tool does',
|
|
286
|
+
readOnlyHint: true, // If tool doesn't modify state
|
|
287
|
+
destructiveHint: false, // If tool could cause data loss
|
|
288
|
+
idempotentHint: true, // If repeated calls have same effect
|
|
289
|
+
openWorldHint: false // If tool accesses external resources
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Logging
|
|
294
|
+
|
|
295
|
+
- **Structured JSON** to stderr only (never `console.log`)
|
|
296
|
+
- **Never log PII** — Use field redaction for sensitive data
|
|
297
|
+
- **Use timestamps** in ISO 8601 format
|
|
298
|
+
|
|
299
|
+
**Example:**
|
|
300
|
+
```typescript
|
|
301
|
+
console.error(JSON.stringify({
|
|
302
|
+
timestamp: new Date().toISOString(),
|
|
303
|
+
event: 'sanitization_completed',
|
|
304
|
+
patterns_detected: ['role_hijacking'],
|
|
305
|
+
content_modified: true
|
|
306
|
+
}));
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Recognition
|
|
312
|
+
|
|
313
|
+
Contributors who add validated injection patterns that are merged into the main branch will be credited in:
|
|
314
|
+
|
|
315
|
+
- **SECURITY.md** under "Community Patterns"
|
|
316
|
+
- **Release notes** for the version that includes their pattern
|
|
317
|
+
- **GitHub Contributors** page
|
|
318
|
+
|
|
319
|
+
We deeply appreciate the security research community's contributions to making Visus more robust.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Questions?
|
|
324
|
+
|
|
325
|
+
- **General questions**: Open a [GitHub Discussion](https://github.com/visus-mcp/visus-mcp/discussions)
|
|
326
|
+
- **Bug reports**: Use the [Bug Report issue template](https://github.com/visus-mcp/visus-mcp/issues/new?template=bug_report.md)
|
|
327
|
+
- **Security issues**: Email security@lateos.ai (do NOT open public issues)
|
|
328
|
+
|
|
329
|
+
**Built with by Lateos**
|
package/README.md
CHANGED
|
@@ -1,30 +1,55 @@
|
|
|
1
1
|
# Visus — Secure Web Access for Claude
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/visus-mcp)
|
|
4
|
+
[](https://github.com/visus-mcp/visus-mcp)
|
|
5
|
+
[](https://github.com/visus-mcp/visus-mcp)
|
|
6
|
+
[](https://modelcontextprotocol.io)
|
|
7
|
+
[](https://github.com/visus-mcp/visus-mcp/blob/main/LICENSE)
|
|
8
|
+
[](https://github.com/visus-mcp/visus-mcp/blob/main/SECURITY.md)
|
|
9
|
+
[](https://www.iso.org/standard/81230.html)
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
> **Your AI agent shouldn't have to read garbage.**
|
|
12
|
+
> **visus-mcp makes sure it doesn't.**
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
When your agent fetches a webpage it reads everything — nav bars, cookie banners, tracking scripts, ads, SEO spam. Every token costs money. Some pages also embed hidden instructions designed to manipulate your agent's behaviour.
|
|
15
|
+
|
|
16
|
+
Claude handles most of it. But it still has to read all of it first. You still pay for every token.
|
|
17
|
+
|
|
18
|
+
**visus-mcp is a pre-filter.** It strips the noise before a single character enters Claude's context window — reducing token consumption on bloated pages by up to 70%, redacting PII before it enters conversation history, and producing a compliance-grade audit log when it finds something worth flagging.
|
|
19
|
+
|
|
20
|
+
Built as infrastructure, not a replacement for Claude's own safety training. The two layers together are stronger than either alone.
|
|
21
|
+
```bash
|
|
22
|
+
npx visus-mcp@0.6.0
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
*"What the web shows you, Lateos reads safely."*
|
|
8
26
|
|
|
9
27
|
---
|
|
10
28
|
|
|
11
|
-
##
|
|
29
|
+
## Why Your Agent Is Reading Too Much
|
|
12
30
|
|
|
13
|
-
|
|
31
|
+
A typical news article: **12,000 tokens** of raw HTML.
|
|
32
|
+
The actual article content: **~800 tokens**.
|
|
14
33
|
|
|
15
|
-
|
|
16
|
-
- **Personal identifiable information (PII)** can leak into conversation logs
|
|
17
|
-
- **Malicious instructions** hidden in web pages can compromise your AI agent
|
|
34
|
+
You're paying for the nav bar. The footer. The cookie banner. The analytics scripts. The related articles sidebar. The ads.
|
|
18
35
|
|
|
19
|
-
|
|
36
|
+
visus-mcp fetches the same page and delivers:
|
|
37
|
+
- **visus_read** — article content only via Mozilla Readability (~70% token reduction on content-heavy pages)
|
|
38
|
+
- **visus_fetch** — full page with noise stripped and format-converted (JSON/XML/RSS auto-detected)
|
|
39
|
+
- **visus_search** — sanitized DuckDuckGo results, SEO spam removed before it hits context
|
|
40
|
+
|
|
41
|
+
**Real example from today:** fetching npmjs.com/package/visus-mcp returned 149,589 bytes raw. visus-mcp delivered 44,129 bytes to Claude. Same information. 70% fewer tokens.
|
|
42
|
+
|
|
43
|
+
**And when a page is actively trying to manipulate your agent** — hidden instructions, obfuscated scripts, role hijacking attempts — visus strips those too and logs them in a structured compliance report. Not because Claude can't handle them. Because your agent shouldn't have to spend tokens reading attack attempts in the first place.
|
|
20
44
|
|
|
21
45
|
---
|
|
22
46
|
|
|
23
47
|
## How Visus Works
|
|
24
48
|
|
|
25
49
|
```
|
|
26
|
-
|
|
27
|
-
|
|
50
|
+
URL → Playwright Render → Format Detection (HTML/JSON/XML/RSS)
|
|
51
|
+
→ Reader Extraction (optional) → Injection Sanitizer (43 patterns)
|
|
52
|
+
→ PII Redactor → Token Ceiling (24k cap) → Clean Content → Claude
|
|
28
53
|
```
|
|
29
54
|
|
|
30
55
|
### Security Pipeline
|
|
@@ -32,9 +57,9 @@ PII Redactor → Clean Content → Claude via MCP
|
|
|
32
57
|
1. **Browser Rendering**: Headless Chromium via Playwright fetches the page
|
|
33
58
|
2. **Injection Detection**: 43 pattern categories scan for prompt injection attempts
|
|
34
59
|
3. **PII Redaction**: Emails, phone numbers, SSNs, credit cards, and IP addresses are redacted
|
|
35
|
-
4. **Clean Delivery**:
|
|
60
|
+
4. **Clean Delivery**: Stripped, formatted, token-efficient content reaches your LLM — with a compliance report attached if anything was flagged
|
|
36
61
|
|
|
37
|
-
**This pipeline
|
|
62
|
+
**This pipeline runs before content enters Claude's context window** — reducing token consumption, keeping PII out of conversation history, and generating audit logs when injection patterns are detected.
|
|
38
63
|
|
|
39
64
|
---
|
|
40
65
|
|
|
@@ -80,11 +105,16 @@ npx visus-mcp
|
|
|
80
105
|
|
|
81
106
|
### Claude Desktop Configuration
|
|
82
107
|
|
|
83
|
-
|
|
108
|
+
> [!NOTE]
|
|
109
|
+
> **No API key required.** The open-source tier works out of the box with `npx visus-mcp`.
|
|
110
|
+
> Sanitization always runs locally — web content never reaches Lateos infrastructure
|
|
111
|
+
> unless you explicitly configure the managed renderer URL.
|
|
112
|
+
|
|
113
|
+
Visus supports three deployment tiers:
|
|
84
114
|
|
|
85
|
-
**
|
|
115
|
+
**Tier 1 — Open Source / Default (No env vars required):**
|
|
86
116
|
|
|
87
|
-
|
|
117
|
+
Uses Playwright locally with full JavaScript support. Works immediately, zero configuration:
|
|
88
118
|
|
|
89
119
|
```json
|
|
90
120
|
{
|
|
@@ -97,9 +127,11 @@ Basic configuration using undici HTTP fetch (no JavaScript execution):
|
|
|
97
127
|
}
|
|
98
128
|
```
|
|
99
129
|
|
|
100
|
-
**
|
|
130
|
+
**Tier 2 — Managed / Lateos (Hosted renderer) — Coming Phase 2:**
|
|
101
131
|
|
|
102
|
-
|
|
132
|
+
> [!NOTE]
|
|
133
|
+
> The hosted Lateos renderer is part of Phase 2 and is not yet publicly available.
|
|
134
|
+
> Sign up for early access at [lateos.ai](https://lateos.ai).
|
|
103
135
|
|
|
104
136
|
```json
|
|
105
137
|
{
|
|
@@ -108,15 +140,16 @@ Use Lateos managed Lambda renderer with Playwright (supports JavaScript, SPAs):
|
|
|
108
140
|
"command": "npx",
|
|
109
141
|
"args": ["visus-mcp"],
|
|
110
142
|
"env": {
|
|
111
|
-
"VISUS_RENDERER_URL": "https://renderer.lateos.ai"
|
|
112
|
-
"NODE_EXTRA_CA_CERTS": "/path/to/system-ca-bundle.pem"
|
|
143
|
+
"VISUS_RENDERER_URL": "https://renderer.lateos.ai"
|
|
113
144
|
}
|
|
114
145
|
}
|
|
115
146
|
}
|
|
116
147
|
}
|
|
117
148
|
```
|
|
118
149
|
|
|
119
|
-
|
|
150
|
+
The sanitization pipeline always runs locally. This config simply routes page rendering (JavaScript execution) through a hosted Playwright Lambda instead of local Playwright. Available Phase 2.
|
|
151
|
+
|
|
152
|
+
**Tier 3 — BYOC (Bring Your Own Cloud):**
|
|
120
153
|
|
|
121
154
|
Deploy your own Lambda renderer (see [visus-mcp-renderer](https://github.com/visus-mcp/visus-mcp-renderer)):
|
|
122
155
|
|
|
@@ -127,8 +160,7 @@ Deploy your own Lambda renderer (see [visus-mcp-renderer](https://github.com/vis
|
|
|
127
160
|
"command": "npx",
|
|
128
161
|
"args": ["visus-mcp"],
|
|
129
162
|
"env": {
|
|
130
|
-
"VISUS_RENDERER_URL": "https://YOUR_API_ID.execute-api.YOUR_REGION.amazonaws.com"
|
|
131
|
-
"NODE_EXTRA_CA_CERTS": "/path/to/system-ca-bundle.pem"
|
|
163
|
+
"VISUS_RENDERER_URL": "https://YOUR_API_ID.execute-api.YOUR_REGION.amazonaws.com"
|
|
132
164
|
}
|
|
133
165
|
}
|
|
134
166
|
}
|
|
@@ -137,7 +169,7 @@ Deploy your own Lambda renderer (see [visus-mcp-renderer](https://github.com/vis
|
|
|
137
169
|
|
|
138
170
|
Replace `YOUR_API_ID` and `YOUR_REGION` with values from your CDK deployment output.
|
|
139
171
|
|
|
140
|
-
**CRITICAL SECURITY NOTE:** The sanitizer ALWAYS runs locally, regardless of which
|
|
172
|
+
**CRITICAL SECURITY NOTE:** The sanitizer ALWAYS runs locally, regardless of which tier you use. Rendered HTML is returned to your local visus-mcp process before Claude sees it. Web content never touches Lateos infrastructure unless you explicitly configure the managed renderer URL.
|
|
141
173
|
|
|
142
174
|
Restart Claude Desktop. Visus tools are now available to Claude.
|
|
143
175
|
|
|
@@ -147,7 +179,7 @@ Restart Claude Desktop. Visus tools are now available to Claude.
|
|
|
147
179
|
|
|
148
180
|
### `visus_fetch`
|
|
149
181
|
|
|
150
|
-
Fetch and sanitize a web page with automatic format detection. Supports HTML, JSON, XML, and RSS/Atom feeds. Includes NIST AI 600-1 / OWASP LLM / MITRE ATLAS aligned threat report when injection or PII is detected.
|
|
182
|
+
Fetch and sanitize a web page with automatic format detection. Supports HTML, JSON, XML, and RSS/Atom feeds. Includes NIST AI 600-1 / OWASP LLM / MITRE ATLAS / ISO/IEC 42001 aligned threat report when injection or PII is detected.
|
|
151
183
|
|
|
152
184
|
**Supported Formats:**
|
|
153
185
|
- **HTML** (`text/html`, `application/xhtml+xml`) - Standard web pages, returned as-is
|
|
@@ -157,7 +189,7 @@ Fetch and sanitize a web page with automatic format detection. Supports HTML, JS
|
|
|
157
189
|
|
|
158
190
|
### `visus_read`
|
|
159
191
|
|
|
160
|
-
Extract clean article content from a web page using Mozilla Readability (reader mode). Includes NIST AI 600-1 / OWASP LLM / MITRE ATLAS aligned threat report when injection or PII is detected.
|
|
192
|
+
Extract clean article content from a web page using Mozilla Readability (reader mode). Includes NIST AI 600-1 / OWASP LLM / MITRE ATLAS / ISO/IEC 42001 aligned threat report when injection or PII is detected.
|
|
161
193
|
|
|
162
194
|
**Input:**
|
|
163
195
|
```json
|
|
@@ -189,7 +221,7 @@ Extract clean article content from a web page using Mozilla Readability (reader
|
|
|
189
221
|
|
|
190
222
|
### `visus_search`
|
|
191
223
|
|
|
192
|
-
Search the web via DuckDuckGo and return sanitized results with prompt injection and PII removed. Use before `visus_fetch` or `visus_read` to safely discover and then read pages. Includes NIST AI 600-1 / OWASP LLM / MITRE ATLAS aligned threat report when injection or PII is detected.
|
|
224
|
+
Search the web via DuckDuckGo and return sanitized results with prompt injection and PII removed. Use before `visus_fetch` or `visus_read` to safely discover and then read pages. Includes NIST AI 600-1 / OWASP LLM / MITRE ATLAS / ISO/IEC 42001 aligned threat report when injection or PII is detected.
|
|
193
225
|
|
|
194
226
|
**Input:**
|
|
195
227
|
```json
|
|
@@ -222,7 +254,7 @@ All search result titles and snippets are independently sanitized before reachin
|
|
|
222
254
|
|
|
223
255
|
### `visus_fetch_structured`
|
|
224
256
|
|
|
225
|
-
Extract structured data from a web page according to a schema. Includes NIST AI 600-1 / OWASP LLM / MITRE ATLAS aligned threat report when injection or PII is detected.
|
|
257
|
+
Extract structured data from a web page according to a schema. Includes NIST AI 600-1 / OWASP LLM / MITRE ATLAS / ISO/IEC 42001 aligned threat report when injection or PII is detected.
|
|
226
258
|
|
|
227
259
|
**Input:**
|
|
228
260
|
```json
|
|
@@ -275,7 +307,7 @@ Findings are encoded using [TOON format](https://toonformat.dev) for token effic
|
|
|
275
307
|
- Pattern ID and category
|
|
276
308
|
- Severity level (CRITICAL, HIGH, MEDIUM, LOW)
|
|
277
309
|
- Confidence score
|
|
278
|
-
- Framework alignments (OWASP LLM Top 10, NIST AI 600-1, MITRE ATLAS)
|
|
310
|
+
- Framework alignments (OWASP LLM Top 10, NIST AI 600-1, MITRE ATLAS, ISO/IEC 42001)
|
|
279
311
|
- Remediation status
|
|
280
312
|
|
|
281
313
|
### 2. Markdown Compliance Report (Human-Readable)
|
|
@@ -290,11 +322,12 @@ A formatted Markdown table renders cleanly in Claude Desktop and GitHub, showing
|
|
|
290
322
|
|
|
291
323
|
### Framework Alignments
|
|
292
324
|
|
|
293
|
-
Every detected threat is mapped to
|
|
325
|
+
Every detected threat is mapped to four compliance frameworks:
|
|
294
326
|
|
|
295
327
|
- **[OWASP LLM Top 10 (2025)](https://owasp.org/www-project-top-10-for-large-language-model-applications/)**: Industry-standard LLM security risks
|
|
296
328
|
- **[NIST AI 600-1](https://csrc.nist.gov/pubs/ai/600/1/final)**: Generative AI Profile for risk management
|
|
297
329
|
- **[MITRE ATLAS](https://atlas.mitre.org/)**: Adversarial Threat Landscape for AI Systems
|
|
330
|
+
- **[ISO/IEC 42001:2023](https://www.iso.org/standard/81230.html)**: International AI Management System standard — Annex A controls for AI system security, data quality, and responsible AI governance. Globally recognized for enterprise and regulatory procurement.
|
|
298
331
|
|
|
299
332
|
### When Reports Are Generated
|
|
300
333
|
|
|
@@ -303,6 +336,32 @@ Threat reports are included in tool responses **only when findings exist**:
|
|
|
303
336
|
- ✅ PII redacted → Report included
|
|
304
337
|
- ❌ Clean content → Report omitted (zero overhead)
|
|
305
338
|
|
|
339
|
+
### Human-in-the-Loop Security
|
|
340
|
+
|
|
341
|
+
When Visus detects a **CRITICAL** severity threat, it pauses execution and surfaces a confirmation dialog before returning content:
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
⚠️ Visus blocked a CRITICAL threat on this page.
|
|
345
|
+
|
|
346
|
+
2 injection attempt(s) detected on: https://malicious.example.com
|
|
347
|
+
|
|
348
|
+
Highest severity finding: role_hijacking
|
|
349
|
+
(LLM01:2025 | AML.T0051.000)
|
|
350
|
+
|
|
351
|
+
Content has been sanitized. Proceed with clean version?
|
|
352
|
+
|
|
353
|
+
[ ✓ Proceed with sanitized content ] [ ✓ Include threat report ]
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Three outcomes:**
|
|
357
|
+
- **Accept** → Sanitized content delivered, threat report attached if requested
|
|
358
|
+
- **Decline** → Request blocked, threat details returned for review
|
|
359
|
+
- **No response / timeout** → Sanitized content delivered (fail-safe)
|
|
360
|
+
|
|
361
|
+
**Important:** HITL triggers only on CRITICAL findings. HIGH/MEDIUM/LOW findings are sanitized silently with threat report attached — no interruption to workflow.
|
|
362
|
+
|
|
363
|
+
**Security model:** Sanitization is the security gate. HITL is UX. Content is ALWAYS sanitized before reaching the LLM, whether or not you accept the elicitation prompt.
|
|
364
|
+
|
|
306
365
|
### Example Threat Report
|
|
307
366
|
|
|
308
367
|
When a HIGH severity injection is detected:
|
|
@@ -313,7 +372,7 @@ When a HIGH severity injection is detected:
|
|
|
313
372
|
**Generated:** 2026-03-23T14:30:00.000Z
|
|
314
373
|
**Source:** https://malicious.example.com
|
|
315
374
|
**Overall Severity:** HIGH
|
|
316
|
-
**Framework:** OWASP LLM Top 10 | NIST AI 600-1 | MITRE ATLAS
|
|
375
|
+
**Framework:** OWASP LLM Top 10 | NIST AI 600-1 | MITRE ATLAS | ISO/IEC 42001
|
|
317
376
|
|
|
318
377
|
### Findings Summary
|
|
319
378
|
| Severity | Count |
|
|
@@ -324,9 +383,9 @@ When a HIGH severity injection is detected:
|
|
|
324
383
|
| 🟢 LOW | 0 |
|
|
325
384
|
|
|
326
385
|
### Findings Detail
|
|
327
|
-
| # | Category | Severity | Confidence | OWASP | MITRE |
|
|
328
|
-
|
|
329
|
-
| 1 | role_hijacking | CRITICAL | 95% | LLM01:2025 | AML.T0051.000 |
|
|
386
|
+
| # | Category | Severity | Confidence | OWASP | MITRE | ISO 42001 |
|
|
387
|
+
|---|---|---|---|---|---|---|
|
|
388
|
+
| 1 | role_hijacking | CRITICAL | 95% | LLM01:2025 | AML.T0051.000 | A.6.1.5 |
|
|
330
389
|
|
|
331
390
|
### Remediation Status
|
|
332
391
|
✅ All findings sanitized. Content delivered clean.
|
|
@@ -698,7 +757,7 @@ Visus is part of the **Lateos** platform — a security-by-design AI agent frame
|
|
|
698
757
|
|
|
699
758
|
- **AWS Serverless**: Lambda, Step Functions, API Gateway, Cognito
|
|
700
759
|
- **Security**: Bedrock Guardrails, KMS encryption, Secrets Manager
|
|
701
|
-
- **Validated Patterns**: 43 injection patterns,
|
|
760
|
+
- **Validated Patterns**: 43 injection patterns, 274/274 passing tests
|
|
702
761
|
- **CISSP/CEH-Informed**: Designed by security professionals
|
|
703
762
|
|
|
704
763
|
Learn more: [lateos.ai](https://lateos.ai) (Phase 2)
|
|
@@ -749,17 +808,21 @@ npm start
|
|
|
749
808
|
|
|
750
809
|
## Project Status
|
|
751
810
|
|
|
752
|
-
|
|
811
|
+
| Version | Status | Highlights |
|
|
812
|
+
|---|---|---|
|
|
813
|
+
| v0.7.0 | ✅ Complete | HITL Elicitation Bridge for CRITICAL threats |
|
|
814
|
+
| v0.6.0 | ✅ Released | Content-Type detection (JSON/XML/RSS) |
|
|
815
|
+
| v0.5.0 | ✅ Released | TOON threat reports, NIST/OWASP/MITRE/ISO42001 |
|
|
816
|
+
| v0.4.0 | ✅ Released | Safe DuckDuckGo search |
|
|
817
|
+
| v0.3.2 | ✅ Released | Reader mode (Mozilla Readability) |
|
|
818
|
+
| v0.3.1 | ✅ Released | Security hardening, 100% compliance |
|
|
819
|
+
| v0.3.0 | ✅ Released | PII allowlist (health authority numbers) |
|
|
753
820
|
|
|
754
|
-
**Phase
|
|
755
|
-
- Lateos cloud integration for audit logging
|
|
756
|
-
- User session relay for authenticated pages
|
|
757
|
-
- Hosted tier with SLA guarantees
|
|
821
|
+
**Phase 3 — Anthropic MCP Directory submission in progress.**
|
|
758
822
|
|
|
759
|
-
|
|
760
|
-
-
|
|
761
|
-
|
|
762
|
-
- Custom pattern libraries
|
|
823
|
+
Roadmap: `visus_report` PDF export · Docker image ·
|
|
824
|
+
`visus-file-mcp` (document sanitization) ·
|
|
825
|
+
Chrome extension for authenticated pages (LinkedIn, X, dashboards)
|
|
763
826
|
|
|
764
827
|
---
|
|
765
828
|
|
|
@@ -777,7 +840,7 @@ Report vulnerabilities: **security@lateos.ai** or [GitHub Security](https://gith
|
|
|
777
840
|
|
|
778
841
|
MIT License
|
|
779
842
|
|
|
780
|
-
Copyright (c)
|
|
843
|
+
Copyright (c) 2026 Lateos (Leo Chongolnee)
|
|
781
844
|
|
|
782
845
|
---
|
|
783
846
|
|
|
@@ -791,6 +854,9 @@ Inspired by the MCP ecosystem and informed by CISSP/CEH security principles.
|
|
|
791
854
|
|
|
792
855
|
## FAQ
|
|
793
856
|
|
|
857
|
+
**Q: Does visus-mcp replace Claude's own safety features?**
|
|
858
|
+
A: No — and it's not trying to. Claude handles most injection attempts natively through its safety training. visus-mcp is a pre-filter that runs before content enters Claude's context window. The benefit is efficiency: your agent doesn't spend tokens processing noise, ads, tracking scripts, or known injection patterns that would be stripped anyway. Think of it as a pre-processor, not a replacement for model-level safety. The two layers together are more robust than either alone.
|
|
859
|
+
|
|
794
860
|
**Q: Does Visus slow down web fetching?**
|
|
795
861
|
A: Minimal overhead. Sanitization adds ~50-200ms per page.
|
|
796
862
|
|