CVE-2026-109433: How One Disabled Permission Disabled Them All in Camunda Cockpit
CVE-2026-109433
7.1
Cockpit keeps a private copy of the engine's "is this permission disabled?" predicate with the comparison reversed. It returns the right answer for an empty list and the wrong answer for every other input. The empty list is the shipped default, which is why it survived seven years.
Severity | High, 7.1 (CVSS 4.0) / Medium, 6.5 (CVSS 3.1) |
Confirmed on |
|
Status | CVE-2026-109433, reported through VulnCheck as CNA |
TL;DR
Defect:
AbstractCockpitPluginResource.isPermissionDisabled()returnstruewhen the configured list contains any permission other than the one being tested. Its caller then skips adding the permission check, so the plugin query runs with no authorization filter.Impact: With one unrelated entry in
disabledPermissions, a user holding onlyAPPLICATION cockpit ACCESSand no data grants received every process instance, definition and incident in the engine from the Cockpit plugin endpoints.Precondition:
disabledPermissionsnon-empty. It defaults toCollections.emptyList(), so a stock deployment is unaffected. One entry, any entry, is enough.Status: CVE-2026-109433, reported through VulnCheck as CNA. Camunda 7 Community Edition is End of Life, so a local patch is the realistic remedy.
At a Glance
Product | Camunda 7 ( |
|---|---|
Issue | High: Cockpit plugin endpoints skip authorization filtering when |
CVE | CVE-2026-109433 (an earlier identifier, CVE-2026-77228, was assigned to the same defect) |
Class | Incorrect authorization from a duplicated security predicate with a reversed comparison |
Root cause |
|
Impact | A user with only |
Requires | Not the default. |
Introduced | Commit |
Tested on |
|
Severity | 7.1, High CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N |
Fix status | Camunda 7 Community Edition is End of Life; a local patch is the realistic remedy |
Disclosure | Reported through VulnCheck as CNA |
Found by | CodeAnt AI Security Research Team |
The Same Predicate, Written Twice
disabledPermissions is a documented process-engine option. Its javadoc describes it as the permissions that will not be taken into account in authorization checks, and the intended behaviour is narrow: drop the named permission, keep enforcing everything else. The engine's test suite pins exactly that, in DisabledPermissionsAuthorizationTest.testDeploymentStatisticsIgnoreReadInstance(), which disables READ_INSTANCE, asserts the instance count becomes visible, and asserts in the same test that the unrelated incident check still filters.
The engine implements it the way you would expect:
Null-guarded, and true only when the permission under test is the one named. Cockpit has its own copy, in its own class, in a different module:
One character of difference, and the null guard is gone too. Put the two side by side on real inputs and the copy is right in precisely one column:
| Testing | Engine says | Cockpit says | |
|---|---|---|---|---|
| is READ disabled? | false | false | agree |
| is READ disabled? | true | false | inverted |
| is READ disabled? | false | true | check dropped |
| is READ disabled? | true | true | right answer, wrong reason |
The third row is the vulnerability. An operator disables one permission for an entirely unrelated reason and Cockpit begins reporting that every permission is disabled.
What The Caller Does With That Answer
The predicate would be harmless if its result were advisory. It is not. The caller uses it to decide whether to attach the authorization filter to the query at all:
No filter is added, so the SQL runs unrestricted. The endpoint does not fail, it over-answers.
The base plugin's process-instance resource adds exactly two checks this way, and with one unrelated permission in the list both are dropped in the same request:
The same pattern appears in IncidentRestService, ProcessDefinitionRestService and the base-plugin sub-resources.
Born Inverted, And We Got The Blame Wrong First
We originally attributed this to commit 7655ff35c7 (2019-03-26), "chore(webapp): adjust disabledPermissions property type", and told the story as a mechanical type refactor that silently inverted a security check. That attribution went into our advisory and into writing with the CNA. It is wrong, and the correction is worth more than the original.
7655ff35c7 changed only the type:
The negation is on both sides of the diff. The refactor inherited the bug, it did not cause it.
The real origin is 1d53b8de3a (2019-03-18), "fix(webapp): move permission check to webapp", eight days earlier. That commit added isPermissionDisabled to the Cockpit class, with the comparison already reversed. There was never a correct version of this method in the webapp.
Which makes it a better story, not a worse one. Nobody's refactor broke a working control. Somebody moved a permission check out of the engine and into the webapp, hand-wrote the predicate from scratch in the new location, reversed the comparison on the first attempt, and no test existed to notice. Nothing under webapps/assembly/src/test/java references isPermissionDisabled or disabledPermissions to this day.
Duplicating a security predicate into a second module is the defect. The reversed comparison is just the form it took.
A Single-variable A/B
Two instances, same image, same configuration, same user whose only grant is APPLICATION cockpit ACCESS, differing by one property. The control instance is checked for cleanliness first, so the difference cannot be misattributed:
One entry, naming a permission neither dropped check tests, removes both dropped checks.
What This Does Not Do
Our own report overstated this in two places, and both corrections cut the impact down. They are here rather than buried, because a maintainer who finds them first is entitled to discount everything else we wrote.
No process variables, no incident messages, no stack traces. The report claimed all three. The exposure is metadata:
ProcessInstanceDtocarriesid,businessKey,startTime,incidentsandsuspensionState, andProcessDefinitionDtocarriesid,name,key,version,calledFromActivityIdsandfailedJobs. We pivoted a leaked instance id toengine-rest/process-instance/<id>/variablesand gotAuthorizationException. Variables appear in this plugin layer only as query criteria, never in the response.Not across every tenant. The report claimed tenant-wide reach. Tenant isolation is a separate mechanism,
configureTenantCheck, and it still works. We verified it does.Invisible in the UI, which cuts both ways. Cockpit still renders "No process definitions deployed" for an affected user. The leak is in the HTTP response body, reachable by curl, devtools or
fetch()with an ordinary Cockpit session cookie. A tester who only clicks through the interface will wrongly conclude the instance is unaffected.
What remains is a read-only breach of the authorization model for the Cockpit plugin surface: instance ids, definition keys and names, instance and incident counts, start times. Enough to map an organisation's processes and their volumes, not enough to read their contents.
Severity
Version | Vector | Score |
|---|---|---|
CVSS 3.1 |
| 6.5 Medium |
CVSS 4.0 |
| 7.1 High |
This one crosses the severity word between versions, which is a property of the two formulas rather than a disagreement about the facts. The same vector content is Medium under 3.1 and High under 4.0, and we are publishing both rather than picking the flattering one.
We tried to justify 7.0 or above under 3.1 and could not. Two routes exist and both fail on the evidence. PR:N would give 7.5, but the exploit needs an authenticated session holding APPLICATION cockpit ACCESS, so PR:L is simply what the precondition is. S:C would give 7.7, and we argued it adversarially on the grounds that leaking across an authorization boundary crosses a security authority; the argument does not hold, because the leak stays inside the engine's own authorization scope and the separate tenant mechanism remains intact.
I:N and A:N are deliberate. The affected code path is query filtering, and no write path was shown to be affected.
Fix And Detection
Delete the duplicate. PermissionCheckBuilder:110-112 already reaches the engine's implementation, which is the non-duplicating route the codebase itself demonstrates. If the copy has to stay, it is one line:
That restores the null guard the copy dropped, and the comparison the copy never had.
Check whether the property is set, and if it is, that is the whole answer. Any non-empty
disabledPermissionson a 7.x deployment means Cockpit plugin queries are unfiltered today.Add the test as an A/B. The engine has
DisabledPermissionsAuthorizationTest; the webapp has nothing. The control arm matters as much as the test arm, since the bug is the difference between them.Detection is silent by design. Nothing is logged when the property is set, Cockpit renders normally, and an operator who enabled the option for performance has no signal that they also disabled authorization filtering.
Credit & Scope
Finding | CodeAnt AI Security Research Team. The divergence between the two implementations, the single-variable A/B and the browser confirmation that the UI hides it are ours. |
Corrected here | Our advisory attributed the inversion to |
Coordination | Reported through VulnCheck as CNA. CVE-2026-109433. An earlier identifier, CVE-2026-77228, was assigned to the same defect. |
Testing | Locally-provisioned containers only. Read-only; nothing destroyed, nothing exfiltrated, no third-party system touched. |
[FAQ]
Frequently Asked
Questions
What is CVE-2026-109433?
Does this affect every Camunda 7 installation?
How do I know if I’m affected?
Does an attacker need an account?
What data is exposed?
Does this cross tenant boundaries?
Why can’t I see the leak in the Cockpit UI?
More from CodeAnt
[GET STARTED]
START PENTEST







