Skip to content

Security audit: pin openai, and re-review the four digest-pinned entries - #9148

Merged
danielhanchen merged 8 commits into
mainfrom
fix-security-audit-openai-pin
Aug 18, 2026
Merged

danielhanchen merged 8 commits into
mainfrom
fix-security-audit-openai-pin

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

The extras shard of the security audit has been red on main since 2026-08-17
19:14Z with four non-baselined CRITICALs, all in openai:

Harvests environment variables/secrets AND makes network calls
  openai/_client.py, openai/lib/azure.py, openai/lib/bedrock.py
Accesses cloud metadata/IMDS AND makes network calls
  openai/auth/_workload.py

It is not a code change here. The last green run was 18:23Z and every run after
18:40Z failed on the same four findings, on main and on unrelated PR branches
alike, which is upstream drift rather than anything in the tree.

openai>=2.7.2 is the only floating spec in extras.txt that carries digest-pinned
baseline entries. openai 3.2.0 published at 19:14Z and changed all four files.
Confirmed by digest: the four file_sha256 values in the baseline match openai
3.0.0 and 3.1.0 exactly and none of 3.2.0's.

The pin is deliberate and correct (#8104, #8565): the evidence for these entries
records that a network call exists but not where it goes, so client.post(..., data=api_key) appended to one of these files would leave the evidence hash
untouched. Only the file digest can reopen that, so any edit upstream makes must
red the gate until someone re-reads it.

Which is the review, done here. Every destination in the four files at 3.2.0 is
first-party or a documented cloud metadata endpoint: api.openai.com,
auth.openai.com, 169.254.169.254 (Azure IMDS), metadata.google.internal,
management.azure.com, and bedrock-mantle.{region}.api.aws. The one instance-level
POST is the workload-identity token exchange, which defaults to
https://auth.openai.com/oauth/token and sets follow_redirects=False. The env reads
are OPENAI_API_KEY, OPENAI_ADMIN_KEY, OPENAI_WEBHOOK_SECRET,
AZURE_OPENAI_API_KEY, AZURE_OPENAI_AD_TOKEN and AWS_BEARER_TOKEN_BEDROCK, each
used to authenticate to its own service. Same benign patterns as the reviewed
3.0.0, so the four entries are re-pinned to 3.2.0's digests and nothing else in
the 214-entry baseline is touched.

Re-baselining alone would only buy time until the next release, so the spec is
pinned too. Every other requirement in extras.txt is already an exact pin (#8408
pinned the bare ones and skipped this one because it had a specifier). Pinning
makes the bump deliberate: whoever raises the version is the one who re-runs
--write-baseline and re-reads the diff, instead of the gate going red on release
day for a change nobody here made.

The new test asserts that invariant directly, since it is the part that will be
got wrong again: a package with digest-pinned baseline entries must not float in
studio/backend/requirements. Reverting the spec to openai>=2.7.2 fails it and
names extras.txt:62.

Verified:

  • scan_packages.py 'openai==3.2.0' exits 0; on 3.1.0 it exits 1, so the pin
    still bites and the entries were not widened.
  • The whole extras shard (--with-deps -r extras.txt, 128 archives) exits 0
    with 0 active CRITICAL/HIGH, against 4 CRITICAL and exit 1 before.
  • tests/security/test_scan_packages.py: 121 passed.

Unrelated, and not a defect: the openai-whisper==20250625 line in the same log
is an INFO, not the failure. That version is on PyPI as an sdist only, and the
bulk resolve runs --only-binary :all: so it never executes a setup.py, which is
the scanner's whole security model. "from versions: none" is what pip says about
an sdist-only package under that flag, and the per-spec fallback plus the direct
sdist fetch that follow are the designed path working. The scipy
assert_raises(ValueError, CubicSpline, ...) evidence quoted alongside it is a
MEDIUM and cannot fail the gate; it is a false positive on bc1, a boundary
condition variable in scipy's own test file that looks like a bech32 address.

The extras shard of the security audit has been red on main since 2026-08-17
19:14Z with four non-baselined CRITICALs, all in `openai`:

    Harvests environment variables/secrets AND makes network calls
      openai/_client.py, openai/lib/azure.py, openai/lib/bedrock.py
    Accesses cloud metadata/IMDS AND makes network calls
      openai/auth/_workload.py

It is not a code change here. The last green run was 18:23Z and every run after
18:40Z failed on the same four findings, on main and on unrelated PR branches
alike, which is upstream drift rather than anything in the tree.

`openai>=2.7.2` is the only floating spec in extras.txt that carries digest-pinned
baseline entries. openai 3.2.0 published at 19:14Z and changed all four files.
Confirmed by digest: the four `file_sha256` values in the baseline match openai
3.0.0 and 3.1.0 exactly and none of 3.2.0's.

The pin is deliberate and correct (#8104, #8565): the evidence for these entries
records that a network call exists but not where it goes, so `client.post(...,
data=api_key)` appended to one of these files would leave the evidence hash
untouched. Only the file digest can reopen that, so any edit upstream makes must
red the gate until someone re-reads it.

Which is the review, done here. Every destination in the four files at 3.2.0 is
first-party or a documented cloud metadata endpoint: api.openai.com,
auth.openai.com, 169.254.169.254 (Azure IMDS), metadata.google.internal,
management.azure.com, and bedrock-mantle.{region}.api.aws. The one instance-level
POST is the workload-identity token exchange, which defaults to
https://auth.openai.com/oauth/token and sets follow_redirects=False. The env reads
are OPENAI_API_KEY, OPENAI_ADMIN_KEY, OPENAI_WEBHOOK_SECRET,
AZURE_OPENAI_API_KEY, AZURE_OPENAI_AD_TOKEN and AWS_BEARER_TOKEN_BEDROCK, each
used to authenticate to its own service. Same benign patterns as the reviewed
3.0.0, so the four entries are re-pinned to 3.2.0's digests and nothing else in
the 214-entry baseline is touched.

Re-baselining alone would only buy time until the next release, so the spec is
pinned too. Every other requirement in extras.txt is already an exact pin (#8408
pinned the bare ones and skipped this one because it had a specifier). Pinning
makes the bump deliberate: whoever raises the version is the one who re-runs
--write-baseline and re-reads the diff, instead of the gate going red on release
day for a change nobody here made.

The new test asserts that invariant directly, since it is the part that will be
got wrong again: a package with digest-pinned baseline entries must not float in
studio/backend/requirements. Reverting the spec to `openai>=2.7.2` fails it and
names extras.txt:62.

Verified:
  - `scan_packages.py 'openai==3.2.0'` exits 0; on 3.1.0 it exits 1, so the pin
    still bites and the entries were not widened.
  - The whole extras shard (`--with-deps -r extras.txt`, 128 archives) exits 0
    with 0 active CRITICAL/HIGH, against 4 CRITICAL and exit 1 before.
  - tests/security/test_scan_packages.py: 121 passed.

Unrelated, and not a defect: the `openai-whisper==20250625` line in the same log
is an INFO, not the failure. That version is on PyPI as an sdist only, and the
bulk resolve runs `--only-binary :all:` so it never executes a setup.py, which is
the scanner's whole security model. "from versions: none" is what pip says about
an sdist-only package under that flag, and the per-spec fallback plus the direct
sdist fetch that follow are the designed path working. The scipy
`assert_raises(ValueError, CubicSpline, ...)` evidence quoted alongside it is a
MEDIUM and cannot fail the gate; it is a false positive on `bc1`, a boundary
condition variable in scipy's own test file that looks like a bech32 address.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eca61f808e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tests/security/test_scan_packages.py Outdated
Comment on lines +2085 to +2087
req_dir = root / "studio" / "backend" / "requirements"
offenders = []
for req in sorted(req_dir.glob("*.txt")):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Inspect the pyproject audit inputs too

Extend this check to the pyproject.toml dependency lists consumed by security-audit.yml, rather than examining only top-level Studio requirement files. There is already a live counterexample: the baseline digest-pins unsloth-zoo, while pyproject.toml:145 declares unsloth_zoo>=2026.8.12; the workflow generates audit-reqs/unsloth-deps.txt from that list. A new unsloth-zoo release can therefore change the pinned file and turn the audit red while this test remains green.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Correct, and the counterexample is real. The guard read studio/backend/requirements/*.txt only, while security-audit.yml also builds audit-reqs/unsloth-deps.txt from pyproject's project.dependencies plus the huggingfacenotorch extra. It now reads both, from one helper mirroring the workflow's own input transform. Reproduced first: adding openai>=2.7.2 to pyproject's dependencies was green before the change and now fails naming pyproject.toml.

On unsloth_zoo specifically, it stays >=. What this guard prevents is an upstream release we do not control changing the bytes and reddening main on a day nobody touched the repo. unsloth_zoo is ours and ships in lockstep, so an exact pin would break that, and a reopened digest there is one of our own changes that the pin is meant to send back for review. It is now a named exemption rather than an unexamined gap, and the exemption is asserted: only genuinely digest-pinned names may appear in it, and at least one third-party package must remain outside it, so it cannot be defused by growing the list.

Comment thread tests/security/test_scan_packages.py Outdated
Comment on lines +2096 to +2098
# `==` on the bare name, not `>=`, `~=` or a bare requirement.
if not re.search(rf"^{re.escape(name)}\s*==", spec):
offenders.append(f"{req.name}:{lineno}: {spec}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject wildcard equality specifiers

Require a concrete version after ==; this prefix regex also accepts requirements such as openai==3.*, which pip treats as a floating prefix match and may resolve to newly published file bytes. Such a spec recreates exactly the release-day baseline failure this guard is intended to prevent, so parse the requirement and reject wildcard equality versions.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Correct. The prefix regex accepted openai==3., which pip resolves as a prefix match to whatever 3.x is newest, so the guard could be satisfied by a spec that recreates exactly the release-day failure it exists to prevent. The specifier is now parsed with packaging.requirements instead of pattern-matched: exactly one clause, operator ==, and a version not ending in .. Parsing also removes the hand-rolled name and marker splitting, so extras and whitespace stop being edge cases. Verified red on openai==3.* and green after.

pre-commit-ci Bot and others added 2 commits August 18, 2026 04:38
`openai==3.2.0` on its own was a regression, caught simulating the install rather
than reading it. openai 3.x is `requires-python >=3.10` and this project is
`>=3.9,<3.15`, so an exact pin resolves to nothing at all on 3.9, where `>=2.7.2`
had quietly been selecting 2.48.0. Fixing a CI gate by breaking an install is not
a fix.

Split on the marker instead, which is what the rest of this file already does for
soundfile and tabulate. 2.48.0 is the newest release that accepts 3.9, so the 3.9
branch pins exactly what 3.9 was resolving to before.

Resolved with uv against every interpreter in requires-python, --no-deps, three
ways (main, the bad pin, this):

  spec           3.9        3.10     3.12     3.13     3.14
  >=2.7.2        2.48.0     3.2.0    3.2.0    3.2.0    3.2.0
  ==3.2.0        NO SOLUTION  3.2.0  3.2.0    3.2.0    3.2.0
  split          2.48.0     3.2.0    3.2.0    3.2.0    3.2.0

So this is behaviour-preserving: identical to main on every supported Python, and
the whole of extras.txt resolves to the same 31 packages on 3.10, 3.12, 3.13 and
3.14 before and after, differing only in the `# via -r` filename. 3.9 fails on
both sides for an unrelated and pre-existing reason (scikit-learn==1.7.1 is itself
>=3.10), so nothing here changes that either.

The security audit is unaffected: it runs on 3.12, markers are evaluated by pip,
and the scan of the two-line spec fetches only 3.2.0 and exits 0. The 3.9 branch
is never scanned, exactly as the floating spec's 3.9 resolution never was.

The guard is widened to the second half of the same invariant: the exact pins for
a digest-pinned package must cover every supported Python between them, so a
marker partition with a hole in it fails. Its limit is now written down rather
than implied. It is marker-only and offline, so it catches a `>= "3.11"` beside a
`< "3.10"`, but it cannot catch a single unmarked pin whose release does not
support 3.9, because that means asking PyPI for the release's requires-python.
The resolution simulation above is what covers that case, and the test module
stays network-free.

Verified: reverting to `openai>=2.7.2` still fails the guard naming extras.txt.
tests/security/test_scan_packages.py: 121 passed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 344e3d8ddb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tests/security/test_scan_packages.py Outdated
def _supported_python_versions(root):
"""Every `python_version` marker value pyproject's requires-python admits."""
import re
import tomllib

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fall back to tomli below Python 3.11

On Python 3.9 or 3.10, which this change explicitly treats as supported, calling this helper raises ModuleNotFoundError because tomllib was only added in Python 3.11. Since pyproject.toml configures bare pytest to run tests/security, contributors on those interpreters cannot run the default suite; use the same tomli fallback employed by the repository's other TOML-reading tests.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Correct, and it is a real break rather than a theoretical one. pyproject sets requires-python >=3.9,<3.15 and testpaths ["tests/security"], so a bare pytest from the repo root collects this module on 3.9 and 3.10, and tomllib only landed in 3.11 (PEP 680). Reproduced by blocking the import: both helpers raise ModuleNotFoundError rather than reporting anything. It is also the only 3.11+ name anywhere under tests/security, so this file is what newly breaks that directory there, and the irony is that _supported_python_versions exists to assert openai stays installable on 3.9 while being unable to run on it.

Fixed in 08aae77 with the fallback four other suites here already use, and tomli is already pinned for python_version < "3.11" in extras-no-deps.txt.

One thing worth recording, since it nearly made the fix worthless: tests-security installs only pytest==9.0.3 and pyyaml==6.0.2, so a regression test that leaned on a real tomli being importable would importorskip its way to green in CI and never once execute the branch. The test therefore supplies the parser this interpreter does have under the name the fallback looks for, and makes import tomllib fail for the duration so the fallback is provably what gets consulted. Checked red on the unfixed helpers, green on the fixed ones, and still green with tomli hidden from the meta path.

pre-commit-ci Bot and others added 2 commits August 18, 2026 05:05
…ersion

Two review findings, both real, both reproduced before fixing.

The guard read `studio/backend/requirements/*.txt` and stopped there, but
security-audit.yml builds `audit-reqs/unsloth-deps.txt` out of pyproject's
`project.dependencies` plus the `huggingfacenotorch` extra and feeds that to the
hf-stack shard. A digest-pinned package declared there was invisible to the guard,
which is the whole failure mode it exists to catch, one file over. It now reads
both halves, from one helper that mirrors the workflow's own input transform.

Reading pyproject turns up the counterexample the finding named: unsloth_zoo is
digest-pinned (hf_xet_health.py, the credential send that motivated #8104) and
declared `unsloth_zoo>=2026.8.12`. That one is deliberately not version-pinned and
must not be. The recurrence being prevented is an upstream release WE DO NOT
CONTROL changing the bytes and reddening main on a day nobody touched the repo;
unsloth_zoo is ours, released in lockstep, and an exact pin would break that. When
its digest reopens, the change is one of ours and re-reviewing it is the point of
the pin. So it is named in an exemption rather than quietly skipped, and the
exemption is itself asserted: it must contain only names that really are digest
pinned, and at least one third-party package must remain, so the guard cannot be
defused by growing the list. That assertion earned its keep immediately, failing
on `unsloth` until it was removed for having no pinned entry.

Second, `==` has to mean one version. The old prefix regex accepted `openai==3.*`,
which pip resolves as a prefix match to whatever 3.x is newest, recreating the
exact release-day failure. The specifier is now parsed with
packaging.requirements rather than pattern-matched: exactly one clause, operator
`==`, version not ending in `.*`. Parsing also drops the hand-rolled name and
marker splitting, so extras and whitespace stop being edge cases.

Four negative controls, each red with its own message and green after:
  - `openai>=2.7.2` in extras.txt: not pinned to one version, names extras.txt:68.
  - `openai==3.*`: same assertion, names the wildcard spec.
  - `< "3.9"` instead of `< "3.10"`: openai uncovered on 3.9.
  - `openai>=2.7.2` added to pyproject's dependencies: names pyproject.toml, which
    is the finding above and was green before this commit.

tests/security/test_scan_packages.py: 121 passed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99d1a8f02e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

pkg: sorted(
set(pythons) - covered.get(pkg, set()), key = lambda v: int(v.split(".")[1])
)
for pkg in sorted(present)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require every digest-pinned package to be declared

If a direct pin is removed while the package remains in the audit's transitive dependency tree, present no longer contains it and this comprehension omits it from gaps, so the test passes even though security-audit.yml still scans the package via --with-deps at whatever version its parent permits. That recreates the upstream-release failure this guard is intended to prevent; assert that every non-exempt third_party package is present in the audited requirements before calculating coverage.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Checked this one properly because the shape of the claim, a guard that passes while the thing it guards is broken, is the one shape that does earn a test change. It does not hold here, because the second half of it is not true.

Built the tree it describes: removed both openai== lines from extras.txt and ran the guard.

python3 -m pytest tests/security/test_scan_packages.py::test_digest_pinned_packages_are_pinned_on_every_supported_python -q
1 passed in 0.06s

So it is green, as you say. But the premise that security-audit.yml would still scan the package is what makes green wrong, and openai is not in that dependency tree. I enumerated every requirement the audit actually feeds the scanner (the six files named at security-audit.yml, 98 top-level packages) and walked PyPI requires_dist over them:

top-level packages: 98
hop1 mandatory packages: 125
openai in hop1 mandatory: False
edge: transformers, openai>=1.98.0; extra == "serving"           extra-only
edge: transformers, openai>=1.98.0; extra == "testing"           extra-only
edge: transformers, openai>=1.98.0; extra == "deepspeed-testing" extra-only
edge: transformers, openai>=1.98.0; extra == "dev"              extra-only

Every openai requirement anywhere is gated on an extra, and none of those extras is requested. So with the direct pin gone openai leaves the scan altogether, the digest entries become inert, there is nothing left to be unpinned on any interpreter, and the guard passing is the correct answer rather than a missed one. Reproducing the failure you describe needs a transitive openai edge to be invented first.

That leaves it as a request for an extra assertion (roughly assert third_party <= present) against a state the inputs do not currently produce, which is below the bar for changing a test, so no change here.

pre-commit-ci Bot and others added 2 commits August 18, 2026 05:14
pyproject sets requires-python >=3.9 and testpaths ["tests/security"], so a
bare pytest from the repo root collects this module on 3.9 and 3.10, where
tomllib does not exist (it landed in 3.11, PEP 680). The two helpers added
here imported it unguarded, so on those interpreters the file raised
ModuleNotFoundError instead of reporting a result. tomli is already pinned for
python_version < 3.11 in extras-no-deps.txt, and four other suites in this repo
already use exactly this fallback.

The regression test supplies the backport rather than requiring it. The
tests-security job installs only pytest and PyYAML, so a test that leaned on a
real tomli being importable would importorskip its way to green there and never
execute the branch at all. Registering the parser this interpreter does have
under the name the fallback looks for keeps it load-bearing everywhere, while
import tomllib is made to fail for the duration so the fallback is provably
what gets consulted. Checked red on the unfixed helpers and green on the fixed
ones, including with tomli hidden.
@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 08aae77276

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@danielhanchen

Copy link
Copy Markdown
Member Author

Verification pass: what this changes, and what a == pin can break

Everything below was run on this branch at 7e4f503ad. Where I could not run something I say so.

1. Before the PR, after the PR

security-audit.yml installs on Python 3.12, so openai>=2.7.2 floated to whatever was newest. The baseline digests were last written against openai 3.0.0. openai 3.2.0 shipped 2026-08-17 and rewrote all four pinned files, so their file_sha256 stopped matching and the four suppressions went inert.

Reproduced with the repo's own scanner, same command, only the baseline swapped:

### openai==3.2.0 against the PRE-PR baseline
  [1] CRITICAL  Harvests environment variables/secrets AND makes network calls
  [2] CRITICAL  Accesses cloud metadata/IMDS AND makes network calls
  [3] CRITICAL  Harvests environment variables/secrets AND makes network calls
  [4] CRITICAL  Harvests environment variables/secrets AND makes network calls
  [5] MEDIUM    Harvests environment variables / secrets
  [6] MEDIUM    Advanced obfuscation patterns
  [7] MEDIUM    Advanced obfuscation patterns
  Summary: 4 CRITICAL, 3 MEDIUM
  1 finding(s) suppressed by baseline
  EXIT_BEFORE=1

### openai==3.2.0 against the PR baseline
  Summary: 3 MEDIUM
  5 finding(s) suppressed by baseline (5 CRITICAL, 0 HIGH, 0 MEDIUM)
  EXIT_AFTER=0

Which version resolved before, and which resolves now, measured rather than assumed (PyPI JSON API):

  • latest openai is 3.2.0, requires_python >=3.10
  • the last release that still admits 3.9 is 2.48.0; 3.9 was dropped at 2.49.0
  • both pinned versions exist and neither is yanked

So before the PR a fresh install already landed on 3.2.0; the pin does not change which version anyone gets, it makes it deterministic and lets the digests be trusted.

The four digests are all openai, and this is the part worth stating precisely:

file file_sha256 evidence_hash
openai/_client.py 389ea48de3b3 -> 6303a1c4b4b0 unchanged
openai/auth/_workload.py 6a01858ee5a3 -> f61ff8db00ec unchanged
openai/lib/azure.py b031fc8ab1d1 -> 7f6f58dd6e00 unchanged
openai/lib/bedrock.py 9d38c8b3d410 -> 75816d2632b7 unchanged

evidence_hash is identical on all four while the file digests moved, which is the signature of code moving rather than new flagged behaviour. The fifth pinned entry, unsloth-zoo/hf_xet_health.py, is untouched in both columns.

2. Real or fake

Real, but defensive, and I would rather say that plainly than dress it up. Nothing here fixes an exploitable path. The four CRITICALs are the scanner's heuristics firing on openai's own credential and cloud-metadata code, which is what an SDK that does workload identity and IMDS is supposed to contain. What was actually broken is the audit: the extras shard exits 1, so the gate stops telling anyone anything. This restores the gate and re-records the evidence it suppresses. Calling it a security fix would be overselling it.

3. Does it break anything

A == narrows the resolver, so I wrote down what could go wrong before designing anything:

  • T1 the marker split is wrong and 3.9 gets a version it cannot install (openai dropped 3.9 at 2.49.0)
  • T2 the pin collides with another pin in the tree; the tight edge is anyio<4.14.0, capped deliberately for the Py3.13 streaming bug in [Bug] Streaming chat completion crashes with RuntimeError (Python 3.13 / anyio cancel scope) #6483
  • T3 an old existing install cannot get there from here
  • T4 a user who never upgrades is affected anyway
  • T5 some other requirement asks for a different openai, which an exact pin makes unsatisfiable
  • T6 openai 3.x pulls a dependency that fights an existing one

T6 is not hypothetical. openai 3.0.0 carries a documented breaking change: HTTPX2 became the default client and httpx is no longer installed automatically. This repo pins httpx==0.28.1.

Simulated in isolated uv venvs, one per interpreter, created once and reused across all three scenarios, nothing heavier than the openai wheel installed:

=== python 3.9 ===
  pin alone           exit=0 openai=2.48.0
  pin + repo pins     exit=0 openai=2.48.0 anyio=4.12.1
  old 2.7.2 -> upgrade exit=0 now=2.48.0
=== python 3.10 ===
  pin alone           exit=0 openai=3.2.0
  pin + repo pins     exit=0 openai=3.2.0  anyio=4.13.0
  old 2.7.2 -> upgrade exit=0 now=3.2.0
=== python 3.11 / 3.12 / 3.13 ===  identical to 3.10

and the resulting graphs, checked rather than assumed:

py3.9   openai=2.48.0 httpx=0.28.1 httpx2=none    anyio=4.12.1  uv pip check: OK
py3.10  openai=3.2.0  httpx=0.28.1 httpx2=2.11.0  anyio=4.13.0  uv pip check: OK
py3.11  openai=3.2.0  httpx=0.28.1 httpx2=2.11.0  anyio=4.13.0  uv pip check: OK
py3.12  openai=3.2.0  httpx=0.28.1 httpx2=2.11.0  anyio=4.13.0  uv pip check: OK
py3.13  openai=3.2.0  httpx=0.28.1 httpx2=2.11.0  anyio=4.13.0  uv pip check: OK

Reading those against the list:

  • T1 clear. 3.9 resolves to 2.48.0 by every route, which is exactly what >=2.7.2 was already giving it, so that branch changes nothing.
  • T2 clear. anyio lands at 4.13.0, one minor under the cap. Worth recording that the margin is one minor version: a future openai wanting anyio>=4.14 would collide with single-env/constraints.txt.
  • T3 clear. Installing 2.7.2 first and then applying the pin succeeds on all five interpreters. This is the load-bearing case and it is a run, not an argument.
  • T4 clear by inspection. The diff is three files: a requirements pin, a JSON allowlist read only by scripts/scan_packages.py in CI, and a test. No studio/backend/** runtime file changes, so there is no schema, migration or settings key in play and an existing ~/.unsloth/studio with settings and threads cannot notice. A user who never upgrades gets nothing at all.
  • T5 clear. openai appears in exactly two lines in the whole tree, the two pin lines. Nothing in pyproject.toml, nothing in any other requirements file.
  • T6 clear, and the reason is worth stating. httpx2 is a different distribution name from httpx, so both install side by side and uv pip check is clean. More to the point, the Studio backend does not import the openai SDK at all. It speaks the OpenAI wire protocol over httpx directly (core/inference/openai_codex_client.py and friends import httpx, not openai). The only non-test import of the SDK anywhere is unsloth/models/_utils.py:2087, and it is already wrapped:
if is_openai_available():
    try:
        from openai import OpenAI
    except:
        print("Unsloth: OpenAI failed to import - ignoring for now.")

So the 3.x HTTPX2 change does not reach Studio, and the one place it could reach degrades instead of raising. That is the concrete answer to whether the pinned version changes behaviour for Studio: it does not.

4. The two review items

The tomli one was right and is fixed in 08aae7727. Worth recording why the regression test is shaped the way it is: tests-security installs only pytest and pyyaml, so a test that leaned on a real tomli being importable would importorskip its way to green in CI and never execute the branch. It now supplies the parser this interpreter does have under the name the fallback looks for, and makes import tomllib fail for the duration. Checked red on the unfixed helpers, green on the fixed ones, and still green with tomli hidden from the meta path, so it is not a test that passes on both trees.

The digest-pinned-package one I disagreed with, with the measurement in the thread: removing the direct pin does make the guard green, but openai then leaves the audit's dependency tree entirely, so green is the correct answer rather than a missed one. All 98 top-level packages walked; every openai edge that exists is gated on a transformers extra that is never requested.

5. Platforms

Linux here. Both pinned versions ship py3-none-any wheels with no platform wheels and no compiled extension, and the two pin lines carry only python_version markers with no sys_platform, so all three OSes resolve identically. The baseline digests are computed over bytes read out of the archive, not through a text-mode read, so they are stable across CRLF and locale. The audit job is ubuntu-latest only. macOS and Windows are reasoned from that, not run.

Suite at this head: 410 passed, 6 skipped.

@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@unslothai unslothai deleted a comment from chatgpt-codex-connector Bot Aug 18, 2026
@danielhanchen
danielhanchen merged commit 659b9da into main Aug 18, 2026
71 checks passed
@danielhanchen
danielhanchen deleted the fix-security-audit-openai-pin branch August 18, 2026 09:23
@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

1 similar comment
@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

1 similar comment
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

danielhanchen added a commit that referenced this pull request Aug 19, 2026
Every fresh desktop install on Linux and macOS came up ManagedStale and
repaired itself before it would run. From the app's own log:

  05:44:22 Managed preflight: install probe result Stale { reason: "studio_install_requirements_changed" }
  05:44:22 desktop_preflight completed disposition=ManagedStale
  05:44:22 start_managed_repair command called
  05:44:24 studio install incomplete -- forcing dependency pass to repair...
  05:44:32 Managed preflight: install probe result Ready

The manifest recorded digests from REQ_ROOT, which is the requirements
directory next to whichever install_python_stack.py ran. A desktop bundle
carries its own copy. verify_install reads the INSTALLED package's copy,
because at verify time install_manifest.py is imported out of the venv.
Two different trees, compared to each other.

They agree until a tracked requirement file changes upstream, and then
every install performed by that bundle is stale for ever. v0.1.800-beta
was cut 2026-08-14 and installs unsloth 2026.8.18; #9148 pinned openai in
extras.txt in between. Windows was unaffected only by luck of layout.

The digests now describe the tree the verifier will read. A source or
editable install has no copy under site-packages and still uses the root
it was given, which is what keeps an edited studio.txt invalidating the
manifest on the --local path.

Also: the CI gate reported "saw: ManagedStale" and nothing else on all
four platforms. The reason is logged on its own line, and tail -60 scrolls
it away the moment the backend starts logging, so the one fact that
explained this had to be recovered from an uploaded artifact by hand. All
three log dumps now grep for it.
danielhanchen added a commit that referenced this pull request Aug 19, 2026
* Record the venv's own requirement digests, not the installer's

Every fresh desktop install on Linux and macOS came up ManagedStale and
repaired itself before it would run. From the app's own log:

  05:44:22 Managed preflight: install probe result Stale { reason: "studio_install_requirements_changed" }
  05:44:22 desktop_preflight completed disposition=ManagedStale
  05:44:22 start_managed_repair command called
  05:44:24 studio install incomplete -- forcing dependency pass to repair...
  05:44:32 Managed preflight: install probe result Ready

The manifest recorded digests from REQ_ROOT, which is the requirements
directory next to whichever install_python_stack.py ran. A desktop bundle
carries its own copy. verify_install reads the INSTALLED package's copy,
because at verify time install_manifest.py is imported out of the venv.
Two different trees, compared to each other.

They agree until a tracked requirement file changes upstream, and then
every install performed by that bundle is stale for ever. v0.1.800-beta
was cut 2026-08-14 and installs unsloth 2026.8.18; #9148 pinned openai in
extras.txt in between. Windows was unaffected only by luck of layout.

The digests now describe the tree the verifier will read. A source or
editable install has no copy under site-packages and still uses the root
it was given, which is what keeps an edited studio.txt invalidating the
manifest on the --local path.

Also: the CI gate reported "saw: ManagedStale" and nothing else on all
four platforms. The reason is logged on its own line, and tail -60 scrolls
it away the moment the backend starts logging, so the one fact that
explained this had to be recovered from an uploaded artifact by hand. All
three log dumps now grep for it.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant