On 25 September 2025, Noma Security disclosed ForcedLeak, a CVSS 9.4 flaw in Salesforce Agentforce. An attacker typed hidden instructions into a public Web-to-Lead form. Later, when a salesperson asked Agentforce to look at the new lead, the agent obeyed them and sent CRM data to a domain that was still on Salesforce's allowlist, but had expired. Noma bought it for about $5.
On 25 September 2026, exactly one year later, Zenity Labs disclosed SalesBleed. The attacker again started with the public Web-to-Lead form, and the agent again obeyed text a stranger had typed into it. This time the CRM data went out through a gap in Trusted URLs, the control Salesforce built in response to ForcedLeak.
Both bugs are patched. The question to ask is why the same door let an attacker in twice, and what that means for every other agent built the same way.
Zenity Labs' write-up describes three flaws. Researchers Alex Apostolov, João Donato, Avishai Efrat and Ayush RoyChowdhury reported them to Salesforce on 1 June 2026. The attack chain works like this:
Trusted URLs was supposed to redact any link that wasn't on an approved list. Zenity found two ways around it. The redactor only recognised a fixed set of top-level domains, and .fun wasn't among them. The redactor and the browser also disagreed about where a URL ends: curly braces and square brackets kept a string out of the redactor's view, but the renderer still treated it as a link.
The third flaw turned the agent into a phishing tool. Agentforce's Slack integration could reply to Slack threads without asking the user to confirm. The message appeared as coming from the agent, and the agent kept no record of which user's request caused it. So a poisoned lead could post a convincing internal message into active business threads, and neither the recipient nor the security team could see who was behind it.
| Date | Event |
|---|---|
| 1 June 2026 | Zenity reports all three issues to Salesforce |
| 2 June 2026 | Salesforce confirms and commits to fixes |
| 18–19 August 2026 | Trusted URLs bypass fixed; Zenity verifies |
| 20 August 2026 | Slack attribution fix confirmed |
| 21 September 2026 | User-confirmation requirement for Slack replies completed |
| 25 September 2026 | Public disclosure |
No CVEs were assigned. Zenity says Salesforce was collaborative throughout, and the full chain no longer works. It also points out that the new confirmation step for Slack replies can be turned off with a single click. That makes it a setting to check, not just a fix to note.
Put the two disclosures next to each other and a pattern appears:
| ForcedLeak (2025) | SalesBleed (2026) | |
|---|---|---|
| How the instructions got in | Public Web-to-Lead form | Public Web-to-Lead form |
| Why the agent could reach the data | CRM tools scoped to far more than one lead | Query Records could read Accounts during a leads task |
| How the data left | Expired domain still on an allowlist | Redaction gaps in Trusted URLs, image auto-load, Slack unfurl |
| What the fix targeted | The exit (Trusted URLs enforcement) | The exit (hardened Trusted URLs), plus Slack confirmation and attribution |
Both fixes worked, and both were aimed at the exit. That is understandable. Web-to-Lead is supposed to accept text from strangers; you cannot close it. The General CRM subagent is supposed to be broadly useful. The exit is the part that looks fixable.
But an exit filter is a list of known-bad patterns trying to keep up with a model that will write URLs any way it is told. Last year the gap was a forgotten domain. This year it was an unrecognised TLD and a disagreement over a curly brace. Next year there will probably be another. A filter like this is worth having, but on its own it doesn't close the problem.
Researcher Simon Willison has a name for the underlying setup: the "lethal trifecta." An agent that (1) reads untrusted content, (2) can access private data, and (3) can communicate externally can be turned against its owner. Both Agentforce bugs had all three. So does almost every "summarise the inbound request and look up the customer" agent that companies are building right now, whether for support tickets, supplier invoices, job applications or web chat.
We have made a related point before, about email summarisers that read hidden text their users cannot see. SalesBleed adds something worse. The injected text doesn't only change what the agent says. It changes what the agent queries, and which identity it speaks with.
If you run Agentforce:
For every agent you build that reads public input:
Salesforce fixes Agentforce itself. What most organisations lack are the same controls on the other agents they build: agents that read public forms, tickets and email and then call a model. Governance AI sits in the request path for those agents and addresses each part of the trifecta separately, rather than relying on the exit alone.
Inspect what comes in. When an agent pulls a lead, ticket or email into its prompt, that content passes through inspection before it reaches the model. Jailbreak and injection detectors run in-path, and a violation can block the request, flag it, or send it to the Review Queue.
Limit what the agent can reach. Allow-lists register the tools each agent may call. A lead-triage agent approved for one object gets its call to the accounts table blocked, whatever the prompt told it to do. Tool calls are their own decision point in Policy Sets, so "query outside scope" is a rule you write once and attach to the agent.
Check what goes out. Model responses are inspected before delivery, including the URL detector, and the domain allow-list catches attempts to reach unregistered endpoints. This is still an exit control, and it is not the only layer, which is the point.
Know who asked. Every prompt, response, tool call and decision is written to the Audit Ledger with the agent and the initiating identity attached. The anonymous-message problem in SalesBleed's Slack flaw cannot happen when every agent action already carries the user who triggered it.
Salesforce fixed ForcedLeak, then fixed SalesBleed, quickly and cooperatively both times. The pattern is still worth noticing. Two teams of researchers, a year apart, walked in through the same public form, because letting the public in is that form's job.
So the question is not is our exit filter complete? It won't ever be. It is: for each agent that reads text from strangers, what stops that text from changing which data the agent reads, and would you know which user's request set it off?
Explore Governance AI or talk to us about putting inspection, scoped tools and an identity-linked audit trail around the agents that read your public inbox.
Obiguard sits in front of every AI request your organization makes — screening prompts and outputs against the guardrails, compliance frameworks, and audit trails that stories like this one make necessary.
See how it works →