<?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: chinmay garg</title>
    <description>The latest articles on DEV Community by chinmay garg (@chinmay_garg).</description>
    <link>https://dev.to/chinmay_garg</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%2F217922%2Fea7c2e55-e473-4f83-9a95-e162c758259c.jpg</url>
      <title>DEV Community: chinmay garg</title>
      <link>https://dev.to/chinmay_garg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chinmay_garg"/>
    <language>en</language>
    <item>
      <title>A Per-Run Identity and Audit Log for AI Agents: A Minimal Pattern After India's CERT-In Advisory</title>
      <dc:creator>chinmay garg</dc:creator>
      <pubDate>Mon, 19 Oct 2026 08:34:01 +0000</pubDate>
      <link>https://dev.to/chinmay_garg/a-per-run-identity-and-audit-log-for-ai-agents-a-minimal-pattern-after-indias-cert-in-advisory-52cm</link>
      <guid>https://dev.to/chinmay_garg/a-per-run-identity-and-audit-log-for-ai-agents-a-minimal-pattern-after-indias-cert-in-advisory-52cm</guid>
      <description>&lt;p&gt;On October 15, 2026, India's CERT-In issued advisory CIAD-2026-0049. AI agents run by model providers during testing reached the live systems of Indian organisations and read personal data and business records. The way in was ordinary: credentials left in config files and public repositories, and cloud roles with too many permissions. None of the affected organisations detected it. The providers reported it themselves.&lt;/p&gt;

&lt;p&gt;The advisory has a short list of agent-specific controls. Two of them are the subject of this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give each agent run its own short-lived, least-privilege identity.&lt;/li&gt;
&lt;li&gt;Keep a tamper-resistant log of agent actions, and keep API logs for at least 180 days.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is a small pattern that does both, with code I ran before writing this. It is a teaching sketch, not a product. I will say where it stops being enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  What problem are we solving?
&lt;/h2&gt;

&lt;p&gt;Most agent logs today look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-10-19 02:14:07  service-account-12  GET /customers/88  200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That line has an event and no actor. You cannot tell which agent made the call, who deployed it, what task it was on, or whether the task was allowed to touch customer data. If personal data is involved, those are the first questions a customer, an auditor or a regulator will ask.&lt;/p&gt;

&lt;p&gt;What we want instead, for every call an agent makes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A credential that belongs to one run of one agent, and expires quickly.&lt;/li&gt;
&lt;li&gt;Claims inside it: which agent, which accountable owner, which purpose, which scopes.&lt;/li&gt;
&lt;li&gt;A check at the gateway that verifies the credential and the scope before the call goes through.&lt;/li&gt;
&lt;li&gt;A log entry for every decision, allowed or denied, that cannot be quietly edited later.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Issue a credential per run
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;

&lt;span class="c1"&gt;# Demo only. In production use asymmetric keys held in a KMS or HSM.
&lt;/span&gt;&lt;span class="n"&gt;SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo-only-secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;b64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unb64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;b64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;issue_run_credential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;One credential per agent run: short-lived, narrow, and named.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;purpose&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;purpose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scopes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ttl_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;b64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each run gets a new &lt;code&gt;run_id&lt;/code&gt;. The &lt;code&gt;owner&lt;/code&gt; field is the accountable human or legal entity. The &lt;code&gt;purpose&lt;/code&gt; field says why the run exists. &lt;code&gt;scopes&lt;/code&gt; lists the exact actions allowed. The default lifetime is 15 minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Verify at the gateway
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;PermissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bad signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;unb64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;PermissionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expired&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Log every decision in a hash chain
&lt;/h3&gt;

&lt;p&gt;Each entry carries the hash of the entry before it. Change an old entry and every later hash stops matching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditLog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Append-only log where each entry carries the hash of the one before it.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unverified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unverified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unverified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;purpose&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;purpose&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unverified&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resource&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allowed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chain_is_intact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
            &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Authorize, log, and support a kill switch
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;REVOKED_RUNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;PermissionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({},&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REVOKED_RUNS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scopes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that denied calls are logged too, including calls with a forged token. Those are the entries you most want during an investigation. An unverified caller is recorded as &lt;code&gt;unverified&lt;/code&gt;, never silently dropped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AuditLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;issue_run_credential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice-agent-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;finance-ops@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;purpose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reconcile vendor invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read:invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read:invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice/1042&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# True
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read:customers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer/88&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# False, out of scope
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;forged.token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read:invoices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice/1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# False, unverified
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chain intact:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chain_is_intact&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allowed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# tamper with history
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chain intact after tampering:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chain_is_intact&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;True
False
False
chain intact: True
chain intact after tampering: False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first call is in scope. The second asks for &lt;code&gt;read:customers&lt;/code&gt;, which the credential never had. The third is a forged token. The last two lines show that editing history is detectable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this stops being enough
&lt;/h2&gt;

&lt;p&gt;This is deliberately small. Before you use anything like it, know the limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared secret.&lt;/strong&gt; The demo signs with HMAC and one secret. Anyone who holds the secret can mint credentials. In production use asymmetric keys held in a KMS or HSM, so the gateway can verify without being able to issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log storage.&lt;/strong&gt; A hash chain shows tampering only if you keep the latest hash somewhere the attacker cannot rewrite, such as a write-once store or an external anchor. Otherwise they can rebuild the whole chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retention.&lt;/strong&gt; CERT-In asks for API logs for at least 180 days. Build retention and legal-hold rules around the log, not just the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Egress.&lt;/strong&gt; CERT-In also asks for outbound traffic to be blocked by default and enforced at the network level, not through model instructions. A credential check at the gateway does nothing if the agent can call out directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credential theft.&lt;/strong&gt; The advisory's root cause was exposed credentials. Short lifetimes and narrow scopes shrink the damage. They do not replace secret scanning in code, config and CI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-declared names.&lt;/strong&gt; &lt;code&gt;agent_id&lt;/code&gt; here is assigned by whoever issues the credential. It is not whatever the agent says about itself. This week, researchers reported an agent that claimed to be Claude when it likely was not. A name in a request is a claim. A signed credential you issued is evidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where DPDP fits
&lt;/h2&gt;

&lt;p&gt;Under India's DPDP Act, a Data Fiduciary is responsible for personal data in its control, including processing done on its behalf. Consent Manager registration opens on November 13, 2026, and the main duties, including breach reporting, apply from May 13, 2027.&lt;/p&gt;

&lt;p&gt;The credential above is the natural place to carry two more claims: the consent or lawful basis the run relies on, and a reference to the Data Fiduciary that answers for it. I left them out of the code because there is no agreed standard for them yet. NPCI is building a registry of AI agents for UPI, Amex has released an Intent ID and Proof of Intent Token for agent purchases, and DPDP will have Consent Managers. I could not find a public description that ties an agent's identity to any of the three consent records.&lt;/p&gt;

&lt;p&gt;That is the part I think is worth building in the open. This is not legal advice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions for you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Do you issue credentials per agent run, or per agent, or per team?&lt;/li&gt;
&lt;li&gt;What do you use as the trust anchor so the log cannot be rebuilt?&lt;/li&gt;
&lt;li&gt;For on-device agents, where should the log live?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would like to hear what has held up in production.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sources: CERT-In advisory CIAD-2026-0049 as reported by The Hacker News and TechCrunch (Oct 16, 2026); TechCrunch on the Amex Intent Intelligence Framework (Oct 13, 2026); TechNode on NPCI's AI-agent registry (Sep 12, 2026).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>privacy</category>
      <category>python</category>
    </item>
    <item>
      <title>AI agents read Indian firms' live data with leaked keys: what the CERT-In advisory means for agent identity and DPDP</title>
      <dc:creator>chinmay garg</dc:creator>
      <pubDate>Thu, 15 Oct 2026 18:30:56 +0000</pubDate>
      <link>https://dev.to/chinmay_garg/ai-agents-read-indian-firms-live-data-with-leaked-keys-what-the-cert-in-advisory-means-for-agent-3nd4</link>
      <guid>https://dev.to/chinmay_garg/ai-agents-read-indian-firms-live-data-with-leaked-keys-what-the-cert-in-advisory-means-for-agent-3nd4</guid>
      <description>&lt;p&gt;On 15 October 2026, CERT-In issued an advisory it rated critical. It says some AI model providers reported that their models and agents reached the live systems of several Indian organisations during research and testing. This post is about what that means for how we identify agents, and what a data audit needs. I am working from press coverage of the advisory, so check the original text for exact wording.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did CERT-In say?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The agents read business records, including personal data, using credentials and connection settings left in application config, public code repositories and permissive cloud roles.&lt;/li&gt;
&lt;li&gt;None of the affected organisations detected it. The AI companies disclosed it after their own reviews.&lt;/li&gt;
&lt;li&gt;CERT-In also saw financially motivated campaigns using agents with ready-made intrusion tools. Time to impact fell from days to a few hours. Partial fixes were followed by bigger repeat attacks. In disclosed cases, agents re-used the same exposed credential in separate operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this is an identity problem, not only a secrets problem
&lt;/h2&gt;

&lt;p&gt;Leaked keys are an old story. Scan your repos, rotate, move on. That advice is right and you should follow it.&lt;/p&gt;

&lt;p&gt;But look at what the agents did with the key. They found a credential, used it, and later found it again and used it again. From the victim's side, every one of those reads looked like the key's rightful owner doing normal work. There was no agent in the log. There was only a credential.&lt;/p&gt;

&lt;p&gt;An audit under DPDP asks something different. For a given personal-data record: who accessed it, under which purpose, and on what consent basis? A bare credential cannot answer that. An identity can.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small design that helps
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. One identity per agent instance.&lt;/strong&gt; Not the service account. Not the developer's token. Each running agent gets its own ID, tied to the team or legal entity that answers for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Short-lived, narrow credentials.&lt;/strong&gt; Issued per task, with a purpose and an expiry. A key found in a repo next month should already be dead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Canary credentials.&lt;/strong&gt; Plant fake keys in config and repos that no real system uses. Any use of one triggers an alert. Agents that scrape for credentials will find them. Since none of the firms in the advisory detected the access, this is the cheapest detection you can add.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. A provenance record on every read and write of personal data.&lt;/strong&gt; Agent ID, who it acts for, purpose, consent reference, credential ID, time. Written in the same transaction as the data operation, append-only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Test and live kept apart, with synthetic credentials in test.&lt;/strong&gt; This also keeps real data out of any agent experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Storage for the records is small next to the payload. The real cost is credential issuing and rotation per agent, which is plumbing you build once. The alternative cost arrives per incident, and CERT-In's six-hour reporting window does not wait for you to find out who did what.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DPDP angle
&lt;/h2&gt;

&lt;p&gt;Security safeguard and breach duties under the DPDP Rules take full effect on or about 13 May 2027. That is months away. Teams that wait for the date will be adding identity to systems already in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I am unsure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When the breach clock starts if an agent read personal data and nobody noticed for weeks. When it happened, or when the fiduciary became aware?&lt;/li&gt;
&lt;li&gt;Whether an agent should ever act under its operator's identity, or always under its own, with the operator named as the accountable party.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run agents against live data, I would like to hear how you handle both.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Moneycontrol, "AI agents accessed Indian firms' live systems during testing, read confidential data: CERT-In", 15 Oct 2026&lt;/li&gt;
&lt;li&gt;Business Standard, "AI agents accessed Indian systems, sensitive data without consent: Cert-In", 15 Oct 2026&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>agents</category>
      <category>ai</category>
    </item>
    <item>
      <title>One consent store, three ways in: designing for Consent Managers and AI agents under DPDP</title>
      <dc:creator>chinmay garg</dc:creator>
      <pubDate>Wed, 14 Oct 2026 18:05:26 +0000</pubDate>
      <link>https://dev.to/chinmay_garg/one-consent-store-three-ways-in-designing-for-consent-managers-and-ai-agents-under-dpdp-2252</link>
      <guid>https://dev.to/chinmay_garg/one-consent-store-three-ways-in-designing-for-consent-managers-and-ai-agents-under-dpdp-2252</guid>
      <description>&lt;h2&gt;
  
  
  What Rule 4 changes in your data model, and what it does not.
&lt;/h2&gt;

&lt;p&gt;On or about November 13, Rule 4 of the DPDP Rules starts. It opens registration for Consent Managers. It does not require an ordinary Data Fiduciary to use one. Still, if a customer picks a Consent Manager, your system has to accept that consent and honour a withdrawal that comes through it.&lt;/p&gt;

&lt;p&gt;Here is how I would model that so it does not turn into a second, separate consent system.&lt;/p&gt;

&lt;h2&gt;
  
  
  One record, more than one channel
&lt;/h2&gt;

&lt;p&gt;A Consent Manager is a new source of consent. It is not a new kind of consent. So it goes into the same table as direct consent, with a field that says how it arrived.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;consent_record
  id
  principal_ref        -- the Data Principal
  purpose
  method               -- direct | consent_manager
  status               -- active | withdrawn
  cm_id                -- null for direct consent
  cm_artifact_id       -- null for direct consent
  cm_artifact_hash     -- null for direct consent
  cm_signature         -- null for direct consent
  created_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The four &lt;code&gt;cm_&lt;/code&gt; fields stay empty for direct consent. When a Consent Manager sends consent, you store its artifact, the hash of that artifact, and its signature next to the record. You can then prove what it sent, unchanged.&lt;/p&gt;

&lt;p&gt;You also need a small table for each Consent Manager you connect to: its name, registry ID, API endpoint and public key. The public key is how you check the signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  One audit log
&lt;/h2&gt;

&lt;p&gt;The audit log should be append-only and hash-chained. Each entry carries the hash of the one before it, so an edit anywhere breaks the chain. Consent given, changed and withdrawn all land in this one log, whichever channel they came from. A withdrawal from a Consent Manager follows the same rule as a withdrawal from your own form.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part most designs miss: who acted
&lt;/h2&gt;

&lt;p&gt;Consent is only half the story. The other half is who touched the record afterward. A common setup has an enrich agent, a summary agent and an archive agent all running under one service account. The log then says "pipeline-svc did it" and nothing more.&lt;/p&gt;

&lt;p&gt;What I would add to each audit entry, as a pattern and not a standard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;audit_log
  seq, prev_hash, hash
  consent_id
  event                -- given | changed | withdrawn | read | copied
  actor_type           -- person | system | agent
  actor_id             -- unique per agent, not a shared account
  ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, the question "who handled this record, and under which consent?" is one query.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am not sure about
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The Consent Manager artifact format is not final. My draft follows the Account Aggregator consent artifact. The real DPDP format is not yet specified, so expect it to change.&lt;/li&gt;
&lt;li&gt;The Board has not published its registration process or technical standard, as far as I can find. Any API you build now is a guess. Keep the Consent Manager side behind a thin adapter.&lt;/li&gt;
&lt;li&gt;When consent is withdrawn and an agent has already copied the record, should the system fail the next read, or finish and flag it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have built any of this, I would like to hear what broke.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dpdp</category>
      <category>privacy</category>
      <category>agents</category>
    </item>
    <item>
      <title>Muse can act for you. Nothing outside Meta can check who it is.</title>
      <dc:creator>chinmay garg</dc:creator>
      <pubDate>Sun, 11 Oct 2026 09:00:51 +0000</pubDate>
      <link>https://dev.to/chinmay_garg/muse-can-act-for-you-nothing-outside-meta-can-check-who-it-is-3f9k</link>
      <guid>https://dev.to/chinmay_garg/muse-can-act-for-you-nothing-outside-meta-can-check-who-it-is-3f9k</guid>
      <description>&lt;p&gt;Meta launched Muse on September 8. It is a personal agent that sends email, books travel, fills in forms, negotiates and pays with a one-time card. Reports say it passed 3 million weekly users within weeks.&lt;/p&gt;

&lt;p&gt;I want to look at it as an architect, not as a critic. Meta has put real work into the inside of this system. The outside is the problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Meta built
&lt;/h3&gt;

&lt;p&gt;From Meta's own announcement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each agent runs in a dedicated cloud VM, isolated from other agents.&lt;/li&gt;
&lt;li&gt;A second agent, Sentinel, sits on the same machine and approves anything that leaves for the internet.&lt;/li&gt;
&lt;li&gt;Credentials sit in secure storage. Muse can use them without seeing them.&lt;/li&gt;
&lt;li&gt;Muse asks before sending email or making a purchase, and keeps an audit trail.
These are good controls. They are also all inside one company's boundary, and they are Meta's claims. Nobody outside can check them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What the other side sees
&lt;/h3&gt;

&lt;p&gt;Think about everyone Muse deals with: a shop, a bank, a buyer on a marketplace, a hospital portal. What does that party actually receive?&lt;/p&gt;

&lt;p&gt;An account, a session, a message. It does not receive a statement like "this is an agent, here is who answers for it, and here is what it may do for this user."&lt;/p&gt;

&lt;p&gt;Fast Company reports one case. A YouTuber, Matt Robb, asked Muse to sell some equipment. Muse accepted a $600 offer and posted his home address on Marketplace. He had ticked an "allow always" box during setup and expected to be asked before a sale was final. A buyer showed up at his building.&lt;/p&gt;

&lt;p&gt;I don't know the internals of that case, and I'm not going to guess whether it was a bug. The design lesson is easier to see than the cause.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blanket consent is the wrong shape
&lt;/h3&gt;

&lt;p&gt;"Allow always" is one broad permission, given once, with no end date. Delegation to an agent should look different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scoped to a task ("list this item, accept offers above X, ask me first below that"),&lt;/li&gt;
&lt;li&gt;limited by action ("never share my address"),&lt;/li&gt;
&lt;li&gt;time-bound,&lt;/li&gt;
&lt;li&gt;revocable, and checkable by the other party.
If the buyer's side could read a credential that said "this agent may negotiate but may not disclose a home address," the mistake has a place to be caught. Today it lives only in a settings screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bystanders
&lt;/h3&gt;

&lt;p&gt;TIME reports that, going by Muse's internal instructions, it keeps hourly-updated dossiers on its users and also maps people who never signed up, through other users' agents. Meta did not dispute the findings and says each VM is isolated.&lt;/p&gt;

&lt;p&gt;Whatever the details, the structural point holds. When your agent processes other people's personal data, the question "who is the accountable party for that processing?" needs an answer that a regulator can read, not a settings page.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a relying party should be able to check
&lt;/h3&gt;

&lt;p&gt;Before an agent touches personal data or moves money, the receiving side should be able to answer three questions, using facts the agent cannot write for itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verify_credential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;# who is it? signed, not revoked
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;revoked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;deny&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accountable_entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;deny&lt;/span&gt;      &lt;span class="c1"&gt;# who answers for it?
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;deny&lt;/span&gt;       &lt;span class="c1"&gt;# what may it do?
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;touches_personal_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data_protection_status_ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;deny&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;log &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No model score is in this path. A model can recommend. The decision comes from facts. NPCI's chairman said something close to this about UPI agents last month: AI may recommend, but authentication and settlement must follow deterministic, auditable rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  One credential, three claims
&lt;/h3&gt;

&lt;p&gt;This is the idea behind Saakshya, the agent identity registry I'm working on. An agent carries one signed credential that binds its cryptographic identity, its legal owner with data-protection status, and its certified operating scope. The other side checks one object and gets all three answers. I'm leaving the design details out of this post. The shape is the point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open question
&lt;/h3&gt;

&lt;p&gt;If an agent of a large platform processes data about people who never used the platform, who is the accountable party, and where should a regulator be able to read that? Is it the user, the platform, or both? I'd like to hear from people who have had to answer this for an auditor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Meta, Introducing Muse (Sep 8, 2026): &lt;a href="https://about.fb.com/news/2026/09/introducing-muse-personal-ai-agent/" rel="noopener noreferrer"&gt;https://about.fb.com/news/2026/09/introducing-muse-personal-ai-agent/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;TIME, Meta's Muse AI agent is building a dossier on you (Oct 6, 2026): &lt;a href="https://time.com/article/2026/10/06/meta-muse-ai-agent-privacy/" rel="noopener noreferrer"&gt;https://time.com/article/2026/10/06/meta-muse-ai-agent-privacy/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Fast Company, Meta's Muse is taking off. So are the privacy concerns: &lt;a href="https://www.fastcompany.com/91620148/metas-muse-is-taking-off-so-are-the-privacy-concerns" rel="noopener noreferrer"&gt;https://www.fastcompany.com/91620148/metas-muse-is-taking-off-so-are-the-privacy-concerns&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>privacy</category>
      <category>architecture</category>
    </item>
    <item>
      <title>On-device agents need their own identity: a delegation design for the edge</title>
      <dc:creator>chinmay garg</dc:creator>
      <pubDate>Sat, 03 Oct 2026 04:30:00 +0000</pubDate>
      <link>https://dev.to/chinmay_garg/on-device-agents-need-their-own-identity-a-delegation-design-for-the-edge-4cj8</link>
      <guid>https://dev.to/chinmay_garg/on-device-agents-need-their-own-identity-a-delegation-design-for-the-edge-4cj8</guid>
      <description>&lt;p&gt;Local inference solves where the data lives. It says nothing about who the agent is when it calls out.&lt;/p&gt;

&lt;p&gt;Phone silicon can now run large mixture-of-experts models from flash and build a personal knowledge graph without a network call. Qualcomm's Snapdragon Summit pitch (Sep 22-24) is a personal agent that keeps your data on the device. Its own list of what such an agent needs includes "secure permissions," and I found no published spec for how an on-device agent proves its identity or its delegated authority to the service it calls. The one place delegation limits were named was the Qualcomm-Mastercard agentic commerce announcement on Sep 22.&lt;/p&gt;

&lt;p&gt;I haven't built on this silicon. This post is about the design problem, which applies whatever the chip.&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%2Fv03tedzc6nvbn23jjnn9.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%2Fv03tedzc6nvbn23jjnn9.png" alt="AI Agents working within governance" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode
&lt;/h2&gt;

&lt;p&gt;Local processing is not local authority. Sooner or later the agent calls an API, and that API has to answer three questions: which agent is this, who delegated this action, and when does the delegation expire. A long-lived token in app storage answers none of them. It also sits next to a model that reads untrusted text all day.&lt;/p&gt;

&lt;p&gt;The cloud version of this failure is public. An OpenAI agent routed around blocks on Services Australia's Medicare portal. The access happened Jun 18, the agency was notified Sep 10, and it was disclosed Sep 24. That is 84 days from access to disclosure, in part because the activity was hard to attribute.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;A design sketch&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Per-instance agent key.&lt;/strong&gt; Generate a non-exportable keypair in the hardware-backed keystore when the agent is installed. The agent's identity is that key, not the user's session.&lt;br&gt;
&lt;strong&gt;Delegation as a signed, short-lived token.&lt;/strong&gt; The user authorizes a task through an OS-level user-presence prompt. The result is a token naming the agent key, the task, the allowed tools and resources, and an expiry in minutes.&lt;br&gt;
&lt;strong&gt;Proof of possession on every call.&lt;/strong&gt; The agent signs each outbound request with its key. A lifted token is useless without the key that never leaves the hardware.&lt;br&gt;
&lt;strong&gt;Approval out of band.&lt;/strong&gt; "The user approved" is a signed assertion from the OS prompt, checked by the receiving service. A field the agent wrote into the request is untrusted data.&lt;br&gt;
&lt;strong&gt;Separate logs.&lt;/strong&gt; The device logs agent actions apart from user actions, and the receiving service logs the agent key and delegation ID next to the user account.&lt;/p&gt;

&lt;p&gt;The receiving side verifies signatures. It never has to trust the agent's description of itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it costs&lt;/strong&gt;&lt;br&gt;
Extra latency per call: one signature, plus verification on the server.&lt;br&gt;
Operational: token issuance and expiry handling for every task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where I am unsure&lt;/strong&gt;&lt;br&gt;
What happens when the device is offline and the delegation expires mid-task. Fail closed is safe and annoying.&lt;br&gt;
Whether attestation should tell the receiving service which model the agent runs. Useful for risk decisions, and easy to spoof without a trusted root.&lt;/p&gt;

&lt;p&gt;How this maps to India's DPDP Act, which expects you to show who handled a personal-data record and on what basis. An agent on a phone that forwards a record to a cloud service breaks that trail unless the delegation reference travels with it.&lt;/p&gt;

&lt;p&gt;If you have shipped any of this on-device, I would like to hear what broke.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>agents</category>
    </item>
    <item>
      <title>AI on Edge: One forward pass, one letter: a decision model that runs on a laptop</title>
      <dc:creator>chinmay garg</dc:creator>
      <pubDate>Fri, 02 Oct 2026 04:58:31 +0000</pubDate>
      <link>https://dev.to/chinmay_garg/ai-on-edge-one-forward-pass-one-letter-a-decision-model-that-runs-on-a-laptop-1c82</link>
      <guid>https://dev.to/chinmay_garg/ai-on-edge-one-forward-pass-one-letter-a-decision-model-that-runs-on-a-laptop-1c82</guid>
      <description>&lt;p&gt;&lt;strong&gt;How I get a confidence score for every option without generating text, and why that score should only ever make a gate stricter.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A voice bot does not need a paragraph about what the caller wants. It needs a label, and a number that says how sure the model is. So I trained a small model to give exactly that, and I wanted to see how far one 24 GB MacBook could take it.&lt;/p&gt;

&lt;p&gt;TypeSafe AI has described this style publicly as "decision models". This is my own version, built in the open-source forge fine-tuning tool. It is not their model, and I make no comparison to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model is Gemma 4 E2B (4-bit) with a LoRA adapter, rank 16. The adapter is 52 MB. Training peaked at about 5.7 GB of memory and took about an hour. The data was seven public datasets, plus synthetic rows for four behaviour tasks made by a local model on the same Mac. No paid API and no cloud GPU.&lt;/p&gt;

&lt;p&gt;Every example has the same shape: the conversation, a question, and lettered options.&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="err"&gt;Customer:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;need&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;move&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;appointment,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;it's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;urgent.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Question:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Which&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;customer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;making?&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Options:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;A)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;reschedule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;appointment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;B)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;cancel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;appointment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;C)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;about&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;pricing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model is trained to answer with one letter. At test time I do not let it write anything. I run the prompt once, read the raw scores for the letters A, B and C, and turn those into probabilities. A temperature fitted on a validation set makes them well calibrated. There is nothing to parse, and the answer can never be an invalid label.&lt;/p&gt;

&lt;p&gt;Three details mattered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Options are shuffled and the question is reworded on every training row, because models favour certain letters. Evaluation keeps a fixed order.&lt;/li&gt;
&lt;li&gt;Multi-select questions become many yes/no questions, so one format covers everything.&lt;/li&gt;
&lt;li&gt;I read the scores in-process. The server I use only returns the top 11 log-probabilities, so a 13-option list would silently lose probability mass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What good calibration gives you&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On about 68,000 test rows, the calibration error was between 0.006 and 0.04 on most tasks. Median latency was about 85 ms per decision (p95 117 ms), measured on the laptop GPU. That allows a simple rule: act when the model is at least 80% sure, and otherwise hand off.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Handled at 0.8 or higher&lt;/th&gt;
&lt;th&gt;Accuracy on those&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CLINC150 (held out)&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;98.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MASSIVE intent&lt;/td&gt;
&lt;td&gt;79%&lt;/td&gt;
&lt;td&gt;95.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BANKING77 (held out)&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;td&gt;92.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The part that is not about the model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A confident 0.92 says the label is probably right. It does not say the agent is allowed to act. The message being scored is text the caller wrote, so any model that reads it can be pushed around by it.&lt;/p&gt;

&lt;p&gt;This is how I would combine the two in an agent gateway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;check identity and scoped permission first
if denied: stop

score = decision_model(message)
if score is below the threshold: send to a human
else: go ahead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The score can turn a "go ahead" into a "send to a human". It can never turn a "denied" into a "go ahead". Identity and scoped permission decide. The model only narrows. And because the model runs locally, the customer's message never goes to a third-party API, which helps with DPDPA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I am not claiming&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The "held-out" intent tests pick from about ten options with random wrong answers. That is easier than the published 77-way and 151-way benchmarks.&lt;br&gt;
The behaviour-task tests are small, and the test rows were made by the same kind of model that made the training rows.&lt;br&gt;
I have not tested on real recorded calls. Everything above is on public data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where I am stuck&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Training loss stays flat near ln(26), which is a uniform guess over the answer letters, for roughly the first 2,000 steps. Then it drops sharply. A lower learning rate moved the plateau but did not remove it. I do not know why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open question:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;where should the hand-off threshold live? Per task, per data type, or per permission? I lean towards per data type. If you run something like this in production, I would like to know what you chose.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>slm</category>
    </item>
  </channel>
</rss>
