Mostly safe — a couple of notes worth reading.
Scanned 5/3/2026, 6:41:15 PM·Cached result·Fast Scan·45 rules·How we decide ↗
AIVSS Score
Low
Severity Breakdown
0
critical
0
high
110
medium
126
low
MCP Server Information
Findings
This package receives a B grade with a safety score of 56/100, driven primarily by 126 readiness issues and 69 server configuration concerns that could affect deployment stability and security posture. While no critical or high-severity vulnerabilities were detected, the 110 medium-severity findings—including 16 cases of potential ANSI escape injection, 12 instances of verbose error handling, and resource exhaustion risks—warrant review before production use. The readiness gaps suggest this package may require additional hardening or configuration work to meet enterprise security standards.
No known CVEs found for this package or its dependencies.
Scan Details
Want deeper analysis?
Fast scan found 20 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.
20 of 20 findings
20 findings
Dockerfile never sets a non-root `USER` directive, so the CMD runs as root by default. Any RCE or library-level vulnerability exploited inside this container gets full privileges (MCP Top-10 R3). Add `USER <non-root>` before CMD / ENTRYPOINT in the final stage — e.g. `USER 1000`, `USER nobody`, or `USER nonroot` on distroless.
Evidence
| 1 | FROM python:3.12-slim |
| 2 | WORKDIR /app |
| 3 | |
| 4 | RUN apt-get update && apt-get install -y curl wget && \ |
| 5 | wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/local/bin/mc && \ |
| 6 | chmod +x /usr/local/bin/mc && \ |
| 7 | apt-get clean && rm -rf /var/lib/apt/lists/* |
| 8 | |
| 9 | RUN pip install uv |
| 10 | |
| 11 | # Copy dependency files only |
| 12 | COPY pyproject.toml /tmp/ |
| 13 | #COPY uv.lock /tmp/ |
| 14 | |
| 15 | # Install dependencies |
| 16 | WORKDIR /tmp |
| 17 | RUN uv pip install --system . |
| 18 | |
| 19 | # Switch back to /app (this will be overridden by volume mount) |
| 20 | WORKDIR / |
Remediation
Create and switch to a non-root user before the CMD / ENTRYPOINT: RUN adduser --system --uid 1000 app USER 1000 Or reuse the base image's shipped non-root account (e.g. `USER nobody`, `USER nonroot` on distroless). Multi-stage builds only need the USER directive in the final stage.
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
| 478 | self._queue.task_done() |
| 479 | raise |
| 480 | except Exception as e: |
| 481 | print(f"Error in event processing loop: {e}") |
| 482 | # Mark task done for this event |
| 483 | if event is not None and self._queue is not None: |
| 484 | self._queue.task_done() |
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
| 64 | self.conn = conn |
| 65 | |
| 66 | async def sessionUpdate(self, params): |
| 67 | print(f"Session update: {params}") |
| 68 | |
| 69 | # Add other required methods... |
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
| 216 | elif key in {"q", "Q"}: # Quit |
| 217 | break |
| 218 | except Exception as e: |
| 219 | print(f"\nError handling input: {e}") |
| 220 | break |
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
| 213 | print_formatted_text(HTML("<b>Final selection</b>")) |
| 214 | print_formatted_text(f"Resolved model: {resolved_model}") |
| 215 | print(json.dumps(payload, indent=2)) |
| 216 | return 0 |
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
| 63 | # Example 2: Secure credential entry via tool call |
| 64 | console.print("[bold yellow]Example 2: Secure Credential Entry[/bold yellow]") |
| 65 | console.print( |
| 66 | "[dim]The server will request API key entry via a secure external page.[/dim]\n" |
| 67 | ) |
| 68 | result = await agent.send('***CALL_TOOL enter_api_key {"api_name": "OpenAI"}') |
| 69 | panel = Panel( |
| 70 | result, |
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
| 176 | return decorator |
| 177 | |
| 178 | async def signal(self, signal) -> None: |
| 179 | print(f"[SIGNAL SENT: {signal.name}] Value: {signal.payload}") |
| 180 | |
| 181 | handlers = self._handlers.get(signal.name, []) |
| 182 | await gather_with_cancel(handler(signal) for handler in handlers) |
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
| 2452 | print( |
| 2453 | f"Cache: read={turn_usage.cache_usage.cache_read_tokens}, write={turn_usage.cache_usage.cache_write_tokens}" |
| 2454 | ) |
| 2455 | print(f"Effective input: {turn_usage.effective_input_tokens}") |
| 2456 | print( |
| 2457 | f"Accumulator: total_turns={self.usage_accumulator.turn_count}, cumulative_billing={self.usage_accumulator.cumulative_billing_tokens}, current_context={self.usage_accumulator.current_context_tokens}" |
| 2458 | ) |
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
| 165 | ), |
| 166 | ).run() |
| 167 | |
| 168 | print(json.dumps(payload, indent=2)) |
| 169 | return 0 |
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
| 605 | if issues: |
| 606 | if elicitations: |
| 607 | console.console.print( |
| 608 | "[yellow]" |
| 609 | f"MCP server {server_name} returned non-compliant URL elicitation payload:" |
| 610 | "[/yellow]" |
| 611 | ) |
| 612 | else: |
| 613 | console.console.print( |
| 614 | "[yellow]" |
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
| 152 | loop.run_in_executor(None, input, "Enter value: "), timeout_seconds |
| 153 | ) |
| 154 | except asyncio.TimeoutError: |
| 155 | print("\nTimeout waiting for input") |
| 156 | raise |
| 157 | else: |
| 158 | value = await loop.run_in_executor(None, input, "Enter value: ") |
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
| 163 | _validate_json_payload(structured_text) |
| 164 | print("structured JSON validated against supplied schema") |
| 165 | except RequestError as exc: |
| 166 | print(f"ACP request failed: {exc}", file=sys.stderr) |
| 167 | if exc.data is not None: |
| 168 | print(json.dumps(exc.data, indent=2), file=sys.stderr) |
| 169 | if exc.code == -32000: |
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
| 53 | ) |
| 54 | async def main() -> None: |
| 55 | console = Console(file=sys.stderr) |
| 56 | console.print( |
| 57 | "\n[bright_red]Router Workflow Demo[/bright_red]\n\n" |
| 58 | "Enter a request to route it to the appropriate agent.\nEnter [bright_red]STOP[/bright_red] to run the demo, [bright_red]EXIT[/bright_red] to leave" |
| 59 | ) |
| 60 | |
| 61 | async with fast.run() as agent: |
| 62 | await agent.interactive(agent_name="route") |
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
| 2446 | # Print raw usage for debugging |
| 2447 | print(f"\n=== USAGE DEBUG ({turn_usage.model}) ===") |
| 2448 | print(f"Raw usage: {raw_usage}") |
| 2449 | print( |
| 2450 | f"Turn usage: input={turn_usage.input_tokens}, output={turn_usage.output_tokens}, current_context={turn_usage.current_context_tokens}" |
| 2451 | ) |
| 2452 | print( |
| 2453 | f"Cache: read={turn_usage.cache_usage.cache_read_tokens}, write={turn_usage.cache_usage.cache_write_tokens}" |
| 2454 | ) |
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
| 49 | # Example 1: OAuth-like authorization via tool call |
| 50 | console.print("[bold yellow]Example 1: OAuth Authorization Flow[/bold yellow]") |
| 51 | console.print("[dim]The server will request authorization for an external service.[/dim]\n") |
| 52 | result = await agent.send('***CALL_TOOL authorize_api_access {"service_name": "GitHub"}') |
| 53 | panel = Panel( |
| 54 | result, |
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.
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
| 909 | # Build schema for structured output |
| 910 | schema = None |
| 911 | try: |
| 912 | schema = model.model_json_schema() |
| 913 | except Exception: |
| 914 | pass |
| 915 | response_schema = model if schema is None else schema |
| 916 | |
| 917 | # Convert the last user message to provider-native content for the current turn |
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.
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
| 292 | finally: |
| 293 | self._runtime_context_loading.discard(model_id) |
| 294 | try: |
| 295 | self.app.invalidate() |
| 296 | except Exception: |
| 297 | pass |
| 298 | |
| 299 | @classmethod |
| 300 | def _model_row_label( |
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.
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
| 2430 | remaining_instances = list(self._server_managed_instances) |
| 2431 | for instance in remaining_instances: |
| 2432 | try: |
| 2433 | await self._server_instance_dispose(instance) |
| 2434 | except Exception: |
| 2435 | pass |
| 2436 | self._server_managed_instances.clear() |
| 2437 | return |
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.
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
| 621 | try: |
| 622 | sys.stderr.write(line) |
| 623 | sys.stderr.flush() |
| 624 | return |
| 625 | except Exception: |
| 626 | pass |
| 627 | |
| 628 | try: |
| 629 | fd = os.open("/dev/tty", os.O_WRONLY | os.O_NOCTTY) |
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.
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
| 79 | except OSError: |
| 80 | return None |
| 81 | try: |
| 82 | os.set_blocking(tty_fd, True) |
| 83 | except Exception: |
| 84 | pass |
| 85 | return os.fdopen(tty_fd, "w", buffering=1, encoding="utf-8", errors="replace") |
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.
route