<?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: Talha Waqas</title>
    <description>The latest articles on DEV Community by Talha Waqas (@talha_waqas_d304e9e5c54d5).</description>
    <link>https://dev.to/talha_waqas_d304e9e5c54d5</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%2F4213106%2Fe4fd02e9-c423-47ff-a1a7-f280411994c6.png</url>
      <title>DEV Community: Talha Waqas</title>
      <link>https://dev.to/talha_waqas_d304e9e5c54d5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/talha_waqas_d304e9e5c54d5"/>
    <language>en</language>
    <item>
      <title>Letting an AI assistant act on business records without giving it more power than the user</title>
      <dc:creator>Talha Waqas</dc:creator>
      <pubDate>Wed, 28 Oct 2026 12:52:22 +0000</pubDate>
      <link>https://dev.to/talha_waqas_d304e9e5c54d5/letting-an-ai-assistant-act-on-business-records-without-giving-it-more-power-than-the-user-5gk3</link>
      <guid>https://dev.to/talha_waqas_d304e9e5c54d5/letting-an-ai-assistant-act-on-business-records-without-giving-it-more-power-than-the-user-5gk3</guid>
      <description>&lt;p&gt;Adding an AI assistant to business software comes with an obvious temptation. You give it a service account, let it call your APIs, and it can "do things": approve a leave request, mark an invoice paid, move a purchase order forward.&lt;/p&gt;

&lt;p&gt;The problem is that the assistant now has more power than the person talking to it. A junior employee who can't approve a purchase order can ask the assistant to, and if the assistant runs with broad permissions, it will.&lt;/p&gt;

&lt;p&gt;In Inaya Business Workspace we took a stricter approach, which we call &lt;strong&gt;guarded execution&lt;/strong&gt;. Here's how it works and what it costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 1: the assistant proposes, it never executes
&lt;/h2&gt;

&lt;p&gt;Every tool that changes something is a &lt;code&gt;propose_*&lt;/code&gt; tool. When the assistant decides an invoice should be marked paid, it doesn't mark it paid. It creates an &lt;strong&gt;action request&lt;/strong&gt;: target record, proposed change and reason, stored with status &lt;code&gt;PENDING&lt;/code&gt;. Nothing about the real record changes yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 2: approval re-checks the real permission
&lt;/h2&gt;

&lt;p&gt;A person approves or rejects the request. Before it's applied, one server-side function decides whether the approver may do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ai-action-approval-gate.js (simplified)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resolveCanApprove&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;orgId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetRecordType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetRecordId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;proposedAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;membership&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetRecordType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TASK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* same check the real task transition uses */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetRecordType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INVOICE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* same check the real invoice transition uses */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// one case per record type&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each record type, it re-derives exactly the check the real &lt;code&gt;transitionX()&lt;/code&gt; function would apply to whoever runs it. An AI-proposed action can never be approved by someone who couldn't already do that thing by hand.&lt;/p&gt;

&lt;p&gt;That gives us one source of truth. We don't keep a separate "AI permissions" model that can drift out of sync with the real one. Adding a new &lt;code&gt;propose_*&lt;/code&gt; tool means adding one case to this function, and the review state machine stays the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 3: it's a plain module, so it's testable
&lt;/h2&gt;

&lt;p&gt;The gate lives in a plain library module with no dependency on the web framework. That lets us unit-test it with &lt;code&gt;node --test&lt;/code&gt;, not only through HTTP round-trips. For a security boundary, fast tests matter: the gate is easy to change, so it needs to be easy to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 4: the record is audited
&lt;/h2&gt;

&lt;p&gt;Proposals, approvals and rejections are written to the activity log, so "the AI did it" is never the whole answer. There's always a person who approved it, and a record of what was proposed.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Friction.&lt;/strong&gt; Users must confirm actions the assistant could technically just do. We think that's correct for finance and HR, and annoying for trivial tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage work.&lt;/strong&gt; Every record type needs its own case in the gate. An action type without a case can't be approved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honesty about limits.&lt;/strong&gt; This protects against privilege escalation through the assistant. It doesn't make the assistant's suggestions correct, which is why a person reviews them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this sits
&lt;/h2&gt;

&lt;p&gt;Inaya encrypts documents on the client (AES-256-GCM) before upload, and its secure chat uses MLS (RFC 9420). The workspace hasn't had an independent security audit yet. We say so in the product, and we're saying it here.&lt;/p&gt;

&lt;p&gt;If you've built AI actions into a multi-user app, I'd like to hear how you handled permissions: did you mirror them, or route everything through a human like we did?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project: &lt;a href="https://www.inayanetwork.com/business" rel="noopener noreferrer"&gt;https://www.inayanetwork.com/business&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Open-source custody SDK: &lt;a href="https://github.com/Talhawaqas/custody-sdk" rel="noopener noreferrer"&gt;https://github.com/Talhawaqas/custody-sdk&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>architecture</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
