Skip to content

Audit Your Agentforce Footprint: Every Agent, Agent User, and Permission in One Pass

Inventory every Agentforce agent, agent user, and permission with sf CLI and SOQL: the exact queries to see what your agents can touch, and from where.

TL;DR

  • Agentforce agents are ordinary Salesforce metadata: Bot and BotVersion for the agent shell, GenAiPlannerBundle for the reasoning planner (API 64+; GenAiPlanner before that), GenAiPlugin for topics, and GenAiFunction for actions. You can list and retrieve all of them with the sf CLI.
  • Every agent runs as a real user record. For service agents that is an auto-created user on the "Einstein Agent User" profile, holding Agentforce permission set licenses. Those users are queryable like any other.
  • An agent's real attack surface is its user's object and field permissions, not its instructions. ObjectPermissions and FieldPermissions queries tell you the truth in seconds.
  • Blast radius = data access multiplied by channel. An agent with broad CRM read behind an internal-only channel is a finding; the same agent on a public Experience Cloud page is an incident waiting for a prompt.
  • Salesforce's own guidance is checklist-style. Everything below is a command you can paste, which means you can script it and run it every week.

What You'll Learn

  • The verified metadata type names behind Agentforce agents, and the sf CLI commands to list and retrieve them
  • The SOQL (Salesforce Object Query Language) to inventory agents, agent users, and their permission set licenses
  • How to map each agent user's CRUD (create, read, update, delete) and FLS (field-level security) with two queries
  • How to find where each agent is exposed: Messaging channels, embedded service deployments, Experience Cloud sites
  • A simple severity model for what you find, and a script skeleton to make the whole audit repeatable

The Problem

Someone in your org piloted Agentforce last year. Maybe it went live, maybe it stalled, maybe the person who built it left. Either way, you now own the question every security review asks first: what agents do we have, and what can they access? Most admins I talk to cannot answer it without clicking through Agentforce Builder one agent at a time.

The guidance that exists does not help much. Salesforce publishes an Agentforce and Einstein Generative AI security white paper and a help page on agent user permissions, and the ecosystem has produced plenty of 40-point PDF checklists. They all tell you what to verify: least privilege on the agent user, field-level security, audit logs. None of them show the actual queries and commands that produce the inventory. A checklist you cannot script is a checklist that gets run once, before go-live, and never again.

This matters more since ForcedLeak demonstrated that a prompt injected through a web form could walk data out through an agent. I covered the attack and the hardening steps in Post-ForcedLeak: Hardening Agentforce Against Prompt Injection. The uncomfortable lesson from that incident is that the agent's instructions were never the control that mattered. The agent user's permissions were. Which means the audit below, boring as inventories are, is the single highest-value hour you can spend on Agentforce security.

Common questions this article answers:

  • How do I list every Agentforce agent in my org from the command line?
  • Which users do Agentforce agents run as, and how do I find them with SOQL?
  • How do I see exactly which objects and fields an agent can read or write?
  • How do I know which agents are exposed to the public internet?

Quick Answer

Agents are metadata and agent users are users, so the whole inventory is four commands. List the agents: sf data query --query "SELECT Id, DeveloperName, MasterLabel, Type FROM BotDefinition" --target-org myorg. Find the agent users: query User where Profile.Name = 'Einstein Agent User', and cross-check PermissionSetLicenseAssign for Agentforce permission set licenses. Map their access: query ObjectPermissions and FieldPermissions filtered to the permission sets assigned to each agent user. Retrieve the agent definitions themselves with sf project retrieve start --metadata GenAiPlannerBundle --target-org myorg (API version 64 and later) plus GenAiPlugin and GenAiFunction for topics and actions. Then check which Messaging channels, embedded service deployments, and Experience Cloud sites expose each agent, because access multiplied by channel is your real risk. The rest of this article is each step in full, with every command written out.

You cannot secure agents you cannot list

Everything Agentforce Builder shows you in a UI exists as metadata underneath, and the names are worth learning because they are what the CLI speaks:

Metadata type What it is Notes
Bot The top-level agent (Agentforce agents build on the Einstein Bots infrastructure) API 60+ covers GenAI-era fields
BotVersion A version of the agent; only one is active Activation state lives here
GenAiPlannerBundle The reasoning planner: the container wiring topics and actions to the LLM (large language model) API 64+; replaces GenAiPlanner, which existed through API 63
GenAiPlugin A topic: a category of related actions with its own instructions An agent has several
GenAiFunction An action: the thing the agent can actually execute (flow, Apex, prompt template) The sharp end

Start by listing what exists. Same pattern as any metadata inventory (the approach from Fetching Security Metadata from Salesforce with sf CLI applies here unchanged: list first, then retrieve by name):

sf org list metadata --metadata-type Bot --target-org myorg
sf org list metadata --metadata-type GenAiPlannerBundle --target-org myorg
sf org list metadata --metadata-type GenAiPlugin --target-org myorg
sf org list metadata --metadata-type GenAiFunction --target-org myorg

If GenAiPlannerBundle comes back empty but you know agents exist, your project's sourceApiVersion in sfdx-project.json is probably below 64.0; older orgs built on API 60 to 63 used GenAiPlanner instead, so list that type too.

For the record-level view, BotDefinition and BotVersion are plain queryable objects, no Tooling API needed:

sf data query --query "SELECT Id, DeveloperName, MasterLabel, Type FROM BotDefinition ORDER BY DeveloperName" --target-org myorg

sf data query --query "SELECT Id, DeveloperName, Status, BotDefinition.DeveloperName, BotDefinition.Type FROM BotVersion WHERE Status = 'Active'" --target-org myorg

The Type field separates classic Einstein Bots from Agentforce agents, which matters because a five-year-old chatbot and a new agent look identical in a raw Bot listing. The second query is your active inventory: agents with a live version. The Tooling API adds the planner view if you want it:

sf data query --query "SELECT Id, DeveloperName, PlannerType FROM GenAiPlannerDefinition" --use-tooling-api --target-org myorg

Now retrieve the definitions and read them. This is where the audit stops being a row count and starts being a review:

sf project retrieve start --metadata GenAiPlannerBundle --target-org myorg
sf project retrieve start --metadata GenAiPlugin --metadata GenAiFunction --target-org myorg

In the retrieved XML, three things deserve your attention. In each GenAiPlugin, read the topic instructions: they are the natural-language guardrails, and vague instructions ("help the customer with their account") are how agents wander. In each GenAiFunction, note what it invokes: a flow that updates records is a write path, an Apex action is whatever that Apex does. And in the GenAiPlannerBundle, see which topics and actions are actually wired to each agent, because an action existing in the org is different from an action being reachable by an agent. Commit the retrieved source to git; the diff between this month's retrieve and last month's is your change log for free.

The agent user is the real attack surface

Here is the mental shift that makes the rest of the audit obvious: an Agentforce agent does not have permissions. Its user does. When you create a service agent, Salesforce auto-creates a user for it (on the "Einstein Agent User" profile, with an Einstein Agent user license), and everything the agent reads or writes happens as that user, governed by that user's profile, permission sets, and sharing. Instructions can be ignored by a sufficiently creative prompt. CRUD and FLS cannot.

So find the users:

sf data query --query "SELECT Id, Name, Username, IsActive, Profile.Name, CreatedDate, LastLoginDate FROM User WHERE Profile.Name = 'Einstein Agent User' AND IsActive = true" --target-org myorg

Cross-check with permission set licenses, which catches agent-related access granted to users who are not on the dedicated profile (including any human user someone gave agent permissions to during the pilot):

sf data query --query "SELECT Assignee.Name, Assignee.Username, Assignee.IsActive, Assignee.Profile.Name, PermissionSetLicense.MasterLabel FROM PermissionSetLicenseAssign WHERE PermissionSetLicense.MasterLabel LIKE '%Agentforce%' OR PermissionSetLicense.MasterLabel LIKE '%Einstein Agent%'" --target-org myorg

I use LIKE filters rather than exact license names deliberately: Salesforce has renamed and split these licenses more than once (the service-agent one ships as "Agentforce Service Agent User" at the time of writing), and a pattern match survives the renames. What you want from this query is a short list: every identity in the org that holds agent-related licensing. In a tidy org that is one user per agent plus the admins who build them. Anything else on the list needs an explanation.

Two red flags to look for at this step. An agent user that also appears in PermissionSetAssignment for admin-grade permission sets means someone "fixed" a pilot error by over-granting. And a human user holding an agent permission set license usually means testing shortcuts that never got cleaned up.

Map the CRUD and FLS behind each agent

Take each agent user Id from the previous step and ask what it can actually do. First, its permission sets:

sf data query --query "SELECT PermissionSet.Label, PermissionSet.Name, PermissionSet.IsOwnedByProfile FROM PermissionSetAssignment WHERE Assignee.Profile.Name = 'Einstein Agent User'" --target-org myorg

Then the object permissions those grants add up to. Replace the Id below with each agent user's Id from your own org:

sf data query --query "SELECT Parent.Label, SobjectType, PermissionsRead, PermissionsCreate, PermissionsEdit, PermissionsDelete, PermissionsViewAllRecords, PermissionsModifyAllRecords FROM ObjectPermissions WHERE ParentId IN (SELECT PermissionSetId FROM PermissionSetAssignment WHERE AssigneeId = '005Aa000001Example') ORDER BY SobjectType" --target-org myorg

This single query is the heart of the audit. Every row is an object the agent can touch, and the boolean columns are the honest answer to "what can our agents access?". PermissionsViewAllRecords or PermissionsModifyAllRecords on an agent user should stop you cold: that is sharing bypass granted to a non-human identity that talks to strangers.

For the objects that matter most (whatever holds your customer data), go one level deeper into field-level security:

sf data query --query "SELECT Parent.Label, Field, PermissionsRead, PermissionsEdit FROM FieldPermissions WHERE SobjectType IN ('Contact','Case','Account') AND ParentId IN (SELECT PermissionSetId FROM PermissionSetAssignment WHERE AssigneeId = '005Aa000001Example') ORDER BY Field" --target-org myorg

In the ForcedLeak write-up I made the point that the exfiltrated data was data the agent user was allowed to read. Nothing was "hacked" at the permission layer. That is why this step is not optional: the FLS rows you are looking at are the exact upper bound of what any prompt injection against that agent can leak. Trim the rows and you trim the worst case, no matter what the prompt says.

Channels decide the blast radius

The same agent with the same permissions is a different risk depending on who can talk to it. An internal-only agent behind SSO (single sign-on) can be probed by employees. An agent on a public web page can be probed by everyone on earth, patiently, by script. So the last inventory dimension is exposure:

sf org list metadata --metadata-type MessagingChannel --target-org myorg
sf org list metadata --metadata-type EmbeddedServiceConfig --target-org myorg
sf data query --query "SELECT Id, Name, Status, UrlPathPrefix FROM Site" --target-org myorg

MessagingChannel covers Messaging for In-App and Web plus other messaging surfaces; EmbeddedServiceConfig is the embedded deployment that puts a chat window on a page; the Site query lists your Experience Cloud and public sites. For each active agent from step one, write down every channel it is connected to (the embedded service deployment names usually make the mapping obvious; where they do not, the agent's channel connections in Setup settle it). If an agent is reachable from an Experience Cloud site, the guest user posture of that site is now part of the agent's threat model too; I covered how to measure that in sf-audit's guest user exposure and reachability checks.

While you are here, make sure agent activity will be visible after the fact. Agent conversations and the API calls their actions make show up in event log files, and even without an Event Monitoring license you can baseline the free subset: see Free Salesforce Event Monitoring with EventLogFile. An agent you cannot observe is an agent you cannot incident-respond.

Score what you found

You now have three lists: agents, their users' access, and their channels. Combine them and the priorities fall out:

Data access Channel Verdict
Broad CRM read (or any write) Public site or web chat Fix now. This is the ForcedLeak shape.
Broad CRM read Internal only Fix this sprint. Insider prompt injection is still injection.
Narrow, task-scoped read Public Acceptable if the FLS list is short and reviewed.
Narrow, task-scoped Internal The target state. Write it down as the standard.

"Fix" here almost never means rewriting instructions. It means shrinking the agent user's permission sets to the objects and fields the agent's actions genuinely need, and nothing else. The ObjectPermissions query gives you the before and after evidence.

Make it a script, not a project

Everything above is deterministic queries and CLI calls, which means it belongs in a script, not a quarterly project plan. A minimal version:

#!/usr/bin/env bash
set -euo pipefail
ORG="myorg"
STAMP=$(date +%Y-%m-%d)
mkdir -p "agent-audit/$STAMP"

sf data query --query "SELECT Id, DeveloperName, MasterLabel, Type FROM BotDefinition" --target-org "$ORG" --result-format csv > "agent-audit/$STAMP/agents.csv"

sf data query --query "SELECT Id, DeveloperName, Status, BotDefinition.DeveloperName FROM BotVersion WHERE Status = 'Active'" --target-org "$ORG" --result-format csv > "agent-audit/$STAMP/active-versions.csv"

sf data query --query "SELECT Id, Name, Username, IsActive, Profile.Name, LastLoginDate FROM User WHERE Profile.Name = 'Einstein Agent User'" --target-org "$ORG" --result-format csv > "agent-audit/$STAMP/agent-users.csv"

sf data query --query "SELECT Assignee.Name, Assignee.Username, PermissionSetLicense.MasterLabel FROM PermissionSetLicenseAssign WHERE PermissionSetLicense.MasterLabel LIKE '%Agentforce%' OR PermissionSetLicense.MasterLabel LIKE '%Einstein Agent%'" --target-org "$ORG" --result-format csv > "agent-audit/$STAMP/agent-licenses.csv"

sf project retrieve start --metadata GenAiPlannerBundle --metadata GenAiPlugin --metadata GenAiFunction --target-org "$ORG"

Run it weekly in CI, commit the output, and git diff becomes your agent change alarm: a new row in agents.csv, a new permission set on an agent user, a topic's instructions edited. Nobody has to remember to check, which is the only kind of checking that survives contact with a busy quarter.

This is the same philosophy behind our sf-audit plugin (@cclabsnz/sf-audit): security posture as a scriptable, diffable artifact rather than a one-off review. To be precise about what it does today: sf audit security runs 82 read-only org-level checks (guest user exposure, connected app scopes, integration user hygiene, event monitoring coverage, and the attack-chain correlation described in sf-audit: 61 Checks, Attack Chains, and Compliance Mapping), plus sf audit events pull for log baselining and sf audit diff for before-and-after comparisons. Those checks cover the org that surrounds your agents: the guest users on the sites that host them, the connected apps beside them, the logs that would catch them misbehaving. Agentforce-specific checks (agent user privilege scoring, channel-times-access blast radius) are on the roadmap, not shipped, which is exactly why the queries in this post exist: run them by hand or in your own CI today, and let the plugin absorb them when those checks land.

Frequently Asked Questions

Q: Do Agentforce agents bypass sharing rules and field-level security?

A: No. The agent executes as its agent user, and standard object permissions, FLS, and sharing apply to that user like any other. The Einstein Trust Layer adds masking and filtering between Salesforce and the LLM, but it does not change what the agent user can query in the first place. That is why auditing the agent user's permissions is the control that matters: it sets the hard upper bound on what any conversation, malicious or not, can reach.

Q: What is the difference between GenAiPlanner and GenAiPlannerBundle?

A: GenAiPlanner was the original planner metadata type, available through API version 63. GenAiPlannerBundle replaced it in API version 64 as a container that bundles the agent's topics, actions, and orchestration in agent-specific subfolders. If your retrieves come back empty, check sourceApiVersion in sfdx-project.json: below 64.0 you get the old type, 64.0 and above you need the bundle. Orgs that built agents early may still have GenAiPlanner components worth retrieving for history.

Q: We only ran a pilot that never went live. Do we still need this audit?

A: Yes, and stalled pilots are often the worst offenders. The pilot created an agent user, someone granted it generous permissions to get the demo working, and then everyone moved on. The user record, its permission sets, and possibly an inactive-but-present agent are all still in the org. The queries in this post take minutes to run; deactivating an orphaned agent user takes one more. That ratio makes "we never went live" an argument for running the audit, not against it.

Q: Can sf-audit run this Agentforce audit for me today?

A: Not yet, and I would rather say that plainly than imply otherwise. Today sf-audit gives you the surrounding baseline: 82 org-level checks including guest user exposure on the Experience Cloud sites that host agents, connected app scope review, and event log baselining. The Agentforce-specific checks are on the roadmap. Until they ship, the script in this post is the audit; it uses the same approach (read-only queries, diffable output) so moving it into CI now means nothing is wasted later.

Q: Where do I see what an agent actually did after the fact?

A: Agent conversations run through the platform like any other session, so the trail is in event log files and setup audit trail rather than a dedicated "agent log" you might expect. Start with the free EventLogFile subset to baseline API and login activity for the agent user, and watch the agent user's LastLoginDate and API volumes for anomalies. If the agent writes records, field history tracking on the target objects gives you record-level evidence.

Key Takeaways

  • Agents are metadata; list them like metadata. BotDefinition and BotVersion queries plus GenAiPlannerBundle, GenAiPlugin, and GenAiFunction retrieves give you the full inventory in five commands.
  • The agent user is the security boundary. Instructions are suggestions; the agent user's CRUD and FLS are enforcement. Audit ObjectPermissions and FieldPermissions, not just Builder screens.
  • Find every identity holding agent licensing, including humans who picked up agent permission sets during pilots. PermissionSetLicenseAssign with a LIKE '%Agentforce%' filter catches renames.
  • Risk is access multiplied by channel. Broad read on a public channel is the fix-now quadrant; map every agent to its Messaging, embedded service, and Experience Cloud surfaces.
  • Script it or it will not happen twice. CSV output plus git diff turns a one-off review into a standing control, which is the entire sf-audit philosophy applied to agents.

What's Next?

Recommended Reading:

Action Items:

  1. Run the BotDefinition and agent user queries against production today and count both lists; if the numbers surprise you, that is the finding.
  2. Pull ObjectPermissions for each agent user and remove every grant the agent's actions do not need, starting with any ViewAllRecords or ModifyAllRecords.
  3. Put the script version in CI on a weekly schedule, commit the CSVs, and review the diff like you review code.

If you want the org-wide baseline around your agents while you work through this, sf audit security from @cclabsnz/sf-audit runs the 82 surrounding checks in one command. And if you would rather have a second pair of eyes on the full agent audit itself, that is work CloudCounsel does; the queries above are exactly where we start.

Resources & References