Notebook · 2026-09-18
When execution forgets who granted it
A critical cross-tenant authorization bug in Appsmith, and a shape I keep meeting somewhere new.
Written with AI assistance. I am one of three credited reporters on this advisory, not the sole finder; the other two are @komyunghan and @nasaa0x. Everything below is drawn from the published advisory. No exploitation detail is added to it, and nothing here concerns any finding still in coordination.
Appsmith published a critical advisory on 14 September 2026. The bug is worth a paragraph. The shape behind it is worth the rest of this page.
The finding
An authenticated Appsmith user could make an action in a public application execute against a datasource belonging to another workspace, because datasource authorization was not enforced during action validation.
The consequences follow from that one omission. Queries run with the victim datasource’s stored credentials. The attacker needs no membership in the victim workspace. The visitors who trigger the public application need not be authenticated at all. It affects Community, Enterprise and Cloud deployments, and requires no special server configuration.
Versions up to and including 2.2 are affected; 2.3 carries the fix. The advisory is GHSA-fhgw-q2jf-8fq7, classified CWE-862, Missing Authorization, with vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N and a score of 9.6. Note S:C — scope changed — which is the formal way of saying the damage lands outside the component that contained the flaw. No CVE has been assigned.
Full details, from the maintainers rather than from me: GHSA-fhgw-q2jf-8fq7.
The shape
Strip the product away and the defect reads like this:
A resource is bound to a caller at one moment. It is executed at a different moment. Between those two moments, nobody re-asks whether this caller may still use this resource.
Authorization was not missing from the system. Appsmith knows perfectly well which workspace owns a datasource, and enforces that elsewhere. What was missing was the check on the path that actually executes. The grant and the use had drifted apart, and only the grant was guarded.
That is why this class survives review. Read the permission model and it is coherent. Read the execution path and the question is simply never asked. The two readings do not contradict each other, so neither reader notices.
Why it is becoming more common, not less
The pattern needs one ingredient: a gap between binding a capability and invoking it. Older architectures had a small gap, because the request that checked your permission was usually the request that did the work.
Modern platforms have widened that gap enormously, and deliberately:
- Low-code and internal-tool builders bind a datasource at design time and execute it at run time, often on behalf of someone who is not the person who bound it.
- Agent and tool-calling systems register a tool once and then dispatch it many times, from a context the registration never anticipated.
- Connector and integration layers hold long-lived credentials on a user’s behalf and replay them later.
Each of those is the same sentence with different nouns. The credential outlives the context that justified it, and the execution path has to re-derive an authorization decision that someone else made earlier, somewhere else. When it does not re-derive it, you get this bug.
What I would check in your own system
Not a methodology. Just the questions that are worth asking once:
- List every place a stored credential is used. For each, name the line that decides the current caller may use it. If you cannot point at the line, that is the finding.
- Look for the word “public”. Public pages, public apps, public endpoints — anywhere the authenticated path and the anonymous path converge on the same executor.
- Separate validation from execution. If your code validates an action in one function and runs it in another, check that both consult the same authority. Validation-time checks that execution does not repeat are the classic form.
- Ask who the “caller” is at each hop. In systems with deferred execution, the identity at dispatch is frequently not the identity at request. Whichever one the authorization check reads, make sure it is the one that matters.
None of that requires a scanner. It requires reading the execution path with the permission model held in your other hand, and noticing when they stop talking to each other.
On the credit
Three of us are credited as reporters: @komyunghan, myself, and @nasaa0x. I want to be exact about that, because independent discovery by several people is itself a fact about the bug — it means the shape is findable, which is the whole argument above.
Thanks to the Appsmith team for a clean disclosure process and a genuinely well-written advisory: it states the impact, names the affected deployments, gives workarounds, and tells operators what to audit after upgrading. That is more than most.