Wrapping a governance check as an Agentforce action — Flow vs Apex vs LWC

sf-dev-ai answers governance questions for an external agent over MCP. But a lot of the audience for those answers is inside Salesforce — admins and architects working in an Agentforce agent, not a separate IDE. So the natural next surface is an Agentforce action: the same check, callable by an in-org agent.

The interesting decisions aren't "how do I call an LLM." They're: which surface (Flow, Apex, or LWC), and how to keep an agent-callable action from becoming a privilege-escalation hole.

The three surfaces

I built the check as Apex. Here's the part that matters:

1/3@InvocableMethod is the seam
GovernanceAction.cls
1@InvocableMethod(label='Audit dangerous permissions')
2public static List<List<Finding>> audit(List<Request> requests) {
3 List<List<Finding>> results = new List<List<Finding>>();
4 for (Request req : requests) {
5 results.add(findGrants(req.permissionApiName));
6 }
7 return results;
8}
9 
10private static List<PermissionSet> permissionSetsGranting(String perm) {
11 List<PermissionSet> granting = new List<PermissionSet>();
12 for (PermissionSet ps : [
13 SELECT Id, Name, Label, PermissionsModifyAllData, PermissionsViewAllData
14 FROM PermissionSet
15 WHERE IsOwnedByProfile = false
16 WITH USER_MODE
17 ]) {
18 Object flag = ps.get(perm);
19 if (flag instanceof Boolean && (Boolean) flag) {
20 granting.add(ps);
21 }
22 }
23 return granting;
24}
This one annotation is what makes a plain Apex method callable from Agentforce's Agent Builder (and from Flow). The List<List<Finding>> shape is the bulkification contract — one result list per request.

The security is the point

An agent-callable action widens who can invoke your code — from "a developer who wrote a SOQL query" to "anyone who can talk to the agent." Two properties keep that safe, and both are in the Apex, not the prompt:

  1. WITH USER_MODE — the query runs in the caller's security context. The action can't return permission data the running user couldn't already see.
  2. The allow-listpermissionApiName is checked against a fixed Set of auditable permissions before it touches a query. The agent can't coerce it into reading an arbitrary field.

I verified the class with the Salesforce Code Analyzer — the same engine sf-dev-ai proxies as run_code_analyzer — and got it to 0 violations (it caught a dynamic-SOQL injection flag and a missing CRUD check on the first pass; both are gone). That's the bar I'd hold a customer's agent actions to: statically clean, user-mode, allow-listed, tested.

Wiring it up

Once deployed, the action shows up in Agent Builder: Topics → New Action → Apex → "Audit dangerous permissions." Map the input, write the instruction for when to use it, activate, and ask the agent "which permission sets grant Modify All Data?" in the preview.

Deploy needs an Agentforce-enabled org — a current free Developer Edition has it. Code, tests, and the wiring steps: sf-dev-ai-agentforce. The governance check is now three surfaces — MCP tool, retrieval corpus, and Agentforce action — over one idea.