Skip to content

fix: pass AI Config model parameters through to every provider handler - #73

Open
apucacao wants to merge 16 commits into
mainfrom
alexis/forward-max-turns
Open

apucacao wants to merge 16 commits into
mainfrom
alexis/forward-max-turns

Conversation

@apucacao

@apucacao apucacao commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Every time I ask "how would I use the turnkey SDK here?", I hear about how model params aren't currently being passed to models. This PR fixes that.

Not entirely sure about the implementation though: we ended up definiting the list of params supported by each of the underlying SDKs, because I couldn't find a better export from those packages, and iterating on types felt harder than the explicit list. The downside is that if/when things change, we'd need to update the code. But presumably we already need to do that for our model parameter schemas in Gonfalon.


Model parameters set in an AI Config were silently ignored by most handlers. Every handler now passes them through to the provider.

  • Configs use snake_case (max_turns, top_p), the providers' own names and what the UI writes.
  • Anthropic and OpenAI clients take snake_case, so those handlers pass keys through as-is.
  • Agent SDKs and LangChain read camelCase, so those handlers convert top-level keys.
  • Nested values are never converted. If both spellings are set, snake_case wins.
  • The Vercel AI SDK handlers (vercel-messages, vercel-agents, and vercelEvaluate) map keys onto the AI SDK's own call settings, such as max_tokens to maxOutputTokens and top_p to topP. The AI SDK silently drops names it does not know.

🤖 Generated with Claude Code


Note

Overview
AI Config model.parameters now reach provider calls instead of being ignored. Shared helpers in @launchdarkly/ai-server (normalizeModelParameters, camelizeModelParameters, pickForwardedModelParameters) centralize how each handler treats the parameter bag.

Framework / agent SDK paths (Claude Agents, OpenAI Agents, LangChain agents/messages/graph) spread normalized parameters into SDK options, camelizing top-level snake_case keys (max_turns → maxTurns) while handler-owned fields (model, tools, system prompt, streaming flags) still win when spread last. Claude Agents refactors buildQueryOptions to separate untrusted modelParameters from trusted internalOptions.

Direct Anthropic/OpenAI client handlers use compile-time-checked allowlists and keep snake_case on the wire; they drop or rename keys that would 400 or break the handler (e.g. Claude Messages moves UI effort into output_config.effort; OpenAI Messages maps token limits to max_output_tokens).

Vercel messages/agents/evaluate replace ad-hoc parameter filtering with dedicated model-parameters builders that map UI names to AI SDK call settings (maxOutputTokens, topP, etc.) and forward only recognized keys.

Coverage adds handler unit tests for forwarding, casing, precedence, and override protection, plus wire tests that stub fetch or use real LangChain/Vercel models to assert actual request bodies.

Reviewed by Cursor Bugbot for commit 4241d75. Bugbot is set up for automated code reviews on this repo. Configure here.

@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

apucacao and others added 4 commits September 22, 2026 10:37
buildQueryOptions already accepted an unused extra options bag; the call
site never filled it. An AI Config could set a turn cap or agent-run
tuning knobs and this handler silently dropped them, so an agent driven
through this SDK ran with no budget at all.

Now config.model.parameters is checked against the Options keys the
Claude Agent SDK actually accepts (maxTurns, maxThinkingTokens,
maxBudgetUsd, effort, fallbackModel, thinking) and only those are
forwarded. Anything else, including temperature and max_tokens which
this SDK has no equivalent for, is ignored rather than passed through.
No default is added, so a config that sets nothing behaves exactly as
before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The handler built responses.create()/responses.stream() calls as only
{model, input, tools}, so a customer who set temperature, top_p, or
max_output_tokens in an AI Config had it silently dropped on every
call, blocking and streaming alike.

config.model.parameters is now checked against an explicit allowlist
of ResponseCreateParams keys the Responses API actually accepts
(temperature, top_p, max_output_tokens, top_logprobs,
parallel_tool_calls, reasoning, truncation, metadata, store,
service_tier, prompt_cache_key, safety_identifier), and only those are
forwarded, on every call site: the initial turn, the tool-loop
follow-up turn, and both branches of the streaming path. The allowlist
is spread first so model, input, tools, and previous_response_id -
computed by the handler itself - always win, so a parameters bag can
never smuggle those back in. A config that sets nothing produces {},
so the request sent is unchanged from before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The handler only ever read config.model.parameters.max_tokens; every
other Anthropic Messages API knob a customer set in an AI Config
(temperature, top_p, top_k, stop_sequences, thinking, and the rest of
MessageCreateParamsBase) was silently dropped.

Now an explicit allowlist, taken from the SDK's own
MessageCreateParamsBase type, is forwarded from model.parameters at
both call sites (the blocking tool loop and the streaming tool loop).
The allowlist excludes model, messages, tools, system, max_tokens, and
stream, so a config value can never override what the handler already
decided for those. max_tokens keeps its existing behaviour and 1024
default unchanged.

A config that sets no parameters, or an empty parameters object,
produces the exact same call as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Runner.run already accepts maxTurns and the Agents SDK's ModelSettings
tunes temperature, topP, and similar knobs, but the handler never read
either from config.model.parameters. An agent driven through this SDK
ran with no turn budget at all, and a customer's temperature/topP/etc.
settings silently did nothing.

Now config.model.parameters is checked against the ModelSettings keys
the Agents SDK's Agent constructor actually accepts (temperature, topP,
frequencyPenalty, presencePenalty, maxTokens, toolChoice,
parallelToolCalls, truncation, store, promptCacheRetention) and only
those are forwarded as modelSettings. maxTurns is read separately and
passed to Runner.run's options, since it caps the agentic loop rather
than tuning a model call. Both the blocking and streaming call paths
are covered. Anything else in model.parameters is ignored rather than
forwarded. No default is added, so a config that sets nothing behaves
exactly as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@apucacao
apucacao force-pushed the alexis/forward-max-turns branch from 6a1c4cf to 1f14632 Compare September 22, 2026 14:39
@apucacao apucacao changed the title fix(claude-agents): forward maxTurns and model.parameters to query() fix: forward model.parameters to every provider handler Sep 22, 2026
apucacao and others added 3 commits September 22, 2026 10:51
Extracts the duplicated `buildModelParameterOptions` / `buildModelSettings`
picker loop (claude-agents, claude-messages, openai-messages, openai-agents)
into `pickForwardedModelParameters`, and the repeated
`config.model?.parameters && typeof ... === 'object' ? ... : {}` guard
(langchain-messages, langchain-agents, langchain-agents/native-graph) into
`normalizeModelParameters`. Both live in packages/client and are exported
from its public index.

Each handler keeps its own FORWARDED_*_KEYS allowlist next to the request
shape it maps onto; the shared picker only owns the loop. openai-agents
keeps collapsing an empty result to `undefined` itself. The LangChain
handlers keep spreading the whole parameters object, unchanged, since their
chat models take an open-ended options bag.

No behaviour change: same keys picked, same spread order, same undefined vs
{} results. Adds unit tests for both new helpers in packages/client.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…bag through

The four allowlist handlers (claude-agents, claude-messages,
openai-messages, openai-agents) each maintained a hand-picked
FORWARDED_*_KEYS list and a pickForwardedModelParameters call to filter
config.model.parameters before forwarding it to the provider SDK. The
allowlists silently dropped valid settings and needed updating whenever
a provider added a new tunable. The LaunchDarkly UI already constrains
which keys a customer can save, so an unsupported key reaching the
provider is an acceptable error, not something this SDK needs to guard.

Replace every allowlist with normalizeModelParameters, the same
pass-through helper the LangChain handlers already use, and delete
pickForwardedModelParameters from @launchdarkly/ai-server now that no
caller is left.

While making every call site spread parameters first, found three
places where a handler-owned key was set with a conditional spread
(`...(cond ? { key } : {})`) instead of an explicit key: when the
handler's own condition was false, the key was never set, so a
customer's model.parameters could smuggle a value through unopposed.
Fixed by always setting the key explicitly, to undefined when absent:
claude-messages' `system`/`tools`, claude-agents' `systemPrompt`, and
openai-messages' `tools`/`previous_response_id`/`text` (structured
output).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
buildQueryOptions took a single `extra` bag that carried both the
customer's model.parameters and this handler's own internal flags
(includePartialMessages). Two different trust levels in one untyped
argument, with the safe ordering only holding by accident of how the
call site happened to build the literal.

Split them: `modelParameters` is required, documented as untrusted, and
spread first so every handler-owned key overrides it. `internalOptions`
carries the handler's own flags and is spread last so it wins over
everything. A reader of the signature can now see which is which.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dlers

The LaunchDarkly UI saves model.parameters keys in snake_case, the wire
name every provider SDK expects (max_turns, top_p, ...). claude-agents,
openai-agents, and both langchain handlers wrap JavaScript frameworks
that only read camelCase option names, so a snake_case key like
max_turns silently vanished instead of reaching the framework: no
error, the value just never took effect.

Add camelizeModelParameters (packages/client/src/utils.ts), converting
top-level snake_case keys to camelCase generated from the key itself.
Nested values (e.g. thinking.budget_tokens) pass through untouched,
and when both spellings of a key are set the snake_case one wins,
deterministically regardless of key order.

Apply it at every call site in the four affected handlers:
claude-agents (query() options, both the blocking and streaming
paths), openai-agents (ModelSettings and buildMaxTurns for
Runner.run), and both langchain handlers' default model
constructors, including langchain-agents' native-graph.ts.

claude-messages and openai-messages wrap raw provider clients that
already read snake_case themselves, so they are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@apucacao apucacao changed the title fix: forward model.parameters to every provider handler fix: pass model.parameters through to every provider handler Sep 24, 2026
… forward it verbatim

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@apucacao apucacao changed the title fix: pass model.parameters through to every provider handler fix: pass AI Config model parameters through to every provider handler Sep 24, 2026
@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

apucacao and others added 4 commits September 24, 2026 11:55
…r request types

The four framework handlers correctly forward the whole model.parameters bag
via normalizeModelParameters, since their frameworks ignore keys they do not
recognize. The two handlers that wrap a raw provider client (claude-messages,
openai-messages) do not have that safety net: an unsupported key reaches the
wire and the provider returns 400.

Restores pickForwardedModelParameters (previously dropped in cf9a611) as a
shared, exported helper: pick the subset of parameters whose keys appear in
an explicit allowlist, drop the rest silently. The two raw-client handlers
use it next to a compiler-checked allowlist derived from each SDK's own
request type, so the list cannot silently drift.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ameters

This handler wraps the raw OpenAI SDK's responses.create / responses.stream,
which reject an unrecognized field with a 400. The LaunchDarkly UI offers the
Chat Completions parameter set, so a config that sets max_tokens,
frequency_penalty, presence_penalty, seed, n, stop, response_format,
logit_bias, logprobs, max_completion_tokens, audio, modalities, or prediction
used to reach the API verbatim and would now break the run.

FORWARDED_MODEL_PARAMETER_KEYS lists every ResponseCreateParamsBase key this
handler forwards. A compile-time exhaustiveness check classifies every other
key on that type as handler-owned (input, model, previous_response_id, text,
tools) or excluded as transport/plumbing, with a reason in the comment above
the list; a key the SDK adds and this file does not classify fails to build,
naming the key.

buildModelParameterOptions also renames the two Chat Completions token-limit
spellings the UI offers, max_tokens and max_completion_tokens, to the
Responses API's max_output_tokens, before filtering. Precedence when a config
sets more than one spelling: an explicit max_output_tokens wins, then
max_completion_tokens, then max_tokens.

Applied at every responses.create / responses.stream call site: the initial
blocking turn, the blocking tool-loop turn, and both branches of the
streaming turn. Behaviour is unchanged when model.parameters is absent or
empty.

Updates the handler.test.ts case that asserted an unrecognized key reached
the request to assert the opposite, and adds coverage for the accepted-key
path, the dropped-key path, both renames, and precedence. Adds
wire.test.ts, which does not mock the openai package and instead stubs
global fetch to assert on the literal JSON body responses.create sends.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…meters

This handler wraps the raw Anthropic SDK's messages.create / messages.stream,
which reject an unrecognized field with a 400. The LaunchDarkly UI offers a
top-level effort setting that the Messages API only accepts nested under
output_config, so a config that set it used to reach the API verbatim and
would now break the run.

FORWARDED_MODEL_PARAMETER_KEYS lists every MessageCreateParamsBase key this
handler forwards. A compile-time exhaustiveness check classifies every other
key on that type as handler-owned (model, messages, system, tools,
max_tokens) or excluded as transport/plumbing (stream, since this handler
selects streaming by choosing between messages.create and messages.stream,
not by setting a field), with the reason in the comment above the list; a key
the SDK adds and this file does not classify fails to build, naming the key.

buildModelParameterOptions also moves a top-level effort into
output_config.effort before filtering. An output_config.effort already
present in the config wins over the renamed value. The handler does not set
output_config itself anywhere (outputFormat is folded into the system prompt
instead), so there is no handler-owned output_config to protect here.

Applied at both call sites inside the tool loop shared by the blocking and
streaming paths. Keeps max_tokens ?? 1024. Behaviour is unchanged when
model.parameters is absent or empty.

Updates the two handler.test.ts cases that asserted an unrecognized key
reached the request to assert the opposite, and adds coverage for the
effort rename, the explicit-output_config.effort-wins case, and the merge
with an output_config that also sets other fields. Adds wire.test.ts, which
does not mock @anthropic-ai/sdk and instead stubs global fetch to assert on
the literal JSON body messages.create sends.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…reak the handler

Orchestrator follow-up: TypeScript and Python must apply the same exclusion
rule, and the previous excluded set went too far. Excluding stream_options,
store, safety_identifier, prompt_cache_key, prompt_cache_retention, include,
context_management, conversation, prompt, and user was based on "is this a
generation setting", not "would this break the handler" — the latter is the
actual contract: forward everything the API accepts unless setting it
produces a broken or missing result.

Moves store, user, safety_identifier, prompt_cache_key,
prompt_cache_retention, include, and context_management from excluded to
forwarded. The excluded set is now exactly five keys, matching the shared
rule:
- stream, stream_options: the handler chooses streaming itself by calling
  responses.create() vs responses.stream().
- background: the call returns before the output exists, so the handler
  gets no result.
- conversation, prompt: supply server-side state/templates that conflict
  with the input the handler builds itself.

The compile-time exhaustiveness check against ResponseCreateParamsBase still
passes with the narrowed OpenAIExcludedKeys union (verified by removing
'user' from FORWARDED_MODEL_PARAMETER_KEYS and confirming tsc names it).

claude-messages needed no change: its excluded set was already exactly
{ stream }, which is what the shared rule requires for the Messages API.
Refreshed its doc comment to state the same "would break the handler" rule
explicitly, no behavior change.

Updates handler.test.ts to add coverage for the seven now-forwarded keys and
to keep asserting the five still-excluded keys are dropped. Extends
wire.test.ts with one case proving `store` reaches the literal wire request
while `background` is dropped from it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@apucacao

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@apucacao
apucacao marked this pull request as ready for review September 25, 2026 14:48

@jeffdupont jeffdupont left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parity note alongside my review of python #107 (launchdarkly/python-ai-sdk#107). The two messages handlers match Python: allowlists, the effort → output_config and max_tokens → max_output_tokens renames, and unknown keys dropped. The agent and LangChain handlers don't match. Python allowlists every handler. Here the whole model.parameters bag is camelCased and spread in with no filtering, so:

  • LangChain (modelConstructorArgs): api_key becomes apiKey, which ChatOpenAI and ChatAnthropic use. anthropic_api_url, configuration, client_options, timeout, max_retries and Bedrock credentials / region / endpoint_host also reach the constructors. Spreading the raw bag predates this PR, but camelCasing is what makes the snake_case spellings the UI writes take effect.
  • Claude Agents (buildQueryOptions): cwd, env, permissionMode, extraArgs, pathToClaudeCodeExecutable, executable and allowDangerouslySkipPermissions can all come from a config. The Python PR has the same problem with its allowlist; details are in my inline comment there.
  • Nested keys aren't converted: thinking.budget_tokens stays snake_case, but the Claude Agent SDK reads budgetTokens, so the thinking budget is probably ignored. The same goes for context_management[].compact_threshold in openai-agents. I haven't confirmed either at runtime.
  • Native graphs: neither the claude-agents nor the openai-agents native graph forwards parameters or maxTurns; Python's do.
  • New public names: camelizeModelParameters, normalizeModelParameters and pickForwardedModelParameters are exported from the client root (index.ts:87,93,97). For the 1.0 surface they belong in the internal group.

The monorepo has no spec for forwarding these parameters, and open monorepo #10 says not to rename or filter keys. I'd suggest agreeing one rule in TESTING.md (allowlist per handler, connection and host keys never forwarded) and landing both PRs against it.

return {
prompt,
options: {
...modelParameters,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Owned keys win over this spread, but everything else in the bag reaches query() options, including cwd, env, permissionMode, pathToClaudeCodeExecutable and allowDangerouslySkipPermissions. That lets an AI Config decide which program runs on the customer's server and what environment it gets. Could this use an allowlist like claude-messages does?

const parameters = {
...(config.model?.parameters && typeof config.model.parameters === 'object' ? config.model.parameters : {}),
};
const parameters = { ...camelizeModelParameters(normalizeModelParameters(config.model?.parameters)) };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With camelCasing, a config's api_key / anthropic_api_url / client_options becomes a working constructor option here. The same line is at langchain-agents/src/handler.ts:265. Python allowlists per model class and excludes credentials and connection settings; matching that would keep the two languages aligned.

apucacao and others added 3 commits October 2, 2026 16:29
The handler spread model.parameters into generateText/streamText as
saved, but the AI SDK only reads its own camelCase call settings and
drops everything else without an error. A config's max_tokens, top_p,
stop_sequences and the like never reached the model; only single-word
keys such as temperature did.

The handler now renames max_tokens / max_completion_tokens /
max_output_tokens to maxOutputTokens and reasoning_effort to reasoning,
camelizes the rest of the top-level keys, and forwards only the AI SDK
call settings (LanguageModelCallOptions and RequestOptions, minus
abortSignal, plus toolChoice and providerOptions). The allowlist is
checked at compile time against the AI SDK's own types, and replaces
the old denylist of handler-owned names.

vercelEvaluate also forwards the three request fields
experimental_evaluate accepts (maxRetries, headers, providerOptions)
from model.parameters; options passed to vercelEvaluate win.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ings

Same problem as vercel-messages: the ToolLoopAgent settings were built
by spreading model.parameters as saved, so snake_case keys such as
max_tokens and top_p were dropped by the AI SDK. The single-agent
handler and every native-graph node now go through the same mapping
and compile-time-checked allowlist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@apucacao

apucacao commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4241d75. Configure here.

...(systemPrompt ? { systemPrompt } : {}),
...extra,
systemPrompt,
...internalOptions,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsandboxed Claude query option spread

High Severity

buildQueryOptions spreads the full model.parameters bag into Claude Agent SDK query() options. Handler-owned keys win, but the rest still reach the subprocess, including cwd, env, permissionMode, pathToClaudeCodeExecutable, and allowDangerouslySkipPermissions. A remotely delivered AI Config can choose the binary and environment that run on the customer's server.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4241d75. Configure here.

const parameters = {
...(config.model?.parameters && typeof config.model.parameters === 'object' ? config.model.parameters : {}),
};
const parameters = { ...camelizeModelParameters(normalizeModelParameters(config.model?.parameters)) };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Camelized params expose constructor secrets

High Severity

Camelizing the unsandboxed model.parameters bag turns UI snake_case keys such as api_key, anthropic_api_url, and client_options into working LangChain constructor options (apiKey, anthropicApiUrl, clientOptions). A config can now override credentials or redirect the client at a remote URL. The Python SDK allowlists per model class and excludes connection settings.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4241d75. Configure here.

function buildModelSettings(parameters: AiConfigRep['model']['parameters']): ModelSettings | undefined {
const settings = camelizeModelParameters(normalizeModelParameters(parameters));
return Object.keys(settings).length > 0 ? (settings as ModelSettings) : undefined;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenAI agents forward untrusted settings

Medium Severity

buildModelSettings camelizes the entire model.parameters bag and passes it through as Agents SDK ModelSettings. That activates real fields such as extraHeaders, extraBody, and extraQuery, so a config can inject request headers or body fields on every model call. maxTurns is also left in the settings object after being copied to Runner.run.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4241d75. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4241d75. Configure here.

...(systemPrompt ? { systemPrompt } : {}),
...extra,
systemPrompt,
...internalOptions,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untrusted params reach Claude query()

High Severity

buildQueryOptions spreads the full camelized model.parameters bag into Claude Agent SDK query() options. Handler-owned keys win, but the rest of the bag is trusted as runtime options, including cwd, env, permissionMode, pathToClaudeCodeExecutable, and allowDangerouslySkipPermissions. A remotely served AI Config can therefore choose which program runs on the customer host and with what environment. The UI allowlist is not a security boundary; configs can be written via the API.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4241d75. Configure here.

const parameters = {
...(config.model?.parameters && typeof config.model.parameters === 'object' ? config.model.parameters : {}),
};
const parameters = { ...camelizeModelParameters(normalizeModelParameters(config.model?.parameters)) };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Camelize enables LangChain credentials

High Severity

camelizeModelParameters turns UI/API snake_case keys such as api_key, anthropic_api_url, and client_options into live LangChain constructor options (apiKey, anthropicApiUrl, clientOptions). Those values are then spread into ChatOpenAI / ChatAnthropic / ChatBedrockConverse. A served AI Config can now override credentials or redirect the client at a hostile base URL, leaking prompts. Python allowlists per model class and excludes connection settings.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4241d75. Configure here.

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