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
1 change: 1 addition & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,4 @@ 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-04-21 - [Path Traversal via Parameter Injection in Path Segments] **Vulnerability:** Path traversal (e.g. `../`) was possible through parameters mapped to URI path segments in `CompositeExecutor` and `MCPServer`'s `encodePathSegment`. Although the input was URL-encoded via `encodeURIComponent`, literal dots (`.`) were not encoded by this function. When the decoded URI path included `../`, it allowed attackers to traverse out of the intended base path in the external API call. **Learning:** While `encodeURIComponent` encodes characters like `/`, it does not encode dots. HTTP clients typically normalize literal `../` sequences during request formulation, but they don't decode and then normalize `%2E%2E/` which protects against traversing beyond the intended URL structure while still being accurately parsed by the downstream server. **Prevention:** To prevent directory traversal attacks through URI parameters, append `.replace(/\./g, '%2E')` to `encodeURIComponent(val)` when substituting user input into URL path paths.
2 changes: 1 addition & 1 deletion src/mcp/mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,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');

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 Preserve pre-encoded path identifiers in resolvePath

This now runs every path parameter through encodeURIComponent, which double-encodes IDs that are already URL-encoded (for example group%2Fproject becomes group%252Fproject). That breaks documented GitLab usage where project_id may be passed as an encoded path, causing lookups by namespace/project path to fail (typically 404) even though they worked before this commit.

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