What does a pilot tell you about least privilege for AI agents?
Least privilege for AI agents means each agent reaches only the tools its work needs, and a pilot has already recorded what that work needed. NIST's least-privilege control, AC-6, allows only the accesses that users, or processes acting for them, need to accomplish assigned tasks (NIST SP 800-53 Rev. 5). An agent calling tools for someone is a process acting on that person's behalf. So narrowing is a measurement problem before it is a policy problem, and the audit log, the append-only record of what happened, holds the measurement.
A six-week pilot ends with traffic, not policy. Twenty or thirty people pointed Claude, ChatGPT or Cursor at Elaichi, connected the accounts they needed, and got on with work. MCP (Model Context Protocol) is the standard way an AI assistant calls tools in other apps (MCP specification), and every call that reached execution left a row. Nobody wrote a rule, and in Elaichi the absence of a rule means allow-all.
Guesswork fails in a specific way. It over-permits the tools people discuss in meetings and under-permits the ones an automation quietly depends on. Asking users does not fix it either, because they describe the outcome they wanted, not the operations the model called to get there. That gap is what breaks a workflow at 3am, and it is why a narrowing pass gets reversed a week later.
A pilot also leaves leftovers. OWASP's entry on excessive agency describes one exactly: an extension trialled during development and dropped for a better one, but still available to the agent (OWASP LLM06:2025). Its first mitigation is to limit the extensions an agent may call to the minimum necessary.
Which audit fields show what the pilot actually used?
The tool, the account reached, the result and who acted. Elaichi writes one entry for each connected-tool call that reaches execution (a call refused earlier writes none), which is the raw material for a narrowing decision.
Alongside the tool and operation, a record holds the connection, the classification, the approval decision and the result. The connection is the account the call actually reached, read from the execution rather than from what was asked. So "which of our two Notion workspaces did the agent write to" has an answer in the row itself.
The actor is a stored field, actor_kind, rather than a guess made afterwards from a user agent. Its values include user, system, staff, scim, api_token and ai_assistant, which marks only the Elaichi Agent. Calls from MCP clients are recorded under the person who signed in, with surface mcp. Audit events and application logs share one record shape, so a single query answers what happened instead of two systems being lined up by eye.
Two limits shape the analysis. The record keeps the one path argument that names the object, as the target id, and nothing else about the arguments, so you can see which object was acted on, not what was written to it. The trail is also eventually consistent, so a row from the last few seconds may not have appeared yet. Neither limit changes the tool inventory you can derive. Both change how you write the query.
Failed calls are as informative as successes. A tool somebody tried twice and abandoned was discovered, not needed. The trail is newest-first, cursor-paginated, and filterable by free text, category, actor, action kind and time, and it is on Gold. Beyond the in-app trail, export to your own Datadog comes with the Black plan, which is launching soon, for teams that would rather group the rows in their own tooling. Elaichi accepts Splunk HEC and Microsoft Sentinel as destinations but delivers events only to Datadog.
How do you turn pilot audit rows into a narrower tool list?
Work from the recorded set, then subtract, then verify. Elaichi serves one organization-wide MCP endpoint (a single network address that receives every client's calls), so the usage matrix is one query rather than one per team.
- Filter or export the pilot window, start to end. Include failures.
- Group by tool, connection and actor. You now have who called what, in which account.
- Separate the automation traffic. Rows with an
actor_kindofapi_token, orai_assistantfor the Elaichi Agent, behave differently from a person exploring. A synthetic tool runs a graph of steps, and every step goes through the same restriction check as any other call. The audit trail holds one row for the whole run, not one per step. - Mark each tool used, tried, or never seen. Never-seen tools are the safe part of the cut.
- Decide where the rule goes, role or user.
- Write the rule, wait, then verify with a real call.
Keep the list of tools that were tried and failed with a permission-shaped error. Those are the calls a user will ask you about within a day of the change, and having the list in hand turns a surprise into a reply.
Should the rule sit on a role or on one person?
On the role, almost always. In Elaichi, restriction targets are role or user only; the organization default is the absence of a rule, which means allow everything.
Every Elaichi member holds exactly one role, enforced by a unique index. A role is therefore a complete persona rather than a bolt-on, and a rule written against it covers everyone who holds it. Reserve user targets for genuine exceptions, such as one contractor who needs less than the team.
Know the limit of a user target before you use one. A rule aimed at a member can only narrow what their role allows, and never replaces or loosens a role rule. A user rule can take tools away from one person, but it cannot add a tool the role withholds. If one person needs an extra tool, they file an access request and an admin approves it in the console. The approval creates a grant for that person alone, and the role rules stay as they are.
Why does a first allow list so often deny everything?
Because an empty allow rule is still a rule. In Elaichi, the allowlist stage engages on the presence of an allow rule, not its contents, so an allow rule that names nothing denies everything. That empty allow list is the most common way a first narrowing pass takes a team offline.
Two rules decide what your written list actually means. Within each layer, allow rules union, block rules union, and blocks always beat allows. Across layers, a member is governed by their role's rules and the rules aimed at them, and a tool is reachable only when both admit it.
Enforcement runs against the same resolver at every place a member can reach a connector or tool, including listing, connecting, advertising, executing, the final outbound-request check and opening a stored tool file. A restricted tool never reaches the list a client is shown. Write the rule against a spare role first if you want to see its shape before a production team feels it.
Does an allow rule survive a tool being renamed?
Yes, because the rule binds the operation rather than the label. A block matches the advertised tool name or the operation recorded at write time. An allow matches that recorded operation only.
A rule is written against a connector and a tool, but the canonical resource and method are pinned against the catalog when the rule is saved. The advertised name of a tool can be changed by whoever edits that connector's documentation, so the name is a token the governed party controls. Governance binds the operation, never the label. The MCP specification is similarly wary of tool metadata: clients must treat tool annotations as untrusted unless they come from a trusted server (MCP tools specification). The reasoning behind the asymmetry is worked through in why a block matches the name and an allow matches the operation.
What happens to a run that is halfway through when a rule lands?
It can finish three steps and fail the fourth. A synthetic tool in Elaichi is a graph of steps with no cycles, each step calls a connection's tool, and every step is checked against restrictions on its own.
That is the failure mode to plan for. The half-finished state sits in the third-party system, not in Elaichi. A run that created a ticket, posted a comment, then could not attach the file leaves a ticket somebody has to finish by hand. The model cannot undo the calls it already made. The run's audit row names the step that failed, so the recovery path is visible. Recovery is still manual.
Three habits reduce it. Read the pilot rows for scheduled or token-driven traffic before you write anything, because those runs have nobody watching them. Cut never-seen tools first and tried-but-unused tools in a second pass. Land the change in a quiet window for the systems involved, not a quiet window for your own calendar.
Does tool search get around a restriction?
No. In Elaichi, connected tools are never listed one by one, however few there are. A model finds a connected tool through search_tools and calls it through execute_tool, and both sit behind the same gates as everything else. The second is only a naming indirection: it unwraps to the same name and arguments and falls through the identical gates, so there is no separate execution path and no privilege in it.
Control-plane operations stay listed individually, and search_tools never returns one. A restricted tool is withheld from the tool list and cannot be called. Search names it, flagged restricted, with no schema. OWASP calls the principle complete mediation: every request is checked against policy, rather than the model deciding what is allowed (OWASP LLM06:2025). Narrowing makes discovery quieter as well as execution safer, and the ranking behind that search is covered in how tool search applies a relevance floor.
How do you confirm a new rule actually took effect?
Wait out the window, then make real calls. A restriction change takes effect within about two minutes, and role membership behaves the same way. Both resolve through a 60-second cache plus edge propagation, on every surface, so the MCP endpoint, the console and the REST API agree inside that window.
Test a minute after saving and you are reading the old state. The test passes, the configuration looks right, and the workflow changes behavior while you are doing something else. Grant revocation, member removal and member suspension hold from the next call instead, and so do revoking a share and disconnecting an account. The OAuth grant, which is the authorization a client holds after signing in, is re-read from the organization store on every single call. In Elaichi, removing or suspending a member revokes every live grant in the same transaction as the membership change. Everything else needs the wait.
Build the wait into the change:
- Write the rule and confirm the allow list is not empty.
- Wait about two minutes by the clock.
- Re-list tools in the client the affected people use. A restricted tool is gone from the list.
- Call one restricted tool and one allowed tool.
- Read the audit row for the allowed call. It names the account reached, so you confirm you narrowed the right connection and not its twin. The restricted tool is withheld before execution, so that call writes no row.
A compliance reviewer can watch all of this without a billable seat. The read-only Auditor role is a free seat, and it lacks tool:execute, so an auditor cannot make the test calls by accident.
Can you lock one argument instead of removing the tool?
Yes. Sometimes the tool is needed and one argument is the problem, and frozen parameters cover that case without cutting the tool. A frozen key disappears from the schema the model works from. The frozen value is merged over the caller's arguments at execution, so passing the key cannot un-freeze it. The order runs entry defaults first, then the caller's or model's arguments, then frozen parameters, which win. In Elaichi, a freeze binds only calls made through its toolbox entry, so share the toolbox with the people it governs, not the connection itself.
This changes the shape of a narrowing pass. A tool that only had to be pinned to one workspace, one pipeline or one folder does not belong on a block list. Keeping it available with a frozen argument is usually less disruptive than removing it and then handling the ticket. How a frozen parameter protects a wire-transfer receiver works one example through.
When is a narrowing pass not yet needed?
When the pilot was small. If it was five people in one team with two connected apps, the audit-driven pass buys little. Read the rows, keep the current list, and revisit when a second team joins or the first automation ships. Restrictions are cheap to add later. The analysis pays off once the usage matrix is wide enough to contain surprises, and when you don't need an MCP gateway yet works through the earlier version of the same judgment.
Two cautions hold at every size. OAuth scopes sit on top of any tool list: mcp:read, mcp:write, mcp:destructive and mcp:tools, and a tool classified forbidden is reachable under no scope. The mcp:tools scope does not replace the ladder, so a connected tool whose method is a delete needs mcp:destructive as well.
The second caution is the prompt-injection write gate. The prompt-injection write gate lives in the Elaichi agent window and does not apply to a raw tool call. An MCP server never sees a user prompt, so the gate has nothing to inspect there. What does hold on the endpoint is RBAC per operation (role-based access control, with one role per member), the forbidden classification, output redaction, scope limits and an audit row for each call that reaches execution. A narrow tool list belongs in that set. It does not replace it.
For a worked single-team example, read how a sales team gets governed Salesforce access. More decisions of this shape sit under governance, the tools you are narrowing come from the connector catalog, and team use cases show which departments usually go first.