Severity Daily

IT and AI security incidents, checked against the primary source

Weights & Biases fixed a path traversal in run file downloads and logged it as an artifact fix

PATCH SOON — Weights & Biases fixed a path traversal in run file downloads and logged it as an artifact fix

Written by

in

The fix is real, it shipped three weeks ago, and the release note describing it names two artifact methods and not the run file API that the CVE published this morning is actually about.

What happened

NVD published CVE-2026-91771 on Tuesday, September 15, 2026, at 2:16 a.m. UTC. The record’s vulnStatus is “Received.” VulnCheck, the assigning CNA, scores it 8.8 under CVSS v3.1 with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H, and 8.7 under CVSS v4.0. The weakness is CWE-22, path traversal.

The description reads: “Weights & Biases wandb before 0.29.0 fails to validate the file name from server responses in the File.download function, allowing path traversal attacks.”

The wandb Python client is the SDK that machine learning teams import to log runs, metrics, and artifacts to Weights & Biases, either the hosted service or a self-managed server. File.download is the public-API method that pulls a file a run previously saved back down to a local directory.

The vulnerable code is short and readable. In wandb/apis/public/files.py at tag v0.27.2, inside the download wrapper:

path = os.path.join(root, self.name)

root is the caller’s destination directory, defaulting to ".". self.name is not the caller’s — it comes from the file’s attributes as returned by the server. os.path.join discards the first argument entirely if the second is absolute, and preserves ../ segments to be resolved on write. The resulting path goes straight to download_file_from_url.

The public report spells out what that buys an attacker. GitHub issue #12592, titled “Path traversal in public API File.download enables arbitrary file write,” was opened by the researcher geo-chen on June 15, 2026. It identifies the os.path.join call by line, describes a malicious or compromised backend returning crafted filenames, and lists the write targets that matter: shell startup files, cron entries, SSH keys, and directories on the Python import path. It also proposed the fix the project eventually used — apply the make_file_path_upload_safe validation already used on the upload side, or verify the resolved destination stays inside the resolved root. The reporter noted follow-ups on June 29 and August 4.

The fix, and what the changelog says about it

Pull request #12516, “fix(artifacts): validate path when using run file api to download,” was opened on August 20, 2026, at 10:19 p.m. UTC and merged on August 21, 2026, at 7:54 p.m. UTC as commit e52558f. The change to files.py is two lines added and one removed: an import of validate_fspath, and the replacement of the os.path.join call with path = validate_fspath(root, self.name). The commit also added validate_relpath and validate_fspath to wandb/sdk/lib/paths.py, a validate_artifact_root_name helper to the artifacts validators, and 28 parametrized test cases.

The PR body explains the reasoning. It cites an internal ticket, WB-38900, and an earlier pull request, #11735, which “added validation to reject relative and abs paths when downloading artifact files.” It then says: “But there are more ways to download: artifact can be downloaded using the file api via artifact.files() file.download, which does not check path. This PR applies same logic to File.download before sending the path to go core for download.”

The body does not reference issue #12592, or any security report, or any reporter.

The release note follows the PR’s framing. Under the Security heading in the 0.29.0 notes, the entry reads: “Reject file or artifact name that contain relative path when downloading artifacts via `artifact.files()`, `artifact.checkout()`”.

That is accurate as far as it goes. artifact.files() returns File objects whose download method is the patched function. But File.download is also what run.files() returns, and the run file API is the entry point the pull request named in its own title and the one the CVE description points at. It appears nowhere in the release notes.

A wandb user reading the 0.29.0 notes to work out whether this affects them sees two artifact methods. If their code does run.files() and iterates file.download(root=...) — the documented way to retrieve run outputs — nothing in the release notes tells them the fix is theirs.

The dates

PyPI’s upload records put wandb 0.29.0 on the index on August 26, 2026, at 11:42 p.m. UTC, five days after the merge. Version 0.30.0 followed on September 9, 2026, at 12:16 a.m. UTC and is the current release; the validate_fspath call is present in both, at the same line.

Laid end to end: the public report landed June 15, the fix merged August 21 — 67 days later — the fixed build reached PyPI on August 26, and the CVE record appeared this morning, 92 days after the report and 20 days after anyone could install the fix.

Why it matters

The interesting part is not the bug. It is a textbook os.path.join traversal, correctly fixed, in a codebase that had already built the validation helper for the upload direction and simply had not wired it into the download direction. That happens everywhere.

What is worth attention is how a fix gets described, and by whom. The pull request was written as the continuation of an earlier piece of hardening work, tracked internally, framed in artifact terms. The changelog entry inherited that frame. Neither document was wrong; both were written by someone solving a class of problem, not answering a report. The report existed, in public, for 67 days, and it named the exact function and the exact attack. Nothing connects the two in the public record except the CVE that arrived this morning, and the CVE arrived from VulnCheck — a third party — not from the vendor.

This has a practical consequence for anyone who triages by changelog, which is most teams that pin dependencies. The signal that would tell a security-conscious wandb user to prioritize 0.29.0 is the phrase “run file” or “File.download,” and it is not there. The signal that is there — “downloading artifacts” — is easy to read as not applicable if your pipeline does not use the artifact API. Severity Daily reported the same shape last week on The Events Calendar, where three releases of changelog never said the word that mattered.

The threat model deserves stating plainly, because the 8.8 overstates how easy this is. The filename is server-controlled, so the attacker needs to control or influence what the W&B backend returns. That means a compromised or malicious self-managed server, a hostile instance an engineer was pointed at, or an attacker who can alter a file’s stored name on a shared team account. It is not a flaw a random internet attacker reaches. But the payoff is a write to ~/.bashrc or a directory on the Python path, on a machine that by definition has cloud credentials and training data on it, and it happens during an operation nobody inspects — downloading your own files from your own experiment tracker.

Finally: the bug sat on a public tracker, readable by anyone, for more than two months while the vulnerable version remained current. That is no criticism of the reporter, who did what the tracker is for. It is a reminder that across much of the ML tooling stack, the gap between a public exploit path and a fixed build runs to months, and nothing in that window emits a signal a scanner can read.

What to do

  • Upgrade to wandb 0.30.0. The fix first shipped in 0.29.0, but 0.30.0 is current and carries the same validate_fspath call. Pin it in requirements.txt, pyproject.toml, and any container base image used for training or evaluation jobs.
  • Check for wandb in build and CI images, not just notebooks. The client is routinely baked into training containers and inherited by every job that runs them.
  • Verify the version with pip show wandb rather than by reading a lockfile, since the affected range is everything below 0.29.0 and many pinned environments sit on the 0.27 and 0.28 lines from earlier this year.
  • Confirm which W&B backend your SDK talks to. If WANDB_BASE_URL points at a self-managed or third-party instance, that host is the trust anchor this bug depends on. Treat unexplained files appearing in home directories or on sys.path on ML workstations as worth investigating.
  • Do not use the 0.29.0 release notes to decide whether you are affected. If your code calls download() on anything returned by run.files(), you were affected, whatever the artifact-worded entry suggests.

Sourcing note

Checked: the NVD record for CVE-2026-91771, carrying VulnCheck’s CVSS v3.1 and v4.0 scores, CWE-22, the affected range, and six reference URLs; the VulnCheck advisory page, which gives the description and the fixed version but publishes no timeline; wandb/apis/public/files.py read directly at tags v0.27.2, v0.29.0, and v0.30.0, confirming the os.path.join call before the fix and the validate_fspath call after it; commit e52558f and pull request #12516 through the GitHub API for the title, author, creation and merge timestamps, body text, and per-file diff; GitHub issue #12592 for the report and its date; the 0.29.0 release notes for the Security entry, quoted verbatim; and PyPI’s upload records for 0.29.0 and 0.30.0.

Not established: whether Weights & Biases and the reporter corresponded outside the public issue, and whether the vendor requested or was consulted on the CVE — the record was assigned by VulnCheck, and no vendor security advisory for this issue was found. The closing state of issue #12592 could not be confirmed; GitHub’s issues API returned 403 to automated fetching. No exploitation has been reported by any source, and the CVE is not in CISA’s Known Exploited Vulnerabilities catalog, read directly from its published JSON at version 2026.09.14. The KEV catalog was reached through that public JSON file rather than cisa.gov, which returns 403 to automated requests.