The message Agentforce showed the employee was reassuring. The content had been blocked by the organization's security policies.
Zenity Labs put that line in its writeup, and then put the correction right next to it: the sensitive CRM data had already been transmitted to the attacker-controlled server. The block worked. It arrived after the data left.
That pairing is the whole story of SalesBleed, the set of three Salesforce Agentforce flaws Zenity disclosed on September 24, 2026. Zenity reported them on June 1. Salesforce confirmed it was working on fixes the next day, shipped the first one on August 18, and had all three closed by September 21. That is 112 days from report to full remediation on a chain that needed no credential, no exploit code and no click from anyone inside the company.
I want to walk through how it actually worked, because the mechanics say something uncomfortable about a class of identity most security teams have not written a policy for yet.
The entry point is a form you deliberately made public
Web-to-Lead is Salesforce's own lead collection feature. You put a form on your marketing site, a stranger fills it in, and the contents land in your CRM as a record. That is the product working correctly. Millions of leads a day arrive exactly this way.
Zenity's attack starts there. An attacker submits a lead that looks ordinary, with a hidden indirect prompt injection sitting in one of the fields. Nothing happens. The payload just sits in the database, patient, indistinguishable from the other leads.
It fires when an employee does something completely normal, like asking Agentforce to summarize recent leads. The agent pulls the record set, reads the poisoned record as part of a legitimate request from a legitimate user, and treats the attacker's text as instructions. According to Zenity's chain, those instructions directed the agent to query CRM records including account data and place selected values into a URL the attacker controlled.
Read that sequence again and notice who did what. The attacker never authenticated. The employee never clicked anything. The agent, holding real permissions granted by your admin, ran the query. Every step in the audit log belongs to a trusted actor.
The bypass was a hostname, not an exploit
Salesforce has a control for exactly this. Trusted URLs is supposed to stop Agentforce from rendering links and images from sources you have not approved. It is the right idea, and it is where the research gets genuinely interesting.
Zenity found several ways through it. Some top-level domains the mechanism did not recognize. Character sequences that interfered with how the URL was parsed. The cleanest trick: encode the stolen data into the first-level domain of the hostname rather than the path, and the untrusted-URL block stops seeing a problem. Data goes out as part of a name lookup and an image fetch, one record at a time, in fragments small enough to look like noise.
Then there is the second flaw, which I think is the most instructive part of the disclosure. The exfiltration did not always require the agent to render anything at all. When the conversation happened in Slack, Slack's link unfurling did the fetch. A user-facing convenience feature, turning URLs into previews, became the outbound channel. The agent produced a string. Slack retrieved it.
That matters for how you hunt. Most data-loss monitoring around AI agents watches the agent's tools: what it queried, what API it called, what it wrote back. Here the tool calls were boring and authorized. The exfiltration lived in the rendering and preview layer, which is not instrumented as a data path in any SaaS deployment I have reviewed. Your egress happened in a component nobody classified as egress.
Flaw three is the one I would brief the exec team on
The third vulnerability has nothing to do with data theft and everything to do with trust.
The Agentforce action for replying to a Slack thread lacked a required user confirmation, and it carried no visible attribution to the person who invoked it. So a malicious insider, or an outside attacker who got a prompt injection into a record, could send messages into Slack wearing the agent's identity while staying anonymous.
Think about the asymmetry there. Your staff have been trained for a decade to be suspicious of email from outside the company. Nobody has been trained to be suspicious of the CRM assistant posting in the deals channel. An internal Slack message from a sanctioned enterprise agent sits near the top of most employees' trust ranking, right next to a message from their own manager. SecurityWeek noted the obvious follow-on: credentials harvested that way open email, Slack and code repositories.
We wrote about a phishing kit that deletes canary tokens before the operator touches a mailbox, which was evidence that attackers now budget engineering effort for beating detections. SalesBleed is the other side of that trend. Why build a convincing lookalike domain when you can borrow a trusted internal identity that your own admin deployed?
The identity nobody onboarded
Here is what I keep coming back to. Nothing in this chain required credential theft, and yet the whole thing is an identity incident.
The agent is an identity. It has permissions, it has reach into Accounts and Leads, it can act in Slack, and it takes instructions from whoever manages to get text in front of it. Palo Alto Networks' 2026 Identity Security Landscape puts machine identities at roughly 109 to 1 against human ones in the average environment. A SANS survey of more than 500 security practitioners found 74% already running AI agents or automations that need their own credentials. Most of those identities went live through a product enablement checkbox, not an access review.
We keep hitting variations of this. Nobody stole a password when a forged JWT minted a SharePoint admin. A stolen session cookie outlived the login it came from. Hundreds of AI gateways were still answering to the key printed in the quickstart. Different bugs, same shape: the authentication event you monitor is not where the access came from.
Zenity's own framing is the most useful sentence in the whole disclosure. Any AI agent that reads records submitted by external untrusted sources, renders links or images back to users, and holds tool access to sensitive backend data has the same three ingredients sitting in the same place. That is not a Salesforce bug. That is a deployment pattern, and it is the default pattern for every CRM, ticketing and support agent shipping this year.
What the patch fixed, and what it did not
Salesforce fixed the URL parsing weaknesses and added the missing confirmation. Those were real bugs and they are closed. Nothing here suggests otherwise.
But look at the history for a second. In September 2025, after Noma Security's ForcedLeak research showed CRM data leaving Agentforce through a Web-to-Lead injection and an expired allowlisted domain, Salesforce rolled out Trusted URLs enforcement for Agentforce and Einstein. That control was the answer to this exact class of attack. A year later, Zenity walked through it using hostname encoding and an unrecognized TLD.
That is the pattern I would expect to continue, and it is why I do not treat an allowlist as a boundary. An allowlist is a parser, and parsers lose. Every added format, every internationalized domain, every legitimate marketing subdomain widens the surface the parser has to get right. Meanwhile the agent still holds the same permissions, which is the same reason patching Artifactory did not evict anyone holding a token. Fixing the door does not change who is already inside it.
The decoy lead: a test you can run this week
Here is the part I would actually build, and it is cheap.
You already own a public Web-to-Lead form. Submit your own lead through it. Fill the description field with a plausible-looking instruction and a URL on a domain you control, chosen so nothing legitimate would ever fetch it. Encode the record ID into the hostname. Then wait.
If that URL is ever requested, you have learned three facts in one event. Your agent reads record content as instructions. Your rendering path reaches the internet. And you now know which record did it, because the ID is in the hostname. No volume threshold, no baselining argument, no model behavior to interpret. One request, one answer.
This is the property that makes decoys different from tuning detections. A honeytoken has no legitimate consumer, so there is no benign explanation to rule out, which is why deception-based detection produces no false positives to triage away. Extend it past the lead form: a decoy contact record with a fake AWS access key in the notes field, a decoy opportunity with a plausible name and an unusually large deal size. Any agent that has been told to enumerate and exfiltrate "sensitive" records will grab the largest deal it can find. That is not a flaw in the injection, it is the instruction working as written.
One edge case from doing this in real tenants, because it will bite you. Canary URLs sitting in CRM fields get fetched by legitimate things: Slack unfurling, mail security scanners, Salesforce's own previews, link checkers in marketing tools. Baseline for a week before you treat a hit as an incident, and keep the decoy out of channels that auto-fetch by design. The version I trust most is a decoy credential rather than a decoy URL, because a fake AWS key has no automated crawler that would ever try to use it. Nothing on earth authenticates with a string it found in a CRM note except an attacker or an agent that was told to.
Three questions for your Salesforce admin
Not a maturity model. Three questions with checkable answers.
What can the agent read, and who approved that? Not what it is meant to read. What its Query Records tool can actually reach if the instructions change. If the answer includes the full Accounts table, you have the third SalesBleed ingredient in place already.
Which record fields can a stranger write to? Web-to-Lead, support portals, case comments, chat transcripts. Every one of those is an unauthenticated text path into your agent's context window. Treat them as input from the internet, because they are.
Who is the agent when it posts in Slack? If the answer is that messages appear under the agent's name with no attribution to the invoking human, you have an impersonation channel whether or not anyone is abusing it today.
Agentforce will be patched again, and something adjacent will come out next quarter. The structural problem is that we are deploying identities with real permissions and no ability to distinguish data from instructions, then monitoring them with tools built to watch humans log in. If you want to see what a decoy record looks like when an agent enumerates its way into one, book a walkthrough with our team and bring a sandbox org.
Neha
Cloud Security Architect, Mine2
Neha works on cloud and identity security at Mine2, covering SaaS, OAuth, and the credential-theft paths attackers favour in the cloud.
Recent Articles
Need Security Help?
Protect your organization with MINE2's cyber deception platform.
