fix: encode caller-supplied path parameters in legacy modules#1662
Conversation
The hand-written legacy modules interpolate caller-supplied identifiers (user IDs, invitation tokens, MFA factor IDs, organization IDs, external IDs, role/permission slugs) directly into URL path templates without encoding. HttpClient.getResourceURL then resolves the result with the WHATWG URL parser, which honors ../ dot-segments and ?/# metacharacters. An integrator that forwards an attacker-influenced identifier into one of these methods could have its API-key-authenticated request retargeted to an arbitrary same-verb WorkOS endpoint. Wrap each interpolated path parameter in a shared encodePathParameter helper, matching the encodeURIComponent pattern already used by the auto-generated modules. The helper preserves ':' so existing RBAC slugs (e.g. users:read) keep their wire format.
Original prompt from Linear User
|
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Greptile SummaryThis PR prevents caller-supplied identifiers from changing legacy SDK request paths. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (2): Last reviewed commit: "Reject dot-only path segments in encodeP..." | Re-trigger Greptile |
encodeURIComponent leaves '.' and '..' unchanged, and the WHATWG URL
parser removes them as relative path segments. For a non-terminal
template such as /feature-flags/${slug}/enable a slug of '..' still
climbs and retargets the request (e.g. to /enable). Percent-encoding the
dots does not help because the parser also treats the %2e forms as dot
segments. Fail closed: throw for '.' and '..', which are never valid
WorkOS identifiers.
Description
Fixes a path-parameter injection in the hand-written "legacy" SDK modules (User Management, MFA, SSO, Organizations, Organization Domains, Directory Sync, Authorization/FGA, API Keys, Feature Flags, Audit Logs, Passwordless).
Vulnerability. These modules interpolate caller-supplied identifiers straight into URL path templates without encoding:
HttpClient.getResourceURLthen resolves the path with the WHATWGURLparser (new URL(path, baseURL)), which honors../dot-segments and?/#metacharacters. So an integrator that forwards an attacker-influenced identifier into one of these methods can have its API-key-authenticated request retargeted to an arbitrary same-verb WorkOS endpoint. Observed today (fetch intercepted, key redacted):The auto-generated modules (
webhooks,vault, ...) are unaffected because they already wrap parameters inencodeURIComponent.Fix. Wrap every interpolated path parameter in the legacy modules with a shared
encodePathParameterhelper (src/common/utils/encode-path-parameter.ts), matching the encoding pattern already used by the auto-generated modules. After the fix the same payloads stay within one path segment:The helper is
encodeURIComponentwith one deviation: a literal:is preserved. Colons are valid path-segment characters and are used by WorkOS RBAC slugs (e.g.users:read); the interpolated value is always preceded by a/-delimited segment, so a:can never be read as a URL scheme. This keeps the wire format identical for existing slugs (no behavior change for legitimate identifiers —encodePathParameter("user_01ABC") === "user_01ABC").Scope: 99 interpolation sites across 11 legacy modules; each interpolated value is a single identifier/slug/token path segment. No auto-generated files changed.
Testing
encode-path-parameter.spec.ts(identity for normal IDs,:preserved,/ ? #and..encoded).npm test(906 tests),npm run lint,npm run prettier,tsc --noEmitall pass.Documentation
Does this require changes to the WorkOS Docs? E.g. the API Reference or code snippets need updates.
Link to Devin session: https://app.devin.ai/sessions/77c068d1a6b94f87bb7779fd80c111a9