Repository navigation
fix: reject dot-segment path parameters; disable Speakeasy persistent edits - #1372
Merged
Merged
Conversation
mattapperson
force-pushed
the
chore/remove-call-model
branch
from
November 5, 2026 17:26
e1b6c79 to
3f1d10b
Compare
mattapperson
added a commit
that referenced
this pull request
Nov 5, 2026
Addresses review feedback on #1372. With persistent edits off, the OPENROUTER_BASE_URL fallback lives in an SDKInitHook, so only client construction honors it. The generated serverURLFromOptions() helper reads only the options it is given. Keeping the env fallback in that helper would need a hand edit to generated config.ts, which a regen would now silently overwrite. Document this in the v2 migration notes, explain it in the hook's comment, and add a test that pins the helper's behavior.
Set persistentEdits.enabled to "never" so generated files are plain generator output and Speakeasy does not prompt to turn the feature back on. Every Speakeasy-tracked file was compared against its pristine blob in gen.lock. Five carried hand edits; each is handled without persistent edits: - src/lib/config.ts, src/lib/env.ts: OPENROUTER_BASE_URL support (#832) moves to an SDKInitHook (src/hooks/base-url-env.ts) registered from the hand-written src/hooks/registration.ts. Precedence is unchanged (serverURL > server > OPENROUTER_BASE_URL > production) and an invalid value still throws a ZodError at client construction. - RUNTIMES.md (Node v22 note) and .devcontainer/devcontainer.json (Node 24 image): added to .genignore. - tsconfig.json: a whitespace-only line, back to the generated form. The exported serverURLFromOptions() helper is generated code and no longer reads OPENROUTER_BASE_URL when called directly; clients built with new OpenRouter() are unaffected. A unit test pins this.
Output of the pinned Speakeasy 1.803.2 with persistent edits off, generated from the same spec revision as the current generated code. Removes the `@generated-id` header line from 1,392 generated files and drops the matching tracking data from gen.lock. No other source changes. The SDK version is left for the release workflow to bump.
Left over from the callModel removal (#1356); nothing imports it.
mattapperson
force-pushed
the
chore/remove-call-model
branch
from
November 11, 2026 17:41
fb77bcd to
6c531cc
Compare
…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.
christineschen
approved these changes
Nov 11, 2026
christineschen
left a comment
Contributor
There was a problem hiding this comment.
one small nit but otherwise lgtm
Take main's env.ts and gen.lock, keep src/lib/url.ts out of the tracked files, strip @generated-id headers that main reintroduced, and drop the whole-path dot-segment backstop in pathToFunc so base URLs that contain dot segments normalise as before.
The base-URL env var is read by the base-url-env hook on this branch; the merge from main brought back the hand edit in src/lib/env.ts. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
alexb-openrouter
added a commit
that referenced
this pull request
Nov 13, 2026
## Summary The last three Publish runs (1.5.101, 1.5.102, and the merge of #1372) failed in `npm run build` with `FATAL ERROR: ... JavaScript heap out of memory` in `tsc`, so nothing has reached npm since 1.5.100. Cause: the Publish workflow runs `npm install` without a lockfile, so it resolves the newest `zod` 4.x (4.6.7). `pnpm-lock.yaml`, used by PR validation and local builds, pins 4.2.1. Type-checking the generated models against zod 4.6.7 peaks above the default Node heap limit on the runner (about 4 GB). With the lockfile, the build peaks at about 2.4 GB. Change, in both `.speakeasy/gen.yaml` `additionalScripts` (source of truth) and `package.json`: ```diff - build: tsc && node scripts/unbarrel-imports.js + build: node --max-old-space-size=8192 ./node_modules/typescript/bin/tsc && node scripts/unbarrel-imports.js (same for compile) ``` `typecheck` is unchanged. Verified on a fresh clone of this branch with `npm install --ignore-scripts && npm run build`. It resolves zod 4.6.7 the same way Publish does, and the build completes in 37s with a peak RSS of 4.5 GB. This PR doesn't touch `.speakeasy/gen.lock`, so merging it doesn't trigger Publish. After merging, run Publish manually (`workflow_dispatch`) to release the current `main` version. Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two things, both patch-level:
..or.no longer sends the request to a different route. Supersedes fix(url): reject "." and ".." path parameters so URL resolution can't change the route #1390, whose fix was a hand edit to a generated file and would be overwritten once persistent edits are off.No version change and no API removal: this is intended to ship as an ordinary patch release.
The branch is up to date with
main(8d929a22, SDK 1.5.102). The merge takesmain's generatedsrc/lib/env.ts(theOPENROUTER_DEBUGparser fix) without theOPENROUTER_BASE_URLhand edit, keepssrc/lib/url.tsout ofgen.locktrackedFiles, and strips@generated-idheaders on files added since. See "Dropped from the original PR" at the bottom.Security fix: dot-segment path parameters
Generated operations percent-encode path parameters, which leaves
.and..unchanged.ClientSDK._createRequest(src/lib/sdks.ts) then builds the URL withnew URL(path, baseURL), which resolves them as dot segments. Code that passes an untrusted value as a path parameter could therefore have the request, with the client's API key, sent to another route:Behaviour.
pathToFunc(src/lib/url.ts), which every operation uses to fill its path template, now throws when a parameter that forms a whole path segment is exactly.or... There is no whole-path check, so a base URL that itself contains a dot segment (serverURLorOPENROUTER_BASE_URL) normalises as onmain....,.env,a..bare unaffected,%2e%2eis still sent double-encoded as%252e%252e, and the existing "is required" / "must be a string or number" errors are unchanged. The only inputs that behave differently are ones that were already being misrouted.Error type. A plain
Error, as a rejected promise, the same as the existing "Parameter 'x' is required" error. It is thrown before_createRequest, so it is not wrapped in the SDK'sResult/InvalidRequestError, including for callers of the standalonefuncs/*functions.Mechanism:
src/lib/url.tsis added to.genignoreand is now hand-maintained. The alternatives were tried first:patternon path-parameter schemas via an overlayout.openapi.yaml, and regeneration produced no change undersrc/. The generator does not emitpattern(orminLength: the spec has both today and the generated zod schemas have neither).gen.yamloptionbeforeCreateRequestruns at the end of_createRequest, afternew URL(path, baseURL), and receives only the resolved URL;HookContexthas no path template or raw parameters. A misrouted/files/file-abc/contentis indistinguishable from a real call to that route.Regenerating with the entry in place leaves
src/lib/url.tsbyte-identical, and the generator removes the file's entry fromgen.locktrackedFiles, which is committed here.Cost and mitigation. Generator updates to this one file no longer arrive.
tests/unit/path-param-dot-segments.test.tshas a "generator contract" block that fails if generated operations start using the file differently (the exported signature, what they import from it, the call shape).Removing it. The bug has been reported to Speakeasy. When the generator rejects or safely encodes dot segments: delete the
src/lib/url.tsline from.genignore, regenerate, keep the behaviour tests. The file's header comment says the same.Consumer-visible behaviour changes
Please read these before deciding this is fine as a patch.
serverURLFromOptions()called directly no longer readsOPENROUTER_BASE_URL. With the env var set,serverURLFromOptions({})now returns the production URL.new OpenRouter()and every sub-SDK (chat, etc.) still honour the env var with the same precedence, through a hook. This one is not avoidable while persistent edits are off: the helper lives in generatedsrc/lib/config.ts, and Speakeasy's TypeScript target has no setting that makes the server URL read an env var (envVarPrefixcovers security and global parameters only). Workaround for direct callers:serverURLFromOptions({ serverURL: process.env.OPENROUTER_BASE_URL }).EnvandenvSchemainsrc/lib/env.tsno longer have anOPENROUTER_BASE_URLfield. Only code that deep-imports@openrouter/sdk/lib/env.jsand readsenv().OPENROUTER_BASE_URLis affected. Same cause as (1).OPENROUTER_BASE_URLeach time a client is constructed. Before, the value was memoised with the rest ofenv()on first use.client._options.serverURLis now populated with the env value when it is applied.Not changed:
serverURL>server>OPENROUTER_BASE_URL> production.OPENROUTER_BASE_URLstill throws aZodError(issue path["OPENROUTER_BASE_URL"]) at client construction, including whenserverURLis passed explicitly. The original version of this PR changed it to aTypeError; that is gone.// @generated-idcomment line disappears from generated sources. Comment only, no runtime or type effect.Audit of hand edits
Every non-deleted file under
trackedFilesin.speakeasy/gen.lock(3,955 files) was hashed and compared with itspristine_git_object. Re-run in this round againstmainata709c011: the same five differ, nothing new.src/lib/config.tsOPENROUTER_BASE_URLfallback inserverURLFromOptions(#832)SDKInitHookinsrc/hooks/base-url-env.ts, registered insrc/hooks/registration.ts. Direct helper calls lose it (change 1 above).src/lib/env.tsOPENROUTER_BASE_URLinEnv/envSchema(#832)RUNTIMES.md.genignore.devcontainer/devcontainer.json.genignoretsconfig.jsonsrc/sdk/sdk.tsno longer differs from pristine: thecallModelregions were removed onmainin #1356.enableCustomCodeRegionsis a separate feature and stays on.Existing hooks are unaffected:
src/hooks/signal-hooks.tsandregistration.tsare not generator-owned, and the regen did not touch them. The new hook is registered beforeSignalTimeoutHook; the two do not interact.Commits
chore: disable Speakeasy persistent edits:persistentEdits.enabled: never,.genignore, the hook and its registration, tests,CLAUDE.md, and the regeneratedconfig.ts/env.ts/tsconfig.json. This is the one to review.chore: regenerate SDK without @generated-id headers: generator output only. 1,392 files lose the one header line,gen.lockdrops the matching tracking data, and Speakeasy removes one blank line from.gitignore. Nothing else.test: remove unused schema-test-helpers: a test helper left behind by SEC-768: refactor: remove the old callModel code that moved to @openrouter/agent #1356 that nothing imports.fix(url): reject "." and ".." path segments so URL resolution can't change the route:src/lib/url.ts, its.genignoreentry, thegen.lockentry removal, tests,CLAUDE.md.Version
Left at
1.5.85ingen.yaml,package.json,jsr.jsonandSDK_METADATA, exactly as onmain. Versioning isversioningStrategy: automaticand the Generate workflows runsdk-generation-actioninmode: pr, which bumps the patch when it regenerates.gen.lockhere keeps the olddocChecksumand has a newconfigChecksum, so the next release regen sees a change and bumps to the next patch.How it was regenerated
Pinned Speakeasy 1.803.2, local only:
with the two
registry:blocks removed from an uncommitted copy ofworkflow.yaml(code samples written to a local file), so nothing was uploaded or tagged.workflow.yamlandworkflow.lockare unchanged frommain.main'sin.openapi.yamlis one spec update (#1434) ahead of its generated code. To keep this diff to the persistent-edits change, generation used the spec revision the current generated code was built from. The pending spec changes are left for the normal Generate run.A run on the final tree (after commit 4) produced no diff apart from key ordering inside
package.json, which the generator shuffles on every run and which is not committed.src/lib/url.tshad the same hash before and after.Testing
pnpm run lint,pnpm run typecheck,pnpm run build: pass.pnpm test: 122/122 in 10 files, no type errors.tests/unit/path-param-dot-segments.test.ts(28 tests) runs three operations (containers.downloadContainerFileContent,batch.getBatches,presets.restore) against a local mock server and checks the route hit, that./..reject with the parameter named and send nothing, the unaffected values, numeric parameters, and the generator contract. 11 of them fail withmain'surl.ts.tests/unit/server-url-env.test.ts(10 tests) goes throughnew OpenRouter()and covers: unset, env applied, env applied tochat,serverURLover env,serverover env,serverURLoverserver, invalid value is aZodErrorwith the right path, invalid value still throws with explicitserverURL, empty value throws, and the direct-helper behaviour.esm/against a local HTTP stub: withOPENROUTER_BASE_URLset,chat.sendhit the stub at the env path; an explicitserverURLwon;server: "production"won; unset fell back to production.esm/built from the regenerated tree:containerId..and.,batch.getBatches({ id: ".." })andfiles.download({ fileId: "." })threw and the stub received nothing; a normal id and%2e%2ehit the container route.Dropped from the original PR
feat!/chore!, no "Migrating from v1" README section. The README onmainalready sayscallModelmoved to@openrouter/agent.mainvia SEC-768: refactor: remove the old callModel code that moved to @openrouter/agent #1356. Of the 37 files that commit deleted, two are still onmain:tests/utils/schema-test-helpers.ts(unreferenced, removed here) andsrc/models/claude-message.ts(kept: it is listed as hand-written inCLAUDE.mdand is reachable as a public deep import, so deleting it would be a consumer-visible removal).TypeErrorfor an invalidOPENROUTER_BASE_URL: reverted to the existingZodError.Notes
CLAUDE.mdis updated to say so.src/lib/url.tsis the exception, because it is in.genignore.