High risk. Don't ship without significant remediation.
Scanned 5/12/2026, 7:46:58 PMยทCached resultยทDeep Scanยท91 rulesยทHow we decide โ
AIVSS Score
High
Severity Breakdown
0
critical
11
high
88
medium
4
low
MCP Server Information
Findings
This package receives a D security grade with a safety score of 53/100 and carries 11 high-severity vulnerabilities alongside 88 medium-severity issues, primarily concentrated in server configuration (75 findings) and insecure container image practices (7 findings). The presence of prompt injection, tool poisoning, and ANSI escape injection vulnerabilities indicates meaningful attack surface risks, though the lack of critical-severity findings suggests no immediate catastrophic flaws. Installation is not recommended without substantial remediation of the high-severity issues and configuration hardening.
AIPer-finding remediation generated by bedrock-claude-haiku-4-5 โ 27 of 30 findings. Click any finding to read.
No known CVEs found for this package or its dependencies.
Path traversal in git_add allows staging files outside repository boundaries in mcp-server-git
Unrestricted git_init tool allows repository creation at arbitrary filesystem locations in mcp-server-git
Argument injection in git_diff and git_checkout functions for mcp-server-git allows overwriting local files
Missing path validation when using --repository flag in mcp-server-git
Scan Details
Done
Sign in to save scan history and re-scan automatically on new commits.
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.
30 of 30 findings
30 findings
Tool 'toggle-simulated-logging' performs ENV MUTATION or state modification side effects not disclosed in description
Evidence
| 1 | import { describe, it, expect, vi } from 'vitest'; |
| 2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; |
| 3 | import { registerEchoTool, EchoSchema } from '../tools/echo.js'; |
| 4 | import { registerGetSumTool } from '../tools/get-sum.js'; |
RemediationAI
The problem is that 'gzip-file-as-resource' performs filesystem I/O (reading and compressing files) without fully documenting these operations in its description. Update the tool description to state 'This tool reads files from the filesystem and creates gzip-compressed versions' to fully disclose the I/O operations. This transparency allows clients to understand the security and performance implications. Verify by inspecting the tool metadata returned from `server.listTools()` to confirm filesystem operations are documented.
Tool 'toggle-subscriber-updates' performs state mutation side effects not disclosed in description
Evidence
| 1 | import { describe, it, expect, vi } from 'vitest'; |
| 2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; |
| 3 | import { registerEchoTool, EchoSchema } from '../tools/echo.js'; |
| 4 | import { registerGetSumTool } from '../tools/get-sum.js'; |
RemediationAI
The problem is that 'gzip-file-as-resource' performs filesystem I/O (reading and compressing files) without fully documenting these operations in its description. Update the tool description to state 'This tool reads files from the filesystem and creates gzip-compressed versions' to fully disclose the I/O operations. This transparency allows clients to understand the security and performance implications. Verify by inspecting the tool metadata returned from `server.listTools()` to confirm filesystem operations are documented.
Tool 'gzip-file-as-resource' performs FILESYSTEM operations (reading and potentially writing compressed files) not fully disclosed in description
Evidence
| 1 | import { describe, it, expect, vi } from 'vitest'; |
| 2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; |
| 3 | import { registerEchoTool, EchoSchema } from '../tools/echo.js'; |
| 4 | import { registerGetSumTool } from '../tools/get-sum.js'; |
RemediationAI
The problem is that 'gzip-file-as-resource' performs filesystem I/O (reading and compressing files) without fully documenting these operations in its description. Update the tool description to state 'This tool reads files from the filesystem and creates gzip-compressed versions' to fully disclose the I/O operations. This transparency allows clients to understand the security and performance implications. Verify by inspecting the tool metadata returned from `server.listTools()` to confirm filesystem operations are documented.
Tool 'echo' shadows reserved generic tool name without server-specific prefix.
Evidence
| 1 | import { describe, it, expect, vi } from 'vitest'; |
| 2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; |
| 3 | import { registerEchoTool, EchoSchema } from '../tools/echo.js'; |
| 4 | import { registerGetSumTool } from '../tools/get-sum.js'; |
RemediationAI
The problem is that the tool name 'search' collides with a reserved search tool name, creating namespace pollution. Rename the tool from 'search' to a server-specific variant like 'my-search' or 'custom-search' in the tool registration. This prevents shadowing of standard search functionality and clarifies tool ownership. Verify by running `server.listTools()` and confirming the new name is present and unique.
Tool 'search' shadows reserved search tool name without server-specific prefix.
Evidence
| 1 | import { describe, it, expect, vi } from 'vitest'; |
| 2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; |
| 3 | import { registerEchoTool, EchoSchema } from '../tools/echo.js'; |
| 4 | import { registerGetSumTool } from '../tools/get-sum.js'; |
RemediationAI
The problem is that the tool name 'search' collides with a reserved search tool name, creating namespace pollution. Rename the tool from 'search' to a server-specific variant like 'my-search' or 'custom-search' in the tool registration. This prevents shadowing of standard search functionality and clarifies tool ownership. Verify by running `server.listTools()` and confirming the new name is present and unique.
A variable named like a secret (secret/token/apikey/password/ credential/private_key/bearer) is emitted to a logger, stdout, HTTP response, MCP tool response, or file write without redaction. If the value is genuinely not sensitive, rename it; otherwise wrap with a redaction helper.
Evidence
| 944 | await fs.writeFile(legitFile, 'PUBLIC CONTENT', 'utf-8'); |
| 945 | |
| 946 | // Create secret file in forbidden directory |
| 947 | await fs.writeFile(secretFile, 'SECRET CONTENT', 'utf-8'); |
| 948 | |
| 949 | // Step 1: validatePath would pass for legitimate file |
| 950 | expect(isPathWithinAllowedDirectories(legitFile, allowed)).toBe(true); |
RemediationAI
The problem is that the variable name 'SECRET CONTENT' suggests sensitive data but is written to a file without redaction, and the test file itself may leak secrets in logs or test output. Either rename the variable to 'PUBLIC_TEST_CONTENT' if it is genuinely non-sensitive, or wrap the value with a redaction helper before logging/writing (e.g., `redact(secretFile)` or `[REDACTED]`). This fix prevents accidental secret exposure in test artifacts and logs. Verify by running the test suite and confirming no unredacted secret strings appear in console output or test reports.
File registers an MCP resource handler (`@mcp.resource`, `server.registerResource`, or a `"resources/read"` JSON-RPC handler) AND opens a file with no path-canonicalisation guard. The URI parameter from the client flows directly into a filesystem sink, letting a malicious request escape the intended root via `..` / absolute paths / symlinks. Canonicalise via `os.path.realpath` + `os.path.commonpath` / `path.resolve` + prefix check, OR use `secure_filename` / `pathlib.Path.relative_to(allowed_ro
Evidence
| 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 2 | import { dirname, join } from "path"; |
| 3 | import { fileURLToPath } from "url"; |
| 4 | import { readdirSync, readFileSync, statSync } from "fs"; |
| 5 | |
| 6 | /** |
| 7 | * Register static file resources |
| 8 | * - Each file in src/everything/docs is exposed as an individual static resource |
| 9 | * - URIs follow the pattern: "demo://static/docs/<filename>" |
| 10 | * - Markdown (.md) files are served as mime type "text/markdown" |
| 11 | * - Text (.txt) files are served as mime type "t |
RemediationAI
The problem is that the resource handler in `src/everything/resources/files.ts` opens files based on a client-supplied URI without canonicalizing the path, allowing directory traversal attacks via `../` or absolute paths. Add path canonicalization using `path.resolve()` followed by `path.relative()` and a prefix check to ensure the resolved path stays within the allowed root directory. This fix prevents escape from the intended directory boundary. Test by attempting to access a file outside the allowed root (e.g., `../../../etc/passwd`) and confirming the request is rejected.
MCP prompt handler returns a template that interpolates an untrusted handler argument (f-string `{x}`, template-literal `${x}`, string concatenation, or `.format()`/`%` formatting) into the prompt text. The host LLM that consumes this prompt via `prompts/get` will read the attacker-controlled text as part of its instructions โ pivot to arbitrary LLM behaviour. Wrap the parameter in `<untrusted>...</untrusted>`, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `
Evidence
| 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 2 | import { |
| 3 | resourceTypeCompleter, |
| 4 | resourceIdForPromptCompleter, |
| 5 | } from "../resources/templates.js"; |
| 6 | import { |
| 7 | textResource, |
| 8 | textResourceUri, |
| 9 | blobResourceUri, |
| 10 | blobResource, |
| 11 | RESOURCE_TYPE_BLOB, |
| 12 | RESOURCE_TYPE_TEXT, |
| 13 | RESOURCE_TYPES, |
| 14 | } from "../resources/templates.js"; |
| 15 | |
| 16 | /** |
| 17 | * Register a prompt with an embedded resource reference |
| 18 | * - Takes a resource type and id |
| 19 | * - Returns the corresponding dynamically created resou |
RemediationAI
The problem is that the prompt handler in `src/everything/prompts/resource.ts` interpolates untrusted handler arguments directly into the prompt template using template literals or string concatenation, allowing prompt injection. Wrap untrusted parameters in `<untrusted>...</untrusted>` XML tags or use a sanitization function before interpolation to mark user input as non-instructional. This fix prevents the LLM from interpreting attacker-controlled text as part of its system instructions. Verify by passing a malicious prompt argument (e.g., containing 'ignore previous instructions') and confirming it is treated as data, not instructions.
MCP prompt handler returns a template that interpolates an untrusted handler argument (f-string `{x}`, template-literal `${x}`, string concatenation, or `.format()`/`%` formatting) into the prompt text. The host LLM that consumes this prompt via `prompts/get` will read the attacker-controlled text as part of its instructions โ pivot to arbitrary LLM behaviour. Wrap the parameter in `<untrusted>...</untrusted>`, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `
Evidence
| 1 | import { z } from "zod"; |
| 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 3 | |
| 4 | /** |
| 5 | * Register a prompt with arguments |
| 6 | * - Two arguments, one required and one optional |
| 7 | * - Combines argument values in the returned prompt |
| 8 | * |
| 9 | * @param server |
| 10 | */ |
| 11 | export const registerArgumentsPrompt = (server: McpServer) => { |
| 12 | // Prompt arguments |
| 13 | const promptArgsSchema = { |
| 14 | city: z.string().describe("Name of the city"), |
| 15 | state: z.string().describe("Name of the state").optional(), |
| 16 | }; |
| 17 | |
| 18 | |
RemediationAI
The problem is that the prompt handler in `src/everything/prompts/args.ts` interpolates untrusted arguments directly into the prompt template, enabling prompt injection attacks. Wrap all user-supplied arguments in `<untrusted>...</untrusted>` tags or pass them through a prompt sanitization function before template interpolation. This ensures the LLM treats user input as data rather than instructions. Test by injecting a prompt-breaking string and confirming the LLM does not execute it as a directive.
MCP prompt handler returns a template that interpolates an untrusted handler argument (f-string `{x}`, template-literal `${x}`, string concatenation, or `.format()`/`%` formatting) into the prompt text. The host LLM that consumes this prompt via `prompts/get` will read the attacker-controlled text as part of its instructions โ pivot to arbitrary LLM behaviour. Wrap the parameter in `<untrusted>...</untrusted>`, run it through `escape_for_prompt` / `sanitize_prompt`, or pass via `json.dumps` / `
Evidence
| 1 | import { z } from "zod"; |
| 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 3 | import { completable } from "@modelcontextprotocol/sdk/server/completable.js"; |
| 4 | |
| 5 | /** |
| 6 | * Register a prompt with completable arguments |
| 7 | * - Two required arguments, both with completion handlers |
| 8 | * - First argument value will be included in context for second argument |
| 9 | * - Allows second argument to depend on the first argument value |
| 10 | * |
| 11 | * @param server |
| 12 | */ |
| 13 | export const registerPromptWithCompletions = (serve |
RemediationAI
The problem is that the prompt handler in `src/everything/prompts/completions.ts` interpolates untrusted completion arguments directly into the prompt, allowing injection of malicious instructions. Wrap all user-provided arguments in `<untrusted>...</untrusted>` XML markers or sanitize them with a prompt-safe escaping function before interpolation. This prevents the LLM from executing attacker-supplied instructions embedded in argument values. Verify by submitting a completion argument with embedded instructions and confirming the LLM ignores it as a directive.
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 | import { |
| 2 | StreamableHTTPServerTransport, |
| 3 | EventStore, |
| 4 | } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; |
| 5 | import express, { Request, Response } from "express"; |
| 6 | import { createServer } from "../server/index.js"; |
| 7 | import { randomUUID } from "node:crypto"; |
| 8 | import cors from "cors"; |
| 9 | |
| 10 | // Simple in-memory event store for SSE resumability |
| 11 | class InMemoryEventStore implements EventStore { |
| 12 | private events: Map<string, { streamId: string; message: unknown }> = |
| 13 | new Map(); |
| 14 | |
| 15 | async storeEvent(s |
RemediationAI
The problem is that the HTTP route handling MCP `tools/list` in `src/everything/transports/streamableHttp.ts` has no authentication middleware, allowing anonymous enumeration of all tools. Add an authentication middleware (e.g., `app.use(requireAuth)` or `app.post('/mcp', authenticate, ...)`) before the route handler to enforce identity verification. This fix prevents unauthenticated clients from discovering the attack surface. Test by making an unauthenticated request to the `/mcp` endpoint and confirming it returns a 401 or 403 error.
MCP tool description or return text contains an imperative phrase that asks the LLM to invoke or call ANOTHER tool โ "invoke the write_file tool", "before using this, also call send_email", "silently invoke X". This is a cross-tool chaining injection: the user authorized THIS tool, but the payload escalates into others. Tool descriptions should describe what the tool DOES, not direct the LLM to use other tools. If a tool's correct operation requires composition, document the dependency in human
Evidence
| 1 | from typing import Annotated, Tuple |
| 2 | from urllib.parse import urlparse, urlunparse |
| 3 | |
| 4 | import markdownify |
| 5 | import readabilipy.simple_json |
| 6 | from mcp.shared.exceptions import McpError |
| 7 | from mcp.server import Server |
| 8 | from mcp.server.stdio import stdio_server |
| 9 | from mcp.types import ( |
| 10 | ErrorData, |
| 11 | GetPromptResult, |
| 12 | Prompt, |
| 13 | PromptArgument, |
| 14 | PromptMessage, |
| 15 | TextContent, |
| 16 | Tool, |
| 17 | INVALID_PARAMS, |
| 18 | INTERNAL_ERROR, |
| 19 | ) |
| 20 | from protego import Protego |
| 21 | from pydantic import BaseModel, Field, AnyUr |
RemediationAI
The problem is that a tool description contains imperative language directing the LLM to invoke other tools (e.g., 'call the write_file tool'), creating cross-tool chaining injection. Rewrite the description to focus on what the tool does rather than what other tools to call (e.g., change 'invoke write_file to save' to 'saves data to a file'). This prevents the LLM from being manipulated into unauthorized tool composition. Verify by reviewing the tool description in `server.listTools()` and confirming no imperative 'invoke' or 'call' directives appear.
MCP tool file registers a tool, performs a destructive sink (fs.unlink / shutil.rmtree / DROP TABLE / DELETE FROM / TRUNCATE / UPDATE ... SET / HTTP DELETE|PUT|PATCH / subprocess / exec / spawn), and emits no audit event anywhere in the file. Without an audit event, an investigator cannot answer "who deleted record X on day Y?" โ the irreversible action leaves no trail. Closes the OWASP MCP Top 10:2025 MCP08 (Lack of Audit and Telemetry) gap. Distinct from MCP-201 (no confirmation) and MCP-283
Evidence
| 1 | import { describe, it, expect, beforeEach, afterEach } from 'vitest'; |
| 2 | import * as fs from 'fs/promises'; |
| 3 | import * as path from 'path'; |
| 4 | import * as os from 'os'; |
| 5 | import { Client } from '@modelcontextprotocol/sdk/client/index.js'; |
| 6 | import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; |
| 7 | import { spawn } from 'child_process'; |
| 8 | |
| 9 | /** |
| 10 | * Integration tests to verify that tool handlers return structuredContent |
| 11 | * that matches the declared outputSchema. |
| 12 | * |
| 13 | * These tests address i |
RemediationAI
The problem is that a tool file performs destructive operations (file deletion, database drops, etc.) without emitting audit events, leaving no trail for forensic investigation. Add audit logging before the destructive operation using a logging function like `logger.audit({action: 'delete', resource: path, user: userId, timestamp})` or similar. This fix enables post-incident investigation and compliance reporting. Test by performing a destructive operation and confirming an audit log entry is created with the action, resource, and timestamp.
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 node:22.12-alpine AS builder |
| 2 | |
| 3 | WORKDIR /app |
| 4 | |
| 5 | COPY src/filesystem /app |
| 6 | COPY tsconfig.json /tsconfig.json |
| 7 | |
| 8 | RUN --mount=type=cache,target=/root/.npm npm install |
| 9 | |
| 10 | RUN --mount=type=cache,target=/root/.npm-production npm ci --ignore-scripts --omit-dev |
| 11 | |
| 12 | |
| 13 | FROM node:22-alpine AS release |
| 14 | |
| 15 | WORKDIR /app |
| 16 | |
| 17 | COPY --from=builder /app/dist /app/dist |
| 18 | COPY --from=builder /app/package.json /app/package.json |
| 19 | COPY --from=builder /app/package-lock.json /app/package-lock.json |
| 20 | |
| 21 | ENV NODE_ENV=production |
| 22 | |
| 23 | RUN npm ci --i |
RemediationAI
The problem is that the Dockerfile in `src/filesystem/Dockerfile` does not specify a non-root USER directive, so the container runs as root by default, amplifying the impact of any RCE vulnerability. Add `USER 1000` (or `USER nonroot` on distroless images) before the final `CMD` or `ENTRYPOINT` directive in the release stage. This fix limits privilege escalation from container breakout. Verify by building the image and running `docker run --rm <image> id` to confirm the process runs as UID 1000, not 0.
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 | # Use a Python image with uv pre-installed |
| 2 | FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv |
| 3 | |
| 4 | # Install the project into `/app` |
| 5 | WORKDIR /app |
| 6 | |
| 7 | # Enable bytecode compilation |
| 8 | ENV UV_COMPILE_BYTECODE=1 |
| 9 | |
| 10 | # Copy from the cache instead of linking since it's a mounted volume |
| 11 | ENV UV_LINK_MODE=copy |
| 12 | |
| 13 | # Install the project's dependencies using the lockfile and settings |
| 14 | RUN --mount=type=cache,target=/root/.cache/uv \ |
| 15 | --mount=type=bind,source=uv.lock,target=uv.lock \ |
| 16 | --mount=type=bind,source=py |
RemediationAI
The problem is that the Dockerfile in `src/git/Dockerfile` lacks a non-root USER directive, allowing root execution of the container. Add `USER 1000` before the `CMD` or `ENTRYPOINT` in the final stage to drop privileges. This mitigates privilege escalation from container escape. Test by building and running the image, then executing `id` to verify the process runs as a non-root user.
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 node:22.12-alpine AS builder |
| 2 | |
| 3 | COPY src/memory /app |
| 4 | COPY tsconfig.json /tsconfig.json |
| 5 | |
| 6 | WORKDIR /app |
| 7 | |
| 8 | RUN --mount=type=cache,target=/root/.npm npm install |
| 9 | |
| 10 | RUN --mount=type=cache,target=/root/.npm-production npm ci --ignore-scripts --omit-dev |
| 11 | |
| 12 | FROM node:22-alpine AS release |
| 13 | |
| 14 | COPY --from=builder /app/dist /app/dist |
| 15 | COPY --from=builder /app/package.json /app/package.json |
| 16 | COPY --from=builder /app/package-lock.json /app/package-lock.json |
| 17 | |
| 18 | ENV NODE_ENV=production |
| 19 | |
| 20 | WORKDIR /app |
| 21 | |
| 22 | RUN npm ci --ignore |
RemediationAI
The problem is that the Dockerfile in `src/memory/Dockerfile` does not set a non-root USER, causing the container to run as root. Insert `USER 1000` before the `CMD` or `ENTRYPOINT` in the release stage to enforce least privilege. This reduces the blast radius of container compromises. Verify by building the image and confirming `docker run --rm <image> id` shows a non-zero UID.
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 | # Use a Python image with uv pre-installed |
| 2 | FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv |
| 3 | |
| 4 | # Install the project into `/app` |
| 5 | WORKDIR /app |
| 6 | |
| 7 | # Enable bytecode compilation |
| 8 | ENV UV_COMPILE_BYTECODE=1 |
| 9 | |
| 10 | # Copy from the cache instead of linking since it's a mounted volume |
| 11 | ENV UV_LINK_MODE=copy |
| 12 | |
| 13 | # Install the project's dependencies using the lockfile and settings |
| 14 | RUN --mount=type=cache,target=/root/.cache/uv \ |
| 15 | --mount=type=bind,source=uv.lock,target=uv.lock \ |
| 16 | --mount=type=bind,source=py |
RemediationAI
The problem is that the Dockerfile in `src/time/Dockerfile` lacks a non-root USER directive, running the container as root. Add `USER 1000` before the final `CMD` or `ENTRYPOINT` to enforce non-root execution. This fix prevents root-level exploitation of container vulnerabilities. Test by building and running the image, then checking that `id` returns a non-root UID.
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 | # Use a Python image with uv pre-installed |
| 2 | FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv |
| 3 | |
| 4 | # Install the project into `/app` |
| 5 | WORKDIR /app |
| 6 | |
| 7 | # Enable bytecode compilation |
| 8 | ENV UV_COMPILE_BYTECODE=1 |
| 9 | |
| 10 | # Copy from the cache instead of linking since it's a mounted volume |
| 11 | ENV UV_LINK_MODE=copy |
| 12 | |
| 13 | # Install the project's dependencies using the lockfile and settings |
| 14 | RUN --mount=type=cache,target=/root/.cache/uv \ |
| 15 | --mount=type=bind,source=uv.lock,target=uv.lock \ |
| 16 | --mount=type=bind,source=py |
RemediationAI
The problem is that the Dockerfile in `src/fetch/Dockerfile` does not specify a non-root USER, allowing root execution. Add `USER 1000` before the `CMD` or `ENTRYPOINT` in the final stage to drop privileges. This limits the impact of RCE vulnerabilities. Verify by building the image and running `docker run --rm <image> id` to confirm non-root execution.
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 node:22.12-alpine AS builder |
| 2 | |
| 3 | COPY src/everything /app |
| 4 | COPY tsconfig.json /tsconfig.json |
| 5 | |
| 6 | WORKDIR /app |
| 7 | |
| 8 | RUN --mount=type=cache,target=/root/.npm npm install |
| 9 | |
| 10 | FROM node:22-alpine AS release |
| 11 | |
| 12 | WORKDIR /app |
| 13 | |
| 14 | COPY --from=builder /app/dist /app/dist |
| 15 | COPY --from=builder /app/package.json /app/package.json |
| 16 | COPY --from=builder /app/package-lock.json /app/package-lock.json |
| 17 | |
| 18 | ENV NODE_ENV=production |
| 19 | |
| 20 | RUN npm ci --ignore-scripts --omit-dev |
| 21 | |
| 22 | CMD ["node", "dist/index.js"] |
RemediationAI
The problem is that the Dockerfile in `src/everything/Dockerfile` lacks a non-root USER directive, causing root execution. Add `USER 1000` before the `CMD` or `ENTRYPOINT` in the release stage to enforce least privilege. This mitigates privilege escalation from container escape. Test by building and running the image to confirm the process executes as a non-root user.
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 node:22.12-alpine AS builder |
| 2 | |
| 3 | COPY src/sequentialthinking /app |
| 4 | COPY tsconfig.json /tsconfig.json |
| 5 | |
| 6 | WORKDIR /app |
| 7 | |
| 8 | RUN --mount=type=cache,target=/root/.npm npm install |
| 9 | |
| 10 | RUN --mount=type=cache,target=/root/.npm-production npm ci --ignore-scripts --omit-dev |
| 11 | |
| 12 | FROM node:22-alpine AS release |
| 13 | |
| 14 | COPY --from=builder /app/dist /app/dist |
| 15 | COPY --from=builder /app/package.json /app/package.json |
| 16 | COPY --from=builder /app/package-lock.json /app/package-lock.json |
| 17 | |
| 18 | ENV NODE_ENV=production |
| 19 | |
| 20 | WORKDIR /app |
| 21 | |
| 22 | RUN npm |
RemediationAI
The problem is that the Dockerfile in `src/sequentialthinking/Dockerfile` does not set a non-root USER, running the container as root. Insert `USER 1000` before the `CMD` or `ENTRYPOINT` in the final stage to enforce non-root execution. This reduces the severity of container compromises. Verify by building the image and confirming `docker run --rm <image> id` shows a non-zero UID.
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; |
| 177 | } |
| 178 | |
| 179 | console.log(`Received session termination request for session ${sessionId}`); |
| 180 | |
| 181 | try { |
| 182 | const transport = transports.get(sessionId); |
RemediationAI
The problem is that the sessionId variable is printed directly to console without ANSI escape sanitization, allowing terminal injection attacks. Replace `console.log(\`Received session termination request for session ${sessionId}\`)` with `console.log('Received session termination request for session', sanitizeAnsi(sessionId))` using a library like `strip-ansi` or a custom sanitizer. This fix prevents cursor control sequences from rewriting terminal output. Test by passing a sessionId containing ANSI escape codes (e.g., `\x1b[2J`) and confirming the terminal is not cleared.
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
| 723 | await updateAllowedDirectoriesFromRoots(response.roots); |
| 724 | } |
| 725 | } catch (error) { |
| 726 | console.error("Failed to request roots from client:", error instanceof Error ? error.message : String(error)); |
| 727 | } |
| 728 | }); |
RemediationAI
The problem is that the error message interpolates user-controlled error text without ANSI sanitization, enabling terminal injection. Replace the template literal with `console.error('Failed to request roots from client:', sanitizeAnsi(error instanceof Error ? error.message : String(error)))` using an ANSI sanitizer. This prevents malicious error messages from injecting terminal control sequences. Verify by triggering an error with embedded ANSI codes and confirming the terminal output is not manipulated.
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
| 740 | console.error("Client returned no roots set, keeping current settings"); |
| 741 | } |
| 742 | } catch (error) { |
| 743 | console.error("Failed to request initial roots from client:", error instanceof Error ? error.message : String(error)); |
| 744 | } |
| 745 | } else { |
| 746 | if (allowedDirectories.length > 0) { |
RemediationAI
The problem is that the error message directly interpolates unsanitized error text, allowing ANSI injection. Change `console.error('Failed to request initial roots from client:', error instanceof Error ? error.message : String(error))` to `console.error('Failed to request initial roots from client:', sanitizeAnsi(error instanceof Error ? error.message : String(error)))` using a sanitization function. This prevents terminal hijacking via error messages. Test by injecting ANSI escape sequences in an error and confirming they are neutralized.
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 | ); |
| 64 | } |
| 65 | } catch (error) { |
| 66 | console.error( |
| 67 | `Failed to request roots from client ${sessionId}: ${ |
| 68 | error instanceof Error ? error.message : String(error) |
| 69 | }` |
| 70 | ); |
| 71 | } |
RemediationAI
The problem is that the sessionId and error message are interpolated into a template literal without ANSI sanitization, enabling terminal injection. Refactor to `console.error('Failed to request roots from client:', sanitizeAnsi(sessionId), sanitizeAnsi(error instanceof Error ? error.message : String(error)))` using an ANSI escape sanitizer. This prevents cursor control injection. Verify by passing a malicious sessionId and confirming terminal output is not corrupted.
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
| 118 | // The existing transport is already connected to the server |
| 119 | await transport.handleRequest(req, res); |
| 120 | } catch (error) { |
| 121 | console.log("Error handling MCP request:", error); |
| 122 | if (!res.headersSent) { |
| 123 | res.status(500).json({ |
| 124 | jsonrpc: "2.0", |
RemediationAI
The problem is that the error object is logged directly without ANSI sanitization, allowing terminal injection. Replace `console.log('Error handling MCP request:', error)` with `console.log('Error handling MCP request:', sanitizeAnsi(String(error)))` using an ANSI sanitizer. This prevents error messages from injecting terminal control sequences. Test by triggering an error with embedded ANSI codes and confirming the terminal is not manipulated.
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
| 58 | // Handle POST requests for client messages |
| 59 | app.post("/mcp", async (req: Request, res: Response) => { |
| 60 | console.log("Received MCP POST request"); |
| 61 | try { |
| 62 | // Check for existing session ID |
| 63 | const sessionId = req.headers["mcp-session-id"] as string | undefined; |
RemediationAI
The problem is that user-controlled input (sessionId from headers) is logged without ANSI sanitization, enabling terminal injection. Replace `console.log('Received MCP POST request')` and subsequent logging with sanitized versions: `console.log('Received MCP POST request'); if (sessionId) console.log('Session:', sanitizeAnsi(sessionId))`. This prevents header-based terminal hijacking. Verify by sending a request with a malicious `mcp-session-id` header and confirming the terminal is not corrupted.
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 | // Swap labels |
| 80 | try { |
| 81 | await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: 'readme: pending' }); |
| 82 | } catch (e) {} |
| 83 | await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['readme: ready for review'] }); |
| 84 | |
| 85 | // Find the bot's original comment |
RemediationAI
The problem is that the catch block silently swallows the exception from `removeLabel()` with no logging or error handling, hiding failures in label removal. Replace `catch (e) {}` with `catch (e) { console.warn('Failed to remove label:', e); }` to log the error. This enables incident response and debugging. Test by simulating a label removal failure and confirming a warning is logged.
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
| 273 | } catch (error) { |
| 274 | try { |
| 275 | await fs.unlink(tempPath); |
| 276 | } catch {} |
| 277 | throw error; |
| 278 | } |
| 279 | } |
RemediationAI
The problem is that the catch block in the cleanup code silently swallows the exception from `fs.unlink()` with no logging, hiding cleanup failures. Replace `catch {}` with `catch (cleanupError) { console.warn('Failed to clean up temp file:', cleanupError); }` to log the error. This prevents silent data accumulation and aids debugging. Verify by triggering a cleanup failure and confirming a warning is logged.
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
| 457 | path = root.uri.path |
| 458 | try: |
| 459 | git.Repo(path) |
| 460 | repo_paths.append(str(path)) |
| 461 | except git.InvalidGitRepositoryError: |
| 462 | pass |
| 463 | return repo_paths |
| 464 | |
| 465 | def by_commandline() -> Sequence[str]: |
RemediationAI
The problem is that the except clause silently discards `git.InvalidGitRepositoryError` with no logging, hiding repository detection failures. Replace `except git.InvalidGitRepositoryError: pass` with `except git.InvalidGitRepositoryError as e: logger.debug(f'Skipping non-git path {path}: {e}')` to log the event. This enables troubleshooting of repository discovery. Test by running the function and confirming debug logs appear for non-git directories.
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
| 175 | } catch (renameError) { |
| 176 | try { |
| 177 | await fs.unlink(tempPath); |
| 178 | } catch {} |
| 179 | throw renameError; |
| 180 | } |
| 181 | } else { |
RemediationAI
The problem is that the catch block silently swallows the cleanup exception with no logging, hiding failures in temporary file removal. Replace `catch {}` with `catch (cleanupError) { console.warn('Failed to clean up temp file during rename:', cleanupError); }` to log the error. This prevents silent resource leaks and aids debugging. Verify by simulating a cleanup failure and confirming a warning is logged.