<?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: Muhammad Masood Kausar</title>
    <description>The latest articles on DEV Community by Muhammad Masood Kausar (@masaood).</description>
    <link>https://dev.to/masaood</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%2F4038284%2F32413a21-1e19-4677-a6c7-e7bd4c31dc7a.png</url>
      <title>DEV Community: Muhammad Masood Kausar</title>
      <link>https://dev.to/masaood</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/masaood"/>
    <language>en</language>
    <item>
      <title>The Node.js Shortcut That Quietly Created a Security Hole</title>
      <dc:creator>Muhammad Masood Kausar</dc:creator>
      <pubDate>Tue, 21 Jul 2026 07:46:18 +0000</pubDate>
      <link>https://dev.to/masaood/the-nodejs-shortcut-that-quietly-created-a-security-hole-4abo</link>
      <guid>https://dev.to/masaood/the-nodejs-shortcut-that-quietly-created-a-security-hole-4abo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;We verified who made the request, but never verified whether that user was allowed to access the requested resource.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The endpoint rejected unauthenticated requests, so we assumed it was secure.&lt;/p&gt;

&lt;p&gt;It was not.&lt;/p&gt;

&lt;p&gt;The route had authentication middleware. Invalid tokens returned &lt;code&gt;401&lt;/code&gt;. Expired sessions were rejected. The controller received a verified user.&lt;/p&gt;

&lt;p&gt;Everything looked protected.&lt;/p&gt;

&lt;p&gt;But an authenticated user could change the resource ID in the URL and access data belonging to another account.&lt;/p&gt;

&lt;p&gt;Authentication worked exactly as expected.&lt;/p&gt;

&lt;p&gt;Authorization was missing.&lt;/p&gt;

&lt;p&gt;That difference came down to one condition in a database query, but in the application’s security model, it was the entire boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Route Looked Secure
&lt;/h2&gt;

&lt;p&gt;The route looked like a normal protected Node.js endpoint:&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="nx"&gt;router&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/documents/:documentId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;validateAccessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;attachCurrentUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getDocument&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middleware performed all the expected work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;verified the access token&lt;/li&gt;
&lt;li&gt;rejected invalid sessions&lt;/li&gt;
&lt;li&gt;loaded the current user&lt;/li&gt;
&lt;li&gt;attached the user to the request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The controller only ran after authentication succeeded.&lt;/p&gt;

&lt;p&gt;That created a dangerous assumption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authenticated request = trusted request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But authenticated users should not automatically be trusted with every resource in the application.&lt;/p&gt;

&lt;p&gt;A logged-in customer should not be able to read another customer’s invoice.&lt;/p&gt;

&lt;p&gt;A member of one organization should not be able to access another organization’s projects.&lt;/p&gt;

&lt;p&gt;A normal employee should not be able to approve an administrative request.&lt;/p&gt;

&lt;p&gt;A project viewer may be allowed to read a project but not delete it.&lt;/p&gt;

&lt;p&gt;Authentication answers one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who is making this request?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Authorization answers another:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this user allowed to perform this action on this resource?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Identity is only an input to the authorization decision.&lt;/p&gt;

&lt;p&gt;It is not the decision itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shortcut Was Loading Records by ID Alone
&lt;/h2&gt;

&lt;p&gt;The controller accepted a resource ID from the URL:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;documentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentId&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service then loaded the record using only that ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;documentRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code looked clean.&lt;/p&gt;

&lt;p&gt;It was easy to read.&lt;/p&gt;

&lt;p&gt;It also asked the wrong question.&lt;/p&gt;

&lt;p&gt;The query asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does a document with this ID exist?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It did not ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this document belong to the current user or account?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The database had no knowledge of the security boundary.&lt;/p&gt;

&lt;p&gt;For an account-scoped application, the query should include the account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;documentRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For user-owned resources, it might include &lt;code&gt;ownerId&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;documentRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ownerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a multi-tenant system, it might include &lt;code&gt;tenantId&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;documentRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not just another database condition.&lt;/p&gt;

&lt;p&gt;It changes the meaning of the operation.&lt;/p&gt;

&lt;p&gt;The unsafe version means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Find this document.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The scoped version means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Find this document inside the boundary this user is allowed to access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The database query should understand the security boundary, not only the resource identifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Valid Token Made the Bug Harder to Notice
&lt;/h2&gt;

&lt;p&gt;Most authentication tests verify that anonymous users are rejected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rejects requests without a token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/documents/123&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful, but it only proves that unauthenticated users cannot access the endpoint.&lt;/p&gt;

&lt;p&gt;A positive test may verify that the owner can access the document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allows the owner to access the document&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createDocument&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;ownerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&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="s2"&gt;`/documents/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;createToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test also passes.&lt;/p&gt;

&lt;p&gt;The missing scenario is the one that exposes the vulnerability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a real user&lt;/li&gt;
&lt;li&gt;a valid token&lt;/li&gt;
&lt;li&gt;a real resource&lt;/li&gt;
&lt;li&gt;the wrong owner
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;does not expose another user's document&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;otherUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createDocument&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;ownerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&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="s2"&gt;`/documents/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;createToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;otherUser&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most valuable security test often uses a valid token belonging to the wrong user.&lt;/p&gt;

&lt;p&gt;This type of bug can also survive code review because authentication and data access usually live in different layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route
  → Middleware
    → Controller
      → Service
        → Repository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A reviewer sees the authentication middleware in the route file.&lt;/p&gt;

&lt;p&gt;Another reviewer sees a simple &lt;code&gt;findById&lt;/code&gt; method in the repository.&lt;/p&gt;

&lt;p&gt;Each piece looks reasonable in isolation.&lt;/p&gt;

&lt;p&gt;The vulnerability appears only when the complete request path is examined.&lt;/p&gt;

&lt;p&gt;The route proves identity.&lt;/p&gt;

&lt;p&gt;The controller accepts a client-controlled identifier.&lt;/p&gt;

&lt;p&gt;The repository loads the resource without ownership or tenant scoping.&lt;/p&gt;

&lt;p&gt;The response returns the result.&lt;/p&gt;

&lt;p&gt;Each file looked secure on its own.&lt;/p&gt;

&lt;p&gt;The security hole existed between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  UUIDs Did Not Fix the Authorization Problem
&lt;/h2&gt;

&lt;p&gt;A common response to this vulnerability is to replace sequential IDs with UUIDs.&lt;/p&gt;

&lt;p&gt;Sequential IDs are easy to guess:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/documents/1001
/documents/1002
/documents/1003
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UUIDs are much harder to enumerate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/documents/f9a81862-0db8-4f61-b7ea-9a74d97d71b4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That can reduce predictable resource discovery.&lt;/p&gt;

&lt;p&gt;It does not create permission.&lt;/p&gt;

&lt;p&gt;A user may still obtain a valid identifier through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a copied URL&lt;/li&gt;
&lt;li&gt;browser history&lt;/li&gt;
&lt;li&gt;logs&lt;/li&gt;
&lt;li&gt;analytics&lt;/li&gt;
&lt;li&gt;notifications&lt;/li&gt;
&lt;li&gt;support tickets&lt;/li&gt;
&lt;li&gt;screenshots&lt;/li&gt;
&lt;li&gt;frontend state&lt;/li&gt;
&lt;li&gt;another API response&lt;/li&gt;
&lt;li&gt;another vulnerable endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When authorization is missing, possession of the identifier becomes possession of the resource.&lt;/p&gt;

&lt;p&gt;That is not a safe access model.&lt;/p&gt;

&lt;p&gt;A resource ID identifies an object.&lt;/p&gt;

&lt;p&gt;It does not grant permission to access it.&lt;/p&gt;

&lt;p&gt;A UUID can hide the door, but it cannot replace the lock.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Same Shortcut Affected More Than Read Endpoints
&lt;/h2&gt;

&lt;p&gt;Once a service exposes a generic method like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it often becomes the default lookup for several endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET    /documents/:id
PATCH  /documents/:id
DELETE /documents/:id
POST   /documents/:id/share
POST   /documents/:id/archive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The missing boundary may allow another authenticated user to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read private data&lt;/li&gt;
&lt;li&gt;update records&lt;/li&gt;
&lt;li&gt;delete content&lt;/li&gt;
&lt;li&gt;download attachments&lt;/li&gt;
&lt;li&gt;resend invitations&lt;/li&gt;
&lt;li&gt;trigger workflows&lt;/li&gt;
&lt;li&gt;change ownership&lt;/li&gt;
&lt;li&gt;modify billing information&lt;/li&gt;
&lt;li&gt;approve restricted actions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Authorization also depends on the requested action.&lt;/p&gt;

&lt;p&gt;A user may be allowed to read a project but not delete it.&lt;/p&gt;

&lt;p&gt;An editor may update a draft but not publish it.&lt;/p&gt;

&lt;p&gt;A manager may approve expenses only below a specific amount.&lt;/p&gt;

&lt;p&gt;A support agent may view customer details but not change payment information.&lt;/p&gt;

&lt;p&gt;A better authorization model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User + Action + Resource + Context = Authorization decision
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Valid token = Permission granted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Nested Routes Can Create False Confidence
&lt;/h2&gt;

&lt;p&gt;Nested URLs often look naturally secure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/projects/:projectId/comments/:commentId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The route appears to say that the comment belongs to the project.&lt;/p&gt;

&lt;p&gt;But the URL does not enforce that relationship.&lt;/p&gt;

&lt;p&gt;A controller may verify access to the project and then load the comment using only &lt;code&gt;commentId&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;commentRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;commentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That comment could belong to another project.&lt;/p&gt;

&lt;p&gt;The query should verify both identifiers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;commentRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;commentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project itself must also exist inside the user’s permitted organization, account, or tenant.&lt;/p&gt;

&lt;p&gt;A nested URL does not prove the relationship.&lt;/p&gt;

&lt;p&gt;The data-access layer must enforce it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Fix Was Architectural
&lt;/h2&gt;

&lt;p&gt;The fastest fix may be a manual ownership check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ownerId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ForbiddenError&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is better than no authorization.&lt;/p&gt;

&lt;p&gt;But repeating checks manually across controllers creates another problem.&lt;/p&gt;

&lt;p&gt;Eventually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one endpoint forgets the check&lt;/li&gt;
&lt;li&gt;one endpoint checks ownership but not tenancy&lt;/li&gt;
&lt;li&gt;one endpoint applies a different role rule&lt;/li&gt;
&lt;li&gt;one endpoint loads sensitive data before rejecting the request&lt;/li&gt;
&lt;li&gt;one endpoint handles administrators globally instead of per organization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Authorization becomes duplicated business logic.&lt;/p&gt;

&lt;p&gt;Duplicated security logic eventually becomes inconsistent security logic.&lt;/p&gt;

&lt;p&gt;The stronger approach is to make authorization part of the resource-access design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Scoped repository methods
&lt;/h3&gt;

&lt;p&gt;For simple ownership or account rules, use methods that require the security boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findAccountDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;documentRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method does not provide an easy way to load a document outside the current account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Explicit policy services
&lt;/h3&gt;

&lt;p&gt;More complex permissions may need a dedicated authorization policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;documentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;authorizationService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertCanReadDocument&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful when access depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ownership&lt;/li&gt;
&lt;li&gt;roles&lt;/li&gt;
&lt;li&gt;team membership&lt;/li&gt;
&lt;li&gt;organization membership&lt;/li&gt;
&lt;li&gt;resource state&lt;/li&gt;
&lt;li&gt;sharing rules&lt;/li&gt;
&lt;li&gt;subscription level&lt;/li&gt;
&lt;li&gt;administrative scope&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 3: Framework guards
&lt;/h3&gt;

&lt;p&gt;In NestJS, guards can handle authentication and high-level role checks.&lt;/p&gt;

&lt;p&gt;However, a generic role guard may not have enough information to make a resource-level decision.&lt;/p&gt;

&lt;p&gt;Knowing that a user has the role &lt;code&gt;manager&lt;/code&gt; does not prove that the user manages the organization that owns the requested document.&lt;/p&gt;

&lt;p&gt;Role authorization and resource authorization often need to work together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 4: Database-level isolation
&lt;/h3&gt;

&lt;p&gt;Some applications use tenant-aware database clients or row-level security.&lt;/p&gt;

&lt;p&gt;This adds another layer of protection when application code forgets a filter.&lt;/p&gt;

&lt;p&gt;It can be powerful, but it also adds complexity. Policies must be observable, testable, and understood by the team.&lt;/p&gt;

&lt;p&gt;There is no single authorization design for every application.&lt;/p&gt;

&lt;p&gt;The important requirement is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The security boundary should be explicit, reusable, testable, and difficult to bypass accidentally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The safest authorization check is the one the application cannot easily forget to perform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Security Boundaries, Not Only Endpoints
&lt;/h2&gt;

&lt;p&gt;After finding this bug, the test strategy should change.&lt;/p&gt;

&lt;p&gt;Do not only test whether the endpoint works.&lt;/p&gt;

&lt;p&gt;Test which identities can perform which actions.&lt;/p&gt;

&lt;p&gt;A useful authorization matrix might look like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Expected result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No token&lt;/td&gt;
&lt;td&gt;&lt;code&gt;401&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invalid token&lt;/td&gt;
&lt;td&gt;&lt;code&gt;401&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Owner with valid token&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Different user with valid token&lt;/td&gt;
&lt;td&gt;Rejected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User from another tenant&lt;/td&gt;
&lt;td&gt;Rejected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User with insufficient role&lt;/td&gt;
&lt;td&gt;Rejected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Viewer attempting deletion&lt;/td&gt;
&lt;td&gt;Rejected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Administrator inside allowed scope&lt;/td&gt;
&lt;td&gt;Success&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Test more than reads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create&lt;/li&gt;
&lt;li&gt;read&lt;/li&gt;
&lt;li&gt;update&lt;/li&gt;
&lt;li&gt;delete&lt;/li&gt;
&lt;li&gt;download&lt;/li&gt;
&lt;li&gt;share&lt;/li&gt;
&lt;li&gt;approve&lt;/li&gt;
&lt;li&gt;archive&lt;/li&gt;
&lt;li&gt;transfer ownership&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For multi-tenant systems, create at least two tenant contexts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tenant A user → Tenant A resource → Allowed
Tenant A user → Tenant B resource → Rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For nested resources, intentionally mix identifiers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project A ID
+
Comment from Project B
=
Rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applications should also decide whether unauthorized resource requests return &lt;code&gt;403&lt;/code&gt; or &lt;code&gt;404&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;403&lt;/code&gt; response confirms that the resource exists but access is forbidden.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;404&lt;/code&gt; response can avoid revealing whether a protected resource exists.&lt;/p&gt;

&lt;p&gt;The right choice depends on the API contract and threat model.&lt;/p&gt;

&lt;p&gt;What matters is that the behavior is deliberate and consistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partial Protection Is Still a Security Failure
&lt;/h2&gt;

&lt;p&gt;The shortcut did not look reckless.&lt;/p&gt;

&lt;p&gt;It looked like ordinary controller and repository code behind an authenticated route.&lt;/p&gt;

&lt;p&gt;That was why it survived.&lt;/p&gt;

&lt;p&gt;The token was valid.&lt;/p&gt;

&lt;p&gt;The current user was known.&lt;/p&gt;

&lt;p&gt;Anonymous requests were rejected.&lt;/p&gt;

&lt;p&gt;The endpoint was still unsafe because the requested resource had no enforced ownership, tenant, role, or permission boundary.&lt;/p&gt;

&lt;p&gt;The lasting fix was not a stronger token, a longer identifier, or another scattered condition.&lt;/p&gt;

&lt;p&gt;It was making authorization part of the resource-access model and testing the application with valid users who should still be denied.&lt;/p&gt;

&lt;p&gt;Security failures often survive because partial protection is mistaken for complete protection.&lt;/p&gt;

&lt;p&gt;A valid token should open the application.&lt;/p&gt;

&lt;p&gt;It should never open every record inside it.&lt;/p&gt;

&lt;p&gt;Which authorization boundary is easiest to miss in your APIs: ownership, roles, tenant isolation, or action-specific permissions?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Streaming-First UI Framework Built for the AI Era</title>
      <dc:creator>Muhammad Masood Kausar</dc:creator>
      <pubDate>Mon, 20 Jul 2026 14:04:38 +0000</pubDate>
      <link>https://dev.to/masaood/a-streaming-first-ui-framework-built-for-the-ai-era-4kpk</link>
      <guid>https://dev.to/masaood/a-streaming-first-ui-framework-built-for-the-ai-era-4kpk</guid>
      <description>&lt;p&gt;Most AI products still stream words.&lt;/p&gt;

&lt;p&gt;But what if an AI model could stream the interface itself?&lt;/p&gt;

&lt;p&gt;I recently explored &lt;strong&gt;Aktion&lt;/strong&gt;, an ambitious open-source project created by my mentor, &lt;strong&gt;Asfandiyar Khan&lt;/strong&gt;, a senior frontend engineer based in Germany.&lt;/p&gt;

&lt;p&gt;Aktion is a streaming-first UI framework designed to turn compact, JavaScript-shaped instructions into reactive and interactive user interfaces.&lt;/p&gt;

&lt;p&gt;Instead of producing another Markdown response or a large block of frontend code, an AI model can progressively generate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cards&lt;/li&gt;
&lt;li&gt;Charts&lt;/li&gt;
&lt;li&gt;Forms&lt;/li&gt;
&lt;li&gt;Tables&lt;/li&gt;
&lt;li&gt;Dashboards&lt;/li&gt;
&lt;li&gt;Navigation&lt;/li&gt;
&lt;li&gt;Interactive workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What makes Aktion different?
&lt;/h2&gt;

&lt;p&gt;Traditional AI-generated frontend code often requires the model to coordinate several independent systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Framework syntax&lt;/li&gt;
&lt;li&gt;Package imports&lt;/li&gt;
&lt;li&gt;State management&lt;/li&gt;
&lt;li&gt;Routing&lt;/li&gt;
&lt;li&gt;Form handling&lt;/li&gt;
&lt;li&gt;Styling&lt;/li&gt;
&lt;li&gt;Data fetching&lt;/li&gt;
&lt;li&gt;Component libraries&lt;/li&gt;
&lt;li&gt;Dependency versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aktion takes a different approach.&lt;/p&gt;

&lt;p&gt;It gives developers and AI agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A compact JavaScript-shaped reactive language&lt;/li&gt;
&lt;li&gt;A unified UI runtime&lt;/li&gt;
&lt;li&gt;A consistent component vocabulary&lt;/li&gt;
&lt;li&gt;Built-in application capabilities&lt;/li&gt;
&lt;li&gt;A framework-agnostic Web Component&lt;/li&gt;
&lt;li&gt;Streaming support for progressively generated interfaces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The central integration point is the &lt;code&gt;&amp;lt;aktion-app&amp;gt;&lt;/code&gt; Web Component.&lt;/p&gt;

&lt;p&gt;Because of this approach, Aktion can be embedded inside React, Vue, Angular, Svelte, plain HTML, or another host environment without rebuilding the generated UI layer for every framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why streaming-first UI matters
&lt;/h2&gt;

&lt;p&gt;Most AI interfaces wait until a response is complete before presenting the final output.&lt;/p&gt;

&lt;p&gt;Aktion is designed so completed statements can be rendered while the model is still generating the rest of the response.&lt;/p&gt;

&lt;p&gt;A heading can appear first.&lt;/p&gt;

&lt;p&gt;Then a chart.&lt;/p&gt;

&lt;p&gt;Then filters, forms, cards, and interactive controls.&lt;/p&gt;

&lt;p&gt;The AI response does not only describe an application.&lt;/p&gt;

&lt;p&gt;It gradually becomes the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  A smaller target for AI-generated interfaces
&lt;/h2&gt;

&lt;p&gt;One of Aktion’s most interesting ideas is its constrained language.&lt;/p&gt;

&lt;p&gt;AI models normally have enormous freedom when generating frontend applications. They must choose libraries, understand different APIs, manage imports, and connect tools that were not necessarily designed to work together.&lt;/p&gt;

&lt;p&gt;More freedom can also create more incompatible decisions.&lt;/p&gt;

&lt;p&gt;Aktion reduces that decision space by providing one language, one runtime, and one predictable set of interface primitives.&lt;/p&gt;

&lt;p&gt;The important idea is not only that Aktion contains many features.&lt;/p&gt;

&lt;p&gt;It is that AI-generated interfaces may become more dependable when models generate into a smaller and more controlled environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  More than a component library
&lt;/h2&gt;

&lt;p&gt;Aktion brings several common frontend concerns into one system, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reactive state&lt;/li&gt;
&lt;li&gt;Routing&lt;/li&gt;
&lt;li&gt;Forms&lt;/li&gt;
&lt;li&gt;Data fetching&lt;/li&gt;
&lt;li&gt;Themes&lt;/li&gt;
&lt;li&gt;Responsive styling&lt;/li&gt;
&lt;li&gt;Charts&lt;/li&gt;
&lt;li&gt;Realtime interfaces&lt;/li&gt;
&lt;li&gt;Internationalization&lt;/li&gt;
&lt;li&gt;Testing utilities&lt;/li&gt;
&lt;li&gt;Server-side rendering&lt;/li&gt;
&lt;li&gt;A large built-in component vocabulary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not automatically make it the right solution for every application.&lt;/p&gt;

&lt;p&gt;However, it makes Aktion an interesting experiment in how frontend runtimes could evolve for generative UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The engineering effort behind Aktion
&lt;/h2&gt;

&lt;p&gt;Building a framework is very different from building a small library.&lt;/p&gt;

&lt;p&gt;Aktion brings together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A language&lt;/li&gt;
&lt;li&gt;A parser&lt;/li&gt;
&lt;li&gt;A reactive runtime&lt;/li&gt;
&lt;li&gt;A rendering system&lt;/li&gt;
&lt;li&gt;A component vocabulary&lt;/li&gt;
&lt;li&gt;Streaming behavior&lt;/li&gt;
&lt;li&gt;Developer tooling&lt;/li&gt;
&lt;li&gt;LLM integration&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Interactive demonstrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is inspiring to see my mentor transform years of frontend experience into such an ambitious open-source project.&lt;/p&gt;

&lt;h2&gt;
  
  
  An idea worth exploring
&lt;/h2&gt;

&lt;p&gt;Aktion may still be an early project, and questions around security, runtime size, extensibility, debugging, and long-term stability deserve careful discussion.&lt;/p&gt;

&lt;p&gt;But the architectural question behind it is important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should AI continue generating unrestricted frontend applications, or should we design smaller runtimes that give models a clearer and more predictable target?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The future of generative UI may not depend only on making AI models more capable.&lt;/p&gt;

&lt;p&gt;It may also depend on designing better environments for them to generate into.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explore the project
&lt;/h2&gt;

&lt;p&gt;🌐 &lt;a href="https://asfand-dev.github.io/aktion/index.html" rel="noopener noreferrer"&gt;Visit the Aktion project homepage&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💻 &lt;a href="https://github.com/asfand-dev/aktion" rel="noopener noreferrer"&gt;View Aktion on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📖 &lt;a href="https://medium.com/skillstuff/inside-aktion-a-streaming-first-ui-framework-built-for-the-ai-era-37630984c876" rel="noopener noreferrer"&gt;Read my complete article on Medium&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations to &lt;strong&gt;Asfandiyar Khan&lt;/strong&gt; on this impressive engineering effort and valuable contribution to the open-source community.&lt;/p&gt;

&lt;p&gt;What are your thoughts on streaming-first generative UI?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
