From 2ede5b14b589491e32f0f4c1e850992f72fb9e16 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 07:30:15 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[HIGH]=20Fi?= =?UTF-8?q?x=20Path=20Traversal=20via=20Parameter=20Injection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes a vulnerability where `encodeURIComponent` was used to encode user-provided input that is later used in URL paths. Since `encodeURIComponent` does not encode dot (`.`) characters, an attacker could inject strings like `..` to traverse directory paths. Additionally, in `src/mcp/mcp-server.ts`, the encoding was only applied conditionally if the value contained a slash (`/`), meaning a pure `..` payload would completely bypass the URL encoding logic. This has been fixed to always apply the dot-replacement strategy unconditionally across both `CompositeExecutor` and `mcp-server`. Co-authored-by: davidruzicka <14172985+davidruzicka@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ src/mcp/mcp-server.ts | 2 +- src/tooling/composite-executor-security.test.ts | 2 +- src/tooling/composite-executor.ts | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index a6c92980..d3fc608b 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -126,3 +126,8 @@ 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. + +## 2024-05-08 - [Path Traversal / URL Injection in encodePathSegment] +**Vulnerability:** In `mcp-server.ts`, dot characters (`.`) injected into the URL paths were not encoded by `encodeURIComponent`. +**Learning:** `encodeURIComponent` ignores `.`. In addition, using a ternary check like `val.includes('/') ? encodeURIComponent(val) : val;` completely bypassed the encoding check if the malicious string was exactly `..` (because it does not contain a slash). +**Prevention:** Always encode all URL path values without conditional string presence checks (e.g. unconditionally calling `encodeURIComponent(val).replace(/\./g, '%2E')` handles both directory traversal payloads and standard pathing safely). diff --git a/src/mcp/mcp-server.ts b/src/mcp/mcp-server.ts index e86823cf..2c73eaed 100644 --- a/src/mcp/mcp-server.ts +++ b/src/mcp/mcp-server.ts @@ -1205,7 +1205,7 @@ export class MCPServer { */ private encodePathSegment(value: unknown): string { const val = String(value); - return val.includes('/') ? encodeURIComponent(val) : val; + return encodeURIComponent(val).replace(/\./g, '%2E'); } /** diff --git a/src/tooling/composite-executor-security.test.ts b/src/tooling/composite-executor-security.test.ts index 4f7f1ca3..97a3b0b6 100644 --- a/src/tooling/composite-executor-security.test.ts +++ b/src/tooling/composite-executor-security.test.ts @@ -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); }); diff --git a/src/tooling/composite-executor.ts b/src/tooling/composite-executor.ts index 34e467a3..9f4f6d01 100644 --- a/src/tooling/composite-executor.ts +++ b/src/tooling/composite-executor.ts @@ -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'); } }