
Autopsy 15 · SaaS agent context boundary | Comic Strip 3 · Forced Lead
Would Ethosure have caught this? Salesforce Agentforce’s ForcedLeak indirect prompt injection
Noma Labs’ Sasi Levi demonstrated a CVSS 9.4 chain in Salesforce Agentforce: a malicious Web-to-Lead submission stored 42,000 characters of injected instructions in the CRM, then exfiltrated data through a rendered image on an expired trusted domain. Salesforce patched via Trusted URLs Enforcement on September 8, 2025. Tenant-owned CRM records were the prompt-injection vector. The fix is policy on the agent, not just on the domain allowlist.
The incident
Noma Labs researcher Sasi Levi discovered and reported ForcedLeak to Salesforce on July 28, 2025. Salesforce acknowledged the report on July 31 and rolled out Trusted URLs Enforcement for Agentforce and Einstein AI on September 8. Noma disclosed publicly on September 25 at CVSS 9.4. The attack chain: an external attacker submits a Web-to-Lead form (unauthenticated by design) with hidden prompt-injection instructions in the Description field, which accepts up to 42,000 characters compared with 40 characters for Company and 80 for Email. When an internal employee later asks Agentforce a normal question about the lead, the agent reads the attacker-controlled record and treats the embedded instructions as part of its task. The payload asks Agentforce to query CRM data (lead counts, email addresses) and then render an image tag with the answers URL-encoded into the query string of cdn.my-salesforce-cms.com. The exfiltration domain had previously been on Salesforce’s Content Security Policy allowlist. Salesforce’s post-incident control — Trusted URLs Enforcement — tightens the domain allowlist. It does not close the general class of indirect prompt injection through tenant-owned CRM records.
- Noma Security — ForcedLeak: AI agent risks exposed in Salesforce Agentforce
- Ibid. — Software Insights — Post-ForcedLeak: hardening Agentforce against prompt injection
- Ibid. — HackRead — ForcedLeak flaw in Salesforce Agentforce AI agent exposed CRM data
- Ibid. — Security Buzz — Salesforce vulnerability chain exposes AI agent risks
The applicable coding-ethos policy
tenant-content-is-untrusted-by-defaultCoding-ethos policies are compiled from principle-owned CEL expressions. Every expression in the Ethosure corpus passes three gates: policyvalidate (schema + CEL compile), audit_runtime.py (the fields the expression references are guaranteed present on the runtime event path), and policytest (behavioral fixtures — the policy fires on the positive case and stays quiet on the near miss). The corpus ships 190 policies across 49 principles in six packs, with 422 fixtures and zero failures.
Walkthrough
- The Salesforce fix — tightening the CSP allowlist so the exfiltration domain no longer resolves — is the right response for this specific payload. It is not the right response for the class of attack. Any SaaS agent that reads tenant records as trusted prompt context can be steered by whoever can write into those records, and Web-to-Lead is unauthenticated by design. The boundary that matters is between record content and agent instructions.
agent.crm_context.record_content_untrusted_by_defaultfires onPreToolUsefor any agent whose prompt is assembled from CRM record content that was originally written by an external, unauthenticated identity, unless the content has passed through a sanitisation pipeline with a recorded lease. Severityblock. Web-to-Lead submissions land in that category by default.agent.rendered_content.egress_via_image_url_requires_data_class_checkis the second expression. Data exfiltration through image URLs is the classic indirect-prompt-injection egress vector; it worked for ForcedLeak, EchoLeak, and most of the public agent-exfil demonstrations of the past year. The expression fires on any agent response that includes a URL parameter whose value matches a data-classifier hit on CRM record content. Severityblock. The trusted-URL allowlist becomes belt-and-braces.agent.context_source.field_length_asymmetry_gateis the third. Salesforce’s Description field accepts 42,000 characters while Company is 40 and Email is 80. A prompt-injection payload cannot hide in an 80-character field. The expression fires on any prompt assembly whose largest single field exceeds a per-object threshold declared by the tenant’s data-steward. Severitywarn, escalating toblockif the field contains inline instructions the sanitiser flags.
Policy YAML
# Pack: regulated-enterprise-base (v2)
# Overlay: model-provider-governance
principles:
- id: tenant-content-is-untrusted-by-default
title: Tenant Content Is Untrusted By Default
directive: >-
SaaS agents must treat tenant CRM records authored by external identities as
untrusted prompt input, must gate rendered-content egress against data-classifier
hits, and must surface field-length asymmetries that hide prompt-injection payloads.
policy:
expressions:
- id: agent.crm_context.record_content_untrusted_by_default
scope: agent_action
severity: block
event: PreToolUse
principle_ids:
- tenant-content-is-untrusted-by-default
- security-by-design
skill_id: safe-crm-agent-context
when: >
prompt.assembled_from.exists(s,
s.source_kind == "crm_record"
&& s.record.authored_by.identity_kind == "external_unauthenticated"
&& !s.sanitisation_lease.exists(l, l.state == "active"))
message: >-
Agent prompt is being assembled from a CRM record authored by an external
unauthenticated identity with no recorded sanitisation lease.
advice: >-
Route the record through the prompt-sanitisation pipeline. Web-to-Lead
submissions are the ForcedLeak vector; treat them as untrusted by default.
- id: agent.rendered_content.egress_via_image_url_requires_data_class_check
scope: agent_action
severity: block
event: PreToolUse
principle_ids:
- tenant-content-is-untrusted-by-default
- radical-visibility
when: >
tool_call.category == "render_content"
&& response.rendered_urls.exists(u,
u.query_params.exists(p,
dlp_facts.exists(f, f.matches(p.value)
&& f.classification != "public")))
message: >-
Agent-rendered content includes a URL whose query string contains a data-
classifier hit on non-public content.
advice: >-
Reject the render or strip the parameter. This is the exfiltration primitive
behind ForcedLeak and every subsequent image-URL leak.
- id: agent.context_source.field_length_asymmetry_gate
scope: agent_action
severity: warn
event: PreToolUse
principle_ids:
- tenant-content-is-untrusted-by-default
- radical-visibility
when: >
prompt.assembled_from.exists(s,
s.source_kind == "crm_record"
&& s.field.length > tenant.field_length_thresholds
.for_object(s.field.object).max_chars)
message: >-
CRM record field exceeds the tenant-declared length threshold for its object.
advice: >-
Surface the asymmetry to the data-steward. A 42,000-character Description field
when Company is 40 characters is where prompt-injection payloads hide.
Assurance boundary. Fixtures prove that synthetic positive cases fire and near-misses stay quiet. They do not establish production alert rates. DLP-backed controls depend on upstream detection, shell-command regexes are defense in depth rather than a sandbox boundary, and regulatory tags are engineering aids requiring counsel review.
Where this policy lives in the corpus
The Ethosure importable corpus ships six coding-ethos packs. Each pack composes with the regulated-enterprise base pack, which owns cross-cutting controls for secrets, destructive actions, and human oversight.
- Regulated-enterprise base pack — secrets, destructive actions, human-in-the-loop, evidence-layer recording.
- Financial services overlay — OSFI E-23 alignment, FINTRAC-adjacent data handling, transaction-boundary controls.
- Healthcare & life sciences overlay — PHI handling, model-provenance requirements, evaluation-set contamination checks.
- Government & critical infrastructure overlay — identity-provenance requirements, review-channel signatures, air-gap enforcement labels.
- Model & provider governance pack — egress allowlists, evaluation-runtime isolation, cross-runtime coordination detection.
- AI cost control pack — payload-size and endpoint-visible model checks (preventive), token-total and response-side model facts (detective), external ledger for cumulative budgets.
Published September 3, 2026 · Ethosure · Part of the Autopsy Series.