modelcontextprotocol / modelcontextprotocol/servers

server-everything: get-env tool returns full process.env without any filtering

Open
#3,986 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
90.5k
Forks
11.7k
Avg merge
2d 2h
Merged PRs (30d)
5

Description

I was doing a security audit of the official MCP servers and stumbled into something that gave me pause.

The get-env tool in server-everything returns the entire process.env object -- unfiltered, unsanitized, with no parameter required. Call it with empty args and you get ~6KB of every environment variable on the host machine.

What I saw

The tool's handler is literally:

server.registerTool(name, config, async (args): Promise<CallToolResult> => {
  return {
    content: [{ type: "text", text: JSON.stringify(process.env, null, 2) }],
  };
});

inputSchema is {}, so zero arguments triggers it. No annotations, no filtering, no opt-in.

What this leaks

On a typical Windows dev box this dumps:

  • USERNAME, USERDOMAIN, COMPUTERNAME -- host and user identity
  • PATH, APPDATA, LOCALAPPDATA -- full directory structure
  • HOMEPATH, USERPROFILE -- home directory path
  • Any API keys the user happens to have in env: OPENAI_API_KEY, ANTHROPIC_API_KEY, GITHUB_TOKEN, etc.

The "etc" is the problem. You can't know what's in someone's env.

Why this matters for an official example

server-everything is the demo server. People copy it to learn the protocol. If get-env stays as-is, that pattern propagates. I've already seen community servers that copied the "return raw env" pattern because "the official one does it."

Also: the tool has no annotations object, so an MCP client has zero signal that this is a high-sensitivity read operation.

Suggested fixes

Option 1 (safest): Require a specific key parameter.

inputSchema: {
  type: 'object',
  properties: {
    key: { type: 'string', description: 'Specific environment variable name' }
  },
  required: ['key']
}

Option 2 (if you want to keep the bulk dump): Filter known-sensitive patterns.

const SENSITIVE = /TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL|AUTH/i;
const filtered = Object.fromEntries(
  Object.entries(process.env).filter(([k]) => !SENSITIVE.test(k))
);

Option 3 (annotation-only, weakest but still useful): Add readOnlyHint: true and a big warning in the description that this returns the full environment.

My vote is Option 1. "Get one env var by name" is still a useful demo of a tool that reads from the host, without being a foot-gun.


Happy to open a PR if the maintainers agree on a direction.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the get-env tool handler in server-everything and review its current inputSchema and registration. Confirm the maintainers' preferred remediation before changing behavior, then verify that an empty invocation no longer exposes the full process environment and that the selected restricted behavior is covered by relevant tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.