Abstract technical illustration of two routes reaching one sealed vault. An upper route meets a red translucent permission membrane and stops against it. A lower route bends down through a chain of small hexagon nodes, slips beneath the membrane, and reaches the vault's side door along a green thread. A brass key sits behind frosted glass inside the vault. Dark graphite palette, no text, no logos, no people.
AI Safety Incidents

MaxKB: the agent dispatch path lets a user denied a tool still call it and read its credentials

Author

Rowan Sterling

Date Published

Latest models · 20View all models →
Benchmarks: Artificial Analysis · updated daily
Back to all posts

MaxKB, the open-source enterprise AI assistant maintained by 1Panel-dev, ships a per-user, per-tool permission list so an administrator can grant a workspace member one tool and deny another. According to the security advisory published on 2 September 2026, that denial held on the tool's own routes but not on the path an agent or workflow uses to call the same tool. A workspace member holding the lowest role could name a denied tool's identifier when building a workflow, run the workflow, and receive not only the tool's output but the credentials MaxKB decrypts server-side in order to run it. Versions 2.0.0 through 2.9.2 are affected, and the advisory named no patched version. This record — the MaxKB tool-dispatch authorization bypass — is one entry in the OrcaRouter AI Incident Archive, which holds the full record set and this incident's neighbouring entries.

What makes the case worth reading closely is not the score. It is that the product already had the right check, applied it on the obvious door, and left a second door to the same capability unwatched. The advisory, GHSA-383v-fx78-pphm, was written by the researcher who found it and published through GitHub's advisory system at 07:43 UTC on 2 September 2026, crediting Matteo Panzeri (@matte1782). The CVE record, CVE-2026-77516, followed on 21 September 2026.

What the advisory establishes

MaxKB's per-tool model is WorkspaceUserResourcePermission, described in the advisory as a default-deny allow-list resolved at request time. The dedicated tool routes enforce it. The advisory lists the enforcement in apps/tools/views/tool.py, where Operate.put, Operate.get, Debug.post, Export.get and Delete each carry a view-permission check against the caller's workspace tool permission. Debug.post is the route closest to simply running a tool, and it is gated.

The agent and workflow dispatch path does not re-apply that check. The advisory traces three separate places where it goes missing. In apps/application/serializers/application.py, the application serializer binds tool_ids, skill_tool_ids and mcp_tool_ids straight from request input with no per-tool authorization — and the advisory points out that the asymmetry is visible in the same file, because knowledge_id_list is validated against the user's accessible knowledge bases a few lines below while tools are not. In apps/common/utils/shared_resource_auth.py, filter_authorized_ids passes every tool in the caller's own workspace unconditionally; the cross-workspace gate it relies on is an enterprise-only hook that is absent from the community edition, and it never consults WorkspaceUserResourcePermission. And at the sink, apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py runs the tool's stored code with its init_params decrypted by rsa_long_decrypt under the application's identity, with no per-tool grant check.

The advisory calls the result a confused-deputy execution: the workflow dispatch runs a tool, and its stored secrets, that the same caller is refused on the tool's own routes. The credential exposure is the part that exceeds ordinary unauthorized tool use. A tool's init_params are encrypted at rest with RSA and decrypted server-side only at execution time, so a caller who triggers that execution receives values they never held and could not have supplied.

Diagram titled 'Two doors to the same tool'. A dedicated tool route passes a WorkspaceUserResourcePermission gate and returns 403. The agent and workflow dispatch route bypasses the gate, binds tool_ids without a per-tool check, and returns 200 with the tool's decrypted secret.

The demonstration, in the researcher's own numbers

The advisory reports dynamic confirmation on v2.9.2, run from the official all-in-one image, 1panel/maxkb:v2.9.2. The setup was one workspace, an administrator, and a second member holding the USER role. The administrator created Tool A and Tool B, put a fresh random secret in Tool B's init_params, and wrote Tool B's body to return that secret when executed. The member was then granted a per-tool permission for Tool A only, and the advisory says the zero-grant state for Tool B was re-checked in the database after the run.

The denied half of the test returned what the product promises. As the member, the debug route for Tool B answered 403, the update route answered 403, the export route answered 403, and the granted Tool A returned 200 as a sanity check. The bypass half then bound Tool B into a workflow application through a tool-library node — the bind was accepted with HTTP 200 and no per-tool check — and running the workflow returned HTTP 200 with a body containing the tool's name, the sentinel secret, and the key list ["api_key"].

The advisory says the secret surfaced at three layers: in the workflow API response, in a tool execution record with state SUCCESS, and in the database row where Tool B's init_params sits as a 344-byte RSA ciphertext — which is how the researcher argues the value was decrypted server-side under the application identity rather than supplied by the caller. The same member who was refused on the tool's run route obtained the tool's decrypted secret by binding it into a workflow.

One score published, a higher one argued

The score attached to CVE-2026-77516 is CVSS 3.1 5.4, Medium, with the vector AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N. NVD's record shows that score sourced from GitHub's advisory feed rather than from NVD's own analysis, and the entry carries the status Deferred, meaning NVD has not enriched it. The archive's own classification follows that published score: medium severity, no recorded real-world harm.

The advisory's own impact section argues for more. It states a severity of 7.1, High, on a vector that raises confidentiality impact to high, and says the figure rises to 8.1 when the dispatched tool performs privileged writes under the application identity. That is a disagreement about how to count the credential exposure, not about whether the bypass exists — the same document both demonstrates the flaw and argues it is being underweighted. Nothing in the public record resolves which figure is right, and the archive's medium rating tracks the published CVE score rather than the advisory's argument.

Two further assessments are on the record. CISA's SSVC decision points, added to the NVD entry on 28 September 2026, record exploitation as "poc", automatable as "no", and technical impact as "partial" — the "poc" value reflects that a proof of concept exists, which it does, in the advisory itself; it is not a finding of exploitation in the wild. FIRST's EPSS model put the probability of exploitation in the next 30 days at 0.242%, at the 15.2nd percentile, in the estimate dated 27 October 2026.

Score card comparing three figures: published CVSS base score 5.4 medium with vector AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N; 7.1 high argued in the advisory's impact section, rising to 8.1 for privileged writes; and FIRST EPSS 0.242 percent at the 15.2nd percentile, dated 27 October 2026.

A fix that predates the advisory, and none inside the affected range

The advisory's version table says affected 2.0.0 through 2.9.2, patched versions none, and its text says no fixed version is available as of the review, adding that a fix in a tagged release would let a CVE anchor to it. The vendor's repository tells a slightly different story, and the difference matters to anyone deciding what to run.

In the MaxKB repository, the functions that close the dispatch path — validate_bound_tool_permissions and get_authorized_tool_ids in apps/application/serializers/application.py — are absent from the v2.9.2 tag and from v2.10.2-lts, v2.10.3-lts and v2.10.4-lts, and present from v2.10.5-lts onward. That release was published on 6 August 2026, four weeks before the advisory. The same tag adds a per-user tool check to base_tool_lib_node.py, drawing on WorkspaceUserResourcePermission, and adds a user-aware filter to shared_resource_auth.py — which is to say it implements, in outline, the three remediation steps the advisory later recommends.

Nobody has stated that connection publicly. The v2.10.5-lts release notes do not mention a tool-permission fix; they list four unrelated security items and a batch of feature and bug work. The advisory does not name 2.10.5, and its affected range simply stops at 2.9.2, the last release of the 2.9 line. The release history shows no 2.9.x patch after the advisory, so a deployment on 2.9.2 has no fixed build inside its own line. The current release is v2.10.8-lts, published on 22 October 2026, whose notes describe X-Pack fixes to shared-tool authorization rather than anything in the community edition's dispatch path.

Timeline card. 6 August 2026, v2.10.5-lts published with the per-user tool check present in tagged source. 2 September 2026, advisory GHSA-383v-fx78-pphm published, credited to Matteo Panzeri. 21 September 2026, CVE-2026-77516 published to NVD. 22 October 2026, v2.10.8-lts is the current release.

What is not established

Three things are worth separating from the demonstration. First, there is no public report of the bypass being used against anyone. The record is a vulnerability that was found, not an intrusion that was investigated; it carries no victim, no attributed actor and no harm, and the advisory's proof of concept was run by the researcher in a controlled workspace. Second, no news organisation, regulator or second research team appears to have analysed the flaw: the coverage that exists is confined to vulnerability databases and aggregators — VulDB, Rapid7's database, Armis, SOCRadar and openCVE among them — which list the CVE and carry the advisory's description, and none of which offers an independent assessment of the code. The mechanism as described here therefore rests on one advisory and on the vendor's own source code. Third, no figure exists for how many MaxKB deployments sit in the affected range, and neither the vendor nor the researcher has published one.

The population at risk is narrow but not trivial to count: self-hosted community-edition MaxKB installations on 2.0.0 through 2.9.2 that have more than one workspace member, where an administrator has used per-tool permissions to withhold a tool that carries credentials. The flaw is not remote code execution and needs a valid low-privilege account, which is why the confidentiality impact is what drives the score rather than the attack path. What an attacker gains is bounded by what the denied tools hold — API keys, tokens, and whatever those unlock — which is also why the advisory's authors argue the impact is larger than a low confidentiality rating implies.

What an operator can do

The advisory's remediation is addressed to the maintainers rather than to operators, and its three steps map onto the code that appeared in v2.10.5-lts: apply the per-tool grant before executing a tool in the tool-library and tool node implementations, validate every bound tool_ids, skill_tool_ids and mcp_tool_ids at binding time the way knowledge_id_list already is, and make filter_authorized_ids consult the per-tool grant rather than workspace membership alone.

For a team running an affected build, the practical steps that follow from the advisory's own description are these.

• Treat every secret stored in a tool's init_params on an affected deployment as reachable by any workspace member, including the lowest role, and rotate those credentials rather than assuming the per-tool denial protected them.

• Audit workspace membership. The bypass needs an authenticated account in the workspace, so removing dormant or untrusted members reduces the exposure even where the code cannot be patched.

• Review which workflows and agents bind tools that carry credentials, since that binding is the step the advisory says is accepted without a per-tool check.

• If upgrading, note that the per-user check appears in the repository from v2.10.5-lts onward and that no release is designated as the fix by the advisory — an operator relying on the upgrade is relying on the tagged source, not on a vendor patch statement.

None of this makes the flaw dramatic. It makes it ordinary in a specific and instructive way: the authorization model was designed correctly, documented correctly, and enforced on the routes someone remembered to gate. The dispatch path was written by people thinking about what a workflow should be able to do, not about who was asking — and in an agent platform, that is the path most requests actually take.

The record's lesson is structural rather than numeric. A per-resource permission is only as strong as its least-guarded entry point, and an agent platform accumulates entry points quickly: a debug route, an export route, a workflow node, a chat trigger. MaxKB had the grant list and the check; what it lacked, for the 2.0 through 2.9 line, was the habit of asking the same question at every door.