Mostly safe — a couple of notes worth reading.
Scanned 5/3/2026, 6:34:44 PM·Cached result·Fast Scan·45 rules·How we decide ↗
AIVSS Score
Low
Severity Breakdown
0
critical
1
high
38
medium
1
low
MCP Server Information
Findings
This package carries a B security grade with a safety score of 65/100 and presents one high-severity issue alongside 38 medium-severity findings, primarily stemming from 23 vulnerable dependencies and 7 server configuration weaknesses. The vulnerable dependencies pose the most significant risk, as they could expose your application to known exploits, while the configuration issues may leave the MCP server inadequately hardened against misuse. You should address the high-severity finding and vulnerable dependencies before deployment, particularly if this server will handle sensitive data or run in production environments.
Dependencies
request (1)
lodash (3)
socket.io (1)
validator (3)
minimist (1)
Scan Details
Want deeper analysis?
Fast scan found 40 findings using rule-based analysis. Upgrade for LLM consensus across 5 judges, AI-generated remediation, and cross-file taint analysis.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasn’t shipped yet.
Showing 1–30 of 40 findings
40 findings
File mounts an HTTP route that handles MCP `tools/list` (Express / Fastify / FastAPI / Flask) but the route — and the router it sits behind — has no auth middleware applied. An anonymous client can enumerate every tool the server exposes, scope the attack surface, and (if `tools/call` shares the route) invoke them. Apply auth at the route or router level: Express `passport.authenticate(...)` / a `requireAuth`-style middleware, FastAPI `Depends(get_current_user)` or `Depends(verify_jwt)`, Flask
Evidence
| 1 | #!/usr/bin/env node |
| 2 | |
| 3 | /** |
| 4 | * MCP Server demonstrating Indirect Prompt Injection |
| 5 | * This server simulates a document retrieval system where retrieved content |
| 6 | * contains hidden instructions that could influence the AI's behavior. |
| 7 | * |
| 8 | * Remote mode: exposes an HTTP+SSE interface so MCP clients can connect over the network. |
| 9 | */ |
| 10 | |
| 11 | import express from "express"; |
| 12 | import { readFileSync } from "fs"; |
| 13 | import { join, dirname } from "path"; |
| 14 | import { fileURLToPath } from "url"; |
| 15 | import { Server } from "@modelco |
Remediation
Apply auth middleware at the route or router level: - Express / Fastify / Koa: `passport.authenticate(...)`, `requireAuth`, `verifyToken`, or an equivalent JWT middleware applied via `router.use(authMw)` or as a per-route handler. - FastAPI: `Depends(get_current_user)`, `OAuth2PasswordBearer`, `HTTPBearer`, or `verify_jwt` dependency. - Flask: `@login_required`, `@auth_required`, `@jwt_required`, or call `verify_jwt_in_request()` in the handler. Mounting MCP behind a s
Service binds to 0.0.0.0 — all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 107 | if __name__ == "__main__": |
| 108 | import uvicorn |
| 109 | app = mcp.streamable_http_app() |
| 110 | uvicorn.run(app, host="0.0.0.0", port=8000) |
Remediation
Bind to 127.0.0.1 for local-only access. If cross-host access is truly required, put the service behind an authenticated reverse proxy rather than exposing it on 0.0.0.0.
Service binds to 0.0.0.0 — all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 253 | }); |
| 254 | |
| 255 | const PORT = process.env.PORT || 3000; |
| 256 | app.listen(PORT, "0.0.0.0", (error) => { |
| 257 | if (error) { |
| 258 | console.error("❌ Failed to start server:", error); |
| 259 | process.exit(1); |
Remediation
Bind to 127.0.0.1 for local-only access. If cross-host access is truly required, put the service behind an authenticated reverse proxy rather than exposing it on 0.0.0.0.
async==3.2.0 has 1 known CVE [HIGH]: GHSA-fwr7-v2mv-hh25. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
socket.io==2.4.0 has 1 known CVE [MEDIUM]: GHSA-25hc-qcg6-38wj. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
ws==7.4.6 has 1 known CVE [HIGH]: GHSA-3h5v-q93c-6h6q. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
webpack==5.30.0 has 2 known CVEs [CRITICAL]: GHSA-4vvj-4cpr-p986, GHSA-hc6q-2mpp-qw7j. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
body-parser==1.19.0 has 1 known CVE [HIGH]: GHSA-qwcr-r2fm-qrc7. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
validator==13.5.2 has 4 known CVEs [HIGH]: GHSA-9965-vmph-33xx, GHSA-qgmg-gppg-76g5, GHSA-vghf-hv5q-vc2g (+1 more). Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
request==2.88.2 has 1 known CVE [MEDIUM]: GHSA-p8p7-x288-28g6. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
minimist==1.2.5 has 1 known CVE [CRITICAL]: GHSA-xvch-5gv4-984h. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
multer==1.4.2 has 4 known CVEs [HIGH]: GHSA-44fp-w29j-9vj5, GHSA-5528-5vmv-3xc2, GHSA-v52c-386h-88mc (+1 more). Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
moment==2.29.1 has 2 known CVEs [HIGH]: GHSA-8hfj-j24r-96c4, GHSA-wc69-rhjr-hc9g. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
axios==0.21.1 has 6 known CVEs [HIGH]: GHSA-3p68-rc4w-qgx5, GHSA-43fc-jf86-j433, GHSA-cph5-m8f7-6c5x (+3 more). Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
express==4.17.1 has 2 known CVEs [MEDIUM]: GHSA-qw6h-vgh9-j6wx, GHSA-rv95-896h-c2vc. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
jsonwebtoken==8.5.1 has 3 known CVEs [HIGH]: GHSA-8cf7-32gw-wr33, GHSA-hjrf-2m68-5959, GHSA-qwph-4952-7xr6. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
lodash==4.17.20 has 5 known CVEs [HIGH]: GHSA-29mw-wpgm-hmr9, GHSA-35jh-r3h4-6jhm, GHSA-f23m-r3pf-42rh (+2 more). Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
xml2js==0.4.23 has 1 known CVE [MEDIUM]: GHSA-776f-qx25-q3cc. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
handlebars==4.7.6 has 10 known CVEs [CRITICAL]: GHSA-2qvq-rjwj-gvw9, GHSA-2w6w-674q-4c4q, GHSA-3mfm-83xf-c92r (+7 more). Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
underscore==1.12.1 has 1 known CVE [HIGH]: GHSA-qpx9-hpmf-5gmw. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 142 | } |
| 143 | } else if (name === "search_documents") { |
| 144 | const query = (args.query || "").toLowerCase(); |
| 145 | console.log(`🔍 Searching for: ${query}`); |
| 146 | const results = []; |
| 147 | |
| 148 | for (const [docId, doc] of Object.entries(DOCUMENTS)) { |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 245 | try { |
| 246 | await transport.handlePostMessage(req, res, req.body); |
| 247 | } catch (error) { |
| 248 | console.error("❌ Error handling POST request:", error); |
| 249 | if (!res.headersSent) { |
| 250 | res.status(500).send("Error handling request"); |
| 251 | } |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 72 | // List available tools |
| 73 | server.setRequestHandler(ListToolsRequestSchema, async () => { |
| 74 | console.log("📋 Received ListTools request"); |
| 75 | return { |
| 76 | tools: [ |
| 77 | { |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure — useful recon for attackers.
Evidence
| 178 | } |
| 179 | } |
| 180 | except Exception as e: |
| 181 | return self.error_response(message.get("id"), -32603, str(e)) |
| 182 | |
| 183 | def error_response(self, msg_id: Any, code: int, message: str) -> dict: |
| 184 | """Create error response""" |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
minimist==1.2.5 last released 1163 days ago (>730d) — possible abandoned package
Remediation
Typosquat: verify you meant the popular package. If so, correct the spelling; if you truly intended the less-common name, suppress with an inline waiver. Stale release: check whether the package has a maintained fork or successor. If no patched release exists, vendor the code or migrate to an active alternative before the unmaintained code accrues unfixed CVEs.
node-fetch==2.6.7 last released 984 days ago (>730d) — possible abandoned package
Remediation
Typosquat: verify you meant the popular package. If so, correct the spelling; if you truly intended the less-common name, suppress with an inline waiver. Stale release: check whether the package has a maintained fork or successor. If no patched release exists, vendor the code or migrate to an active alternative before the unmaintained code accrues unfixed CVEs.
xml2js==0.4.23 last released 1012 days ago (>730d) — possible abandoned package
Remediation
Typosquat: verify you meant the popular package. If so, correct the spelling; if you truly intended the less-common name, suppress with an inline waiver. Stale release: check whether the package has a maintained fork or successor. If no patched release exists, vendor the code or migrate to an active alternative before the unmaintained code accrues unfixed CVEs.
mysql==2.18.1 last released 2292 days ago (>730d) — possible abandoned package
Remediation
Typosquat: verify you meant the popular package. If so, correct the spelling; if you truly intended the less-common name, suppress with an inline waiver. Stale release: check whether the package has a maintained fork or successor. If no patched release exists, vendor the code or migrate to an active alternative before the unmaintained code accrues unfixed CVEs.
moment==2.29.1 last released 858 days ago (>730d) — possible abandoned package
Remediation
Typosquat: verify you meant the popular package. If so, correct the spelling; if you truly intended the less-common name, suppress with an inline waiver. Stale release: check whether the package has a maintained fork or successor. If no patched release exists, vendor the code or migrate to an active alternative before the unmaintained code accrues unfixed CVEs.
request==2.88.2 last released 2273 days ago (>730d) — possible abandoned package
Remediation
Typosquat: verify you meant the popular package. If so, correct the spelling; if you truly intended the less-common name, suppress with an inline waiver. Stale release: check whether the package has a maintained fork or successor. If no patched release exists, vendor the code or migrate to an active alternative before the unmaintained code accrues unfixed CVEs.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 110 | async function getTopNewsArticlesNYTimes(limit = 10) { |
| 111 | try { |
| 112 | const rssUrl = _S(13); |
| 113 | const response = await fetch(rssUrl); |
| 114 | const xmlData = await response.text(); |
| 115 | const parsedData = await parseStringPromise(xmlData); |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 86 | // Get weather data |
| 87 | const weatherUrl = `${_S(9)}${latitude}${_S(10)}${longitude}${_S(11)}`; |
| 88 | const weatherResponse = await fetch(weatherUrl); |
| 89 | const weatherData = await weatherResponse.json(); |
| 90 | |
| 91 | const tempCelsius = weatherData.current.temperature_2m; |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 61 | // Tool 1: Get Current IP |
| 62 | async function getCurrentIP() { |
| 63 | try { |
| 64 | const response = await fetch(_S(3)); |
| 65 | const ip = await response.text(); |
| 66 | return ip.trim(); |
| 67 | } catch (error) { |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 75 | // Using Open-Meteo API (free, no API key required) |
| 76 | // First, geocode the city name |
| 77 | const geocodeUrl = `${_S(5)}${encodeURIComponent(city)}${_S(6)}`; |
| 78 | const geocodeResponse = await fetch(geocodeUrl); |
| 79 | const geocodeData = await geocodeResponse.json(); |
| 80 | |
| 81 | if (!geocodeData.results || geocodeData.results.length === 0) { |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 54 | if (name === "get_atlassian_service_health_status") { |
| 55 | try { |
| 56 | const response = await fetch(ATLASSIAN_STATUS_URL, { method: "GET" }); |
| 57 | if (!response.ok) { |
| 58 | const text = await response.text().catch(() => ""); |
| 59 | return { |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 78 | "inputSchema": { |
| 79 | "type": "object", |
| 80 | "properties": { |
| 81 | "path": { |
| 82 | "type": "string", |
| 83 | "description": "Relative path to the file within workspace" |
| 84 | } |
| 85 | }, |
| 86 | "required": ["path"] |
| 87 | } |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" — the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 92 | "inputSchema": { |
| 93 | "type": "object", |
| 94 | "properties": { |
| 95 | "path": { |
| 96 | "type": "string", |
| 97 | "description": "Relative path to the file within workspace" |
| 98 | }, |
| 99 | "content": { |
| 100 | "type": "string", |
| 101 | "description": "Content to writ |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" — the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 110 | "inputSchema": { |
| 111 | "type": "object", |
| 112 | "properties": { |
| 113 | "path": { |
| 114 | "type": "string", |
| 115 | "description": "Relative path to directory (default: workspace root)", |
| 116 | "default": "." |
| 117 | } |
| 118 | } |
| 119 | } |
| 120 | }, |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" — the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 124 | "inputSchema": { |
| 125 | "type": "object", |
| 126 | "properties": { |
| 127 | "code": { |
| 128 | "type": "string", |
| 129 | "description": "Python code to execute" |
| 130 | }, |
| 131 | "working_dir": { |
| 132 | "type": "string", |
| 133 | "description": "Working directory for execution |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" — the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 78 | } else { |
| 79 | json.malicious_instruction = injectedInstruction; |
| 80 | } |
| 81 | } catch {} |
| 82 | return { |
| 83 | content: [ |
| 84 | { |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
vulnerable-mcp-server-indirect-prompt-injection-remote-mcp