<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shubham Naik</title>
    <description>The latest articles on DEV Community by Shubham Naik (@shubham_naik_b13687e05198).</description>
    <link>https://dev.to/shubham_naik_b13687e05198</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4102812%2Ff78ebe09-ec5a-4243-ad18-4f62124d1c2d.jpeg</url>
      <title>DEV Community: Shubham Naik</title>
      <link>https://dev.to/shubham_naik_b13687e05198</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shubham_naik_b13687e05198"/>
    <language>en</language>
    <item>
      <title>Your AI agent's audit log is signed by the thing it's auditing</title>
      <dc:creator>Shubham Naik</dc:creator>
      <pubDate>Thu, 03 Sep 2026 09:09:14 +0000</pubDate>
      <link>https://dev.to/shubham_naik_b13687e05198/your-ai-agents-audit-log-is-signed-by-the-thing-its-auditing-e16</link>
      <guid>https://dev.to/shubham_naik_b13687e05198/your-ai-agents-audit-log-is-signed-by-the-thing-its-auditing-e16</guid>
      <description>&lt;p&gt;&lt;em&gt;Why in-process audit logs fail under agent compromise, and how to fix them with out-of-process, request-bound signatures.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Several open-source tools promise tamper-evident audit trails for AI agents. They record every tool call in a hash chain and sign entries with an HMAC to satisfy compliance requirements.&lt;/p&gt;

&lt;p&gt;The core mechanism has a gap: the key that signs the log lives in the same process as the agent being logged.&lt;br&gt;
The threat model&lt;br&gt;
Security teams need audit logs because prompt injection can redirect LLM agents. The basic threat model assumes the agent process itself is compromised.&lt;/p&gt;

&lt;p&gt;Most current implementations import an audit library directly into the agent runtime. That library holds an HMAC key or computes unsalted hashes. On every tool call, it appends a record to a local file.&lt;/p&gt;

&lt;p&gt;When an attacker gains control of the agent process, they also control the signing key. They can do a bunch of things such as,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mint valid records attributed to any user.&lt;/li&gt;
&lt;li&gt;Modify history and recompute the entire hash chain.&lt;/li&gt;
&lt;li&gt;Drop log entries completely so actions go unrecorded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the attacker holds the key, the modified chain still verifies.&lt;/p&gt;

&lt;p&gt;A reliable audit system must guarantee that a compromised agent cannot impersonate another principal, alter history, or prevent its actions from being logged.&lt;/p&gt;

&lt;p&gt;Attribution does not prevent an agent from running harmful commands if it already has permission to run them. It only guarantees an accurate record after the fact.&lt;/p&gt;

&lt;p&gt;Meeting this guarantee requires three controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The signing key must live outside the agent process to prevent forgery.&lt;/li&gt;
&lt;li&gt;The signature must bind to the specific request (tool name, argument hash, nonce, and timestamp) so signatures cannot be replayed on different calls.&lt;/li&gt;
&lt;li&gt;The log record must leave the host over a channel the agent cannot reach before the agent gets its signature. Otherwise, the agent can drop the log.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In-process logging tools do not meet these requirements. I wrote mcp-identity-shim to implement them for Model Context Protocol (MCP) tool calls.&lt;/p&gt;

&lt;p&gt;Architecture&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy01ugk2000r1gc3u4ite.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy01ugk2000r1gc3u4ite.png" alt=" " width="799" height="704"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent process holds no key material. &lt;br&gt;
It computes public values: a SHA-256 hash of canonicalised arguments, a UUID nonce, and an RFC 3339 timestamp. It sends these values to a sidecar daemon running under a separate UID over a Unix domain socket with peer-credential checks.&lt;/p&gt;

&lt;p&gt;The sidecar sends the log record to an off-host, append-only sink before returning the signature. Because of this sequence, the agent cannot receive a valid signature while dropping the audit record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Each call binds two identities:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Machine identity&lt;/em&gt;: A SPIFFE JWT-SVID signed by the SPIRE server. The workload does not hold the SPIRE signing key, so machine identity cannot be forged.&lt;br&gt;
&lt;em&gt;User delegation&lt;/em&gt;: An OAuth 2.0 Token Exchange (RFC 8693) act_token. In this token, sub is the human user and act.sub is the agent's SPIFFE ID. This records that Agent X acted for Alice, including nested sub-agent delegations.&lt;br&gt;
Why a second signature is necessary&lt;br&gt;
The SPIFFE Workload API does not support custom claims in a JWT-SVID. It only issues sub and aud. Request-specific data (args_sha256, nonce, ts) cannot be embedded directly in the SVID.&lt;/p&gt;

&lt;p&gt;Binding each request requires a second signature over the full payload. SPIRE delivers the X.509-SVID private key to the workload, so the sidecar runs as the SPIFFE workload instead of the agent. The sidecar holds the X.509 key and signs payloads; the agent remains keyless. The process separation provides the security boundary.&lt;/p&gt;

&lt;p&gt;The signed assertion travels in the MCP request's _meta field under a reverse-DNS key (dev.mcpshim/identity). A server-side verifier can recompute the argument hash and check the signature. Because downstream MCP servers might strip _meta, the off-host sink remains the source of truth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"_meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"dev.mcpshim/identity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"v"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"jwt_svid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;SPIRE-signed: proves WHICH workload&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"act_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;RFC 8693: agent acting-for Alice&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"req"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"enterprise_db_query"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args_sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"nonce"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"sig"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;sidecar signature over everything above&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Verified failure modes&lt;/em&gt;: The test suite in the repository verifies these properties directly&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tampering&lt;/em&gt;: Modifying arguments after signing breaks verification because args_sha256 does not match.&lt;br&gt;
Replay attacks: Attaching an existing signature to a different tool call fails because the signature covers the tool name, argument hash, nonce, and timestamp. Timestamps outside the freshness window fail verification.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Suppression&lt;/em&gt;: Killing the agent process immediately after a tool call does not prevent logging. The sidecar ships the record before returning the signature.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Credential leakage&lt;/em&gt;: The LLM context never contains keys, SVIDs, or tokens. Credentials attach out-of-process after generation, so prompt injection cannot extract them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Scope and limitations&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This shim provides attribution without policy enforcement. It does not inspect arguments or block malicious requests from an authorised agent. If an agent with database access receives a malicious prompt, it can still run destructive queries; the shim only guarantees that the query is permanently logged and attributed.&lt;/li&gt;
&lt;li&gt;Short token lifetimes limit the exposure window, but do not replace active revocation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Current status&lt;/strong&gt;:&lt;br&gt;
The core implementation is functional: Ed25519 signatures, a Unix domain socket sidecar, an append-only hash chain, and roughly 300 tests against stub SPIFFE and STS backends. Live SPIRE integration via py-spiffe, an RFC 8693 client, verifier-side SVID validation, and replay tracking are still in progress.&lt;/p&gt;

&lt;p&gt;IETF working groups (including AIMS, WIMSE, and Transaction Tokens) and the MCP community are standardising SPIFFE and token exchange for agents. Out-of-process signing provides the request binding needed to keep those audit logs reliable when an agent is compromised.&lt;/p&gt;

&lt;p&gt;The repository is available at &lt;a href="https://github.com/naikshubham14/mcp-identity-shim" rel="noopener noreferrer"&gt;mcp-identity-shim&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>cybersecurity</category>
      <category>security</category>
    </item>
  </channel>
</rss>
