Security Model
Tech Debt MCP runs as a local Node process started by your MCP client and reads files from disk on your behalf. The threat surface is small but real — this page documents what's hardened and what isn't.
Trust boundary
The MCP client is trusted. Anything that arrives via tool arguments — file paths, regex patterns, project roots — is not trusted and is validated at the handler boundary.
Path arguments
Tool arguments — Path parameters in tool handlers flow through requireAbsolutePath / optionalAbsolutePath (src/server/inputParser.ts):
path.isAbsolute()rejected if false.- Normalized via
path.resolve()so..traversal collapses before use. - An empty string for an optional path is treated as
undefined.
Path parameters must never be read from args directly — they must flow through requireAbsolutePath or optionalAbsolutePath. Non-path fields (e.g., includeDev, severity) may be read from the validated record returned by requireRecord() after the path is extracted. This convention is enforced across handlers.ts, configValidator.ts, customRulesHandlers.ts, and dependencyHandlers.ts.
Resource templates — MCP resource templates (debt://summary/{+projectPath}, debt://issues/{+projectPath}) validate projectPath via validateAbsolutePath() in src/server/resourceHandlers.ts, which applies the same path.isAbsolute() + path.resolve() checks outside of the inputParser.ts flow.
User-supplied regex
Custom rules accept regex patterns. To prevent catastrophic backtracking and engine abuse:
- Pattern length capped at
MAX_PATTERN_LENGTH = 1,000chars. - Code chunks capped at
MAX_CODE_LENGTH = 500,000chars. - Files larger than
MAX_FILE_SIZE_BYTES = 500,000bytes are rejected with anInvalidParamserror before pattern execution. - Flags allowlisted to
dgimsuvy;uandvare validated as mutually exclusive. - Any captured string interpolated into
new RegExp()is escaped throughescapeRegExp()(src/utils/regexUtils.ts).
These constants live in src/core/customRulesEngine.ts — import them rather than hardcoding values.
Error messages
Domain handlers sanitize paths in error output; unexpected non-McpError exceptions that escape to the top-level catch may still include implementation detail:
getRelativePath()orbasename()sanitize paths in error output.- Raw
err.messagefrom domain-specific handlers (customRulesHandlers.ts,configValidator.ts,dependencyHandlers.ts) is caught and sanitized before returning to the client (e.g., by substitutingbasename()for any path, or by returning a safe text response without the raw exception message). - The top-level catch in
handlers.tswraps unexpected non-McpErrorexceptions usingerror.message; errors that escape domain handlers may therefore include implementation detail. Prefer throwing sanitizedMcpErrorinstances from all code paths to avoid this. - This rule applies to every handler file.
Pre-commit hardening
Doc updates on src/** changes are enforced as a convention (see CLAUDE.md). The test suite enforces:
- Every entry in
TOOL_DEFINITIONScarries anannotationsobject (readOnlyHintordestructiveHint). mcpb/manifest.jsonmirrorsTOOL_DEFINITIONSexactly.
Automated scanning
GitHub Actions runs CodeQL with security-and-quality queries on every push and PR — see .github/workflows/codeql.yml.
Reporting
Found something? Use GitHub Security Advisories. Do not open a public issue for vulnerabilities.
See SECURITY.md for the full disclosure policy.