Skip to content

fix(config): refuse project provider keys that move the API key - #613

Merged
andybons merged 3 commits into
mainfrom
fix/project-config-provider-keys
Oct 14, 2026
Merged

andybons merged 3 commits into
mainfrom
fix/project-config-provider-keys

Conversation

@andybons

@andybons andybons commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A host tool named like a built-in tool, for example bash, fails New. An sdk/embed page tool or a Go Options.Tools tool with that name makes session/new fail with internal, also when the allowed list leaves the built-in out. The browser default set excludes bash, so a page that wants its own bash tool cannot have one.

Design

Spec: docs/architecture.md Tool names (Four rules, AllowedTools bullet), Runtime (New), Built-in tools, and the wasm Tools row. Design: docs/design/acp-wasm.md section 5.1, 5.6.

  • New no longer rejects an Options.Tools name from read_file, write_file, edit_file, glob, grep, ls, bash (Runtime.shadowable, the one owner of the rule).
  • The session source drops the built-in tool of that name, so the model sees one tool with the schema of the host.
  • checkModel fails Create with invalid_request (ErrInvalidRequest, ACP invalid params) and names the tool when AllowedTools is nil, because the built-in tool is then allowed too.
  • Plugin tools, MCP tools, read_tool_result, session_info, process, task, model keep their rules. The spec is silent on plugin and MCP tools, so only host tools change.

Semantic change

A violation fails the load with an error that names the key, the provider, and both files, in place of a warning and a dropped key. A project file cannot set api_key_env on any provider, and a provider that only the project file defines sends no API key: the default key variable (ANTHROPIC_API_KEY, OPENAI_API_KEY) moved into the native provider table of config (NativeKeyEnv, NativeProviders), and the router reads it only for a native entry. An openai type entry under another name now needs api_key_env in the user config. A provider with no key fails its request with "no API key" before any request is sent (an existing behavior of the adapters), so a project-only provider reaches no endpoint with a key; a keyless local model needs an HTTPClient transport that supplies the credentials. harness acp sends the text of an invalid_request error in data.reason, so an editor shows which key, provider, and file failed. The codex contract row that sent a project base_url is replaced by a CLI row that checks the load fails and that neither the endpoint, the evil URL, nor the issuer gets a request. The WebAssembly gzip budget rises by 50,000 bytes: the module was 860 bytes over after this change.

Boxes: cmd/boxinit/config.go writes api_key_env on every provider it writes (bifrost, codex, and the native entries), so no boxes user-config entry relied on the OPENAI_API_KEY fallback. No in-repo fixture did either.

Verification

Table test in config/project_keys_test.go. CLI contract rows: a project anthropic.base_url, a project file that moves the codex login, a project api_key_env, and no OPENAI_API_KEY sent to a project-only provider. One ACP session/new row for a bad project file that checks data.reason. go build, go vet, go test -race ./..., HARNESS_E2E_RUNTIME=1 go test -race ./e2e, gofmt pass.

CI job times, this PR vs main (seconds): Static 42 vs 40, Unit 54 vs 56, Contract 84 vs 89, Runtime contract 53 vs 61, Gates 33 vs 35, Browser 115 vs 120, WebAssembly 22 vs 27, Node wasm 72 vs 65. Node wasm grew 7 s: it runs the new ACP row on the wasm module (about 2.5 s) and the rest is runner noise. No other job grew by more than 5 s.

Copilot AI balanced review requested due to automatic review settings October 14, 2026 02:21
Problem: a project .harness.json could set base_url, responses_path,
extra_headers, api_key_env, and permission_mode on a provider that the
user config defines, so an untrusted checkout could send the API key or
any environment variable to its own endpoint, or set bypassPermissions.

Design: MergeProject checks the project providers in one rule,
rejectProjectProviderKeys. A project file cannot set auth, and cannot set
those five keys on a provider that the user config defines or that is
native (nativeProviderKeys). A provider that only the project defines may
set them. The rule replaces the earlier drop-and-warn auth rule.

Semantic change: the load fails with an error that names the key, the
provider, and both files. binary_path and extra_args stay allowed.

Verification: config table test, CLI contract row, go test -race ./...,
HARNESS_E2E_RUNTIME=1 go test -race ./e2e. The codex row that sent a
project base_url is removed: the new CLI row covers the refusal.
@andybons
andybons force-pushed the fix/project-config-provider-keys branch from 5bf48c5 to 4c9b8e8 Compare October 14, 2026 02:23

Copilot AI 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.

🟡 Changes recommended

The changelog currently makes a materially inaccurate security guarantee about project-defined providers.

1 open finding
What changed in this PR

Restricts project configuration from redirecting credentials through existing or built-in providers.

Changes:

  • Rejects protected project provider keys with explicit errors.
  • Adds unit and CLI contract coverage.
  • Updates architecture documentation and removes obsolete Codex coverage.
File Description
CHANGELOG.md Documents the security fix.
config/​load.go Adds provider-key rejection.
config/​merge.go Removes drop-and-warn behavior.
config/​project_keys_test.go Tests protected and permitted keys.
config/​config_test.go Updates merge expectations.
config/​auth_test.go Removes superseded authentication test.
docs/​architecture.md Defines the new trust boundary.
e2e/​contract_cli_test.go Tests CLI rejection and no request.
e2e/​contract_codex_test.go Removes project-file setup.
e2e/​contract_codex_auth_test.go Removes obsolete Codex scenario.
e2e/​runtime_rows_test.go Removes the obsolete runtime row.
e2e/​testdata/​contract/​codex_login_project_file_cannot_move_the_login_to_another_url.golden.json Deletes the obsolete golden.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread CHANGELOG.md Outdated
… over ACP

Problem: a provider that only the project file defined could still name
api_key_env, or fall back to OPENAI_API_KEY, and send that key to the
base_url of the project. The ACP error for a project file that does not
load said only "harness: invalid request".

Design: rejectProjectProviderKeys fails the load when a project file sets
api_key_env on any provider. The default key variable moves into the
native provider table of config (NativeKeyEnv), and the router reads the
variable only for a native entry, so an openai-type entry under another
name reads no default variable. The router lists the native providers from
the same table. rpcError sends the text of an invalid_request error in
data.reason.

Semantic change: a project-only provider sends no API key. A project file
that sets api_key_env fails to load. A session/new for such a project
answers -32602 with the config error in data.reason.

Verification: config table test, CLI rows (codex login moved by a project
file, api_key_env in a project file, no OPENAI_API_KEY sent to a project
provider), ACP row, go test -race ./..., HARNESS_E2E_RUNTIME=1 e2e.
…the wasm gzip budget

The wasm module grew by 860 bytes of gzip over its budget with the new config check and the data.reason of an invalid_request error. The budget rises by 50,000 bytes as the file asks for a PR that grows the module. The Node wasm host names its user config 'the config of harnessConnect' in an error, so its golden is aligned with the native hosts.
@andybons
andybons merged commit 196c7e5 into main Oct 14, 2026
9 checks passed
@andybons
andybons deleted the fix/project-config-provider-keys branch October 14, 2026 03:01
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