Skip to content

fix(url): reject "." and ".." path parameters so URL resolution can't change the route - #1390

Closed
alexb-openrouter wants to merge 1 commit into
mainfrom
devin/1794055859-reject-dot-segment-path-params
Closed

alexb-openrouter wants to merge 1 commit into
mainfrom
devin/1794055859-reject-dot-segment-path-params

Conversation

@alexb-openrouter

@alexb-openrouter alexb-openrouter commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Risk: code that passes an untrusted value as a path parameter (for example containerId from an end user or a tool call) could set it to exactly .. or .. encodeURIComponent leaves those values unchanged, and new URL(path, baseURL) in ClientSDK._createRequest then resolves them as dot segments, so the request goes to a different /api/v1 route with the app's API key attached. Example:

client.containers.downloadContainerFileContent({ containerId: "..", fileId: "file-abc" })
// before: GET /api/v1/files/file-abc/content  (the Files API download, not the container route)
// after:  throws "Parameter 'container_id' cannot be the dot segment '..'" and sends nothing

The request still carries only the app's own key and can reach only what that key can already reach. The problem is that it bypasses any container-only restriction the app relies on.

Fix: pathToFunc (src/lib/url.ts), which every operation uses to fill path templates, now throws when an encoded parameter is exactly . or ... No request is sent. All other values are left alone. ..., .env and a..b are not dot segments and still pass. %2e%2e was already double-encoded to %252e%252e. Errors for missing or non-string parameters already work this way.

src/lib/url.ts is a Speakeasy-generated file. persistentEdits is enabled in .speakeasy/gen.yaml, so this edit should survive regeneration. Worth checking on the next generate run.

Tests: tests/unit/path-param-dot-segments.test.ts runs the SDK against a mock server on 127.0.0.1. It checks that a normal container download hits the container route, and that containerId .. / . and fileId .. throw without sending any request. It also checks the pathToFunc edge cases directly. These tests fail on main and pass with this change. pnpm run test, pnpm run lint and pnpm run typecheck pass.

@devin-ai-integration

devin-ai-integration Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access that start with 'Devin'.

  • Disable automatic comment, CI, and merge conflict monitoring

mattapperson added a commit that referenced this pull request Nov 11, 2026
…hange the route

Generated operations percent-encode path parameters, which leaves "." and
".." unchanged, and ClientSDK._createRequest resolves the path with
new URL(path, baseURL), which collapses dot segments. A path parameter of
exactly ".." or "." sent the request, with the client's API key, to a
different route.

pathToFunc now throws before any request is built. src/lib/url.ts is added
to .genignore and hand-maintained, because persistent edits are off and
neither a hook, an overlay nor gen.yaml can express the check. gen.lock
drops the file's tracking entry, as the generator does for ignored files.

Supersedes #1390.
@mattapperson

Copy link
Copy Markdown
Contributor

Closing in favour of #1372, which carries this fix.

#1372 turns Speakeasy persistent edits off, so a hand edit to the generated src/lib/url.ts would be overwritten on the next regeneration. #1372 instead lists src/lib/url.ts in .genignore and maintains it by hand, with the same behaviour as this PR (a path parameter that is exactly . or .. fails before any request is sent) and the tests from this PR ported over. The underlying generator bug has also been reported to Speakeasy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants