On August 28, 2026, JFrog published fixes for CVE-2026-82329, an authentication bypass in Artifactory that hands an unauthenticated attacker administrative privileges over the network. On September 2, CISA added it to the Known Exploited Vulnerabilities catalog. It scores CVSS 9.8, it affects self-managed Artifactory in its default configuration, and the software in question is the binary repository that a great many organisations build and ship from.
This advisory is three things. First, a plain explanation of the actual defect, which is a one-line API misuse with an unusually clean root cause. Second, a ready-to-run Nuclei detection template that fingerprints affected instances without authenticating and without exploiting anything. Third, a walkthrough of finding every affected Artifactory across an attack surface with Sn1per.
There is one thing worth knowing before you scan for this, and it is the reason this post exists. A public Nuclei template for CVE-2026-82329 already shipped upstream on September 2. That template confirms the vulnerability by actually completing the authentication bypass, which means every vulnerable host it touches is left holding a live, admin-scoped, non-expiring API token that the scan created and then walked away from. If you are about to point that at your own estate, or worse at a client’s, read the section on active versus passive checks first.
CVE-2026-82329 at a glance
- CVE: CVE-2026-82329, CWE-287 (improper authentication). JFrog assigned no vendor advisory ID for this one, so cite the CVE and the release notes rather than a JFSA number.
- Impact: unauthenticated remote attacker obtains an admin-scoped, non-expiring service token, and from there full administrative control of the instance.
- CVSS: 9.8 Critical,
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. - Affected: self-managed Artifactory across six supported branches, plus every end-of-life 7.x branch below 7.111. Pro, Pro X and Enterprise alike, because the defect is in the shared JFrog Access service.
- Not affected: JFrog Cloud/SaaS, which was fortified separately.
- Fixed: 7.111.21, 7.117.28, 7.125.20, 7.133.29, 7.146.38, 7.161.20. Per branch. There is no single upgrade target.
- CISA KEV: added September 2, 2026, with the forensic-triage flag set.
- Fix now: upgrade to your branch’s fixed release, then audit existing admin tokens. Patching alone does not undo a prior compromise.
What is CVE-2026-82329?
Artifactory does not handle its own authentication. That job belongs to JFrog Access, a separate service bundled inside the same distribution, which issues and validates the tokens every other JFrog component relies on. When JFrog products are deployed as a cluster, a new node proves it belongs by presenting a join key: a shared secret, signed into a JWT, that the Access service checks before admitting the node and issuing it a service token.
The bug is in how Access decides whether a configured join key is usable.
The blank join key
JoinKeyAccess.tryResolveJoinKeys() reads the shared.security.additionalJoinKeys property, which is an optional setting for supplying extra join keys during key rotation. It is unset on a default installation. When unset, the method returns an empty string wrapped in a vavr Try.
The guard that should discard that empty value calls Try.isEmpty(). That method reports whether the Try itself is a failure or contains null. It says nothing about whether the string inside it is empty. A successful Try holding "" is not empty by that definition, so it sails through the check.
The empty string is then registered as a legitimate join key. It is filed under the key ID e3b0c442...b855, which anyone will recognise as the SHA-256 of the empty string, and JoinKeyUtils.getSigningKey("") PKCS7-pads it into a signing key of 32 bytes of 0x20. Thirty-two spaces.
That is the whole vulnerability. The signing key protecting cluster membership is a constant that anyone can derive, and it is the same constant on every default installation on earth. An attacker mints an HS256 JWT signed with thirty-two spaces, posts it to /access/api/v1/registry/join, and Access accepts it as a valid node joining the cluster. ServiceTokenProviderImpl then issues a token scoped admin with expiresIn(0), which in this API means it never expires.
From that token the rest is ordinary API usage: enumerate users, reset the administrator password, mint further tokens, read or replace any artifact in any repository. No credentials were required at any point, and no brute force was involved.
What “default configuration” actually means here
The NVD summary says the bypass is possible “under default configuration,” which reads like a hedge and is worth unpacking, because the logic is inverted from what people usually assume.
You are exposed because you left an optional setting alone. shared.security.additionalJoinKeys is not something a typical single-node Artifactory deployment ever touches – it exists for multi-node key rotation. Leaving it unset is the normal, documented, overwhelmingly common state, and leaving it unset is precisely what produces the empty string that defeats the guard. Setting it to any non-empty value happens to route execution around the defect.
So this is not a hardening failure that careless operators walked into. The careful ones are affected too. That also gives you a genuine stopgap if you cannot patch immediately: setting shared.security.additionalJoinKeys to a non-empty value avoids the broken code path. Treat that as a workaround and not a fix.
CVE-2026-82329 affected versions: six branches, six fixes
Artifactory ships parallel release branches, and JFrog patched this in each supported branch separately. The result is six disjoint vulnerable windows rather than one range, which is the single most common way people get their detection wrong.
| Branch | Vulnerable | Fixed release |
|---|---|---|
| 7.161.x | 7.161.0 through 7.161.19 | 7.161.20 |
| 7.146.x | 7.146.0 through 7.146.36 | 7.146.38 |
| 7.133.x | 7.133.0 through 7.133.28 | 7.133.29 |
| 7.125.x | 7.125.0 through 7.125.19 | 7.125.20 |
| 7.117.x | 7.117.0 through 7.117.27 | 7.117.28 |
| 7.111.x | 7.111.4 through 7.111.20 | 7.111.21 |
| below 7.111 | all releases | no fix exists |
Read that table carefully before writing any comparison logic, because version ordering does not track patch status here. An instance on 7.146.38 is patched. An instance on 7.161.19 is vulnerable. The vulnerable one has the higher version number. Any check shaped like “less than 7.161.20” reports your patched 7.146.38 hosts as vulnerable and, depending on how it is written, may clear genuinely vulnerable ones. You need the branch, then the comparison within that branch.
Why the gaps in that table are not gaps
The obvious question is what happens to 7.112 through 7.116, or 7.147 through 7.160. They sit between the windows and nobody lists them.
They are safe, but not because they were audited and cleared. Those versions were never released to self-managed customers. JFrog publishes discrete branches to self-hosted users – 7.84, 7.90, 7.98, 7.104, 7.111, 7.117, 7.125, 7.133, 7.146, 7.161 – and the numbers in between belong to the Cloud/SaaS release line, which is on a different cadence and was fortified separately. There is no self-managed 7.113.2 to be vulnerable.
The same explanation covers the oddity inside the 7.146 branch, where the last vulnerable release is 7.146.36 and the fix is 7.146.38. There is no 7.146.37. It was never published. If you have been staring at that apparent off-by-one wondering which side of the fix it falls on, that is why you could not find it.
The ambiguity below 7.111.4, and how we resolved it
JFrog’s own two machine-readable artifacts disagree about old versions, and it is worth being explicit about it rather than quietly picking one.
The CNA record – the structured data JFrog submitted with the CVE – gives the first affected range as version 0 up to 7.111.21, meaning everything below the fix is affected. The HTML advisory and the resulting NVD CPE instead start the range at 7.111.4, which reads as though nothing older is affected.
The deciding detail is that 7.111.4 is simply the lowest patch ever released in the 7.111 branch. It is not a commit boundary where the defect appeared. It is the floor of the oldest branch JFrog still supports, which is a statement about support policy rather than about vulnerability. Older branches are not listed because there is nothing to upgrade them to, not because they were found clean.
We therefore treat 7.x below 7.111 as vulnerable with no fix available. Our template reports those separately, under a matcher named artifactory-7.x-eol-branch-no-fix-available, so that you can tell a version comparison against a published fix apart from an inference about an unsupported branch. If you are running one of those, the remediation is moving to a supported branch, and no amount of patching in place will help.
Is my Artifactory vulnerable to CVE-2026-82329?
Artifactory tells you its version without being asked. The X-JFrog-Version response header is emitted by a servlet filter that runs ahead of the authentication chain, so it is present on 401 and 404 responses just as it is on successful ones. That is what makes safe remote detection possible: you never need credentials, and you never need to touch the vulnerable endpoint.
For a single host:
curl -sD- -o /dev/null https://artifactory.example.com/artifactory/api/system/ping | grep -i x-jfrog-version
which returns something shaped like this:
X-JFrog-Version: Artifactory/7.161.16 716116900
Compare 7.161.16 against the fixed release for its own branch in the table above. In this example the branch is 7.161, the fix is 7.161.20, and 7.161.16 is below it, so that host is vulnerable.
Three traps to avoid when doing this by hand:
- Do not use
/access/api/v1/system/ping. It answers with the same header name but reports the JFrog Access service version, currently around 7.189.x, on a completely separate numbering line. It looks like a much newer Artifactory and it is not comparable to anything in the table. - Do not parse the login page.
/ui/login/embeds micro-frontend module versions that do not match the server version. We observed a host reporting server version 7.161.16 while its login HTML advertised 1.720.0, 7.189.5 and 7.161.11 in the same document. - Do not use
/ui/api/v1/system/version. That is the web UI bundle version. It returned1.720.0on both a 7.161.16 host and a 7.166.2 host in our testing, so it cannot distinguish anything.
Note also that the header is not emitted on /, on /artifactory/, or on /ui/login/. Internet-wide scanners fetch the root path, which is why Shodan and Censys banner data for Artifactory does not contain a version, and why this has to be an active probe rather than a corpus lookup.
JFrog Artifactory Nuclei template for CVE-2026-82329
Below is our detection template. It sends one ordinary GET, reads the version Artifactory advertises about itself, and compares it against the fixed release of its own branch. It forges no token, sends no JWT, and never contacts /access/api/v1/registry/join.
Why we did not publish an active check
An upstream Nuclei template for this CVE has existed since September 2 and it is a fine piece of work, but you should understand what it does before you run it. It builds the HS256 JWT signed with thirty-two spaces, posts it to the join endpoint, and matches on an HTTP 201 response containing a token field.
Look at what a match means. A match means the target accepted the forged JWT and issued a real service token, scoped admin, that does not expire. The template does not use that token, and it does not revoke it. It reports the finding and moves on.
Run that across an estate and every vulnerable instance you touch is left holding a live administrative credential that your scan created. It sits in the token store. It outlives the scan, it outlives the patch, and unless somebody goes looking for it specifically, it outlives the incident. On a client engagement that is a finding you caused. In a regulated environment it is arguably an unauthorised credential issuance you will have to explain.
Active confirmation has real value when the alternative is guesswork, and there is a defensible case for using it against a single host you own, with the token revoked immediately afterwards. There is no defensible case for spraying it across a customer’s attack surface for reconnaissance. Version inference answers the same question, at scale, with no footprint.
id: CVE-2026-82329
info:
name: JFrog Artifactory - Access Blank Join Key Authentication Bypass (CVE-2026-82329)
author: xer0dayz
severity: critical
description: |
Authentication bypass (CWE-287) in the JFrog Access service yielding admin
privileges to an unauthenticated remote attacker. Passive version-inference
check: reads the X-JFrog-Version response header and compares against the
fixed release of the instance's own branch. Sends no JWT and never contacts
/access/api/v1/registry/join.
reference:
- https://jfrog.com/help/r/jfrog-release-information/artifactory-security-fixes
- https://www.pruva.dev/reproductions/REPRO-2026-00341
- https://nvd.nist.gov/vuln/detail/CVE-2026-82329
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score: 9.8
cve-id: CVE-2026-82329
cwe-id: CWE-287
cpe: "cpe:2.3:a:jfrog:artifactory:*:*:*:*:*:*:*:*"
metadata:
verified: false
max-request: 2
vendor: jfrog
product: artifactory
patched-versions: "7.111.21, 7.117.28, 7.125.20, 7.133.29, 7.146.38, 7.161.20"
kev: true
kev-date: "2026-09-02"
shodan-query: "http.title:\"JFrog\""
fofa-query: "app=\"JFrog-Artifactory\""
tags: cve,cve2026,jfrog,artifactory,auth-bypass,kev,patch-gap,version-detect,passive
http:
- method: GET
path:
# The header comes from a servlet filter ahead of the auth chain, so it is
# present on 401 and 404 responses too. The second path is a guaranteed 404
# and exists for instances that block the API surface. Neither is emitted on
# "/", which is why Shodan banner data carries no version.
- "{{BaseURL}}/artifactory/api/system/ping"
- "{{BaseURL}}/artifactory/{{randstr}}"
stop-at-first-match: true
redirects: false
# Six disjoint per-branch windows. The gaps between them (7.112-7.116,
# 7.118-7.124, 7.147-7.160) were never released to self-managed customers and
# exist only on the Cloud/SaaS line. Same for the apparent hole at 7.146.37.
matchers-condition: or
matchers:
- type: dsl
name: artifactory-7.111.0-to-7.111.20
dsl:
- "version != '' && compare_versions(version, '>= 7.111.0', '< 7.111.21')"
- type: dsl
name: artifactory-7.117.0-to-7.117.27
dsl:
- "version != '' && compare_versions(version, '>= 7.117.0', '< 7.117.28')"
- type: dsl
name: artifactory-7.125.0-to-7.125.19
dsl:
- "version != '' && compare_versions(version, '>= 7.125.0', '< 7.125.20')"
- type: dsl
name: artifactory-7.133.0-to-7.133.28
dsl:
- "version != '' && compare_versions(version, '>= 7.133.0', '< 7.133.29')"
- type: dsl
name: artifactory-7.146.0-to-7.146.36
dsl:
- "version != '' && compare_versions(version, '>= 7.146.0', '< 7.146.38')"
- type: dsl
name: artifactory-7.161.0-to-7.161.19
dsl:
- "version != '' && compare_versions(version, '>= 7.161.0', '< 7.161.20')"
# Below the oldest supported branch. No fixed release exists for these, so
# they are reported as no-fix-available rather than as a version comparison.
- type: dsl
name: artifactory-7.x-eol-branch-no-fix-available
dsl:
- "version != '' && compare_versions(version, '>= 7.0.0', '< 7.111.0')"
extractors:
# Anchored on the header name so it can only match once per response. An
# unanchored Artifactory/[0-9.]+ would also capture the micro-frontend
# versions in the login HTML, and a multi-match extractor binds the variable
# to a list, which makes compare_versions() return false and turns the whole
# template into a silent false negative.
#
# The literal Artifactory/ prefix is load-bearing: it rejects the
# "JFrog Access/7.189.5" form of the same header, a different product on a
# different numbering line.
- type: regex
name: version
part: response
group: 1
internal: true
regex:
- '(?i)x-jfrog-version:\s*Artifactory/([0-9]+\.[0-9]+\.[0-9]+)'
- type: regex
name: artifactory_version
part: response
group: 1
regex:
- '(?i)x-jfrog-version:\s*Artifactory/([0-9]+\.[0-9]+\.[0-9]+)'
Save it and run it against a single target:
nuclei -t CVE-2026-82329.yaml -u https://artifactory.example.com
A hit looks like this, with the branch-specific matcher name telling you which window applied and the extracted version shown at the end:
[CVE-2026-82329:artifactory-7.161.0-to-7.161.19] [http] [critical] https://artifactory.example.com/artifactory/api/system/ping ["7.161.19"]
The template was validated against twenty-two boundary cases covering the last vulnerable and first patched release of every branch, both end-of-life branches, the never-released inter-window versions, a header-stripping reverse proxy, and a host serving only the JFrog Access form of the header. It was then verified end to end against live instances.
The false positive you will hit, and why we left it in
JFrog’s own public instances are patched out of band while still advertising an in-window version. releases.jfrog.io reports 7.161.16 – inside the vulnerable range – and is fortified. Our template flags it, and so will every other version-based check, including the commercial scanners.
We could suppress known JFrog-managed hostnames, and chose not to. A hostname allowlist would silently clear any instance an attacker could make look JFrog-managed, and it would go stale the moment JFrog changed infrastructure. A version-inference check should report what the version says and let you adjudicate. Treat a hit as “unpatched Artifactory on the attack surface,” confirm ownership and patch state, then escalate.
The corresponding false negative matters more. A reverse proxy, CDN or WAF in front of Artifactory can strip the header, in which case the template reports nothing at all. Absence of a finding here is not evidence of patching.
Detecting CVE-2026-82329 across your attack surface with Sn1per
One curl works for one host you already know about. The harder problem is the Artifactory instance nobody told you about: the one a team stood up for a proof of concept in 2022, pointed a wildcard DNS record at, and never decommissioned. That is the one running an end-of-life branch with no fix available.
The template above ships in the Sn1per curated Nuclei set, so a normal scan picks it up with no extra configuration:
sniper -t artifactory.example.com -m web
To sweep a whole estate rather than a single host, discover first and then scan what discovery found:
sniper -t example.com -m recon
sniper -f /usr/share/sniper/loot/workspace/example.com/domains/domains.txt -m web
The reason to run this as attack-surface work rather than as a one-off check is that Artifactory is rarely where you expect it. It sits on a subdomain, often behind a proxy that may or may not preserve the version header, sometimes on a non-standard port. External attack surface management is the discipline of finding the hosts before you scan them, and continuous testing is what catches the instance that appears three weeks after your last audit.
Which Sn1per edition fits
| Edition | Fits when |
|---|---|
| Community | You want to run the template and scan hosts you already know about. |
| Professional | You need scheduled rescans, the web interface, and reporting you can hand to somebody else. |
| Enterprise | You are tracking many workspaces, need per-asset risk tiers, and want this running on-premises with nothing leaving your perimeter. |
If you are weighing the last two, the Professional versus Enterprise comparison covers the differences in detail.
Remediation: patching is necessary and not sufficient
Upgrade to the fixed release of your branch: 7.111.21, 7.117.28, 7.125.20, 7.133.29, 7.146.38 or 7.161.20. You do not need to jump branches. If you are below 7.111 there is no fixed release and you have to move to a supported branch.
Then do the part most coverage omits.
Artifactory access tokens have lifecycles independent of the server version, so upgrading does not revoke a token that was already minted through this bug. The token this vulnerability produces is admin-scoped and set to never expire. If somebody used this against your instance before you patched, the credential they walked away with still works on your patched instance. The upgrade closes the door; it does not evict anyone already inside.
This is exactly why CISA set the forensic-triage flag on this KEV entry, an obligation to determine whether you were already compromised and to preserve evidence before remediating, rather than simply to patch. That flag is the genuinely notable thing about the KEV listing. The three-day federal deadline attached to it is not: under BOD 26-04, which replaced BOD 22-01 in June 2026, roughly three quarters of new KEV entries get three days automatically. It is a scoring table output, not a statement about this bug in particular.
After upgrading, work through the following:
- Enumerate admin-scoped tokens via
GET /access/api/v1/tokensand revoke anything you cannot account for. Pay attention to tokens with no expiry. - Review
/access/api/v1/usersfor accounts you did not create. Backdoor user creation has been reported in a small number of observed cases. - Search server logs for
Adding join key with kid: e3b0c442, which is the artifact left by a successful join against the blank key. - Rotate the administrator password and any credentials that were reachable from an admin session.
- Because Artifactory stores build artifacts, consider whether anything published during the exposure window needs to be re-verified. Write access to a binary repository is write access to whatever your pipelines pull from it.
What is actually known about exploitation
This CVE is in KEV, which means CISA has evidence of exploitation, and it should be treated as urgent. It is still worth being precise about what is publicly established, because the reporting has compressed a narrow claim into a broad one.
The in-the-wild reporting traces to a single source: watchTowr, whose honeypots observed admin-token minting, enumeration of users and tokens, and in a limited number of cases the creation of backdoor accounts. Their own characterisation is measured – a small number of IP addresses, and no evidence of broad-scale scanning or mass exploitation at the time of writing. As of this writing, GreyNoise and Shadowserver have published no corroborating telemetry, no indicators of compromise have been released, and JFrog has not confirmed exploitation.
None of that argues for patching more slowly. It argues for describing the threat accurately: a trivially weaponisable bug with public proof-of-concept code, confirmed exploitation from one credible source, and no public evidence yet of mass exploitation. Those are the conditions under which you patch this week rather than the conditions under which you declare an incident.
One correction worth making, because it is circulating: this is not the JFrog supply-chain incident. That was CVE-2026-66384, a path traversal permitting a write to the Docker cache, added to KEV on August 27, 2026. Two different JFrog CVEs a week apart, and the supply-chain compromise narrative belongs to the other one.
Frequently asked questions
Is CVE-2026-82329 exploitable without credentials?
Yes. The attacker needs network access to the Artifactory instance and nothing else. The forged JWT is signed with a key that is a constant on every default installation, so there is no secret to obtain and nothing to guess. That is why it scores 9.8 with attack vector Network and privileges required None.
Does CVE-2026-82329 affect JFrog Cloud or SaaS?
No. The vulnerability applies to self-managed Artifactory. JFrog Cloud instances were fortified separately and are out of scope. If you run both, scope your remediation to the self-hosted estate.
I upgraded to 7.161.20. Am I done?
Not quite. Upgrading closes the vulnerability but does not revoke admin tokens that were minted through it beforehand, because Artifactory token lifecycles are independent of the server version, and the tokens this bug produces never expire. Audit GET /access/api/v1/tokens for admin-scoped tokens you did not create, and check /access/api/v1/users for unfamiliar accounts. CISA’s forensic-triage flag on this KEV entry is asking for exactly that.
Why does my patched 7.146.38 instance get flagged by some scanners?
Because they are comparing against a single version rather than against your branch’s fix. Remediation here is per-branch, so 7.146.38 is patched even though it is numerically lower than the vulnerable 7.161.19. Any check written as “less than 7.161.20” gets this wrong. The template in this post carries six separate branch windows for that reason.
Can I detect CVE-2026-82329 without exploiting it?
Yes, and you should prefer to. Artifactory emits its version in the X-JFrog-Version response header from a filter that runs before authentication, so an unauthenticated GET to /artifactory/api/system/ping is enough to determine patch state. The publicly available active check confirms the bug by completing the authentication bypass, which leaves a real admin-scoped, non-expiring token on every vulnerable host it touches.