MCP Client (VS Code/Claude Desktop) ←→ MCP Server (TypeScript) ←→ File System (.mcp-tasks/)
Untrusted Input Validation Layer Protected Resources
- Malicious MCP Clients: Could send crafted requests with path traversal, oversized inputs, or injection attacks
- Compromised File Paths: User-controlled paths attempting to escape
.mcp-tasks/directory - Untrusted User Input: Task names, descriptions, and metadata that could contain malicious content
Attack: Client sends file path like ../../etc/passwd or ..\..\Windows\System32\config\SAM
Mitigation:
- Resolve all paths with
path.resolve() - Validate paths start with
DATA_DIR - Reject any path outside allowed directory
Attack: Oversized task names (DoS), special characters in JSON, malformed GUIDs Mitigation:
- Zod schemas validate ALL inputs
- Max length checks (name: 500 chars, description: 5000 chars)
- GUID format validation for task IDs
Attack: Malformed JSON, schema version mismatches, prototype pollution Mitigation:
- Schema version validation before processing
- Migration logic for old versions
- Zod parsing (rejects unexpected fields)
Attack: Creating millions of tasks, infinite loops, file exhaustion Mitigation:
- Input size limits (Zod max length)
- File count monitoring (future enhancement)
- Graceful error handling
Authentication: NOT APPLICABLE
- MCP server runs locally via STDIO
- Single-user context (trust local user)
Authorization: File System Controls
- REQUIRED: All file operations constrained to
DATA_DIR - REQUIRED: Path sanitization before ANY file I/O
- REQUIRED: No access to parent directories
ALL tool parameters MUST be validated with Zod schemas:
// Example: Task creation
const CreateTaskSchema = z.object({
name: z.string().min(1).max(500),
description: z.string().max(5000),
dependencies: z.array(z.string()).optional(),
});
// Validation in tool
const validated = CreateTaskSchema.parse(params); // Throws on invalidValidation Rules:
- Type validation (string, number, boolean, array)
- Length validation (min/max)
- Format validation (GUID, enum values)
- Business logic validation (status transitions)
FORBIDDEN: Hardcoded secrets in code or JSON files REQUIRED: Environment variables ONLY
// ✅ Correct
const dataDir = process.env.DATA_DIR || '.mcp-tasks';
// ❌ Forbidden
const apiKey = 'sk-1234567890abcdef';User-Facing Errors: Generic messages ONLY Server Logs: Detailed error context
try {
return await taskStore.getById(taskId);
} catch (error) {
logger.error({ error, taskId }, 'Failed to retrieve task');
throw new Error('Task not found. Please verify the task ID.');
// ❌ Never return: error.message, error.stack, file paths
}REQUIRED: Log security events
- Path validation failures
- Input validation failures
- File operation errors
FORBIDDEN: Log sensitive data
- Task content (may contain PII)
- File paths (may reveal internal structure)
- Stack traces (in user-facing messages)
Encryption: NOT REQUIRED
- Local file system (user's machine)
- OS-level encryption (BitLocker, FileVault)
Encryption: NOT APPLICABLE
- STDIO communication (local process)
- No network transmission
Snapshots: Completed tasks archived to memory/ directory
Backups: Created before destructive operations (clear_all_tasks)
Cleanup: User responsibility (no auto-deletion)
Status: ✅ IMPLEMENTED
- Documented threat model and attack vectors (above)
- Created abuse cases (path traversal, injection, DoS)
- Attack surface analysis complete
- Security requirements defined for each tool
Implementation:
- This SECURITY.md document
- Security-focused architecture decisions
- ESLint security rules enforced
Status: ✅ IMPLEMENTED
- Using
zodv3.22.4 for input validation (industry-standard) - Using
@modelcontextprotocol/sdkv1.0.2 (official MCP implementation) - Using
pinov8.17.2 for structured logging - All dependencies tracked and audited via
pnpm audit
Why NOT Custom:
- ❌ No custom crypto (none needed for local STDIO)
- ❌ No custom validation (Zod handles all)
- ❌ No custom serialization (JSON.parse with Zod validation)
Status: ✅ IMPLEMENTED (JSON file storage)
- NO SQL/NoSQL database (JSON files only)
- Schema validation prevents injection via Zod
- Atomic file operations prevent corruption
- No dynamic query construction
Safe Patterns:
// ✅ Type-safe, no injection possible
const task = taskDocument.tasks.find(t => t.id === validatedId);Status:
- JSON serialization: Native
JSON.stringify()(safe) - Markdown templates: Will use Handlebars (auto-escaping)
- Log output: Pino sanitizes sensitive data
TODO:
- Implement template engine with auto-escaping (Task 12)
- Add log sanitization for PII
Status: ✅ IMPLEMENTED
- ALL 19 MCP tool parameters validated with Zod schemas
- Server-side validation ONLY (no client-side trust)
- Type, length, range, format checks enforced
- Allowlist validation for enums (status, update mode)
Coverage:
- HTTP params: N/A (STDIO only)
- Tool parameters: 100% (19/19 tools)
- File inputs: Zod validation on read
- External APIs: N/A (no external calls)
Example:
// CreateTaskParamsSchema validates:
// - name: 1-500 chars (prevents DoS)
// - description: max 5000 chars
// - taskId: UUID format (prevents injection)Status: ❌ NOT APPLICABLE
- Local STDIO execution (no network authentication)
- Single-user context (trusts local OS user)
- No password storage or session management needed
Future Consideration:
- If MCP server becomes networked: implement OAuth 2.0 + PKCE
Status: ✅ IMPLEMENTED
- Path traversal prevention (pathResolver.ts)
- All file operations constrained to
DATA_DIR - Path sanitization before ANY fs operations
- No access to parent directories or system files
Implementation:
// pathResolver.ts
function sanitizePath(userPath: string): string {
const resolved = path.resolve(DATA_DIR, userPath);
if (!resolved.startsWith(DATA_DIR)) {
throw new SecurityError('Path traversal detected');
}
return resolved;
}Test Coverage:
../../etc/passwd→ REJECTED..\..\Windows\System32→ REJECTED- Symbolic links → REJECTED
- Absolute paths outside DATA_DIR → REJECTED
Status:
- TLS: N/A (local STDIO, no network)
- Encryption at rest: User responsibility (OS-level BitLocker/FileVault)
- Secret management: Environment variables ONLY
- Logging: NO PII, passwords, or sensitive task content
Implementation:
// ✅ Safe
const dataDir = process.env.DATA_DIR || '.mcp-tasks';
// ❌ Forbidden
logger.info({ taskName: task.name }); // May contain PIITODO:
- Add log sanitization middleware (Task 9)
- Document OS-level encryption recommendations
Status: ✅ IMPLEMENTED
- Structured logging with Pino
- Correlation IDs for request tracing
- Security event logging (validation failures, path violations)
Logged Events:
- ✅ Authentication events: N/A (local execution)
- ✅ Authorization failures: Path traversal attempts
- ✅ Input validation failures: Zod schema rejections
- ✅ Admin actions: clear_all_tasks, delete_task
⚠️ Sensitive data access: TODO (task query operations)
NOT Logged:
- ❌ Task content (may contain PII)
- ❌ Full file paths (information disclosure)
- ❌ Stack traces (in production logs)
Status: ✅ IMPLEMENTED
- User errors: Generic messages ("Task not found")
- Server logs: Detailed context (task ID, operation, stack trace)
- Fail securely: Reject operation on error (no partial state)
Pattern:
try {
const task = await taskStore.getById(taskId);
return task;
} catch (error) {
// ✅ Detailed server log
logger.error({ error, taskId }, 'Failed to retrieve task');
// ✅ Generic user message (no internal details)
throw new Error('Task not found. Please verify the task ID.');
// ❌ NEVER expose: error.message, stack, file paths, SQL queries
}Security Boundaries:
- Internal errors → Logged + Generic message
- Validation errors → Specific (safe schema error)
- System errors → Logged + "An error occurred"
- ✅ Path sanitization (prevent directory traversal)
- ✅ Input validation (Zod schema rejection)
- ✅ Error handling (no stack trace leaks)
- ✅ Schema migration (backward compatibility)
# Run before every commit
npm audit --audit-level=moderateContact: File GitHub issue with [SECURITY] prefix
Response Time: Best effort (educational project)
- No rate limiting: Local STDIO, not needed
- No authentication: Single-user local context
- No encryption: Local files, OS-level encryption recommended
Last Updated: 2026-01-18 Review Frequency: Before major releases Responsible: Project maintainers