Security

ContextWorks assembles every payload as the calling user. Enrichment sources are the one deliberate exception.

How it works

Every record query runs in user mode, so Salesforce enforces access on the query itself.

CheckEffect
Object permissionsNo read access, no rows
Field-level securityA field the user can’t see never reaches the payload
Record sharingPrivate records never appear, including inside related lists and query results

ContextWorks never fetches a value and then hides it. 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. The field is simply absent from the payload, and the agent answers from thinner context.

To see which fields dropped out, open Quality for FLS coverage % — the share of designed fields this caller could see — and Issues, which names stripped fields under fls_strip.

A field can also be absent because you never curated it, or because it sits below the requested level. Only permission strips appear under fls_strip.

Curate for relevance, secure with field-level security

Removing a field from an entity doesn’t protect it. A user with object access still reaches that field 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: a user without access still won’t receive it.

Which catalog a user gets

Discovering a domain and reading records through it are two different questions. Sharing a domain answers the first: a user reads only the domains shared to them. A scoped tool is the exception. It names its domain in Apex, so whoever can run the class reads through it.

Neither one widens data access. Two people holding the same domain still get different records, and a scoped tool still queries as its caller. → Domain access

The permission sets

Permission setGrants
ContextWorks AdminWrite access to the configuration objects
ContextWorks Domain ManagerConfiguration rights over entities homed in specific domains. Does nothing unless domain management is on
ContextWorks UserClass access to the eight runtime agent actions, plus read on the domain object so ContextWorks can resolve sharing. 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.

Assign ContextWorks User to whoever the call runs as — a Flow or an Apex job included. Salesforce checks Apex class access when an agent action or MCP tool runs, and every configuration read the engine makes runs in user mode, so a caller without the permission set resolves no entity and no domain. → Install and set up

Enrichment sources: the one exception

Enrichment sources run code: Apex, an invocable action, or a flow. The engine handles what they return two ways.

  • The engine re-checks record results. It 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 list columns. A record or field the caller can’t see never appears, whatever the source saw. The refetch drops computed per-row values, so return those as scalars instead.

  • The engine trusts scalar values 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.

Debug logging

Time-boxed payload logging stores the exact text a caller received, record data included. One caller’s permissions shape that log, so someone reading the Logs page may see data they could not query themselves. Logging expires by design: it’s a debugging tool, not an audit trail.

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 and never sees the exception text.

What’s next