Skip to content

fix(security): read provider host allowlist from KV at request time - #598

Closed
cursor[bot] wants to merge 11 commits into
mainfrom
cursor/bc-d87a9410-ee7f-4007-8505-faa5405f4191-d30e
Closed

fix(security): read provider host allowlist from KV at request time#598
cursor[bot] wants to merge 11 commits into
mainfrom
cursor/bc-d87a9410-ee7f-4007-8505-faa5405f4191-d30e

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown

Summary

  • Close the last request-time os.getenv leftover on provider egress: ModelClient._validate_provider now reads provider_egress.allowed_provider_hosts from the process KV (allowed_provider_hosts()), never CONTEXTUAL_ORCHESTRATOR_ALLOWED_PROVIDER_HOSTS during a call.
  • Env remains bootstrap transport. seed_provider_egress_from_environ() copies the env CSV into the KV once at python -m contextual_orchestrator / serve() when that KV key is still empty. Later env edits on a running process do not change policy.
  • HTTP/unit honesty: tests/test_provider_host_allowlist_kv.py proves env-only allowlists are ignored, KV CSV is honored, bootstrap copies once, and https://api.openai.com is rejected when the KV lists only example.com.
  • Docs: APA 7th citations for NIST SP 800-53 Rev. 5 SC-7 and ISO/IEC 27001:2022 A.8.20 (cite + link; ISO text not attached).

Merger next action

Independent non-author APPROVE still required. Product gates: Full unit + Semgrep.

Test plan

  • python3 tests/test_provider_host_allowlist_kv.py
  • python3 tests/test_security_hardening.py
  • python3 tests/test_self_check.py test_paper_contracts.py test_conventions.py test_api_contract.py test_product_planning_contract.py
  • python3 tests/test_provider_tls.py test_provider_reliability.py test_repository_security_metadata.py
  • CI Full unit + Semgrep

Buyer next action

Seed provider_egress.allowed_provider_hosts in the KV (or start the process with CONTEXTUAL_ORCHESTRATOR_ALLOWED_PROVIDER_HOSTS set so bootstrap can copy it). Do not expect a later env edit to change egress on a live process.

References

  • Joint Task Force. (2020). Security and privacy controls for information systems and organizations (NIST Special Publication 800-53 Rev. 5). https://doi.org/10.6028/NIST.SP.800-53r5
  • International Organization for Standardization. (2022). Information security, cybersecurity and privacy protection — Information security controls (ISO/IEC 27001:2022). https://www.iso.org/standard/27001
Open in Web View Automation 

seonghobae and others added 11 commits August 16, 2026 21:18
…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.
Validate weight/prefix/refusal/annotations before the tools early-return so
SDK tool-calling bodies cannot smuggle unsupported message fields. Strip
omit-equivalent max_tool_calls before provider passthrough and name-reject
the field on /v1/completions.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Reject non-OpenAI keys on assistant tool_calls objects with named
unknown_tool_call_fields / unknown_tool_call_function_fields. Accept
optional non-negative index (null omit) for stream-assembled histories.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Developer role, empty user/system content, multimodal part shape, and
participant name were only checked in _validate_messages, which the
tools/response_format early-return skips. SDK tool-calling bodies could
therefore proxy unsupported values. Run the same fail-closed checks
before passthrough and cover the realistic tools+field HTTP cases.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Completions and chat applied temperature, top_p, penalties, and
max_tokens by mutating shared ModelClient defaults. Concurrent
ThreadingHTTPServer workers could observe another request's knobs.
request_sampling stores overrides on threading.local and restores
them on exit; chat/stream_chat read the calling thread first.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
_validate_messages is skipped on the tools/response_format early-return.
Require a non-empty messages array before proxy so SDK tool-calling bodies
cannot bill a completion with no prompt. Split the optional tool_calls
index case so omit-key is actually tested.

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>
@cursor
cursor Bot requested a review from seonghobae August 16, 2026 15:32
@seonghobae
seonghobae marked this pull request as ready for review August 16, 2026 15:40
@cursor

cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Author

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.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Review (#598 unique slice a50911f)

The unique 14-file allowlist change is the right request-time source: ModelClient._validate_provider reads allowed_provider_hosts() only, and seed_provider_egress_from_environ() is on both __main__.main and serve(). Empty process KV still means "no extra hostname filter"; private/loopback/reserved stay fail-closed. No leftover os.getenv on the request path. Naming and citations (NIST SP 800-53 Rev. 5 SC-7; ISO/IEC 27001:2022 A.8.20) are in place.

This PR must not land as the 152-file stack on #589.

Why not this head

  • Stacked on #589 (b4318b3), not on the current honesty-stack landing vehicle #601 (7e9a339, already includes omit-real tool.function nulls and streamed request temperature).
  • test_seed_provider_egress_from_environ_copies_once never called seed() a second time, so a recopying seed would still pass.
  • Env-only ignore was not proven on _validate_provider (helper-only).
  • Docs said "seed the KV" without stating the process-wide InMemoryConfigStore is not a detached Postgres com_config write.

Merger next action

Prefer #611 (c91629e on 7e9a339): same unique allowlist, rebased onto #601, with seed-once / env-ignore-on-request-path / detached-store isolation tests, seed lock, and a _parse_host_allowlist fuzz seam.

Do not merge #598, #589, or #591 in parallel. Independent non-author APPROVE is still required on #611. I am not approving this PR.

Buyer next action after #611 lands: set_runtime_config("provider_egress", "allowed_provider_hosts", "api.example.com") (or start the process with the env var set so bootstrap can copy it once). Do not write the key only into a new get_config_store(postgres_dsn=...) instance.

View PR

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Copy link
Copy Markdown
Contributor

Superseded by #607. #607 explicitly reapplies this PR’s unique KV-backed provider-host allowlist slice (a50911f) onto the newer honesty stack and adds stronger seed-once/request-time environment-ignore regressions on its current head. Keeping both lanes open would duplicate the same security ownership on different substrates. No checks, reviews, or approvals from this PR transfer to #607.

@seonghobae seonghobae closed this Aug 17, 2026
auto-merge was automatically disabled August 17, 2026 13:35

Pull request was closed

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