Security
Every payload is assembled as the calling user — with one deliberate exception.
The guarantee
All record queries run in user mode, so Salesforce enforces on the query itself.
| Check | Effect |
|---|---|
| Object permissions | No read access, no rows |
| Field-level security | A field the user can’t see is not returned |
| Record sharing | Private records are excluded, including inside related lists and query results |
No value is fetched and then hidden, and two users calling the same tool on the same record get different payloads with no per-user configuration.
What a stripped field looks like
Nothing — it is simply absent, and the agent answers from thinner context. The Quality page reports FLS coverage %, the share of designed fields this caller could see, and Issues names stripped fields under fls_strip. A field can also be absent because it is not curated or is below the requested level; only permission strips appear there.
Curation is not security
Removing a field from an entity does not protect it: a user with object access still reaches it through a report, the API, or another entity. Use field-level security for access control and curation for relevance. Curating in a sensitive field is safe, because a user without access still will not receive it.
Which catalog a user gets
What a user can discover is a separate boundary from what data they receive. Record sharing on the domain decides it: a user reads the domains shared to them and no others. That limits discovery only and can never widen data access — two people holding the same domain still get different records. → Domain access
The permission sets
| Permission set | Grants |
|---|---|
| ContextWorks Admin | Write access to the configuration objects |
| ContextWorks Domain Manager | Configuration rights over entities homed in specific domains. Inert unless domain management is on |
| ContextWorks User | Class access to the four agent actions, plus read on the domain object so sharing can be resolved. What Agentforce and MCP callers need, and nothing else |
None of the three grants access to record data: an admin still sees only the records their own permissions allow, in preview and everywhere else. ContextWorks User exists because Salesforce checks Apex class access when an agent action or MCP tool runs, so assign it to whoever the call runs as. Flow and Apex callers need no permission set at all. → Install and set up
Enrichment sources: the one exception
Enrichment sources run code — Apex, an invocable action, or a flow — and their results are handled two ways:
- Record results are re-checked. The engine keeps only the IDs, discards every field value the source returned, and re-queries them as the calling user to render the target entity’s
listcolumns. A record or field the caller can’t see never appears, whatever the source saw. Computed per-row values are lost in the refetch — return them as scalar values instead. - Scalar values are trusted as returned. A constructed number isn’t a field, so the engine can’t re-check it. Whoever writes a provider owns what its scalar values expose: query as the calling user (
WITH USER_MODE) so a total can’t leak to a caller who couldn’t have computed it. See the provider SPI reference.
WARNING
Data 360 DMO reads are not governed by CRM sharing — what such a provider returns is what every caller sees. Fine for aggregate engagement data; decide deliberately.
Error handling
If an enrichment source fails, the engine drops that one element, delivers the rest, and records an element_failure issue on the Issues page. The agent gets a payload without that section, never the exception text.
Debug logging
Time-boxed payload logging stores the exact text a caller received, record data included. A log is shaped by one caller’s permissions, so someone reading the Logs page may see data they could not query themselves. Logging expires by design: it is a debugging tool, not an audit trail.
What’s next
- Payloads — markdown and JSON.