If the contact form on your website sends enquiries into a CRM, and anyone at your business now asks an AI assistant inside that CRM to “look at my latest leads”, this is for you. It is written around Salesforce because Salesforce is where both public cases happened, but the pattern is not Salesforce’s alone, and the questions at the end apply to any CRM that has added an assistant.

The short version: whatever a stranger types into your website form becomes text your AI assistant reads later, with the assistant’s permissions. Twice now, in September 2025 and September 2026, researchers have shown that text being used to walk data out of Salesforce with no login and no click. Both were fixed. The second one got through the fix for the first.

Everything below was checked on September 29, 2026. Where a fact comes from the organization that published it, we say which one. The TLD count, the URL parsing test and the contact form test are ours, and each is written so you can repeat it.

What happened, twice

When What
July 28, 2025 Noma Security reports ForcedLeak to Salesforce: a prompt injection planted through Web-to-Lead that made Agentforce send CRM data to an allowlisted domain that had expired, which Noma bought for $5
September 8, 2025 Salesforce enforces Trusted URLs for Agentforce and Einstein AI, so agent output can no longer point at untrusted addresses, and re-secures the expired domain
September 25, 2025 ForcedLeak disclosed. Noma rates it CVSS 9.4
June 1, 2026 Zenity Labs reports SalesBleed to Salesforce: two ways around Trusted URLs, and a Slack action with no confirmation and no attribution
August 18 and 19, 2026 Salesforce confirms the Trusted URLs fix; Zenity confirms it the next day
August 20, 2026 Zenity confirms the Slack reply action now names the person who triggered it
September 21, 2026 Zenity confirms the last fix: the Slack reply action now asks for confirmation by default
September 24, 2026 SalesBleed disclosed in two posts from Zenity Labs

Salesforce’s statement to SecurityWeek was: “We’re aware of the analysis and have no evidence at this time that the reported issue was exploited against any customer.” None of the three flaws has a CVE identifier. That is common for issues fixed entirely on a vendor’s servers, and it means none of this will ever appear in a vulnerability scanner or on CISA’s exploited list, whatever happens next.

How the attack worked

Salesforce’s Web-to-Lead turns a form on your website into a lead record. It is unauthenticated on purpose: the point is that strangers can fill it in. The form posts to a Salesforce address with your organization’s ID in it, and that ID sits in your page’s HTML for anyone to read.

Zenity’s attacker submitted one ordinary looking lead with instructions hidden in a field. Nothing happened until an employee asked Agentforce something routine. Zenity’s example is “check my latest leads and help me with the newest one”. The agent read the lead, and with it the instructions, which told it to:

  1. query the Accounts table with the same Query Records tool it had just used on Leads,
  2. take a company name and a deal size,
  3. paste them into the subdomain of an address the attacker controls, like Acme-712412.attacker.oast.fun,
  4. print that address back as an image.

The chat window tried to load the image. Loading an image means looking up its hostname, and that lookup travels to the attacker’s own name server, which reads the company name and deal size out of it. Whether the image ever loads is irrelevant; the data left in the lookup. Zenity notes that this is also why blocking outbound web traffic does not help. A second variant needed no image at all: in Slack, the agent only had to print the address, because Slack fetches links to build previews.

Two details in Zenity’s write-up are the ones to hold on to.

Nothing was escalated. The default General CRM subagent can read both Leads and Accounts. The injection used permissions the agent already had. Zenity’s words: “Those are defaults, not misconfigurations.”

One lead keeps working. The poisoned record sits in the Leads table like any other and runs again every time someone asks the agent about it. There is no session to expire and no login to revoke, because the attacker never logged in.

The Slack flaw was the same entry point with a different exit. Agentforce’s Reply to a Slack Thread action did not ask the user before sending and did not show who had asked for the message. A lead could tell the agent to post a link into a busy channel, and it arrived as a message from the company’s own assistant, with no person attached to it. Sending a direct message, by contrast, already asked for confirmation and named the person.

The control that was bypassed is the one added last year

Trusted URLs is how Salesforce closed ForcedLeak. It works by finding addresses in the agent’s answer and removing any that are not on your approved list, after the model has written the answer and before you see it. Zenity got around it by exploiting a disagreement between two pieces of software about what counts as an address.

The filter recognized a fixed list of top-level domains. .fun was not on it, so a hostname ending in .fun did not register as a hostname. To see how hard a fixed list is to keep right, we downloaded IANA’s list of top-level domains this morning (version 2026092900). It has 1,438 entries: 248 two-letter country codes, 151 internationalized names, and 1,039 others. .fun is one of them. A filter has to know all of them, and the list changes.

The filter and the browser disagreed about where an address ends. Zenity’s string was https://random_string.oast.fun/{email}. Curly braces are not allowed in a URL under RFC 3986, so a strict filter sees a malformed non-address and leaves it alone. Browsers follow a different, more forgiving standard. We ran that exact shape through the WHATWG URL parser built into Node.js, which is the same standard browsers use:

new URL('https://Acme-712412.x.oast.fun/{email}')
  hostname  acme-712412.x.oast.fun
  pathname  /%7Bemail%7D

The browser does not reject it. It quietly encodes the braces and goes to look up the hostname, which is the only part the attack needed. Zenity’s summary of this class of defence is the sentence we would underline: output redaction “is a race between a parser and every renderer downstream of it.”

Salesforce’s fix did what it was designed to do, and the gap was in the approach. Filtering what an agent says after it has read hostile text is the last line, and a year of evidence says the last line leaks eventually.

Two dates in the story do not agree

Zenity’s press release says Salesforce addressed the Trusted URLs bypasses “within approximately two weeks”. Zenity’s own technical post gives the report as June 1 and the fix as confirmed on August 18, which is 78 days, about eleven weeks. The post also records that Salesforce closed the reports on June 17 while engineering worked on a fix. We cannot tell from the public record which interval the release means. Neither version changes what you should do, since every fix is in place now, but if you are quoting a turnaround time, quote the timeline.

The Slack confirmation fix is the one to notice. It was confirmed on September 21, three days before disclosure, and Zenity warns that turning the confirmation requirement off takes a single click. A default that changed in the last fortnight is a default worth checking on your own agents, in case someone switched it off before the change or will switch it off after.

What the website form can and cannot do

We build contact forms, so we ran the obvious test on our own. Our contact form screens for bot spam: a web address in the name field, forum link markup, more than three links in a message. It also has an invisible Cloudflare Turnstile check and a signed form token. We wrote a 257-character message in plain prose that asks “the assistant reviewing this lead” to look up the three largest accounts and put their names in an image address, with one link in it, and passed it to the same screening function the live form uses. It returned no objection.

That is the correct result for a spam filter. A human writing a sentence to a machine looks exactly like a human writing a sentence to a person. The same goes for Salesforce’s own protection on Web-to-Lead: Salesforce documents reCAPTCHA as on by default “to prevent spambots from polluting your lead data with fake records”. It stops bots. SalesBleed needed one person to submit one form once.

So the form can make the attack a little more expensive and much easier to find later. It cannot stop it. The things worth doing at the form are:

  • Keep free text short and in one place. A contact form needs one message field. Every extra free-text field is another place for instructions to hide, and a sensible length limit on each one costs a real enquirer nothing. Noma chose the lead Description field for ForcedLeak for its length.
  • Keep bot checks on. They will not stop this, but they stop the version where an attacker submits a thousand leads to widen the channel, which Zenity describes as the next step.
  • Keep a copy of what arrived. Have the form email each enquiry as well as sending it to the CRM. If you ever need to know what a lead said before anything touched it, a plain copy outside the CRM is the record. (Our own form only sends email, so every enquiry we get already arrives that way.)

What to check if you use Salesforce with Agentforce

Salesforce has fixed the reported chains, so there is nothing to patch. What is left is configuration, and it is yours.

  1. Find out whether your Web-to-Lead forms feed an org where Agentforce is switched on. If the answer is no, you can stop here.
  2. Check which records your agents can query. The default General CRM subagent reads Leads and Accounts together. If your team only needs the assistant to summarize new leads, an agent that cannot see Accounts cannot leak them.
  3. Check every action that sends something. Slack replies, direct messages, emails. Each should ask the person before it sends and should show who asked. Zenity’s point about the single click applies to all of them.
  4. Review your Trusted URLs list. ForcedLeak worked because an allowlisted domain had expired. Anything on that list you no longer own, or no longer use, should come off.
  5. Look at old leads before you point an agent at them. An injected lead persists. If a lead’s description reads as if it is addressed to software, delete it before an assistant reads it for you.

If your CRM is not Salesforce

Zenity’s own conclusion is that “this type of vulnerability isn’t Salesforce-specific”. It names three ingredients: an agent that reads records submitted by outsiders, renders links or images back to a user, and has access to sensitive data. Simon Willison called the same combination the lethal trifecta in June 2025: private data, untrusted content, and a way to communicate out.

Your website form is the untrusted content, permanently. You cannot fix that side and should not try, because a form that only trusted people can fill in is not a contact form. So the question for any CRM vendor selling you an assistant is about the other two:

When your assistant reads a lead that came from our public website form, what else can it read in the same conversation, and what can it send or display without one of us approving it first?

A vendor who answers that clearly has thought about it. We covered the general rule, and why no vendor can patch prompt injection away, in our earlier piece on AI agents and prompt injection.

The part to remember

In September 2025 a Salesforce website form was used to make Agentforce leak CRM data, and Salesforce added a filter. In September 2026 the same form was used again, and the filter was the thing that failed. Both were found by researchers and fixed before anyone is known to have used them. Nothing on the form will reliably catch the next one, so the protection that holds is limiting what the assistant can read, and making it ask before anything leaves.

If you want someone to look at how your website’s forms reach your CRM, and what reads them after they land, get in touch. We will tell you what we find, including when the answer is that you are fine.

Sources

  • SalesBleed: Indirect Prompt Injection and 0-Click Data Exfiltration on Agentforce, Zenity Labs, September 24, 2026, retrieved September 29, 2026, for the attack sequence and the example request “check my latest leads and help me with the newest one”, the Web-to-Lead endpoint format with the organization ID, the General CRM subagent’s Query Records access to Leads and Accounts, the image tag and Slack unfurling variants, the DNS exfiltration and its survival of HTTP egress controls, the fixed TLD list and .fun, the curly brace and square bracket termination behaviour, the example string https://random_string.oast.fun/{email} and its RFC 3986 status, redaction running after generation, persistence of the injected lead, the quoted “Those are defaults, not misconfigurations”, the quoted parser race sentence, the quoted “this type of vulnerability isn’t Salesforce-specific”, and the disclosure timeline from June 1 to August 19, 2026
  • SalesBleed: Hijacking Agentforce in Slack for Anonymous Phishing Attacks, Zenity Labs, September 24, 2026, retrieved September 29, 2026, for the Slack Knowledge subagent, the missing confirmation and attribution on Reply to a Slack Thread, the contrast with Send a Slack Direct Message, the insider and external attacker scenarios, the timeline entries for August 20, August 25, September 10 and September 21, 2026, and the warning that re-disabling confirmation takes a single click
  • Zenity Labs Uncovers SalesBleed, Zenity press release via Business Wire, September 24, 2026, for the “within approximately two weeks” wording compared against the technical post’s own timeline above
  • SalesBleed Flaws in Salesforce Agentforce Enabled Zero-Click Data Exfiltration, SecurityWeek, for Salesforce’s quoted statement and the absence of CVE identifiers
  • Salesforce Agentforce vulns allowed 0-click CRM data theft, anonymous phishing, The Register, September 24, 2026, for the September 21 confirmation of all fixes ahead of disclosure
  • ForcedLeak: AI agent risks exposed in Salesforce Agentforce, Noma Security, September 25, 2025, retrieved September 29, 2026, for the CVSS 9.4 rating, the Web-to-Lead Description field as the injection point, the expired allowlisted domain my-salesforce-cms.com and its $5 cost, Salesforce re-securing it, Trusted URLs Enforcement for Agentforce and Einstein AI, and the July 28, September 8 and September 25, 2025 dates
  • Generate Leads from Your Website with Web-to-Lead, Salesforce Help, for the quoted reCAPTCHA description, reCAPTCHA being on by default, and the 500 leads per day limit
  • The lethal trifecta for AI agents, Simon Willison, June 16, 2025, for the three ingredients
  • List of top-level domains, IANA, version 2026092900, downloaded September 29, 2026. The count of 1,438 and the split into 248 two-letter, 151 XN-- and 1,039 other entries is ours
  • RFC 3986, Uniform Resource Identifier: Generic Syntax and the WHATWG URL Standard, for the two parsing rules. The parsing output shown is ours, from the URL class in Node.js 22
  • The contact form test is ours: the 257-character message described above, passed to screenContent in this site’s own src/lib/contactGuard.ts, which returned no objection