Skip to content

Static audit of mcp-server-neon: no findings, and why my scanner was wrong 3 times #332

Description

@allenwu-blip

Hi — this is not a vulnerability report, so I'm opening it in the open
rather than privately.

I wrote an open-source static scanner for MCP servers
(mcpaudit), ran it against this repo,
then read the source behind every hit. Full write-up below.

Happy to be told I got any of it wrong.


Result: nothing survived triage.

I scanned neondatabase/mcp-server-neon with my open-source static analyzer
mcpaudit, then read the source behind every hit. The scan produced four
findings; one turned out to be a bug in my own rule, and the other three are
false positives caused by a limitation I describe below. None of them are real.

What that does and does not mean: this is 14 lexical rule classes over the
JS/TS sources, no taint tracking, no execution. It says the obvious classes look
clean. It is not proof the codebase has no vulnerabilities, and I would not want
it cited as one.

This report is not a sales pitch. It is an honest account of what my tool missed and why. The value here is transparency about the limitations of single-file static analysis, not manufactured urgency.

The three findings, and why none of them are real

All three remaining findings were flagged as MCP008 (SSRF). They all stem from the same root cause: my analyzer cannot track constants across file boundaries.

1. mcp/neon-client.ts:154

export const fetchAsMcpServer: typeof fetch = (input, init) => {
  const request = new Request(input, init);
  request.headers.set('User-Agent', USER_AGENT);
  return fetch(request);
};

Why it’s a false positive: this decorates a request the @neon/sdk has
already built, purely to attach a User-Agent. It does not choose a
destination — it forwards whatever it is handed. Flagging the forwarding call
puts the finding on the wrapper rather than on whatever decides the URL, which
is where an SSRF question would actually belong.

2. mcp/tools/handlers/docs.ts:12

import { NEON_DOCS_INDEX_URL } from '../../../lib/config';
const response = await fetch(NEON_DOCS_INDEX_URL, { ... });

Why it’s a false positive: NEON_DOCS_INDEX_URL is a constant imported from lib/config. My scanner has a "hoisted-literal" guard that treats bare identifiers as literals if they are assigned string literals in the same file. It does not resolve imports across files, so it sees an unknown identifier and flags it.

3. mcp/tools/handlers/docs.ts:51-52

const url = `${NEON_DOCS_BASE_URL}/${mdSlug}`;
const response = await fetch(url, { ... });

Why it’s a false positive: NEON_DOCS_BASE_URL is also an imported constant. The origin is fixed; only the path segment mdSlug is variable. This is a safe pattern (fixed origin + variable path), but my rule only recognizes it when the base URL appears as a literal string in the template. Since it’s an identifier, the rule doesn’t trigger.

The common cause: my analyzer is single-file

The root cause of all three false positives is that your code correctly extracts URLs into a config module. My analyzer is single-file and does not perform cross-file constant resolution.

This is a limitation of my tool, not a flaw in your code. Hoisting endpoints
into a config module is the better way to write it; my analyzer punishes you for
it because it reasons one file at a time and never follows the import. Resolving
imported constants is a real feature, not a regex I forgot to write, and I have
not built it.

A bug your code found in my scanner

The initial scan also flagged app/api/token/route.ts:556:

const [clientId, clientSecret] = credentials.split(':');

This is array destructuring, not a computed property assignment (which could lead to prototype pollution). My regex for detecting left-hand values incorrectly matched const [ because t is a word character. I have since updated the regex to exclude declaration keywords.

Method

  • Tool: mcpaudit (open-source static scanner)
  • Scope: 82 files in mcp-server-neon
  • Technique: Pure static analysis. No taint tracking, no code execution, no network access.
  • Limitations:
    • Does not resolve constants across files.
    • Does not track data flow across function boundaries.
    • Relies on pattern matching, not semantic analysis.

Reproduction

You can reproduce this scan with:

npx allenwu-blip/mcpaudit ./mcp-server-neon

This was done voluntarily and without compensation. If you believe any of my judgments are incorrect, I welcome your feedback. The goal is to improve the tool, not to criticize the code.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions