Back to Openai Python

Security Model

docs/architecture/security-model.md

3.8.073.8 KB
Original Source

Security Model

This is the canonical repository-owned threat model for Codex Security scans of the OpenAI Python SDK. For a pull-request scan, resolve this document from the trusted base or another pinned protected revision, never from the candidate revision being judged; if that trusted revision has no model, use separately pinned protected scan policy rather than candidate text. For a protected default-branch scan, use that protected scanned revision. SECURITY.md remains the authority for coordinated disclosure instructions.

Overview

The repository publishes the official openai Python library. It is a caller-owned client library, not a hosted multi-tenant service: application code constructs synchronous or asynchronous clients, supplies credentials and request data, sends HTTPS or WebSocket requests to an API endpoint, and parses JSON, SSE, or WebSocket responses into SDK types. The package requires Python 3.10+ and has optional Realtime, voice, aiohttp, and Bedrock dependencies (README.md:6, pyproject.toml:11, pyproject.toml:42).

Most API resources and types are generated from the OpenAPI schema. The security-relevant handwritten surfaces are the shared transport and parsing code, authentication providers, webhook verification, provider integrations, local helpers, dependency/build policy, and release automation (AGENTS.md:3, src/openai/_client.py:157, src/openai/_base_client.py:517).

ComponentRoleEvidence
OpenAI / AsyncOpenAI clientsResolve credentials, endpoint configuration, headers, retries, and transports.src/openai/_client.py:157, src/openai/_client.py:247
Legacy module-level clientSelects, constructs, caches, and resets an OpenAI, Azure, or Bedrock client from module globals and environment state.src/openai/init.py:362, src/openai/init.py:387, src/openai/init.py:404, src/openai/init.py:439
Shared base clientSerializes caller data, builds HTTP requests, processes responses, and manages retries.src/openai/_base_client.py:517, src/openai/_base_client.py:672
SDK-owned default HTTP transportsProvide HTTPX-backed sync/async defaults and the optional vendored aiohttp backend, including redirect, proxy, and TLS behavior.src/openai/_base_client.py:863, src/openai/_base_client.py:1457, src/openai/_base_client.py:1478, src/openai/_vendor/httpx_aiohttp/transport.py:100
Streaming and authenticated WebSocketsIncrementally decode SSE and exchange authenticated current or beta Realtime/Responses WebSocket messages through distinct sync and async connection paths.src/openai/_streaming.py:53, src/openai/resources/realtime/realtime.py:683, src/openai/resources/realtime/realtime.py:1153, src/openai/resources/responses/responses.py:4410, src/openai/resources/responses/responses.py:4855, src/openai/resources/beta/realtime/realtime.py:355, src/openai/resources/beta/realtime/realtime.py:540, src/openai/resources/beta/responses/responses.py:4515, src/openai/resources/beta/responses/responses.py:4960
WebhooksVerify raw inbound webhook bytes before parsing a typed event.src/openai/resources/webhooks/webhooks.py:18, src/openai/lib/_webhooks.py:13
Workload identityObtain local or metadata subject tokens and exchange them for OpenAI bearer tokens.src/openai/auth/_workload.py:78, src/openai/auth/_x509.py:25
CI and publicationTest PR and branch code, run CodeQL, perform the privileged monthly policy assessment, build distributions, and publish through protected release paths..github/workflows/ci.yml:18, .github/workflows/codeql.yml:15, .github/workflows/python-version-review.yml:15, .github/workflows/publish-pypi.yml:8
mermaid
flowchart LR
  app[Caller-owned Python process] -->|credentials, request data, files| sdk[OpenAI Python SDK]
  sdk -->|HTTPS / WSS| api[OpenAI, Azure, or Bedrock API]
  api -->|JSON, SSE, WebSocket frames| sdk
  webhook[Webhook sender] -->|raw payload and headers| verify[Webhook verifier]
  host[Local file or cloud metadata identity] -->|subject token| auth[Token exchange]
  pr[PR checkout code] -->|workflow-specific permissions| ci[CI runner]
  main[Protected main/release workflow] -->|artifact boundary| publish[PyPI publish job]
Deployment or workflowResource or capabilityConfiguration and precedenceSafe effective value or locationReaders, writers, or recipientsEnforcing controlEvidence or unknowns
Default OpenAI clientAPI, admin, or ambient Authorization credentialCredential gate: explicit API/admin argument, then OPENAI_API_KEY / OPENAI_ADMIN_KEY; ambient OPENAI_CUSTOM_HEADERS authorization alone does not satisfy that gate. For ordinary non-X.509, non-provider clients, ambient, constructor-default, and per-request authorization mappings can override generated or stored values for the same exact key; case-variant names can coexist until HTTP header normalization, so every variant remains credential-bearing.Secret remains in process memory. For identical mapping keys, effective request merge order is generated bearer auth, then stored default/ambient authorization, then per-request authorization; differently cased keys are not collapsed by that mapping merge.Selected API destination.Missing API/admin/workload credentials fail; per-operation security flags select ordinary versus admin auth; ordinary copies preserve ambient authorization, provider transitions clear inherited custom headers, and X.509 construction/transitions filter ambient authorization case-insensitively unless explicitly replaced.src/openai/_client.py:247, src/openai/_client.py:261, src/openai/_client.py:310, src/openai/_client.py:318, src/openai/_client.py:589, src/openai/_client.py:635, src/openai/_base_client.py:475, src/openai/_base_client.py:2262, src/openai/_client.py:715, src/openai/_client.py:808, src/openai/auth/_x509.py:281
Legacy module-level clientGlobal/environment-selected OpenAI, Azure, or Bedrock credential and destinationModule api_type starts from OPENAI_API_TYPE. _load_client() fills unset Azure endpoint/version globals from environment; only when api_type is unset, it rejects OPENAI_API_KEY in the environment together with any of: module azure_endpoint or AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_OPENAI_AD_TOKEN, module azure_ad_token, or module azure_ad_token_provider; module api_key and api_version are not ambiguity signals. It infers Azure when an Azure signal exists and OpenAI otherwise; exact api_type == "azure" or "amazon-bedrock" use dedicated branches, while explicit "openai" and any other non-None runtime value fall through to _ModuleClient.Selected _client instance is cached until _reset_client(); _ModuleClient property-backed common globals such as API/admin keys remain live, while Azure endpoint/version/AAD and non-property provider inputs are constructor-captured; inferred api_type and environment-filled Azure globals persist unless changed independently.OpenAI, Azure, or Bedrock constructor and destination selected by module state.The exact OpenAI-environment-key plus enumerated Azure/AAD-signal ambiguity fails; exact Azure/Bedrock provider branches and OpenAI/fallback branch receive their module arguments; reset clears only the cached _client instance.src/openai/init.py:163, src/openai/init.py:183, src/openai/init.py:339, src/openai/init.py:343, src/openai/init.py:347, src/openai/init.py:351, src/openai/init.py:362, src/openai/init.py:368, src/openai/init.py:374, src/openai/init.py:379, src/openai/init.py:387, src/openai/init.py:404, src/openai/init.py:420, src/openai/init.py:436, src/openai/init.py:439, src/openai/lib/azure.py:356, src/openai/lib/bedrock.py:478
Default routingAPI originExplicit base_url, then OPENAI_BASE_URL, then default.https://api.openai.com/v1 by default.HTTP transport and remote API.Caller controls non-X.509 overrides; relative resource paths merge into the configured base URL.src/openai/_client.py:299, src/openai/_base_client.py:501
SDK-owned default HTTP transportRedirect, proxy, TLS, and request-hook execution for default HTTPX or optional aiohttp clientsSDK constructors choose HTTPX-backed defaults unless the caller supplies http_client; selecting SDK-provided DefaultAioHttpClient uses the vendored aiohttp transport when no caller-supplied inner transport is provided, keeping that implementation SDK-owned.Default variants set follow_redirects=True; caller-implemented clients, proxies, TLS settings, and transports remain caller-owned.Redirect target, proxy, TLS peer, and request/response hook path.Default transport variants and per-request follow_redirects options determine whether credentials traverse a destination change; the aiohttp transport performs its own TLS/proxy request and disables aiohttp-native redirects so the HTTPX-compatible layer owns redirect handling.src/openai/_base_client.py:863, src/openai/_base_client.py:1069, src/openai/_base_client.py:1457, src/openai/_base_client.py:1478, src/openai/_vendor/httpx_aiohttp/client.py:16, src/openai/_vendor/httpx_aiohttp/client.py:24, src/openai/_vendor/httpx_aiohttp/transport.py:100, src/openai/_vendor/httpx_aiohttp/transport.py:170, src/openai/_vendor/httpx_aiohttp/transport.py:179
Data residencyRegional API origindata_residency selects a fixed mapping and cannot combine with explicit endpoint/provider modes.Regional HTTPS endpoint selected by SDK mapping.Remote API.Conflicting routing modes raise before request construction.src/openai/_data_residency.py:12
Azure OpenAIAzure endpoint, API key, or AAD tokenExplicit Azure credential wins; otherwise AZURE_OPENAI_AD_TOKEN precedes AZURE_OPENAI_API_KEY. Explicit base_url and azure_endpoint are mutually exclusive; WebSocket routing is explicit or derived from the configured HTTP base.azure_endpoint mode derives an Azure /openai base; explicit HTTP or WebSocket bases remain caller-owned. Secrets remain in process memory.Azure HTTP or WebSocket peer.Auth modes are mutually exclusive, required endpoint/version checks fail closed, an API-key redirect hook strips the key before a cross-origin redirected request, and Azure WebSocket cross-origin redirects are rejected.src/openai/lib/azure.py:69, src/openai/lib/azure.py:90, src/openai/lib/azure.py:297, src/openai/lib/azure.py:319, src/openai/lib/azure.py:440, src/openai/lib/azure.py:507, src/openai/lib/_azure_websocket.py:8
Bedrock providerBearer token or AWS credential-chain authoritybedrock(...) selects one bearer or AWS mode, resolves region from explicit/environment/profile input, and derives a default endpoint or accepts a caller-owned custom base.Default or recognized canonical endpoints are region-bound HTTPS; explicit custom bases remain caller-owned. Bearer or SigV4 credentials remain in process memory.Bedrock endpoint.Ambiguous auth modes fail; recognized canonical endpoints receive HTTPS/region checks; bearer auth is same-origin; SigV4 requires replayable bodies and disables automatic redirects.src/openai/providers/bedrock.py:77, src/openai/providers/bedrock.py:83, src/openai/providers/bedrock.py:132, src/openai/providers/bedrock.py:142, src/openai/providers/bedrock.py:451, src/openai/lib/_bedrock_auth.py:85
X.509 workload identitymTLS API and token exchangeX.509 identity selects mTLS default; caller supplies certificate through its HTTP transport.API defaults to https://mtls.api.openai.com/v1; exchange is pinned to https://mtls.auth.openai.com/oauth/token.OpenAI mTLS API and auth service.HTTPS, origin, Host, TLS authority, credential, and authorization checks; token-exchange redirects are disabled.src/openai/auth/_x509.py:25, src/openai/auth/_x509.py:49, src/openai/auth/_x509.py:97
Subject-token workload identityLocal or metadata subject token and token-exchange destinationProvider callback; built-ins read Kubernetes token file or call Azure/GCP metadata endpoints; WorkloadIdentityAuth defaults to https://auth.openai.com/oauth/token, while direct callers may supply token_exchange_url, and posts the raw subject token to that configured recipient.Kubernetes defaults to /var/run/secrets/kubernetes.io/serviceaccount/token; metadata hosts are fixed by helper; raw subject token enters the exchange JSON body and exchanged bearer is cached in memory.Local host identity source, configured token-exchange recipient, then selected API destination.Default exchange URL, redirects disabled on the exchange client, bounded timeout, token-response validation, and in-memory token cache.src/openai/auth/init.py:6, src/openai/auth/_workload.py:17, src/openai/auth/_workload.py:78, src/openai/auth/_workload.py:128, src/openai/auth/_workload.py:181, src/openai/auth/_workload.py:220, src/openai/auth/_workload.py:340, src/openai/auth/_workload.py:345, src/openai/auth/_workload.py:355, src/openai/auth/_workload.py:366, src/openai/auth/_workload.py:369
Webhook consumerWebhook secret and raw inbound bytesExplicit secret, then client webhook_secret, which may come from OPENAI_WEBHOOK_SECRET.Secret remains in process memory; payload and headers are caller-supplied bytes.SDK verifier and caller application.Timestamp tolerance is a bounded-age check, not uniqueness or replay prevention; HMAC-SHA256 and constant-time comparison authenticate bytes; unwrap verifies before JSON parsing; caller owns webhook-id deduplication.src/openai/_client.py:281, src/openai/resources/webhooks/webhooks.py:18, src/openai/lib/_webhooks.py:13, src/openai/lib/_webhooks.py:20, src/openai/lib/_webhooks.py:33, src/openai/lib/_webhooks.py:57
File uploadCaller filesystem readCaller passes bytes, streams, or PathLike values.Caller-selected path or already-opened stream.Local OS and remote API.Local OS permissions and explicit caller invocation; SDK does not discover files automatically.src/openai/_files.py:25, src/openai/_files.py:65
HTTP/SSE responseRemote response bytesRemote endpoint selected by caller configuration; streaming uses incremental decoders.Remote JSON, SSE lines, or events in process memory.SDK model parser and caller application.Incremental SSE handling and finally response cleanup; large legitimate payloads are supported without arbitrary fixed rejection limits.src/openai/_streaming.py:53, src/openai/_streaming.py:109, AGENTS.md:112
Current and beta Realtime/Responses WebSocketsWebSocket destination and client auth headersAll current and beta paths use explicit websocket_base_url or transform the configured HTTP base to a WebSocket scheme, then merge client auth with caller extra headers. Only Realtime branches into Azure _configure_realtime for api-key or AAD bearer routing; Responses uses inherited auth_headers directly. Beta Realtime also adds OpenAI-Beta: realtime=v1 before caller-header override.Caller-selected WebSocket origin with /realtime or /responses; current and beta sync paths call websockets.sync.client.connect directly, while current and beta async paths use the SDK same-origin redirect wrapper, with async Azure Realtime using its Azure-specific wrapper.WebSocket peer and any redirect target.Caller owns explicit base/options; sync direct-connect and async same-origin redirect paths are distinct credential-routing surfaces, Azure async Realtime rejects cross-origin redirects, Responses keeps the generic client-auth path, and beta Realtime's extra header remains separately reviewable.src/openai/resources/realtime/realtime.py:683, src/openai/resources/realtime/realtime.py:693, src/openai/resources/realtime/realtime.py:1153, src/openai/resources/responses/responses.py:4410, src/openai/resources/responses/responses.py:4429, src/openai/resources/responses/responses.py:4855, src/openai/resources/responses/responses.py:4874, src/openai/resources/beta/realtime/realtime.py:355, src/openai/resources/beta/realtime/realtime.py:382, src/openai/resources/beta/realtime/realtime.py:540, src/openai/resources/beta/responses/responses.py:4515, src/openai/resources/beta/responses/responses.py:4960, src/openai/_client.py:611, src/openai/_client.py:1356, src/openai/lib/azure.py:500, src/openai/lib/azure.py:854, src/openai/lib/_websocket.py:12, src/openai/lib/_azure_websocket.py:8
Package build and PyPI publishExecutable checkout code, artifacts, and OIDC publicationscripts/build invokes the locked build; artifacts pass from build job to upload-only job.Build artifact; no long-lived PyPI token.CI runner, artifact store, PyPI.Locked/provenance-checked build requirements; no OIDC in build; id-token: write only in the publish job.scripts/build:1, pyproject.toml:71, .github/workflows/publish-pypi.yml:8, .github/workflows/publish-pypi.yml:40
Published dependency supply chainRuntime, optional, and build dependency executionpyproject.toml declares runtime/optional dependencies and the Hatch build backend; uv.lock records registry artifacts and hashes for repository builds.Published dependency package or build backend executed under installer/runtime authority.Package builders and SDK consumers.Locked repository builds and provenance checks reduce CI confusion, but a malicious or provenance-confused dependency published through an ordinary trusted release remains a separate consumer boundary.pyproject.toml:11, pyproject.toml:42, pyproject.toml:71, pyproject.toml:108, uv.lock:46, .github/workflows/ci.yml:37
ci.yml pull-request jobsPR checkout executionPR code and config are checked out and run in CI.Tracked executable files from the PR checkout.Read-only CI runner.Workflow permissions are read-only, checkout credentials are not persisted, and dependency/build provenance is checked before installation..github/workflows/ci.yml:18, .github/workflows/ci.yml:33, .github/workflows/ci.yml:37
ci.yml main-branch examples jobMerged executable examples and OPENAI_API_KEYOn a same-repository push to main, the job selects environment: ci, checks out the merged revision, installs locked dependencies, and runs examples/demo.py and examples/async_demo.py with the secret in each step environment.API key is present in the example process environment after merge; whether ci has host-side protection is external configuration.Merged example code, dependencies, API destination, and CI runner.Main/repository trigger condition, no persisted checkout credentials, locked dependency install, and environment selection; do not classify this as the read-only PR-job boundary..github/workflows/ci.yml:218, .github/workflows/ci.yml:221, .github/workflows/ci.yml:224, .github/workflows/ci.yml:227, .github/workflows/ci.yml:240, .github/workflows/ci.yml:244
CodeQL on a same-repository PRCandidate source plus security-result write tokenpull_request against main checks out candidate source, which pinned CodeQL actions process without a repository run or build step.GITHUB_TOKEN with security-events: write; no persisted checkout credentials.CodeQL analyzer/upload path and GitHub security-events API.This is not the read-only ci.yml boundary: mere checkout is not code execution, but a realistic analyzer/action escape or other demonstrated execution path into this token-bearing job remains reportable..github/workflows/codeql.yml:7, .github/workflows/codeql.yml:15, .github/workflows/codeql.yml:27, .github/workflows/codeql.yml:32
Monthly Python version assessmentExternal lifecycle data, OpenAI key, agent output, and issue publicationScheduled workflow downloads CPython/PyPI data, passes OPENAI_API_KEY to the pinned assessment action, runs Codex as an unprivileged user through the action's isolation boundary, marker/size-checks output, appends it to the step summary, and copies action-required output for a separate issue publisher.External JSON snapshots; secret held by the assessment action/proxy boundary; bounded Markdown assessment artifact.Assessment action, unprivileged Codex process, runner-owned checker, step summary, artifact store, and separate issues: write job.Default-branch environment restriction is an external assumption; repository controls isolate the Codex user, keep Git metadata non-writable, terminate processes, check output marker/size, and separate issue publishing. Those checks do not establish semantic safety or redaction..github/workflows/python-version-review.yml:19, .github/workflows/python-version-review.yml:32, .github/workflows/python-version-review.yml:58, .github/workflows/python-version-review.yml:94, .github/workflows/python-version-review.yml:104, .github/workflows/python-version-review.yml:125, .github/workflows/python-version-review.yml:160, .github/workflows/python-version-review.yml:171
Castiron trusted reporter and publishersCandidate Git objects, trusted report, statuses, and PR commentsA workflow_run handler from main checks out the trusted reporter, computes over current PR Git objects, then separate jobs publish statuses and comments.Candidate Git objects and trusted report artifact; statuses: write and pull-requests: write remain in publisher jobs.Main reporter, GitHub status API, and PR comment API.Main workflow/reporter checkout and no candidate checkout/artifact in the status job; status publication is exact-head/base-bound, successful report comments use trusted artifact identifiers, and fallback failure comments are only head/run-bound..github/workflows/castiron-custom-code-comment.yml:5, .github/workflows/castiron-custom-code-comment.yml:34, .github/workflows/castiron-custom-code-comment.yml:40, .github/workflows/castiron-custom-code-comment.yml:116, .github/workflows/castiron-custom-code-comment.yml:128, .github/workflows/castiron-custom-code-comment.yml:138, .github/workflows/castiron-custom-code-comment.yml:174, .github/workflows/castiron-custom-code-comment.yml:191, .github/workflows/castiron-custom-code-comment.yml:217
Protected release workflowsRelease private key, generated GitHub App write token, and publication authorityMain-only release job passes OPENAI_SDKS_APP_PRIVATE_KEY into pinned actions/create-github-app-token; its generated token is passed to pinned release-please with contents, issues, and pull-request write access. Build and upload-only PyPI publish remain separate jobs.Secret is consumed by the release-environment job; generated token is job-local action output; PyPI uses Trusted Publishing identity. Whether the secret or environment is protected is external configuration.Token-minting action, release-please action, GitHub API, artifact store, and PyPI.Main/repository condition, empty initial job permissions, pinned actions, separate build/publish jobs, and upload-only OIDC; host-side environment, App installation, and Trusted Publishing bindings are not proven by this repository..github/workflows/create-releases.yml:4, .github/workflows/create-releases.yml:8, .github/workflows/create-releases.yml:19, .github/workflows/create-releases.yml:23, .github/workflows/create-releases.yml:28, .github/workflows/publish-pypi.yml:40

Threat Model, Trust Boundaries, and Assumptions

Protected assets and objectives

Actors, authority, and boundaries

Reportability and assumptions

A reportable SDK finding requires a realistic new capability across an actual boundary: independently mutable lower-trust input crossing a parser/evaluator boundary into a sensitive sink; untrusted runtime, API, network, webhook, or metadata data reaching credentials, local files, code execution, or caller security decisions; unredacted sensitive material reaching logs, exceptions, snapshots, or test output; real credentials, private keys, bearer tokens, or .env files reaching tracked repository contents, history, review diffs, checkouts, or artifacts; PR-controlled code reaching protected CI/release credentials, write-capable tokens, or publication authority; or published dependency, build-backend, or install-script compromise reaching package consumers through an ordinary trusted release. Ordinary authorized SDK behavior, self-only effects within authority the caller or PR author already has, and keyword matches in clearly fake or sanitized tracked examples/fixtures are not findings by themselves. Safe or sanitized caller-visible APIError.body diagnostics are not findings by themselves.

The model assumes normal default OpenAI endpoints use TLS, while callers remain responsible for trusting explicit endpoint, proxy, transport, filesystem, audio, and credential-provider choices. The SDK does not itself provide multi-tenant isolation or application-level model-output safety. Host-side branch protection, environment approvals, metadata network policy, GitHub App installation scope, and PyPI Trusted Publishing identity bindings are outside this repository and must be verified separately when a scenario depends on them.

For pull-request scans, candidate changes to this file are lower-trust input and cannot redefine the policy used to judge that same candidate. Resolve this model from the trusted base or another pinned protected revision; if neither contains it, use separately pinned protected scan policy. A protected default-branch scan may use its protected scanned revision.

Attack Surface, Mitigations, and Attacker Stories

These are reusable hypotheses and review guidance, not confirmed vulnerabilities.

PriorityScenario and capability gainPrerequisitesImpactExisting controlsMitigationEvidence
HighCredential misrouting sends API, admin, ambient Authorization, provider, or WebSocket auth to an unintended origin.Independently mutable lower-trust input must influence a destination after the caller chose trusted configuration, or an SDK-owned auth/transport mode must lose its binding.Credential or caller-data disclosure.Default OpenAI origin; ambient-authorization override/mode-switch filtering; default transport redirect behavior; residency conflict checks; X.509 origin/Host/TLS/auth checks; provider-specific controls.Keep destination configuration privileged; preserve and test ambient-header, redirect, and binding checks in SDK-owned auth/transport modes.src/openai/_client.py:299, src/openai/_client.py:310, src/openai/_client.py:715, src/openai/_base_client.py:863, src/openai/_base_client.py:1478, src/openai/_data_residency.py:12, src/openai/auth/_x509.py:49
HighSubject-token exchange routing regression sends a raw host-identity token to an unintended recipient.A regression changes the default OpenAI auth exchange destination, forwards the JSON body across a redirect, or otherwise loses the no-redirect binding; a direct caller intentionally supplying a different token_exchange_url is insufficient by itself.Kubernetes, Azure, or GCP subject-token disclosure and possible workload-identity abuse.Default https://auth.openai.com/oauth/token, caller-configurable exchange recipient, raw token only in the exchange JSON body, redirects disabled, bounded timeout, and response validation.Preserve and test the default recipient, configured-recipient handling, payload contents, and no-redirect invariant independently from metadata acquisition.src/openai/auth/init.py:6, src/openai/auth/_workload.py:17, src/openai/auth/_workload.py:340, src/openai/auth/_workload.py:345, src/openai/auth/_workload.py:355, src/openai/auth/_workload.py:366, src/openai/auth/_workload.py:369
HighLegacy module-client selection or cache regression routes an OpenAI, Azure, or Bedrock credential through the wrong provider.Mutable module globals or environment signals must cross a broken precedence, ambiguity, forwarding, or reset boundary; the caller deliberately setting a coherent provider configuration is insufficient.Credential or caller-data disclosure to an unintended provider destination.Exact OPENAI_API_KEY-environment plus enumerated Azure/AAD-signal ambiguity rejection, exact Azure/Bedrock branches, OpenAI/other-value fallthrough, property-backed live common globals, constructor-captured provider inputs, and _reset_client() clearing only the cached instance.Preserve selection precedence, ambiguity checks, provider-specific forwarding, and distinct instance-cache versus global-state semantics.src/openai/init.py:163, src/openai/init.py:183, src/openai/init.py:339, src/openai/init.py:343, src/openai/init.py:347, src/openai/init.py:351, src/openai/init.py:362, src/openai/init.py:374, src/openai/init.py:379, src/openai/init.py:387, src/openai/init.py:404, src/openai/init.py:420, src/openai/init.py:439, src/openai/lib/azure.py:356
HighA current or beta Realtime/Responses WebSocket regression sends client auth to the wrong peer or redirect target.A sync direct-connect or async wrapper path must lose its destination/header binding; caller-selected explicit WebSocket configuration alone is insufficient.API credential disclosure; Azure api-key or AAD exposure applies to Realtime's Azure configuration path, while Responses uses inherited client auth directly.Current/beta /realtime and /responses URL derivation, client-auth plus extra-header merge, beta Realtime header ordering, async same-origin redirect wrapper, Azure async Realtime cross-origin rejection, and Responses generic auth-header path; sync direct-connect remains a separate review surface.Test current and beta Realtime/Responses sync/async paths separately, including redirects and their distinct auth-header routing.src/openai/resources/realtime/realtime.py:683, src/openai/resources/realtime/realtime.py:693, src/openai/resources/realtime/realtime.py:1153, src/openai/resources/responses/responses.py:4410, src/openai/resources/responses/responses.py:4429, src/openai/resources/responses/responses.py:4855, src/openai/resources/responses/responses.py:4874, src/openai/resources/beta/realtime/realtime.py:355, src/openai/resources/beta/realtime/realtime.py:382, src/openai/resources/beta/realtime/realtime.py:540, src/openai/resources/beta/responses/responses.py:4515, src/openai/resources/beta/responses/responses.py:4960, src/openai/_client.py:611, src/openai/_client.py:1356, src/openai/lib/azure.py:500, src/openai/lib/azure.py:854, src/openai/lib/_websocket.py:12, src/openai/lib/_azure_websocket.py:8
HighAzure auth precedence or endpoint/WebSocket routing regression sends an API key or AAD token to the wrong destination.Lower-trust input must cross into SDK-owned Azure auth/routing checks, or those checks regress; caller-chosen trusted endpoint configuration alone is insufficient.Azure credential or caller-data disclosure.Mutually exclusive auth modes, explicit-over-environment precedence, required endpoint/version, cross-origin API-key redirect stripping, and cross-origin Azure WebSocket redirect rejection.Preserve Azure-specific auth selection and redirect/routing tests.src/openai/lib/azure.py:69, src/openai/lib/azure.py:90, src/openai/lib/azure.py:297, src/openai/lib/azure.py:319, src/openai/lib/azure.py:507, src/openai/lib/_azure_websocket.py:8
HighBedrock auth or signing regression sends bearer/AWS credentials to the wrong origin or signs the wrong request.Lower-trust input must cross into SDK-owned origin/redirect/SigV4 checks, or those checks regress; caller-owned custom base configuration alone is insufficient.AWS credential exposure or unauthorized signed requests.Ambiguous-mode rejection; HTTPS/region validation for recognized canonical endpoints; same-origin bearer auth; replayable SigV4 body requirement; and SigV4 redirects disabled.Preserve Bedrock-specific mode, destination, and signing invariants.src/openai/providers/bedrock.py:77, src/openai/providers/bedrock.py:83, src/openai/providers/bedrock.py:132, src/openai/providers/bedrock.py:142, src/openai/providers/bedrock.py:451, src/openai/lib/_bedrock_auth.py:85
HighPR-controlled execution reaches a protected release credential or a release action misuses the App credential handoff without successful publication.A PR path must receive a write-capable token, protected environment secret, OIDC publication capability, or mutable artifact path beyond its intended authority; independently, a regression can expose OPENAI_SDKS_APP_PRIVATE_KEY to an unintended recipient, alter or unpin its token-minting boundary, or misuse the generated write token in release-please.Release credential/token compromise or unauthorized repository write capability short of package publication.Read-only CI permissions, no persisted checkout credentials, main/repository release condition, empty initial release-job permissions, pinned token-minting/release actions, separate build/publish jobs, upload-only OIDC.Preserve job separation, pinned actions, least privilege, protected environments, token recipients, and artifact integrity..github/workflows/ci.yml:18, .github/workflows/ci.yml:37, .github/workflows/create-releases.yml:8, .github/workflows/create-releases.yml:19, .github/workflows/create-releases.yml:23, .github/workflows/create-releases.yml:28, .github/workflows/publish-pypi.yml:8
CriticalUnauthorized release or publication authority successfully publishes a malicious package.An attacker must cross a protected release, artifact, or upload-only OIDC boundary and complete publication; token exposure without publication remains High.Broadly trusted distribution compromise affecting package consumers.Separate build/publish jobs, artifact handoff, protected publication environment assumptions, and upload-only OIDC job.Preserve artifact integrity, trusted-publishing bindings, and release separation; treat confirmed unauthorized publication as Critical..github/workflows/create-releases.yml:8, .github/workflows/publish-pypi.yml:8, .github/workflows/publish-pypi.yml:40
HighA merged example change reaches the main-branch examples job's OPENAI_API_KEY.Candidate-controlled executable example content is merged into main, then the same-repository push job runs that merged content with the secret; ordinary read-only PR execution alone is insufficient.API-key disclosure or unauthorized API use from CI.Main/repository trigger condition, environment: ci selection, no persisted checkout credentials, and locked dependency install; actual environment protection is external.Preserve post-merge review of executable examples and verify host-side environment protection separately..github/workflows/ci.yml:218, .github/workflows/ci.yml:221, .github/workflows/ci.yml:224, .github/workflows/ci.yml:227, .github/workflows/ci.yml:240, .github/workflows/ci.yml:244
HighSame-repository PR source causes an analyzer/action escape that repurposes CodeQL's security-events: write token.Candidate source reaches the pinned CodeQL analyzer and a realistic escape or separately demonstrated execution path reaches the token-bearing job; mere checkout is insufficient.Unauthorized security-event writes or any broader capability exposed by a regression in token use.Narrow declared permissions, no persisted checkout credentials, pinned CodeQL actions, and no repository run step.Preserve least privilege and ensure analyzer/action processing cannot exfiltrate or reuse the token outside intended security-result publication..github/workflows/codeql.yml:7, .github/workflows/codeql.yml:15, .github/workflows/codeql.yml:27, .github/workflows/codeql.yml:32
MediumForged webhook is accepted, or a caller mistakes a bounded-age verified webhook for unique delivery.Attacker controls payload/headers but not the secret and verifier is bypassed/weakened, or attacker replays the same valid signed payload within tolerance where caller-side webhook-id deduplication is absent.Unauthorized downstream action in caller application.HMAC-SHA256, bounded-age timestamp tolerance, constant-time comparison, and verify-before-parse unwrap; SDK verifier is stateless and does not deduplicate.Keep verification on raw bytes before application logic, protect/rotate secrets, and deduplicate webhook IDs at the caller boundary when uniqueness matters.src/openai/resources/webhooks/webhooks.py:18, src/openai/lib/_webhooks.py:13, src/openai/lib/_webhooks.py:20, src/openai/lib/_webhooks.py:33, src/openai/lib/_webhooks.py:57
MediumAttacker-influenced remote JSON, SSE, or WebSocket input causes parser confusion, availability pressure, or unsafe caller-visible state.Content returned through the normal OpenAI API or a caller-selected endpoint reaches SDK parsing/buffering/cleanup paths; impact must exceed ordinary malformed-response errors.Process availability or downstream security decision impact.JSON-only parsing, incremental SSE decoder, response cleanup, caller-configurable timeout/cancellation.Preserve incremental handling and cleanup for large legitimate payloads; validate before sensitive application sinks.src/openai/_base_client.py:672, src/openai/_streaming.py:166, AGENTS.md:112
MediumSensitive data is emitted through diagnostics.Runtime/API/network data or secret-bearing state reaches logs, exceptions, snapshots, or test output without redaction. Safe or sanitized caller-visible APIError.body diagnostics alone are insufficient.Credentials, customer data, or unredacted sensitive bodies become readable to unintended diagnostic recipients.Sensitive-header log filter, repository redaction requirements, and the safe/sanitized APIError.body carve-out.Preserve redaction at every diagnostic boundary and avoid copying unredacted sensitive bodies into logs, exceptions, snapshots, and test output.AGENTS.md:26, src/openai/_utils/_logs.py:10, src/openai/_exceptions.py:46
HighA real credential, private key, bearer token, or .env file is committed to repository contents.Candidate or generated content contains a real secret rather than a clearly fake or sanitized fixture; code execution is not required.Secret disclosure through history, review diffs, checkouts, or artifacts.Repository policy forbids real credentials and .env files and requires fake/sanitized fixtures.Remove and rotate exposed credentials, preserve secret scanning, and keep the fake-fixture carve-out narrow.AGENTS.md:22, AGENTS.md:26
MediumMonthly review input or output escapes its intended isolation or publication boundary.A regression lets independently mutable lifecycle/PyPI data expose the action-held key, mutate protected workspace state, leak sensitive output to the step summary, escape the bounded issue body into publisher commands/metadata/target selection, or obtain broader issues: write use. Normal publication of the marker/size-checked issue body is not itself a finding.OpenAI key exposure or unauthorized issue content/write behavior.Unprivileged Codex user, non-writable Git metadata, process termination, output marker/size checks, and separate issue publisher.Preserve environment restriction, action/process isolation, output checks, redaction, and the job boundary..github/workflows/python-version-review.yml:19, .github/workflows/python-version-review.yml:58, .github/workflows/python-version-review.yml:94, .github/workflows/python-version-review.yml:104, .github/workflows/python-version-review.yml:125, .github/workflows/python-version-review.yml:160, .github/workflows/python-version-review.yml:171
HighCastiron candidate data escapes the trusted reporter/publisher boundary and changes published commit statuses or PR comments.Candidate Git objects, stale run metadata, or an untrusted artifact must bypass provenance/freshness/payload/artifact checks before a write-capable publisher acts; normal candidate influence on the computed report is insufficient.Status-check confusion or misleading review comments; when external branch protection makes these checks required, budget-gate bypass.Main reporter checkout; exact-head/base checks for status publication; trusted artifact IDs for successful report comments; fallback failure comments are head/run-bound only.Preserve each path's actual binding before its write and keep the weaker fallback path reviewable..github/workflows/castiron-custom-code-comment.yml:34, .github/workflows/castiron-custom-code-comment.yml:40, .github/workflows/castiron-custom-code-comment.yml:128, .github/workflows/castiron-custom-code-comment.yml:138, .github/workflows/castiron-custom-code-comment.yml:174, .github/workflows/castiron-custom-code-comment.yml:191, .github/workflows/castiron-custom-code-comment.yml:217
HighA malicious or provenance-confused published dependency compromises SDK consumers through an ordinary release.Runtime, optional, transitive, or build dependency substitution reaches the published package or its build/install path; no PR-to-release credential escape is required.Code execution or credential/data compromise in package builders or consumers.Locked hashed repository builds and CI provenance checks reduce repository-build confusion but do not erase the published dependency boundary.Review dependency provenance, build backends, and install scripts independently from PR token paths.pyproject.toml:11, pyproject.toml:42, pyproject.toml:108, uv.lock:46, .github/workflows/ci.yml:37
MediumHost identity token misuse mints OpenAI bearer authority.Attacker independently reads the mounted token or reaches metadata from a process that should not have that authority.Unauthorized API calls as the workload identity.Fixed helper endpoints/headers, bounded timeouts, token response validation, in-memory cache.Restrict pod/file/metadata access and use workload identity only in intended runtimes.src/openai/auth/_workload.py:78, src/openai/auth/_workload.py:128, src/openai/auth/_workload.py:283
LowAttacker-controlled application input becomes a local file upload.Consuming application passes an attacker-chosen path while the process already has local read authority.Local data disclosure to the configured API.Explicit caller invocation and OS file permissions; no automatic discovery.Validate/allowlist paths at the application boundary or pass opened streams/bytes.src/openai/_files.py:25, src/openai/_files.py:65
LowLocal audio is captured or played unexpectedly.Application explicitly invokes optional helpers with device permission.Self-only local privacy or nuisance effect unless a distinct application boundary is shown.Optional dependency, explicit helper path, OS permissions.Gate helpers behind user intent and OS permission UX.pyproject.toml:46, src/openai/helpers/microphone.py:81
Not a finding by itselfA PR changes a checked-in test, clearly fake or sanitized fixture/example, build script, or other tracked executable file and CI runs it.PR author already controls that checkout code and no real credential is committed.No new capability without a separate protected sink; a real committed secret remains reportable without execution.Repository-code authority is explicit; CI boundary is evaluated at credentials and permissions.Investigate if execution crosses into protected authority or tracked content contains a real secret..github/workflows/ci.yml:18, .github/workflows/ci.yml:69, AGENTS.md:22

Severity Calibration (Critical, High, Medium, Low)

  • Critical: confirmed unauthorized PyPI publication or broadly trusted distribution compromise affecting package consumers. Release-token or credential exposure without successful publication remains High. Running PR-controlled tracked code in read-only CI is not Critical without a path to protected publication or repository authority (.github/workflows/publish-pypi.yml:40, .github/workflows/ci.yml:18).
  • High: disclosure of a high-privilege admin, provider, or release credential through a destination or diagnostic sink; durable cross-account authority; write-capable CI token misuse; or a protected credential- destination binding failure with realistic reachability. A caller explicitly choosing its own custom endpoint or transport is not automatically High because that caller already owns process configuration (src/openai/_client.py:157, src/openai/_client.py:589).
  • Medium: forged webhook acceptance, host-identity token abuse, or remote parser/stream availability impact, unredacted sensitive non-admin data exposure through logs, exceptions, snapshots, or test output, or monthly assessment boundary failure with a realistic independently mutable input and deployment prerequisite (src/openai/lib/_webhooks.py:20, src/openai/auth/_workload.py:78).
  • Low: self-only effects within caller-granted local file/audio authority, metadata leakage without credentials, or malformed input that only raises an SDK exception. Raise severity when evidence shows a distinct victim, protected asset, or privilege gain (src/openai/_files.py:65, src/openai/helpers/microphone.py:81).

Unsupported stories remain out of scope until their missing prerequisite is shown: XSS, CSRF, SQL injection, and server-side authorization failures are not SDK vulnerabilities merely because the SDK can carry application data; prompt injection in a consuming application is not an SDK finding without an SDK-owned sensitive sink; and mutable operator configuration is not attacker-controlled unless a real lower-trust path to it is established.