Who deleted the database?

May 29, 20266 min read

"Who deleted the database?" used to be a question with an answer. You read the audit log, found the user, and you were done. The query belonged to a user, the commit to an author, the deploy to an engineer and a ticket.

Agents break that clean lookup. A destructive action is now the end of a chain: a human asked an agent to do something, the agent acted through a runtime, called a tool over an MCP server, and maybe cleared an approval in a different app. And the identity on the final write rarely settles it. A cloud agent acts under a non-human identity, so the log names a service account and the human vanishes. A desktop agent like Claude Code acts under the developer's own credentials, so the log names the human but cannot say whether they ran the command or the agent did it on their behalf. Either way, part of the chain is missing.

Attribution is the work of reconstructing that chain: tying the action back to the human who caused it, the agent that performed it, the credential and runtime that carried it, and the approval that allowed it. It sounds like a lookup. It is a hard correlation problem.

TL;DR

  • Attribution is the foundation. You cannot enforce least privilege, run an incident, or pass an audit on agent activity until you can say who and what caused each action.
  • The hard part is correlation. The signals live in separate systems with no shared key: the human in your IDP, the agent on the endpoint, the approval in Slack, the write in Databricks. None of it was built to be joined.
  • There is no universal parser. Every platform logs differently, so Forge built per-platform correlation, deterministic rules where the format is known and LLMs for the ambiguous joins, across Google Drive, Databricks, GitHub, and the rest.

Why attribution is a correlation problem

The reason the question is hard is that the answer is scattered across systems that do not share an identity.

A developer, Maya, runs Claude Code on her laptop to clean up a staging schema. It reads the repo, runs a few shell commands, queries Databricks to check a table, and runs a migration. One command resolves to the wrong environment, and a production table is gone. Claude Code used Maya's own credentials the whole way, so here is what the logs hold:

Databricks   drop table     by maya@co               14:02:11Z
GitHub       force-push      by maya-dev              14:01:30Z
AWS          s3:DeleteObj    by AKIA3F... (IAM key)   14:00:50Z
Endpoint     claude_code     session s_9f3a (maya)    13:58:02Z

Three platforms name Maya three different ways, and none of them knows an agent was involved at all. Only the endpoint trace knows Claude Code ran the session. There is no foreign key tying the Databricks drop to that session, or telling you whether Maya ran the command or her agent did. You have to infer it: from the timestamps, from the credential, from the session ID, from what the agent read just before. That inference is the correlation problem, and it is the whole game.

That one line, by maya@co, hides three situations a security team has to tell apart: Maya ran the command herself; her agent ran it and she approved that step; or her agent ran it on its own, with no human in the loop. The identity is identical in all three. Whether it was the human or the agent, and whether a human approved it or not, is the thing you actually need, and it is the thing no single log records.

Why it has to be done per platform

There is no generic correlator, because every platform models identity its own way, and most have no concept of an agent at all.

Snowflake attributes every query to a single USER_NAME. Point an agent at a shared service account and every action collapses onto that one name, with no native field for the human behind it. Databricks splits identity across columns: executed_by for who ran a statement, executed_as for the user or service principal whose privilege was used, and an on-behalf-of identity_metadata that does not always reach the logs delivered to your own storage. Google Drive tags each event with a callerType of user or key and an applicationInfo.impersonation flag, but whether an app acting on a user's token surfaces as the user or the app is not guaranteed. GitHub is the exception that proves the rule: its agentic audit events now carry actor_is_agent and an agent_session_id, yet an OAuth app's actions are still not attributed to the app, and a commit's author is just git config, trivially spoofed unless it is signed.

For each integration, Forge worked out what the platform actually records, what its identity fields mean, how an agent action surfaces in them, and where the human is hiding.

Rules where it is deterministic, LLMs where it is not

The data has two kinds of structure, so the correlation takes two kinds of work.

Where a platform's format is known and stable, deterministic rules do the join: parse the Databricks query tag, map the IAM key back to Maya, pull the Claude Code session ID off the endpoint. Rules are precise, fast, and auditable, which matters when the output is evidence in an incident.

Where the link is ambiguous, an LLM does the correlation: matching an approval to the action it authorized when there is no shared ID, resolving an identity that two systems name differently, deciding whether an action was the human, the agent with an approval, or the agent on its own. The model absorbs the fuzziness no static rule can enumerate.

Together they reconstruct the chain: the dropped table came from Maya's Claude Code session, on her laptop, under her credentials, at this step of the run, under this policy.

What the chain unlocks

Once the chain is intact, governance follows. A policy can weigh whether this developer's agent should be touching a production table at all, instead of seeing only an IAM key. An incident can pivot from the dropped table to Maya, her Claude Code session, the runtime, and the command that triggered it, in one step. An audit can show the chain instead of summarizing around it.

That is the difference between blocking a tool and governing the work.

Closing

The old world had a clean join:

user -> credential -> action -> log

The agent world scatters that join across a dozen platforms that were never meant to connect:

human -> agent -> runtime -> tool -> approval -> policy -> action

Reconstructing it, per platform, with rules where the format is known and models where it is not, is the hard part. It is what Forge does.