mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-17 15:53:23 +02:00
Merge remote-tracking branch 'origin/main' into feat/url-elicitation
This commit is contained in:
21
SECURITY.md
21
SECURITY.md
@@ -1,16 +1,21 @@
|
||||
# Security Policy
|
||||
Thank you for helping us keep our MCP servers secure.
|
||||
|
||||
The **reference servers** in this repo are maintained by [Anthropic](https://www.anthropic.com/) as part of the Model Context Protocol project.
|
||||
|
||||
The security of our systems and user data is Anthropic's top priority. We appreciate the work of security researchers acting in good faith in identifying and reporting potential vulnerabilities.
|
||||
Thank you for helping keep the Model Context Protocol and its ecosystem secure.
|
||||
|
||||
## Important Notice
|
||||
|
||||
The servers in this repository are **reference implementations** intended to demonstrate MCP features and SDK usage. They serve as educational examples for developers building their own MCP servers, not as production-ready solutions.
|
||||
The servers in this repository are **reference implementations** intended to demonstrate
|
||||
MCP features and SDK usage. They serve as educational examples for developers building
|
||||
their own MCP servers, not as production-ready solutions.
|
||||
|
||||
**Bug bounties are not awarded for security vulnerabilities found in these reference servers.** Our bug bounty program applies exclusively to the [MCP SDKs](https://github.com/modelcontextprotocol) maintained by Anthropic. If you discover a vulnerability in an MCP SDK that is maintained by Anthropic, please report it through our vulnerability disclosure program below.
|
||||
This repository is **not** eligible for security vulnerability reporting. If you discover
|
||||
a vulnerability in an MCP SDK, please report it in the appropriate SDK repository.
|
||||
|
||||
## Vulnerability Disclosure Program
|
||||
## Reporting Security Issues in MCP SDKs
|
||||
|
||||
Our Vulnerability Program guidelines are defined on our [HackerOne program page](https://hackerone.com/anthropic-vdp). We ask that any validated vulnerability in this functionality be reported through the [submission form](https://hackerone.com/anthropic-vdp/reports/new?type=team&report_type=vulnerability).
|
||||
If you discover a security vulnerability in an MCP SDK, please report it through the
|
||||
[GitHub Security Advisory process](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
|
||||
in the relevant SDK repository.
|
||||
|
||||
Please **do not** report security vulnerabilities through public GitHub issues, discussions,
|
||||
or pull requests.
|
||||
|
||||
@@ -564,6 +564,53 @@ describe('Path Validation', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Test for macOS /tmp -> /private/tmp symlink issue (GitHub issue #3253)
|
||||
// When allowed directories include BOTH original and resolved paths,
|
||||
// paths through either form should be accepted
|
||||
it('allows paths through both original and resolved symlink directories', async () => {
|
||||
try {
|
||||
// Setup: Create the actual target directory with content
|
||||
const actualTargetDir = path.join(testDir, 'actual-target');
|
||||
await fs.mkdir(actualTargetDir, { recursive: true });
|
||||
const targetFile = path.join(actualTargetDir, 'file.txt');
|
||||
await fs.writeFile(targetFile, 'FILE_CONTENT');
|
||||
|
||||
// Setup: Create symlink directory that points to target (simulates /tmp -> /private/tmp)
|
||||
const symlinkDir = path.join(testDir, 'symlink-dir');
|
||||
await fs.symlink(actualTargetDir, symlinkDir);
|
||||
|
||||
// Get the resolved path
|
||||
const resolvedDir = await fs.realpath(symlinkDir);
|
||||
|
||||
// THE FIX: Store BOTH original symlink path AND resolved path in allowed directories
|
||||
// This is what the server should do during startup to fix issue #3253
|
||||
const allowedDirsWithBoth = [symlinkDir, resolvedDir];
|
||||
|
||||
// Test 1: Path through original symlink should pass validation
|
||||
// (e.g., user requests /tmp/file.txt when /tmp is in allowed dirs)
|
||||
const fileViaSymlink = path.join(symlinkDir, 'file.txt');
|
||||
expect(isPathWithinAllowedDirectories(fileViaSymlink, allowedDirsWithBoth)).toBe(true);
|
||||
|
||||
// Test 2: Path through resolved directory should also pass validation
|
||||
// (e.g., user requests /private/tmp/file.txt)
|
||||
const fileViaResolved = path.join(resolvedDir, 'file.txt');
|
||||
expect(isPathWithinAllowedDirectories(fileViaResolved, allowedDirsWithBoth)).toBe(true);
|
||||
|
||||
// Test 3: The resolved path of the symlink file should also pass
|
||||
const resolvedFile = await fs.realpath(fileViaSymlink);
|
||||
expect(isPathWithinAllowedDirectories(resolvedFile, allowedDirsWithBoth)).toBe(true);
|
||||
|
||||
// Verify both paths point to the same actual file
|
||||
expect(resolvedFile).toBe(await fs.realpath(fileViaResolved));
|
||||
|
||||
} catch (error) {
|
||||
// Skip if no symlink permissions on the system
|
||||
if ((error as NodeJS.ErrnoException).code !== 'EPERM') {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('resolves nested symlink chains completely', async () => {
|
||||
try {
|
||||
// Setup: Create target file in forbidden area
|
||||
|
||||
@@ -39,22 +39,32 @@ if (args.length === 0) {
|
||||
}
|
||||
|
||||
// Store allowed directories in normalized and resolved form
|
||||
let allowedDirectories = await Promise.all(
|
||||
// We store BOTH the original path AND the resolved path to handle symlinks correctly
|
||||
// This fixes the macOS /tmp -> /private/tmp symlink issue where users specify /tmp
|
||||
// but the resolved path is /private/tmp
|
||||
let allowedDirectories = (await Promise.all(
|
||||
args.map(async (dir) => {
|
||||
const expanded = expandHome(dir);
|
||||
const absolute = path.resolve(expanded);
|
||||
const normalizedOriginal = normalizePath(absolute);
|
||||
try {
|
||||
// Security: Resolve symlinks in allowed directories during startup
|
||||
// This ensures we know the real paths and can validate against them later
|
||||
const resolved = await fs.realpath(absolute);
|
||||
return normalizePath(resolved);
|
||||
const normalizedResolved = normalizePath(resolved);
|
||||
// Return both original and resolved paths if they differ
|
||||
// This allows matching against either /tmp or /private/tmp on macOS
|
||||
if (normalizedOriginal !== normalizedResolved) {
|
||||
return [normalizedOriginal, normalizedResolved];
|
||||
}
|
||||
return [normalizedResolved];
|
||||
} catch (error) {
|
||||
// If we can't resolve (doesn't exist), use the normalized absolute path
|
||||
// This allows configuring allowed dirs that will be created later
|
||||
return normalizePath(absolute);
|
||||
return [normalizedOriginal];
|
||||
}
|
||||
})
|
||||
);
|
||||
)).flat();
|
||||
|
||||
// Filter to only accessible directories, warn about inaccessible ones
|
||||
const accessibleDirectories: string[] = [];
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from 'path';
|
||||
import os from 'os';
|
||||
import { normalizePath } from './path-utils.js';
|
||||
import type { Root } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
/**
|
||||
* Converts a root URI to a normalized directory path with basic security validation.
|
||||
@@ -11,7 +12,7 @@ import type { Root } from '@modelcontextprotocol/sdk/types.js';
|
||||
*/
|
||||
async function parseRootUri(rootUri: string): Promise<string | null> {
|
||||
try {
|
||||
const rawPath = rootUri.startsWith('file://') ? rootUri.slice(7) : rootUri;
|
||||
const rawPath = rootUri.startsWith('file://') ? fileURLToPath(rootUri) : rootUri;
|
||||
const expandedPath = rawPath.startsWith('~/') || rawPath === '~'
|
||||
? path.join(os.homedir(), rawPath.slice(1))
|
||||
: rawPath;
|
||||
|
||||
Reference in New Issue
Block a user