fix(security): resolve gateway Bearer tokens from the credential KV - #621
fix(security): resolve gateway Bearer tokens from the credential KV#621cursor[bot] wants to merge 20 commits into
Conversation
…closed otherwise Chat history: message-level audio and legacy function_call are null/empty omit no-ops; non-empty fail closed with named errors (including tools passthrough). Tip substrate from #577 assistant refusal/annotations honesty. Local full unit: 940 passed.
…ed otherwise OpenAI fine-tune style message weight is not applied on this gateway. Accept null/0/1 as honest no-ops; reject other types and values with invalid_message_weight. Tip substrate from #578. Local full unit: 943 passed.
…ion role Reject unsupported message keys with named unknown_message_fields (not silent strip or tools-passthrough smuggle). Reject legacy function role with invalid_message_role migration to tool. Tip substrate from #579. Local full unit: 947 passed.
OpenAI partial-assistant prefix flag is not applied on this gateway. null/false are honest no-ops; true and non-booleans fail closed with invalid_message_prefix. Tip substrate from #580. Local full unit: 950 passed.
…therwise Named invalid_max_tool_calls on /v1/chat/completions instead of opaque unknown_fields. Aligns with Responses max_tool_calls honesty; gateway has no multi-step tool loop.
…losed otherwise Legacy /v1/completions treated max_tool_calls as unknown_fields. Accept the key for named invalid_max_tool_calls (null/empty/whitespace omit-equivalent), matching chat/Responses honesty so SDKs get a clear migration path.
SDK clients often send include_usage/include_obfuscation as JSON null. Drop null flag values before validation so null (and null+false mixes) match omit / all-false no-ops on chat, Completions, and Responses. True flags remain fail-closed with invalid_stream_options.
…age honesty Null include_usage/include_obfuscation stay omit-equivalent, but unknown stream_options keys no longer become no-ops just because their value is null. Weight, prefix, refusal, annotations, developer role, empty user/system content, and participant name now use the same named errors on the tools passthrough path as on orchestration. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Hoist stream, required model, stream_options, and temperature/top_p range checks before proxy_completion so a tools or response_format body cannot return a billed JSON completion when the SDK asked for SSE, or silently pick a pool model when model is omitted. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Call the orchestration message, max_tokens, attribution, and routing validators before proxy_completion so a tools or response_format body cannot bill a completion with no prompt, crash on a non-object entry, or silently drop unknown spend/routing keys. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Passthrough has no batch job plane. Reject routing.channel=batch and latency_tolerant=true before proxy_completion so a tool-calling body cannot bill a silent sync completion. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Hoist the orchestration request-knob validators before proxy_completion so a tools or response_format body cannot bill a completion that silently drops seed, stop, n>1, logprobs, logit_bias, or out-of-range penalties. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
stream_chat defaulted to 0.2 even after the HTTP path wrote default_temperature from the request. A streamed invoice summary at temperature=0.8 no longer silently falls back to 0.2. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
… proxy SDK optional defaults serialize omitted fields as JSON null. Accepting those keys without popping them is not omit-equivalent: proxy_completion forwards the body and several providers reject a null JSON Schema object. Pop the keys in place, keep non-null wrong types on invalid_tools, and assert omit-real via mock echo on chat and Responses. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Stop reading CONTEXTUAL_ORCHESTRATOR_ALLOWED_PROVIDER_HOSTS in ModelClient._validate_provider. The allowlist lives in provider_egress.allowed_provider_hosts; env is bootstrap transport via seed_provider_egress_from_environ only. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Prove a second seed() after an env edit keeps the first KV set, that _validate_provider ignores env-only allowlists, and that a detached get_config_store() write is not the request-time source. Hold the runtime lock across seed read+write and fuzz the host CSV parser. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Irreversible email [REDACTED] on opt-in traces made invoice and HR tickets unworkable. Credential shapes stay redacted. Access control plus audit is the PII control (NIST SP 800-122; SP 800-53 AC-3/AU-2). Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
CONTEXTUAL_ORCHESTRATOR_TOKEN and the split admin/inference vars are bootstrap transport via seed_server_auth_from_environ. Explicit --auth-token still wins. A later env edit does not change who can call the API. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
Unique slice (3cfc201 on #616 fe444d2)
SOUND for KV seed-once, CLI-wins, and HTTP 200-vs-401. serve_security_tokens seeds once then reads CLI values and get_credential only. ModelClient is untouched. Argparse token defaults are "", so a later env edit cannot become a CLI winner. --serve still fail-closes when no token is resolved.
Merger next action
Do not merge #611, #616, #598, or draft #607 beside this stack. Do not approve from this automation. Two Important items keep this tip from being the final token landing:
- Seed stores and resolve returns unstripped KV values. A Docker/K8s secret with a trailing newline copies successfully, then every
Bearercall is 401 becauseSecurityConfig.authorizestrips the presented token andcompare_digestfails. docs/kv-credentials.mdsays a later env edit does nothing "until the next bootstrap." That is false forPostgresCredentialBackend: seed skips any non-empty key, so restart does not recopy. Rotate with--auth-tokenorregister-credential.
This run lands those two fixes on a successor from this tip. Prefer that successor over this head for the token slice. #637 remains the sqlite/Clearfolio/TLS path landing — rebase it onto the strip tip. Independent non-author APPROVE + Full unit / Semgrep still required.
Buyer next action
Pass --auth-token (or the split admin/inference pair), or start once with CONTEXTUAL_ORCHESTRATOR_TOKEN so bootstrap can copy it, then send that Bearer value. Do not edit the env var on a live process. After the successor lands, a mounted secret with a trailing newline will authorize the stripped value.
Grounding: Joint Task Force (2020) NIST SP 800-53 Rev. 5 IA-5; Grassi et al. (2017) NIST SP 800-63B.
Sent by Cursor Automation: Fix Issues
| continue | ||
| raw = os.environ.get(env_name, "") | ||
| if raw.strip(): | ||
| backend.set(credential_name, raw) |
There was a problem hiding this comment.
Store raw.strip(), not raw. seed_server_auth_from_environ only checks raw.strip() before write. SecurityConfig.authorize strips the presented Bearer (server.py:172). A Docker/K8s secret or echo newline therefore seeds a value that never matches, so bootstrap "succeeds" and every call is 401. register-credential --value-stdin already strips.
| the split pair) or seed the credential KV, then send that Bearer value. | ||
| """ | ||
| return ( | ||
| (auth_token or "").strip() or get_credential(GATEWAY_AUTH_TOKEN) or "", |
There was a problem hiding this comment.
resolve_server_auth_tokens strips CLI values but not KV values. Strip the get_credential(...) result too so an already-persisted newline still authorizes Bearer <token> after upgrade.
| `CONTEXTUAL_ORCHESTRATOR_TOKEN`, `CONTEXTUAL_ORCHESTRATOR_ADMIN_TOKEN`, | ||
| and `CONTEXTUAL_ORCHESTRATOR_INFERENCE_TOKEN` into those KV names | ||
| **once**, when the name is still empty. After that, changing the env var | ||
| does nothing until the next bootstrap. `python -m contextual_orchestrator |
There was a problem hiding this comment.
"Until the next bootstrap" is false for a persistent KV. Seed skips any non-empty key (credentials.py:247-248). Rotating CONTEXTUAL_ORCHESTRATOR_TOKEN and restarting does not recopy; the old authenticator stays valid. Say: copy only when the KV name is empty; rotate with --auth-token or register-credential. A later env edit does not change a live process or a persisted key.
Pull request was closed
|
Deferring to tip #691 for product-gate focus (Full unit + Semgrep). Reopen if this delta is not on tip substrate. |


Unique slice
3cfc201on #616fe444d2(which already includes #611 allowlist + trusted-trace email).Gateway Bearer authenticators resolve from the credential KV as
gateway_auth_token,admin_auth_token, andinference_auth_token.--auth-tokenand the split pair still win.CONTEXTUAL_ORCHESTRATOR_TOKEN/_ADMIN_TOKEN/_INFERENCE_TOKENare copied once at process start (seed_server_auth_from_environ). A later env edit does not change who can call the API.Local scripts printed ok:
tests/test_server_auth_kv.pyplus self-check, paper/API/admin/conventions, security hardening, allowlist, trusted-trace email, sales readiness, plugin artifacts, and product planning.Merger next action
Prefer this over merging #611 or #616 alone when landing allowlist + email + token KV together. Do not merge #598 or draft #607. Rebase #613 (mode/trace) and #618 (verify/conduct) onto this tip after it lands — do not merge those stacks onto
mainbeside this one.Independent non-author APPROVE plus Full unit / Semgrep are required. This automation will not approve or merge.
Buyer next action
Pass
--auth-token(or the split admin/inference pair), or start once withCONTEXTUAL_ORCHESTRATOR_TOKENso bootstrap can copy it, then send that Bearer value. Do not edit the env var on a live process and expect authorization to change.Grounding: Joint Task Force (2020) NIST SP 800-53 Rev. 5 IA-5; Grassi et al. (2017) NIST SP 800-63B.