Zammad 7.0 shipped in March 2026 with a set of new AI features, including customizable “text tools” that let agents use LLM-powered writing assistance within tickets. Admins can restrict these tools to specific groups, so only the Sales team gets the Sales prompt, and so on.
I audited the 7.0 codebase shortly after release and found two independent authorization failures in the REST endpoint used to execute AI text tools. One bug let an agent invoke tools outside their allowed group scope. The second let the same endpoint resolve unauthorized context objects into the prompt template. As a result, data from tickets outside the agent’s access scope could end up in the AI prompt and potentially in the returned model output.
Zammad patched this in 7.0.1. The findings resulted in two CVEs: CVE-2026-34782 for the text tool authorization bypass and CVE-2026-34837 for the context data IDOR.
A third finding from the same audit, a CSRF in OAuth callback endpoints (CVE-2026-34721), was also patched in 7.0.1 and backported to 6.5.4, but is not covered in this post.
Background
Zammad’s AI text tools are templates with instruction prompts that can reference ticket fields via placeholders like #{ticket.title} or #{customer.firstname}. When an agent runs a tool, Zammad resolves these placeholders against the ticket context and sends the rendered prompt to the configured LLM provider.
Access control for text tools uses Pundit policies. The relevant policy (AI::TextToolPolicy) checks whether the agent’s group memberships overlap with the tool’s assigned groups via an agent_accessible? method. If a tool has no group restriction, it is available to all agents.
Root cause 1: text tool authorization bypass (CVE-2026-34782)
The new desktop UI (built on GraphQL) correctly enforces authorization when resolving the text tool ID. The GraphQL mutation aiAssistanceTextToolsRun resolves the tool via a loads: directive that triggers policy checks, and the listing query aiAssistanceTextToolsList applies the policy scope to filter tools by group access.
The REST endpoint POST /api/v1/ai_assistance/text_tools/:id does not. It loads the tool like this:
text_tool = AI::TextTool.find_by(id: params[:id])
No authorize!(text_tool, :show?) call follows. The Pundit policy exists but is never invoked on this path. An agent can execute any active text tool by ID regardless of group assignment.
Root cause 2: context object IDOR (CVE-2026-34837)
Independent from the first issue, the same REST endpoint accepts ticket_id, and also direct context IDs such as customer_id, group_id, and organization_id that are used to render the tool’s instruction template. The private method template_render_context resolves these via find_by without any object-level authorization:
def template_render_context(params)
result = {}
if params[:ticket_id].present?
result[:ticket] = Ticket.find_by(id: params[:ticket_id])
result[:customer] = result[:ticket]&.customer
# ...
else
result[:customer] = User.find_by(id: params[:customer_id])
result[:group] = Group.find_by(id: params[:group_id])
result[:organization] = Organization.find_by(id: params[:organization_id])
# ...
end
result
end
Any agent can reference any ticket by ID. The ticket’s fields are interpolated into the AI prompt. Since Zammad uses sequential integer IDs for tickets, enumeration is trivial.
This second root cause is worth emphasizing because it is independent. Even if tool-level authorization were fixed, a globally available default text tool (one with no group restriction) would still let any agent feed arbitrary ticket data into the prompt context.
Proof of Concept
Setup:
- Zammad 7.0.0 with an AI provider configured
- Two groups: “Support” and “Sales”
- Agent1 assigned to “Support” only
- A ticket in “Sales” with recognizable content (e.g., title “CONFIDENTIAL-SALES-Q1-REPORT”, customer “Nicole”)
- An AI text tool assigned to “Sales” only, with an instruction containing
#{ticket.title}and#{customer.firstname}
First, confirm that the GraphQL listing correctly hides the Sales tool from Agent1:
curl -u "agent1@test.local:Agent1!" \
-X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{"query":"query { aiAssistanceTextToolsList { id name } }"}'
Response returns only the four default tools. The Sales-scoped tool (ID 6) is not listed. The policy works here.
Now execute the same tool via the REST endpoint, passing the Sales ticket as context:
curl -u "agent1@test.local:Agent1!" \
-X POST http://localhost:8080/api/v1/ai_assistance/text_tools/6 \
-H "Content-Type: application/json" \
-d '{"input":"Summarize the ticket context","ticket_id":3}'
No Pundit::NotAuthorizedError in the server logs. The request passes authentication and proceeds into the AI service layer. In my test environment the AI provider returned a format error, but this happened after the authorization boundary was passed and the prompt was already rendered.
The AI analytics error log (Admin > AI > Analytics > Download Errors) confirms the leak. The prompt_system field contains:
Rewrite the following text for ticket "CONFIDENTIAL-SALES-Q1-REPORT" from customer Nicole: -
The ticket title and customer name from the Sales group were resolved into the AI prompt despite Agent1 having no access to that group.
Impact
Any agent with ticket.agent permission on at least one group could:
- Execute AI text tools assigned to other groups, bypassing the group-based scoping
- Render ticket, customer, group, and organization fields from arbitrary objects into the prompt
- With a working AI provider, potentially receive the exfiltrated data in the returned model output
In a real deployment this means an agent in the Support group could systematically pull data from HR tickets, Sales deals, internal investigations, or anything else separated by Zammad’s group model. The sequential ticket IDs make bulk enumeration straightforward.
I want to thank the Zammad Security Team for their quick and professional communication and handling of this vulnerability.
References
- GHSA-96r7-29c8-2j7q (CVE-2026-34782, text tool authorization)
- GHSA-89vv-6639-wcv8 (CVE-2026-34837, context data IDOR)
- GHSA-mfwp-hx66-626c (CVE-2026-34721, OAuth CSRF, same audit)
- Zammad 7.0.1 release