Three Fixes, Three Half-Lives
Zenity Labs published a line in its SalesBleed write-up that is the most useful sentence written about Agentforce security this year. Output redaction, it says, is "a race between a parser and every renderer downstream of it."
That is a description of the Trusted URLs control, the thing Salesforce shipped in September 2025 to close ForcedLeak. It redacts untrusted links and images from agent output after the agent has produced that output. To work, it has to correctly parse every URL an attacker can construct, and it has to agree with every surface that will later render the result, which currently includes the Agentforce chat window, Slack's link unfurler, and whatever browser the user happens to be in. Each new surface is a fresh opportunity for the parser and the renderer to disagree.
They disagreed twice.
What Zenity found
Three flaws, reported to Salesforce on June 1, 2026, with all fixes confirmed and tested by September 21 and public disclosure on September 24. Zenity co-founder and CTO Michael Bargury's team had demonstrated an earlier Web-to-Lead attack at Black Hat 2025, so this is the same researchers returning to the same door.
The first two are Trusted URLs bypasses. The redactor checked hostnames against a fixed list of top-level domains, and .fun was not on it, so an attacker-controlled hostname under an unlisted TLD passed through unrecognized. Separately, the redactor and the rendering surface disagreed about where a URL ends: curly braces and square brackets survived redaction while still landing inside a clickable link. Zenity's example, https://random_string.oast.fun/{email}, is not a valid URI under RFC 3986. The redactor left it alone and the browser fetched it anyway.
The exfiltration itself is almost quaint. The agent embeds CRM values in a subdomain of the attacker's hostname, prints the result as an HTML image tag, and the rendering surface resolves it. The data leaves in the DNS query and arrives at the attacker's authoritative nameserver. In Slack, link unfurling performs the same lookup without anyone clicking anything.
The third flaw is different in kind. The Reply to a Slack Thread action required neither user confirmation nor attribution, while Send a Slack Direct Message correctly did both. A reply therefore carried, in Zenity's words, "only the agent's identity, while obscuring the origin of the request." An insider could ask the agent to post a phishing message into a channel and appear nowhere in it. An external attacker could achieve the same through an injected lead.
Where my earlier reasoning was incomplete
In May I wrote that the Trusted URLs enforcement was a channel patch standing in for a class fix, that it closed one specific path while leaving the architecture intact, and that the next disclosure would look like the last one with a different exfiltration channel. The scope argument held up. Slack unfurling is exactly the new channel that post predicted.
The reasoning underneath it was not complete, and the gap matters. I treated the control as correct within its own boundary and too small, and argued the problem was the boundary. Zenity found that the control was also wrong inside the boundary, on a hardcoded TLD list and a parsing disagreement. That is a more ordinary kind of defect than the architectural one I described, and it carries a different implication. An architect reading my May post would conclude that Trusted URLs reliably stops what it claims to stop and that the job is covering what it does not reach. At the time Zenity tested it, that was not true.
I would make the same recommendation today and I would give a different reason for it. Do not build on output redaction as a boundary, not because its scope is narrow, but because a post-generation filter has to win every round against every renderer, indefinitely, and it only has to lose once.
The sentence that should worry architects most
Not the parser. This one, from Zenity: "The injection didn't need to escalate privileges, the permissions were already there."
The General CRM subagent held Query Records access to both Leads and Accounts. That is the default configuration. There was no misconfiguration to find, no over-granted permission set left behind by a hurried admin, no escalation step in the chain. The agent read a poisoned lead and queried Accounts because it had always been able to query Accounts.
This is ASI03 from the OWASP Agentic Top 10, the category I described in June as the single highest-leverage control in the platform, demonstrated in shipped defaults rather than in a lab configuration. Salesforce can harden the redactor as many times as it likes. The scope of what an injected instruction can reach is set by the running user's permissions and the subagent's tool access, and nothing in any of these three fixes touched either.
Worth noting what also goes unfixed: Zenity points out that the malicious lead persists in the Leads table. The injected instruction is not a transient event. It re-fires every time an agent reviews that record.
The filter that shows its work
One mechanical detail deserves more attention than it has received. Redaction runs after generation, which means the agent does not know that parts of its own output are being removed. Zenity used that to tune the attack, reading the visible pre-redaction output and stripping the characters that triggered the filter.
A control that reveals its input before acting on it is a control an attacker can iterate against, for free, as many times as they like. That property is independent of how good the TLD list gets.
Three fixes, three half-lives
All three flaws are closed. The useful question is how long each fix stays closed, because the three are not the same kind of thing.
Hardening the Trusted URLs mechanism is a better parser. It resolves the two specific bypasses Zenity reported. It does not change the structural position of a post-generation filter racing downstream renderers, and Salesforce keeps adding renderers. Shortest half-life of the three.
Adding attribution to the Slack reply action is a structural change. The message now carries the invoking user. There is no toggle that removes it and no configuration that reintroduces the ambiguity. This is the fix that will still be holding in three years.
Adding user confirmation to the reply action is a default. Zenity is explicit about the consequence: confirmation can be turned off in a single click, and once it is, "the same attack path can become available again." Salesforce's own framing is that the attack is no longer possible by default, which is a precise and honest way to put it.
A default is a good place to start and a poor place to stop. The friction that confirmation introduces is real, someone will eventually want it gone, and the person who turns it off will not be thinking about an attack chain from a disclosure fifteen months earlier.
Whether anyone outside the room finds out
There is no CVE for SalesBleed. There was none for PipeLeak, and none for ForcedLeak, whose widely-quoted 9.4 was a severity score Noma calculated rather than an assigned identifier.
This is not Salesforce declining to participate. Salesforce is a CNA and has assigned Agentforce CVEs: CVE-2025-64320, CVE-2025-64321, and CVE-2025-64322, all published in November 2025, all for the Agentforce Vibes Extension, each pinned to a version boundary of before 3.2.0 or 3.3.0. Two of the three are prompt-injection flaws in build-time tooling, which is its own quiet confirmation of a point I made here two weeks ago.
The pattern is a line between versioned software and hosted service. When there is a version number to attach, Salesforce issues a CVE. When the fix ships server-side, it does not. That is a defensible position and a common one across SaaS.
It is also not the only available position. Microsoft assigned CVE-2026-21520 to the structurally equivalent Copilot Studio flaw, CVSS 7.5, published in January, with affected versions listed as a dash because there are no versions in a hosted service. Microsoft issued it anyway.
The operational difference lands on the customer. A flaw with no CVE produces no entry in a vulnerability feed, no scanner signature, and no standard identifier. For a health system or any other regulated buyer, that means there is no way to evidence "we assessed and remediated this" against anything more durable than a vendor's blog post. These three flaws were exploitable from at least June 1 to August 19. Nothing in a normal vulnerability intake process would have surfaced them during that window, and nothing will surface them in a retrospective audit now.
What to do about it
- Audit which subagents hold Query Records on objects fed by unauthenticated intake. The General CRM subagent's default access to both Leads and Accounts is what made this chain work. Narrowing tool scope per subagent is the only control here that Salesforce cannot patch on your behalf.
- Treat existing records as part of the attack surface, not just new ones. Poisoned leads persist and re-fire. If you ran Web-to-Lead into an agent at any point in the last year, the records are still sitting there.
- Put write-action confirmation into change control. It is a one-click setting that currently stands between your agent and an impersonation path. Decide who may change it and record when it changes.
- Re-test every new rendering surface as a new exfiltration channel. Each surface Salesforce connects an agent to is a new renderer that must agree with the redactor. Slack was one. There will be others.
- Stop treating output redaction as a boundary in your architecture diagrams. It is a filter with a known adversarial property, not a control plane.
- Build your own record of agent-platform advisories. Since these flaws will not arrive through your vulnerability feed, someone needs to be reading vendor research directly and writing it down where your auditors can find it.
Close
The three fixes Salesforce shipped are all genuine improvements and they will not age at the same rate. The parser fix lasts until the next renderer. The confirmation default lasts until someone needs the friction gone. The attribution change lasts because it altered what the message contains rather than what a setting permits.
That distinction is worth carrying into any agent platform review. When a vendor tells you something is fixed, the follow-up question is which of those three they mean.
Sources credited
Zenity Labs (SalesBleed: 0-Click Data Exfiltration on Agentforce, and SalesBleed: Anonymous Phishing via Agentforce in Slack, both September 24, 2026), Michael Bargury and Zenity, Salesforce Ben (Lab Exposes New Agentforce Data Exploit, September 25, 2026), NIST NVD and the MITRE CVE program (CVE-2025-64320, CVE-2025-64321, CVE-2025-64322, CVE-2026-21520), Noma Security (ForcedLeak), Capsule Security (PipeLeak), OWASP GenAI Security Project (Agentic Top 10, ASI03).
