Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,14 @@ Error messages should never include raw values from sensitive sources like envir
**Prevention:**
1. Avoid including raw values in error messages when the source is potentially sensitive (env vars, auth headers).
2. Use generic error messages for validation failures of sensitive data.

## 2026-03-24 - [CRITICAL] Path Traversal via URL Interpolation with Dots

**Vulnerability:**
The `encodeURIComponent` function used in URL interpolation (`CompositeExecutor` and `encodePathSegment` in `mcp-server.ts`) does not encode dot (`.`) characters. When user input containing `../` or `..` was processed, it was encoded as `..%2F` or left as `..`. Some HTTP clients and proxy servers normalize these path segments before processing the request, allowing an attacker to traverse directory structures in the API path.

**Learning:**
Relying solely on standard `encodeURIComponent` is insufficient for preventing path traversal when interpolating user inputs into URL paths, as standard URI encoding specifications do not require encoding of dots.

**Prevention:**
1. Always encode dot characters (`.`) explicitly using `.replace(/\./g, '%2E')` after calling `encodeURIComponent()` when interpolating into URL path segments.
3 changes: 2 additions & 1 deletion src/mcp/mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,8 @@ export class MCPServer {
*/
private encodePathSegment(value: unknown): string {
const val = String(value);
return val.includes('/') ? encodeURIComponent(val) : val;
// URL-encode slashes and dots to prevent path traversal when interpolating path parameters
return (val.includes('/') || val.includes('.')) ? encodeURIComponent(val).replace(/\./g, '%2E') : val;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject dot-segment path params before URL assembly

Encoding dots to %2E here does not stop traversal for . or .. inputs, because the URL parser used by request construction (fetch/WHATWG URL in HttpClient.requestInternal) normalizes %2E and %2E%2E path segments before sending the request. With an attacker-controlled path parameter like id: "..", the resolved path can still collapse from /users/%2E%2E/profile to /profile, so this fix leaves the traversal primitive reachable; these segment values need to be explicitly rejected (and the same logic applies to the analogous change in CompositeExecutor.resolvePath).

Useful? React with πŸ‘Β / πŸ‘Ž.

}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/tooling/composite-executor-security.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('CompositeExecutor Security', () => {
// Fixed behavior: path contains encoded "%2E%2E"
// Note: encodeURIComponent('../admin/secrets') => ..%2Fadmin%2Fsecrets
// The slashes inside the injected value are encoded, preventing directory traversal
const expectedFixedPath = '/users/..%2Fadmin%2Fsecrets/profile';
const expectedFixedPath = '/users/%2E%2E%2Fadmin%2Fsecrets/profile';

expect(capturedPaths[0]).toBe(expectedFixedPath);
});
Expand Down
4 changes: 2 additions & 2 deletions src/tooling/composite-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,14 @@ export class CompositeExecutor {
return template.replace(/\{(\w+)\}/g, (_, key) => {
// Try direct match first
if (args[key] !== undefined) {
return encodeURIComponent(String(args[key]));
return encodeURIComponent(String(args[key])).replace(/\./g, '%2E');
}

// Try aliases from profile
const possibleAliases = this.parameterAliases[key] || [];
for (const alias of possibleAliases) {
if (args[alias] !== undefined) {
return encodeURIComponent(String(args[alias]));
return encodeURIComponent(String(args[alias])).replace(/\./g, '%2E');
}
}

Expand Down
Loading