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.
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).
| Component | Role | Evidence |
|---|---|---|
OpenAI / AsyncOpenAI clients |
Resolve credentials, endpoint configuration, headers, retries, and transports. | src/openai/_client.py:157, src/openai/_client.py:247 |
| Legacy module-level client | Selects, 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 client | Serializes 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 transports | Provide 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 WebSockets | Incrementally 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 |
| Webhooks | Verify raw inbound webhook bytes before parsing a typed event. | src/openai/resources/webhooks/webhooks.py:18, src/openai/lib/_webhooks.py:13 |
| Workload identity | Obtain 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 publication | Test 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 |
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 workflow | Resource or capability | Configuration and precedence | Safe effective value or location | Readers, writers, or recipients | Enforcing control | Evidence or unknowns |
|---|---|---|---|---|---|---|
| Default OpenAI client | API, admin, or ambient Authorization credential |
Credential 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 client | Global/environment-selected OpenAI, Azure, or Bedrock credential and destination | Module 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 routing | API origin | Explicit 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 transport | Redirect, proxy, TLS, and request-hook execution for default HTTPX or optional aiohttp clients | SDK 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 residency | Regional API origin | data_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 OpenAI | Azure endpoint, API key, or AAD token | Explicit 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 provider | Bearer token or AWS credential-chain authority | bedrock(...) 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 identity | mTLS API and token exchange | X.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 identity | Local or metadata subject token and token-exchange destination | Provider 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 consumer | Webhook secret and raw inbound bytes | Explicit 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 upload | Caller filesystem read | Caller 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 response | Remote response bytes | Remote 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 WebSockets | WebSocket destination and client auth headers | All 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 publish | Executable checkout code, artifacts, and OIDC publication | scripts/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 chain | Runtime, optional, and build dependency execution | pyproject.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 jobs |
PR checkout execution | PR 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 job |
Merged executable examples and OPENAI_API_KEY |
On 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 PR | Candidate source plus security-result write token | pull_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 assessment | External lifecycle data, OpenAI key, agent output, and issue publication | Scheduled 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 publishers | Candidate Git objects, trusted report, statuses, and PR comments | A 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 workflows | Release private key, generated GitHub App write token, and publication authority | Main-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 |
- Keep API keys, admin keys, ambient
Authorizationheaders, webhook secrets, subject tokens, exchanged bearer tokens, provider credentials, and release credentials from reaching unintended recipients (src/openai/_client.py:247, src/openai/_client.py:310, src/openai/auth/_workload.py:220). - Preserve correct destination and credential binding, including distinct ordinary/admin auth selection, residency routing, X.509 authority checks, and provider-specific signing (src/openai/_client.py:589, src/openai/_data_residency.py:12, src/openai/auth/_x509.py:49).
- Verify webhook authenticity and bounded timestamp age before a payload is treated as an OpenAI event; caller-side webhook-id deduplication remains a separate replay boundary (src/openai/resources/webhooks/webhooks.py:18, src/openai/lib/_webhooks.py:20, src/openai/lib/_webhooks.py:33).
- Preserve confidentiality and integrity of caller prompts, uploads, audio, responses, and streaming events as they cross caller-selected transports.
- Keep credentials, authentication headers, customer data, and unredacted
sensitive request or response bodies out of logs, snapshots, test output,
and exceptions; those are independently readable
recipients, not harmless diagnostics. Safe or sanitized caller-visible
APIError.bodydiagnostics may remain (AGENTS.md:26, src/openai/_utils/_logs.py:10, src/openai/_exceptions.py:46). - Keep real API keys, bearer tokens, private keys,
.envfiles, and other credentials out of tracked repository contents, history, review diffs, checkouts, and artifacts. Clearly fake or sanitized examples and fixtures may remain (AGENTS.md:22, AGENTS.md:26). - Preserve package and release integrity: untrusted PR execution must not reach protected release credentials or publication authority (.github/workflows/ci.yml:18, .github/workflows/publish-pypi.yml:40).
- Preserve consumer supply-chain integrity when runtime, optional, transitive, or build dependencies and their install/build scripts enter an ordinary trusted release; this boundary does not require a PR-to-release escape (pyproject.toml:11, pyproject.toml:42, pyproject.toml:108, uv.lock:46).
- Preserve large-payload compatibility through incremental processing, timely cleanup, and caller cancellation rather than arbitrary SDK body/event/line limits (AGENTS.md:112, src/openai/_streaming.py:109).
- The caller application owns its process, explicit client arguments,
environment, filesystem authority, credential callbacks, custom transports,
headers, and endpoint configuration. A caller that already controls
base_url,websocket_base_url, a file path, or an HTTP client does not gain a new SDK privilege merely because the SDK uses that value (src/openai/_client.py:157, src/openai/_client.py:299). - The legacy module-level client is a separate SDK-owned selector over mutable
module globals and environment state. Module
api_typestarts fromOPENAI_API_TYPE;_load_client()fills unset Azure endpoint/version globals from environment. Whenapi_typeis unset, it rejectsOPENAI_API_KEYin the environment together with moduleazure_endpoint/AZURE_OPENAI_ENDPOINT,AZURE_OPENAI_API_KEY,AZURE_OPENAI_AD_TOKEN, moduleazure_ad_token, or moduleazure_ad_token_provider; moduleapi_keyandapi_versionare not ambiguity signals. Exact"azure"and"amazon-bedrock"values use dedicated branches; withapi_typeunset it infers Azure when an enumerated Azure/AAD signal exists and otherwise defaults to OpenAI, while explicit"openai"and any other non-Noneruntime value fall through to_ModuleClient, and caches that client until_reset_client(). The reset clears only_client;_ModuleClientproperty-backed common globals such as API/admin keys remain live, while Azure endpoint/version/AAD and non-property provider inputs are captured by the constructed client; inferredapi_typeor environment-filled Azure globals persist unless changed independently. Regressions in selection, ambiguity detection, argument forwarding, or those distinct cache/global behaviors can route credentials to the wrong provider (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:436, src/openai/init.py:439). - The SDK-owned default HTTPX sync/async clients and optional
DefaultAioHttpClientare distinct from caller-supplied transports. They default to following redirects; absent a caller-supplied inner transport, the vendored aiohttp transport owns its TLS/proxy request path and disables aiohttp-native redirects so the HTTPX-compatible layer handles them. Regressions in those default paths can change whether API, admin, or ambient authorization crosses an origin; a caller-implemented client or transport, proxy, or TLS configuration remains caller-owned; selecting SDK-providedDefaultAioHttpClientdoes not move its implementation out of the SDK-owned boundary (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, src/openai/_vendor/httpx_aiohttp/transport.py:170, src/openai/_vendor/httpx_aiohttp/transport.py:179). - Authenticated current and beta Realtime/Responses WebSockets have eight SDK
connection surfaces: each current or beta sync path calls
websockets.sync.client.connectdirectly, while each current or beta async path uses the SDK same-origin redirect wrapper. All merge client auth with caller extra headers; only Realtime invokes Azure_configure_realtimeforapi-keyor AAD bearer routing, while Responses uses inheritedauth_headersdirectly. Beta Realtime addsOpenAI-Beta: realtime=v1before caller-header override. Async Azure Realtime also uses its Azure-specific redirect wrapper. Review sync and async paths independently because their redirect enforcement is not shared (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:4855, 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/lib/_websocket.py:12, src/openai/lib/_azure_websocket.py:8). - Remote HTTP, SSE, and WebSocket data is independently mutable lower-trust input when it enters parsers and remains lower-trust for sensitive-sink analysis after syntactic parsing or schema validation. Those steps establish structure, not safe semantics; continue tracing to credential use, local-file access, code execution, and caller security decisions unless a boundary-specific semantic validation or authorization step establishes the needed property (src/openai/_base_client.py:672, src/openai/_streaming.py:166).
- Webhook payloads and headers are lower-trust input when they enter signature verification. A successful HMAC and timestamp check establishes provenance and bounded age, not uniqueness or replay prevention; the stateless verifier can accept the same signed webhook again within tolerance, so caller-side webhook-id deduplication remains a separate boundary. Keep tracing payload fields unless a boundary-specific semantic validation or authorization step establishes the needed property. Application behavior after verified delivery remains caller-owned (src/openai/resources/webhooks/webhooks.py:18).
- Local token files and cloud metadata services are host-identity boundaries:
an attacker who can independently read or invoke them may gain a raw subject
token.
WorkloadIdentityAuthdefaults to the OpenAI auth exchange but direct callers can configuretoken_exchange_url; it sends the token in JSON to that configured recipient with redirects disabled. Changing the default destination or redirect behavior is a separate credential-routing boundary before the exchanged bearer reaches the API (src/openai/auth/_workload.py:17, src/openai/auth/_workload.py:78, src/openai/auth/init.py:6, src/openai/auth/_workload.py:128, src/openai/auth/_workload.py:345, src/openai/auth/_workload.py:355, src/openai/auth/_workload.py:366, src/openai/auth/_workload.py:369). - PR authors control their checkout contents and therefore the tracked source, examples, tests, fixtures, build scripts, and other executable files that CI intentionally runs. Those files execute with repository-code authority. A contributor who can change such tracked executable code does not gain a new privilege merely because a test, example, build, or lint step runs it. The real boundary is whether that PR-controlled execution can reach independently protected credentials, tokens, write permissions, release environments, or publication authority (.github/workflows/ci.yml:18, .github/workflows/ci.yml:33, .github/workflows/publish-pypi.yml:40).
- The
ci.ymlexamples job is a distinct post-merge boundary, not part of the read-only pull-request statement: on a same-repository push tomain, it checks out the merged revision and runsexamples/demo.pyandexamples/async_demo.pywithOPENAI_API_KEYin each process environment. The YAML selectsenvironment: ci, but whether that environment actually has host-side protection or approvals must be verified separately (.github/workflows/ci.yml:218, .github/workflows/ci.yml:221, .github/workflows/ci.yml:224, .github/workflows/ci.yml:240, .github/workflows/ci.yml:244). - Workflow-definition changes remain security-sensitive whenever they can alter permissions, secrets, protected environments, artifact provenance, or publication paths.
- The read-only statement above is specific to
ci.yml. CodeQL is a distinct same-repository PR boundary because pinned analyzer actions process candidate source while the job token hassecurity-events: write. The workflow does not run repository scripts merely by checking out the source; scans must investigate a realistic analyzer/action escape or another demonstrated execution path before treating token misuse as reportable (.github/workflows/codeql.yml:7, .github/workflows/codeql.yml:15, .github/workflows/codeql.yml:27, .github/workflows/codeql.yml:32). - The monthly Python-version review is a separate privileged workflow:
independently mutable CPython/PyPI data reaches an unprivileged Codex process
through the pinned assessment action's isolation boundary, while the action
receives
OPENAI_API_KEY. Marker/size-checked output is appended to the step summary, and action-required output becomes the intended issue body for a separateissues: writepublisher. Those checks do not prove semantic safety or redaction. Regressions that expose the key, weaken workspace ownership, leak sensitive output to the summary, escape the intended issue body into publisher commands/metadata/targets, obtain broader issue-write authority, or collapse job separation remain reportable boundaries (.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's
workflow_runhandler is another distinct privileged boundary: main's trusted reporter evaluates candidate Git objects, then publisher jobs holdstatuses: writeandpull-requests: write. Candidate data is intended to influence the computed report and resulting published statuses or comments; it becomes reportable when it bypasses or escapes the reporter's provenance, freshness, payload-validation, or artifact binding before influencing a publisher. Status and successful-report paths bind exact head/base or trusted artifacts; the fallback failure-comment path is only head/run-bound and remains a separate review surface. Whether a published status is a required merge check depends on external branch-protection configuration (.github/workflows/castiron-custom-code-comment.yml:5, .github/workflows/castiron-custom-code-comment.yml:40, .github/workflows/castiron-custom-code-comment.yml:116, .github/workflows/castiron-custom-code-comment.yml:138, .github/workflows/castiron-custom-code-comment.yml:174). - The main-only release job is a separate credential handoff: the
OPENAI_SDKS_APP_PRIVATE_KEYsecret referenced by its release-environment job is received by pinnedactions/create-github-app-token, and its generated contents/issues/pull- requests write token is then received by pinnedrelease-please. A regression in either action pin, input, output, permission, or recipient is reportable independently from PyPI's later upload-only OIDC boundary (.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). - Main/release workflows and PyPI publication are conditional privileged surfaces. Repository YAML shows requested permissions and job separation; external branch, environment, GitHub App, and Trusted Publishing bindings are deployment assumptions, not facts proven by the checkout.
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.
These are reusable hypotheses and review guidance, not confirmed vulnerabilities.
| Priority | Scenario and capability gain | Prerequisites | Impact | Existing controls | Mitigation | Evidence |
|---|---|---|---|---|---|---|
| High | Credential 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 |
| High | Subject-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 |
| High | Legacy 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 |
| High | A 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 |
| High | Azure 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 |
| High | Bedrock 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 |
| High | PR-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 |
| Critical | Unauthorized 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 |
| High | A 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 |
| High | Same-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 |
| Medium | Forged 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 |
| Medium | Attacker-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 |
| Medium | Sensitive 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 |
| High | A 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 |
| Medium | Monthly 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 |
| High | Castiron 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 |
| High | A 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 |
| Medium | Host 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 |
| Low | Attacker-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 |
| Low | Local 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 itself | A 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 |
- 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.