Your build server got patched on September 11. Congratulations — that answers the question "are we still exploitable?" Nobody is asking that question anymore. The question your team is actually sitting on, three weeks after Wiz disclosed the JFrog Artifactory campaign on September 10, is harder: between August 15 and September 8, attackers had administrator access to self-hosted artifact servers and planted Rust backdoors with remote command-and-control on them. Anything your pipeline built, cached, or pulled through one of those servers in that window is now a trust question, and no patch retroactively answers a trust question.
This post is the artifact-trust half of the incident. The patch-hygiene half — upgrade to a fixed build, rotate the join key, revoke tokens — is well covered elsewhere, including Wiz's and JFrog's own guidance. What follows is the part most teams skipped: a triage table for deciding which artifacts are suspect, why a patched server still leaves the doubt open, the rebuild-and-compare procedure that closes it, and the structural fix that makes the next incident's version of this question answerable in an afternoon instead of a quarter.
Multiple distinct threat groups — Wiz and watchTowr both describe several operators working the same bugs concurrently, from opportunistic mass exploitation to hands-on-keyboard triage of high-value targets — compromised self-hosted JFrog Artifactory servers over a 24-day window. WatchTowr confirmed in-the-wild exploitation on September 1; Wiz's September 10 disclosure put the full campaign window at August 15 through September 8, 2026. JFrog Cloud customers were never affected. Every victim ran the server themselves.
CVE
Severity
Patched
What it does
CVE-2026-42018
High
August 12
Improper authentication lets an attacker mint an anonymous-user JWT
CVE-2026-42016
High
July 27
Token scope isn't validated, so a low-privileged token escalates — Wiz observed the chained pair go from anonymous JWT to administrator account in under five minutes
CVE-2026-82329
Critical, CVSS 9.8
August 28 (7.161.20)
Authentication bypass needs no chain at all: on a default install the cluster join key is derivable, so one forged JWT to the join endpoint returns an admin-scoped token. Bishop Fox reproduced the full chain to administrator against a default 7.111.20 instance
All three flaws were already patched when the campaign ran. That is the uncomfortable part: six weeks after the CVE-2026-42016 fix, Wiz measured 59% of organizations still vulnerable to it; four weeks after the CVE-2026-42018 fix, 62% still vulnerable; two weeks after the critical CVE-2026-82329 fix, 49% still vulnerable — the full patch-lag breakdown is grim reading. CISA added all three to its Known Exploited Vulnerabilities catalog, with the critical one listed September 3 — four days after JFrog's patch, before most shops had a maintenance window.
And "admin access" undersells what the attackers did with it. The observed post-exploitation inventory, per Wiz: persistent rogue administrator accounts (including lookalike service names like jfrog-distribution and repo-service alongside obvious PoC names such as 0xTerror), malicious Groovy plugins executing natively on the Artifactory JVM, compiled Rust backdoors with remote C2 for long-term control, cluster join-key theft, SSH keys attached to new accounts, minted access tokens, and configuration exfiltration. An attacker with that inventory doesn't just own your package proxy. They own every build input and output that flowed through it for 24 days.
Here is the core deliverable — the decision table. Find every artifact your pipeline touched between August 15 and September 8 that passed through a self-hosted Artifactory instance, classify it into one of these rows, and apply the verdict. Do this before you argue about anything else.
What passed through the server
Why it's suspect
Verdict
Built on or through it — images, jars, wheels, release tarballs whose build resolved dependencies via Artifactory or published back to it
A Groovy plugin or backdoor on the JVM can alter resolved dependencies or rewrite published artifacts invisibly
Rebuild from clean, pinned sources. The old artifact is untrusted until a clean rebuild reproduces it (or replaces it)
Proxied or cached through it — npm, PyPI, Maven, or container images pulled via Artifactory as a pull-through cache, the most common Artifactory role in CI
A compromised proxy serves whatever bytes it wants for any package name your builds requested; lockfiles pin versions, not the bytes the proxy handed you
Purge the cache, re-pull from upstream, compare digests. Anything whose digest differs from upstream's is evidence of tampering, not a cache quirk
Merely stored on it — release artifacts that sat in a repository but whose builds never touched the server
Lower risk, but config exfiltration plus admin access means bits could have been replaced in place
Diff against a known-good baseline — your release checksums, an offline copy, a Rekor entry — before you ship or sign anything from that repo again
No baseline, no attestations, no idea — the honest row for most small teams
Without a recorded digest or provenance statement, "compare" is not an operation you can perform
Treat window artifacts as untrusted-by-default. The rebuild IS the baseline. Rebuild clean, record the digests this time, and promote only the rebuilt artifacts
Two notes on that last row, because it is where most teams actually live. First, "we have no evidence of tampering" is not evidence of integrity — the whole point of a JVM-level plugin or a proxy serving altered bytes is that the tampering leaves no trace in the artifact itself. Absence of a diff you never computed proves nothing. Second, the honest row is not a shame row. It is the normal state of a team that never needed after-the-fact build forensics before, and the fix for it is section six of this post, not guilt.
Why "we patched on September 11" doesn't close it
Patching removes the exploit path. It does not evict anything the exploit path already installed, and Wiz's and JFrog's guidance is explicit that the observed persistence survives the upgrade. Walk through what that means for artifact trust, mechanism by mechanism:
Already-minted tokens stay valid after the upgrade. An attacker holding a token minted during the window keeps API access to your repositories after you patch, until you revoke it. Every artifact published or modified via that token after your patch date is still attacker-touchable. Revocation, not the upgrade, is the eviction event.
Backdoor admin accounts remain in the user database. The upgrade doesn't audit your user list. A repo-service lookalike created August 20 is still an administrator on September 12, still able to push a replaced release artifact that your deploy pipeline will happily pull.
Groovy plugins keep executing on the JVM. A malicious plugin isn't part of the vulnerable code — it's a file in your plugin directory that the patched server runs with the same enthusiasm as the vulnerable one did. Until you diff your plugin inventory against an approved list and delete strangers, your "patched" server still runs attacker code on every request it chooses to intercept.
Rust backdoors with C2 survive the upgrade entirely. They aren't Artifactory code at all; they're compiled binaries on your build host phoning home. Patching the application leaves the host compromise untouched. This is the mechanism that makes "the window" an exposure window rather than a vulnerability window: for 24 days, arbitrary code ran wherever your artifacts lived.
A stolen cluster join key outlives the patch. If attackers captured it before you rotated it, they can mint fresh access after the upgrade. Rotation is part of eviction, not part of patching.
Notice the pattern: every one of these extends the trust question past the patch date. "We upgraded September 11" bounds the vulnerability. Only "we revoked every token minted since late August, deleted unknown admins and plugins, rotated the join key, rebuilt the host or proved it clean, and re-verified window artifacts" bounds the compromise. Wiz even published behavioral detection signatures to help scope it — the highest-confidence indicator for the chained pair is a request to the token endpoint returning HTTP 401 on the standard path followed by HTTP 200 on a path variant — because scoping the compromise is a forensic task, not a version check.
Triage tells you what's suspect. This closes it. Run it per artifact class from the table above, in this order:
Freeze the suspect set. List every artifact built, cached, or stored through the server between August 15 and September 8. Pull deploy pins and promotion gates back to pre-window artifacts where you can; where you can't, flag the suspect versions in your registry so nobody promotes them further while you work.
Evict first, on a clean host. Upgrade to a fixed build on your train (Wiz/JFrog guidance lists 7.111.21, 7.117.28, 7.125.20, 7.133.29, 7.146.38, and 7.161.20), then do the parts the upgrade doesn't: delete admin accounts you didn't create, remove unknown Groovy plugins, rotate the cluster join key, and revoke all access tokens minted since late August. Treat every credential the server held or proxied — upstream registry tokens, cloud credentials in system config, CI service accounts — as exposed and rotate those too. If a Rust backdoor touched the host, rebuild the host; an application upgrade is not host remediation.
Rebuild from pinned sources. Rebuild each suspect artifact from the exact commit and lockfile it originally used, resolving dependencies from upstream — not from the purged-then-repulled cache, at least until step 4 clears it. Pin everything: commit SHAs, lockfiles, base-image digests.
Compare digests, and take mismatches seriously. For proxied artifacts, compare re-pulled bytes against upstream digests. For rebuilt artifacts, compare against the suspect artifact's digest. A mismatch on a rebuild isn't automatically tampering — toolchains drift — but a mismatch on a re-pull from upstream is very close to proof that the cache served altered bytes. Record every comparison; this log is your evidence file.
Verify provenance where it exists. If any of your window builds emitted SLSA provenance or Sigstore signatures, verify them now — a valid attestation binding the artifact to its source commit and builder is the strongest "built clean" evidence available:
If verification fails on an artifact you expected to be signed, treat that as a finding, not a tooling glitch. And note the gap honestly: provenance proves the build ran as recorded, but if the build's inputs came through the compromised proxy, verify the inputs too — step 4 covers that side.
Promote only the rebuilt artifacts, and record the new baseline. The rebuilt, digest-recorded artifacts become the known-good set your next incident compares against. That sentence is the entire structural fix in miniature, which brings us to it.
The reason this incident turned into weeks of forensic archaeology is that most teams' answer to "was this image built clean?" is a shrug with extra steps. The fix is to make every build emit the evidence its future incident responder needs, by default, before anything is on fire:
SLSA provenance on every build. A provenance statement records the source commit, the builder, the build parameters, and the input digests. SLSA Level 2 — provenance authenticated by the build service — is the minimum worth having; Level 3 adds a hardened, non-falsifiable build environment. GitHub's attest-build-provenance action makes L2 nearly free on Actions; slsa-verifier checks it at consume time.
Sign artifacts with Sigstore/cosign, keylessly. Short-lived certificates bound to your CI's OIDC identity mean no long-lived signing keys to steal — which matters precisely because this incident class starts with credential theft. Log to Rekor so signatures are timestamped in a tamper-evident transparency log; a signature without a transparency-log entry is a claim, not evidence.
Attach an SBOM to every release. SPDX or CycloneDX, generated at build time from the resolved dependency set. When the next incident asks "which shipped artifacts contain package X at version Y," the answer should be a query, not a rebuild-and-inspect project.
Verify at admission, not just at audit. Provenance nobody checks is decoration. Enforce signature and attestation verification at deploy time — Sigstore policy-controller, Kyverno, or your deploy pipeline's own cosign verify gate — so an unsigned or mis-attested artifact can't reach production even when the registry serving it is compromised. Defense in depth means failing early (scan at PR time) and failing late (verify at admission).
None of this would have prevented the Artifactory compromise. All of it would have collapsed the response from "rebuild everything and hope" to "verify everything and know." That is the actual return on build-attestation investment: not preventing the breach, but making the breach answerable.
If you run a platform where tenants push code and you run the builds — which is exactly what a self-hosted PaaS does — this incident is your threat model wearing someone else's logo. Your build service is your tenants' Artifactory: the component whose compromise turns into every tenant's artifact-trust question simultaneously. The borrow list is short: emit SLSA provenance and SBOMs from your build pipeline by default, sign every image your platform produces, verify at deploy admission, and keep your build hosts boring and rebuildable so host-level persistence like a Rust backdoor is a reimage, not a research project. Your tenants will never ask for any of this until the day they desperately need it — which is exactly when it's too late to start generating it.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. This week that means your build pipeline, your attestations, and your deploy admission are all yours to verify: no shared build service whose incident becomes your incident. Star the repo on GitHub or deploy your first app today.