Security audit: pin openai, and re-review the four digest-pinned entries - #9148
Conversation
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.
There was a problem hiding this comment.
💡 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".
| req_dir = root / "studio" / "backend" / "requirements" | ||
| offenders = [] | ||
| for req in sorted(req_dir.glob("*.txt")): |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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.
| # `==` 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}") |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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.
for more information, see https://pre-commit.ci
`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.
There was a problem hiding this comment.
💡 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".
| def _supported_python_versions(root): | ||
| """Every `python_version` marker value pyproject's requires-python admits.""" | ||
| import re | ||
| import tomllib |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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.
for more information, see https://pre-commit.ci
…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.
There was a problem hiding this comment.
💡 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) |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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.
for more information, see https://pre-commit.ci
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.
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
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". |
for more information, see https://pre-commit.ci
Verification pass: what this changes, and what a
|
| 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.2was 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.14would collide withsingle-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.pyin CI, and a test. Nostudio/backend/**runtime file changes, so there is no schema, migration or settings key in play and an existing~/.unsloth/studiowith 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.
httpx2is a different distribution name fromhttpx, so both install side by side anduv pip checkis clean. More to the point, the Studio backend does not import the openai SDK at all. It speaks the OpenAI wire protocol overhttpxdirectly (core/inference/openai_codex_client.pyand friends importhttpx, notopenai). The only non-test import of the SDK anywhere isunsloth/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.
|
@codex review |
|
@codex review |
1 similar comment
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
1 similar comment
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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.
* 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>
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: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.2is the only floating spec in extras.txt that carries digest-pinnedbaseline entries. openai 3.2.0 published at 19:14Z and changed all four files.
Confirmed by digest: the four
file_sha256values in the baseline match openai3.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 hashuntouched. 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.2fails it andnames extras.txt:62.
Verified:
scan_packages.py 'openai==3.2.0'exits 0; on 3.1.0 it exits 1, so the pinstill bites and the entries were not widened.
--with-deps -r extras.txt, 128 archives) exits 0with 0 active CRITICAL/HIGH, against 4 CRITICAL and exit 1 before.
Unrelated, and not a defect: the
openai-whisper==20250625line in the same logis 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 isthe 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 aMEDIUM and cannot fail the gate; it is a false positive on
bc1, a boundarycondition variable in scipy's own test file that looks like a bech32 address.