Skip to content

Proving AI actions in access review evidence

To prove AI actions in access review evidence, record the person and the client behind each call as it happens. SaaS logs name the account, not the client.

Raajshekhar Rajan Updated 9 min read
An access review spreadsheet row showing a Salesforce change with an empty owner column

To prove AI actions in access review evidence, you need a record of who and which client made each call, written when the call happens. The SaaS application's own log cannot supply that. It names the account that called it, and an assistant acting for an employee uses that employee's account. The layer where the tool call is authorized and issued has to write the actor down.

The quarterly access review shows the gap. One row will not close: on March 14 at 02:14, an opportunity in Salesforce moved to Closed Won. Salesforce names the account that made the change. The person who owns that account was asleep, and says so. The owner column stays blank.

MCP (Model Context Protocol) is the standard way an AI assistant calls tools in other apps. It sits one layer above the application that wrote that row, which is why the row cannot say an assistant was involved.

What changes when an agent acts for a person?

The actor stops being obvious. An access review checks that the identities holding access should hold it, and it samples past actions as evidence. In NIST SP 800-53, both duties sit in the account management control, AC-2. It asks organizations to monitor the use of accounts and to review them at a frequency they define.

For years the two kinds of actor were easy to tell apart. A person clicked a button. A scheduled job ran under a service account. The split held up in an audit because it held up in the logs.

An assistant acting for a person is neither. It runs inside that person's session, against that person's connected account, and it picks the call itself. The MCP specification calls tools model-controlled: the model can discover and invoke them on its own, based on the conversation and the user's prompts. Naming the human on the row is true and incomplete. The human did not write the request.

Why do AI actions in access review evidence come back blank?

Because attribution gets rebuilt afterwards from fields that were never meant to carry it. Each SaaS application records the credential that called it. MCP's own authorization model has the client make requests on behalf of a resource owner, meaning the person who signed in. Downstream, the credential Salesforce sees is usually that person's own connected account.

Sometimes it is a shared service account somebody created for automation two years ago. The discussion under AC-2 lists shared accounts among the types organizations may want to prohibit because of the added risk. Either way the row looks like ordinary API traffic.

Reviewers then guess from the hour of day, the call rate or the user agent string. Each is a heuristic. An access review resting on one comes apart at the first follow-up question in a customer security questionnaire.

The blank row is not a logging failure in the destination application. Salesforce recorded exactly what reached it. The missing fact belongs to the place where the tool call was authorized and issued.

Can a user agent string tell you an agent was involved?

Not reliably. A user agent is a string the caller sets, so it says what the caller chose to say about itself. A script on a laptop and an assistant acting for the same person can send the same header, because both use a library rather than a browser. Vendors set the header inconsistently, and a proxy in the path can rewrite it before it lands.

Reading it later has a second problem. The header identifies the software that opened the connection. It does not say whether a person asked for the action, read the result, or approved it. Two calls with identical headers can be a nightly sync and someone typing a request into Cursor.

A user agent is evidence of a client. An access review asks for the actor.

How does Elaichi record that an AI client made the call?

As fields written at the point of action. In Elaichi, a call from Claude, ChatGPT or Cursor is recorded under the person who signed in, with surface mcp and the OAuth client named. Those three clients are marked verified. The actor_kind field has values including user, system, staff, scim, api_token and ai_assistant, and ai_assistant marks the Elaichi Agent only. Nothing downstream has to infer it. A reviewer reads the person and the client on the same row.

The surface shows how the call arrived: mcp for an AI client such as Claude, ChatGPT or Cursor, and console for a person in the console. The client name is checked against the app's registered redirect addresses. A user agent differs, because the client sets it itself.

That works because of where the call passes. Each SaaS account the company uses is connected once, and its tools are served from one organization-wide address, POST /mcp. That address runs standard MCP on the Streamable HTTP transport, with OAuth in front. OAuth is the sign-in flow that issues a scoped grant instead of handing over a password. No member has a URL of their own, and no client config carries a token. Every grant belongs to a named member, so the member and the client both land on the record.

Elaichi does not hold connector credentials. They live in a separate credential service, encrypted at rest, which also refreshes tokens. If a refresh fails, the connection's status becomes needs_reauth, so the failure is on the record rather than silent.

Actor names resolve on the server when the trail is read. A member who has left shows as "Former member" rather than dropping out of the list. In a quarter where somebody resigned, that is the difference between a gap and an answer.

What does one audited tool call record?

Enough to close the row. Elaichi keeps one entry for each connected-tool call that reaches execution (a call refused earlier writes none). Each names the account the call actually reached, read from the execution rather than the request. That lets the pack say which of two Notion workspaces an agent wrote to. The fields an AI audit log should hold lists the rest, with the question each one answers.

Each entry keeps the one path argument that names the object, as the target id, and nothing else about the arguments. You can show which object a delete acted on, not what was written to it. NIST's AU-3 asks an audit record to establish the type of event, when and where it happened, its source, its outcome and the identity of anyone or anything involved. Its discussion also notes that a trail recording inputs can expose personal information, which is the case for leaving argument contents out.

Do you have to line up two exports by timestamp?

No. Elaichi writes audit events and application logs in one record shape, so one query covers both. The trail is append-only and newest-first, pages by cursor, and can be filtered by time, actor, action kind, category or free text.

The type system scopes each organization's audit history, not a WHERE clause. A dropped WHERE clause leaks; a wrong scope returns nothing. That failure direction matters more than usual, because the in-product assistant can read the audit log. Customer-visible audit records and internal application logs sit apart, and the customer read path queries only the audit records.

One trade-off to plan around: the trail is eventually consistent, so a new row can lag the call by a moment. Pull evidence after the review period has closed, and do not build an alarm on the absence of a row.

Why does the trail keep an error code and not the vendor's message?

To keep third-party payload out of places it should not reach. A failed call produces two error strings in Elaichi. The caller gets one built from what the third party sent back, and that one is never stored anywhere else. The audit trail gets a different string, built without reference to the request or the response.

Audit records are visible to the whole organization, and the in-product assistant can read them. Forwarding them to a customer's SIEM, the log platform a security team searches, is part of the design. A remote error body reaching any of those would be third-party payload leaving through the log pipe.

The cost to a reviewer is real. You get a code, the operation and the account, not the vendor's explanation of why the call failed. State that trade in the evidence pack rather than discovering it during an audit call.

Will next quarter's evidence have the same columns?

Yes. In Elaichi, the metadata promoted into fields of its own comes from a fixed allowlist, not from a rule that flattens whatever arrives on the event. Metadata keys can be influenced by users and are unbounded in practice. Flattening all of them would let one organization's traffic grow the field namespace for its whole tenant.

For an access review, that constraint helps. The fields in this quarter's extract are the fields in next quarter's. A new column is a request with a review behind it, not something that appears because an agent sent an unusual payload.

How do you prove a fix took effect after the review?

Record when the change applied, and know which of two timings it falls under. Mixing them up is how an evidence pack becomes wrong.

Grant revocation, member removal and member suspension land on the next call, because revocation state is read fresh from the organization store every time. In Elaichi, removing or suspending a member revokes every live grant in the same transaction as the membership change.

Changes to roles and restrictions take effect within about two minutes, on MCP, the console and REST alike. Record the time the change applied, not the time someone asked for it.

Each control then gets one line. Every member holds exactly one role, which a unique index enforces, so each role is a complete persona. Restrictions decide which connectors and which individual tools a target may reach. In Elaichi, restriction targets are role or user only; the organization default is the absence of a rule, which means allow everything.

When is a manual review still enough?

When one team uses one assistant against read-only connections. Export the assistant's workspace logs, export the target application's logs, line up the timestamps, and ask the four people who had access. At that size a control plane such as Elaichi is overhead that buys nothing, and holding off is a defensible call.

The point where the method stops working is usually visible in advance. A second client appears, so Claude, ChatGPT and Cursor are all in scope. Connections start doing writes and deletes rather than reads. Contractors get access. Or a customer asks, in writing, how you tell an agent action from a human one. From there, manual correlation produces a paragraph of reasoning where the reviewer wanted a field.

What goes in the evidence pack for the next review?

Start from the row you could not close. Decide which columns the reviewer needs, then confirm each one is a recorded field rather than something a person reconstructs.

If the review feeds a SOC 2 report, the auditor works from the AICPA's Trust Services Criteria. The AICPA publishes them as control criteria for evaluating controls over security, availability, processing integrity, confidentiality and privacy. Mapping CC6 and CC7 to AI agent evidence lines each criterion up with the record that answers it.

Give the reviewer their own seat. The Elaichi Auditor role is free and read-only, so a compliance reviewer does not use a license. Auditor lacks tool:execute, the permission that gates the whole MCP endpoint ahead of every scope. That seat sees an empty tools/list, and any call comes back with an in-band error that names the missing permission.

If your team works in a log platform, plan the forwarding. Beyond the in-app trail, export to your own Datadog comes with the Black plan, which is launching soon. Splunk HEC and Microsoft Sentinel destinations are accepted as destinations, but Elaichi delivers events only to Datadog. Read the log type filter twice: absent means forward everything, and an empty list means forward nothing. Elaichi has three regions, EU, US and APAC, chosen when the organization is created. For EU and US, the organization's data store and the execution of its org-scoped requests and tool calls stay in that jurisdiction. APAC uses best-effort placement and is not a residency guarantee. Every organization's audit trail, whatever its region, is stored in one log instance in the EU.

Two limits belong in the pack rather than in a footnote. The trail does not contain the prompt, because an MCP server never sees one. The prompt-injection write gate lives in the Elaichi agent window and does not apply to a raw tool call. Deleting an organization deletes its credentials, but it has no path to purge the audit history, analytics events or the credential service's connector configuration rows, and the product says so by naming that residue. Deletion does not purge the audit history. Each record ages out under the log server's 90-day retention, counted from when it was written. Neither limit is comfortable to write. Both are better written by you than found by an auditor.

The product overview covers how one endpoint serves every connected account, and the security page lists what is enforced in code. What to do when a contractor leaves handles the offboarding half of the same problem. For scope, browse the connector catalog, with 600+ connectors, or the team use cases.

FAQ

Frequently asked questions

How can an audit log show that an AI client made a call?

By recording the surface and the client as fields when the action happens. In Elaichi, a call from Claude, ChatGPT or Cursor is recorded under the person who signed in, with surface mcp and the OAuth client named. The actor_kind field has values including user, system, staff, scim, api_token and ai_assistant, which marks the Elaichi Agent. Both are written at the point of action rather than inferred later from a user agent string, so a reviewer reads the actor instead of reconstructing it.

Can you tell whether an AI or a person made a change from SaaS application logs alone?

Usually not. A SaaS application records the credential that called it, and an assistant acting for an employee commonly uses the same connected account that employee uses by hand. A user agent is a string the caller sets, so it identifies client software rather than the actor. The distinction has to be recorded one layer up, where the tool call was authorized and issued.

Does Elaichi log the arguments an agent passed to a tool?

Elaichi logs the one path argument that names the object, as the target id, and nothing else about the arguments. It keeps one entry for each connected-tool call that reaches execution (a call refused earlier writes none). Each entry records the operation and tool, the connection actually reached, the classification, whether the call was approved, how it ended, and an error code rather than the vendor's message.

Do you need a control plane to pass an access review with one AI client?

No. If one team uses one assistant against read-only connections, exporting the assistant logs and the application logs and asking the handful of people involved is proportionate. The method stops scaling when a second client is added, when connections start performing writes and deletes, when contractors get access, or when a customer asks in writing how agent actions are distinguished from human ones.

How quickly does revoking access take effect after an access review finds a problem?

In Elaichi, grant revocation, member removal and member suspension take effect on the next call, because the revocation state is re-read from the organization store on every call. A role change or a restriction change takes effect within about two minutes, because roles and restrictions pass through a short cache and then edge propagation.

Put agents to work on your own systems

14 days on Gold, no credit card. Start with one app and one team.

Works with
Claude ChatGPT Cursor and any other MCP client, or the Elaichi Agent.
When the trial ends
Nothing is deleted. Connections, roles and the audit log stay where they are, so subscribing picks up exactly where you left off.