<?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: Rishu</title>
    <description>The latest articles on DEV Community by Rishu (@asyncinnovator).</description>
    <link>https://dev.to/asyncinnovator</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%2F3948651%2F4d41437e-a334-4cef-a4da-c2a08124d9e5.jpg</url>
      <title>DEV Community: Rishu</title>
      <link>https://dev.to/asyncinnovator</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/asyncinnovator"/>
    <language>en</language>
    <item>
      <title>If you're using Render's free tier and your backend keeps sleeping, this simple workaround has kept my Flask API alive without paying for upgrades. Hope it saves someone a few hours.</title>
      <dc:creator>Rishu</dc:creator>
      <pubDate>Thu, 30 Jul 2026 07:27:56 +0000</pubDate>
      <link>https://dev.to/asyncinnovator/if-youre-using-renders-free-tier-and-your-backend-keeps-sleeping-this-simple-workaround-has-kept-2181</link>
      <guid>https://dev.to/asyncinnovator/if-youre-using-renders-free-tier-and-your-backend-keeps-sleeping-this-simple-workaround-has-kept-2181</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/asyncinnovator/my-flask-backend-was-falling-asleep-every-15-minutes-heres-how-i-fixed-it-completely-free-ln5" class="crayons-story__hidden-navigation-link"&gt;Stop Your Render Backend From Sleeping Every 15 Minutes (100% Free Fix)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/asyncinnovator" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3948651%2F4d41437e-a334-4cef-a4da-c2a08124d9e5.jpg" alt="asyncinnovator profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/asyncinnovator" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Rishu
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Rishu
                
              
              &lt;div id="story-author-preview-content-4071354" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/asyncinnovator" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3948651%2F4d41437e-a334-4cef-a4da-c2a08124d9e5.jpg" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Rishu&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/asyncinnovator/my-flask-backend-was-falling-asleep-every-15-minutes-heres-how-i-fixed-it-completely-free-ln5" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 5&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/asyncinnovator/my-flask-backend-was-falling-asleep-every-15-minutes-heres-how-i-fixed-it-completely-free-ln5" id="article-link-4071354"&gt;
          Stop Your Render Backend From Sleeping Every 15 Minutes (100% Free Fix)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/python"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;python&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/api"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;api&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/asyncinnovator/my-flask-backend-was-falling-asleep-every-15-minutes-heres-how-i-fixed-it-completely-free-ln5" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/asyncinnovator/my-flask-backend-was-falling-asleep-every-15-minutes-heres-how-i-fixed-it-completely-free-ln5#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>backend</category>
      <category>flask</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Letting AI Write Security Bugs: Introducing "hallint"</title>
      <dc:creator>Rishu</dc:creator>
      <pubDate>Tue, 21 Jul 2026 08:40:47 +0000</pubDate>
      <link>https://dev.to/asyncinnovator/stop-letting-ai-write-security-bugs-introducing-hallint-2hh2</link>
      <guid>https://dev.to/asyncinnovator/stop-letting-ai-write-security-bugs-introducing-hallint-2hh2</guid>
      <description>&lt;p&gt;If you're using Copilot, Cursor, or ChatGPT to ship code faster, you already know the upside. They save time, tackle boilerplate, and help you think through complex logic. But there's a blind spot nobody's tooling for yet: &lt;strong&gt;AI coding assistants generate the exact same class of security bugs, over and over again, with total confidence.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traditional linters were designed for human-written code. They catch unused variables, missing semicolons, and common logic errors. They aren't built to catch the subtle, plausible-looking security holes that LLMs naturally default to.&lt;/p&gt;

&lt;p&gt;That is why I built &lt;strong&gt;hallint&lt;/strong&gt;. It is a free, open-source static analysis tool specifically tuned to catch the failure modes of AI code generation before they reach production.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem AI Creates (That Most Linters Miss)
&lt;/h2&gt;

&lt;p&gt;AI assistants fail differently than humans do. When you ask an LLM to generate an Express route or a database query, it takes the path of least resistance. It writes code that passes casual review, runs perfectly in local development, and creates real vulnerabilities in production.&lt;/p&gt;

&lt;p&gt;Here are the most common AI failure patterns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Hardcoded Secrets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI will happily spit out &lt;code&gt;const API_KEY = "sk-abc123..."&lt;/code&gt;. It passes linting. It works locally. Pushing it to a public repo is a disaster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. SQL Injection by Default&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI loves template literals. &lt;code&gt;db.query("SELECT * FROM users WHERE id = ${req.params.id}")&lt;/code&gt; looks completely fine at a glance, but it is a textbook SQL injection vector.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Missing Authentication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Generating CRUD routes is easy. Remembering to apply auth middleware to every single one of them? AI forgets constantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Auth Masking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the most dangerous one. When asked to add error handling to auth middleware, an LLM will often generate a &lt;code&gt;try/catch&lt;/code&gt; that catches a token error but still calls &lt;code&gt;next()&lt;/code&gt;, silently allowing unauthenticated requests through:&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="c1"&gt;// AI generates this — looks like error handling, is actually a security hole&lt;/span&gt;
&lt;span class="k"&gt;try&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;verifyToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// token verification failed — but we call next() anyway&lt;/span&gt;
  &lt;span class="nf"&gt;next&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;If &lt;code&gt;verifyToken&lt;/code&gt; throws — expired token, invalid signature, missing header — the catch runs and the request proceeds as authenticated. The code looks intentional. It passes review. It is a direct authentication bypass.&lt;/p&gt;

&lt;p&gt;The safe version:&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;try&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;verifyToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorization&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Permissive CORS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Setting &lt;code&gt;cors({ origin: '*' })&lt;/code&gt; is the AI's favourite one-liner to "fix" your CORS errors in development. It ships to production constantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is &lt;code&gt;hallint&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;hallint&lt;/strong&gt; is a TypeScript library and CLI tool that scans your JavaScript, TypeScript, and Python codebases for these specific AI-generated security and quality issues.&lt;/p&gt;

&lt;p&gt;Instead of trying to be a general-purpose linter, it uses two detection layers to target AI-specific patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Regex pattern matching&lt;/strong&gt; — a fast first pass for known bad patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AST-style analysis&lt;/strong&gt; — structural checks that understand multi-line logic, not just single-line text&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Rules Engine
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;hallint&lt;/code&gt; currently ships with &lt;strong&gt;11 targeted rules&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hardcoded-secret&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Critical&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API keys, tokens, and known prefixes (&lt;code&gt;ghp_&lt;/code&gt;, &lt;code&gt;sk-&lt;/code&gt;, &lt;code&gt;AKIA&lt;/code&gt;, &lt;code&gt;xoxb-&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sql-injection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Critical&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User input directly interpolated into SQL queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unsafe-eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Critical&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;eval()&lt;/code&gt; or &lt;code&gt;new Function()&lt;/code&gt; using dynamic input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;auth-masking&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Critical&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Catch blocks that swallow auth errors, making failures silently pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;missing-auth-check&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Route handlers missing authentication middleware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;xss-innerHTML&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unsanitized strings assigned directly to &lt;code&gt;.innerHTML&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;permissive-cors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cors({ origin: '*' })&lt;/code&gt; left in route handlers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jwt-in-localstorage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JWTs or auth tokens stored in &lt;code&gt;localStorage&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;swallowed-error&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Empty or comment-only catch blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;http-not-https&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Medium&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hardcoded &lt;code&gt;http://&lt;/code&gt; URLs in fetch/axios requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;async-no-catch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Medium&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;async&lt;/code&gt; functions with no error handling (&lt;code&gt;--rules all&lt;/code&gt; only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How to Use &lt;code&gt;hallint&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Running via CLI
&lt;/h3&gt;

&lt;p&gt;You don't even need to install it to try it out. Just point it at your source directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @asyncinnovator/hallint-cli ./src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hallint scanning ./src...

src/routes/users.ts
  users.ts:4  CRITICAL  [hardcoded-secret]
  Hardcoded secret detected — API key, token, or password in source code
  &amp;gt; const apiKey = "sk-abc123def456ghi789jkl"
  fix: Move to environment variables: process.env.YOUR_SECRET_NAME

  users.ts:9  CRITICAL  [sql-injection]
  Possible SQL injection — user input directly concatenated into a query string
  &amp;gt; const result = await db.query(`SELECT * FROM users WHERE name = ${req.query.name}`)
  fix: Use parameterized queries: db.query('SELECT * FROM users WHERE name = $1', [req.query.name])

  users.ts:14  CRITICAL  [auth-masking]
  Catch block swallows an auth/token error — failed authentication may silently pass
  &amp;gt; } catch (e) {
  fix: Rethrow or respond with 401: catch (e) { return res.status(401).json({ error: 'Unauthorized' }) }

Summary: 3 issue(s) in 1 file(s) — 14ms
  3 critical
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only show what matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Critical and high only&lt;/span&gt;
npx @asyncinnovator/hallint-cli ./src &lt;span class="nt"&gt;--min-severity&lt;/span&gt; high

&lt;span class="c"&gt;# All rules including noisier heuristics&lt;/span&gt;
npx @asyncinnovator/hallint-cli ./src &lt;span class="nt"&gt;--rules&lt;/span&gt; all

&lt;span class="c"&gt;# CI-friendly, no color&lt;/span&gt;
npx @asyncinnovator/hallint-cli ./src &lt;span class="nt"&gt;--no-color&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit codes: &lt;code&gt;0&lt;/code&gt; = clean, &lt;code&gt;1&lt;/code&gt; = critical/high findings, &lt;code&gt;2&lt;/code&gt; = unexpected error.&lt;/p&gt;




&lt;h3&gt;
  
  
  Using it as a Library
&lt;/h3&gt;

&lt;p&gt;If you are building testing pipelines, pre-commit hooks, or editor plugins, import the core library directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @asyncinnovator/hallint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;scanSource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@asyncinnovator/hallint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// Scan your file system&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;scan&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/**/*.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;recommended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;minSeverity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ruleId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filePath&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;line&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="c1"&gt;// Or scan a raw string — useful in tests or editor integrations&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scanSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`const key = "sk-abc123abc123abc123abc"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;virtual.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Drop it into CI in 2 minutes
&lt;/h3&gt;

&lt;p&gt;hallint exits &lt;code&gt;1&lt;/code&gt; on any critical or high finding, making it a natural PR gate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/hallint.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hallint&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hallint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx @asyncinnovator/hallint-cli ./src --min-severity high&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Dive Deeper: Read the Full Docs on GitHub
&lt;/h2&gt;

&lt;p&gt;The full documentation covers CI/CD integration, inline suppression (&lt;code&gt;// hallint-disable&lt;/code&gt;), public route allowlists to reduce noise on intentional public endpoints, writing your own custom rules in under 30 lines, and the opt-in LLM explanation layer that attaches plain-English notes to each finding.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/Asyncinnovator/hallint" rel="noopener noreferrer"&gt;Read the full documentation on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;AI is changing how we write code, but the security mistakes it makes are consistent and enumerable. hallint is MIT licensed, community-driven, and built to be extended. If you've seen an AI-specific vulnerability pattern that isn't covered yet, pull requests are open.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Asyncinnovator/hallint" rel="noopener noreferrer"&gt;github.com/Asyncinnovator/hallint&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;Core library:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/@asyncinnovator/hallint" rel="noopener noreferrer"&gt;@asyncinnovator/hallint&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;CLI:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/@asyncinnovator/hallint-cli" rel="noopener noreferrer"&gt;@asyncinnovator/hallint-cli&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
    <item>
      <title>We Pulled a Rule From Our AI Linter — Here's Why It Made the Tool Better</title>
      <dc:creator>Rishu</dc:creator>
      <pubDate>Tue, 14 Jul 2026 11:36:38 +0000</pubDate>
      <link>https://dev.to/asyncinnovator/hallint-update-what-we-fixed-what-we-shipped-and-whats-coming-in-v02-35l7</link>
      <guid>https://dev.to/asyncinnovator/hallint-update-what-we-fixed-what-we-shipped-and-whats-coming-in-v02-35l7</guid>
      <description>&lt;p&gt;&lt;em&gt;Follow-up to &lt;a href="https://dev.to/asyncinnovator/i-built-a-linter-that-catches-the-security-bugs-ai-assistants-keep-writing-58m8"&gt;I Built a Linter That Catches the Security Bugs AI Assistants Keep Writing&lt;/a&gt;. The comments on that post shaped most of what's in this update.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;After the first post went up, I got a comment that stuck with me. &lt;a href="https://dev.to/nazar-boyko"&gt;Nazar Boyko&lt;/a&gt; wrote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Seven of your eight rules are 'this is a vulnerability.' &lt;code&gt;async-no-catch&lt;/code&gt; is the odd one out... The first time someone runs hallint on a real codebase and gets forty of those next to one genuine hardcoded key, the signal you worked for is gone."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He was right. And fixing it taught me something about what makes a security linter trustworthy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Signal Problem
&lt;/h2&gt;

&lt;p&gt;The whole point of hallint is to give you a reliable signal: &lt;em&gt;this line is wrong&lt;/em&gt;. That's what makes it useful as a CI gate. When a finding fires, a developer — or an AI agent — should be able to look at the flagged line and agree without needing more context.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;async-no-catch&lt;/code&gt; broke that bar. Plenty of correct async code has no try/catch because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The caller handles the rejection&lt;/li&gt;
&lt;li&gt;An Express error middleware catches it upstream
&lt;/li&gt;
&lt;li&gt;Throwing is the intended behavior
Medium severity didn't save it. The moment you get forty &lt;code&gt;async-no-catch&lt;/code&gt; warnings alongside one genuine hardcoded API key, you stop trusting the output. You turn the rule off. And now you've lost the one thing a security linter has to protect: the cost of ignoring a finding is zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; &lt;code&gt;async-no-catch&lt;/code&gt; is now removed from &lt;code&gt;recommended&lt;/code&gt;. It still exists — &lt;code&gt;--rules all&lt;/code&gt; opts you in — but it no longer ships alongside the seven security rules by default. &lt;code&gt;recommended&lt;/code&gt; stays clean and gateable.&lt;/p&gt;

&lt;p&gt;This is the difference between a linter that's interesting and one that's actually wired into CI.&lt;/p&gt;




&lt;h2&gt;
  
  
  The False Positive Problem on &lt;code&gt;missing-auth-check&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/dipankar_sarkar"&gt;Dipankar Sarkar&lt;/a&gt; and &lt;a href="https://dev.to/nark3d"&gt;Adam Lewis&lt;/a&gt; both flagged the same issue independently: health checks, webhooks, and public endpoints are &lt;em&gt;supposed&lt;/em&gt; to have no auth middleware. Flagging them is noise. And noise gets rules disabled.&lt;/p&gt;

&lt;p&gt;The fix needed to be explicit, not inferred. hallint now recognizes three inline suppression markers:&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="c1"&gt;// public&lt;/span&gt;
&lt;span class="nx"&gt;app&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="s1"&gt;/health&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;

&lt;span class="c1"&gt;// hallint-public&lt;/span&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="s1"&gt;/metrics&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* hallint-public */&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;204&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;Any of these tells hallint the route is intentionally public and skips the auth check. The marker can go on the line above or inline on the same line. No config file needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Other Fixes Shipped Since v0.1.0
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;hardcoded-secret — dual-pass detection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The original regex caught assignment-style secrets (&lt;code&gt;api_key = "abc123"&lt;/code&gt;). It missed token prefixes that are dead giveaways regardless of variable name. The rule now runs a second pass targeting known provider-issued formats: &lt;code&gt;ghp_&lt;/code&gt;, &lt;code&gt;ghs_&lt;/code&gt;, &lt;code&gt;sk-&lt;/code&gt;, &lt;code&gt;AKIA&lt;/code&gt;, &lt;code&gt;xoxb-&lt;/code&gt;, &lt;code&gt;xoxp-&lt;/code&gt;, &lt;code&gt;AIza&lt;/code&gt;, &lt;code&gt;ya29.&lt;/code&gt;. A line containing any of these is flagged as critical even if the variable name looks innocent. Comment lines and &lt;code&gt;process.env.&lt;/code&gt; reads are excluded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sql-injection — honest message copy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The original message claimed "user input flowing into query." The detection was pattern-based — it couldn't actually prove data flow, just that a template literal appeared inside a query call. The message now says what the rule actually detects: template literal interpolation in a query string. Accurate scope, no false confidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scanner dispatch — behavior now follows code, not metadata&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Previously the scanner used &lt;code&gt;rule.layer&lt;/code&gt; to decide whether to run regex or &lt;code&gt;match()&lt;/code&gt;. A rule with &lt;code&gt;layer: "ast"&lt;/code&gt; but no &lt;code&gt;match()&lt;/code&gt; function would be silently skipped. Now &lt;code&gt;rule.match()&lt;/code&gt; presence is the dispatch signal — if a rule defines &lt;code&gt;match()&lt;/code&gt; it gets AST treatment, otherwise regex. &lt;code&gt;layer&lt;/code&gt; is metadata only. Rule authoring is now harder to break.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;async-no-catch brace counting — &lt;code&gt;stripStrings()&lt;/code&gt; heuristic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before being moved out of &lt;code&gt;recommended&lt;/code&gt;, the rule's brace counter was made more robust. Template literals and inline strings were throwing off the depth counter, causing false positives. A &lt;code&gt;stripStrings()&lt;/code&gt; pass now removes string content before counting &lt;code&gt;{&lt;/code&gt; / &lt;code&gt;}&lt;/code&gt;, significantly reducing noise.&lt;/p&gt;




&lt;h2&gt;
  
  
  Current Versions
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@asyncinnovator/hallint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v0.1.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@asyncinnovator/hallint-cli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;v0.1.7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @asyncinnovator/hallint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or run without installing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @asyncinnovator/hallint-cli ./src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  CI Integration
&lt;/h2&gt;

&lt;p&gt;hallint exits &lt;code&gt;1&lt;/code&gt; on critical or high findings. Drop it into GitHub Actions in four lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx @asyncinnovator/hallint-cli ./src&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This blocks merges on real findings. The LLM layer — when it ships — will never contribute to exit code &lt;code&gt;1&lt;/code&gt;. Non-determinism doesn't belong in a CI gate.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Coming in v0.2
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cross-file router composition tracking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Right now &lt;code&gt;missing-auth-check&lt;/code&gt; is same-file only. Middleware registered in &lt;code&gt;app.ts&lt;/code&gt; isn't connected to routes defined in &lt;code&gt;routes/users.ts&lt;/code&gt;. This is the honest limitation of regex analysis — you can't follow imports. v0.2 introduces tree-sitter, which makes cross-file tracking possible. This is the main structural change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM layer — opt-in only&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The optional LLM review pass ships in v0.2 as a strict opt-in (&lt;code&gt;--llm ollama&lt;/code&gt; or &lt;code&gt;--llm anthropic&lt;/code&gt;). It surfaces semantic issues and context-blind patterns the regex and AST layers miss, but those findings go in a separate output section and never block a merge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New rules under consideration for v0.2–v0.3&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;silent-error-swallow&lt;/code&gt; — bare catch blocks that swallow errors without logging or rethrowing. Looks handled. Isn't.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jwt-in-localstorage&lt;/code&gt; — storing tokens where XSS can reach them.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hallucinated-dependency&lt;/code&gt; — imports of packages that don't exist in &lt;code&gt;package.json&lt;/code&gt; (a genuine AI-specific failure mode)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;Each rule is a single file, ~30 lines, with a &lt;code&gt;bad.ts&lt;/code&gt; and &lt;code&gt;good.ts&lt;/code&gt; fixture. If you've seen a pattern AI assistants keep producing that hallint doesn't catch, the path from "I noticed this" to "I shipped a fix" is short. Issues labeled &lt;strong&gt;good first issue&lt;/strong&gt; are pre-scoped and ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Asyncinnovator/hallint" rel="noopener noreferrer"&gt;github.com/Asyncinnovator/hallint&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/@asyncinnovator/hallint" rel="noopener noreferrer"&gt;@asyncinnovator/hallint&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/@asyncinnovator/hallint-cli" rel="noopener noreferrer"&gt;@asyncinnovator/hallint-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT licensed. Free for personal and commercial use.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Built a Linter That Catches the Security Bugs AI Assistants Keep Writing</title>
      <dc:creator>Rishu</dc:creator>
      <pubDate>Fri, 10 Jul 2026 07:11:18 +0000</pubDate>
      <link>https://dev.to/asyncinnovator/i-built-a-linter-that-catches-the-security-bugs-ai-assistants-keep-writing-58m8</link>
      <guid>https://dev.to/asyncinnovator/i-built-a-linter-that-catches-the-security-bugs-ai-assistants-keep-writing-58m8</guid>
      <description>&lt;p&gt;I've been writing code with AI assistants for a while now. Copilot, Claude, ChatGPT — I've used them all. And for the most part, they're genuinely impressive. They save time. They help me think through problems. They write boilerplate I'd rather not write myself.&lt;/p&gt;

&lt;p&gt;But here's the thing nobody talks about enough: &lt;strong&gt;AI-generated code has a specific category of bugs that normal linters don't catch.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not syntax errors. Not style violations. I'm talking about security vulnerabilities, false-confidence patterns, and subtle logic issues that look completely correct — until they're not.&lt;/p&gt;

&lt;p&gt;I got burned enough times that I built something about it. It's called &lt;strong&gt;hallint&lt;/strong&gt;, and it's a free, open-source static analysis tool designed specifically for AI-generated code. You can find it on GitHub: &lt;a href="https://github.com/Asyncinnovator/hallint" rel="noopener noreferrer"&gt;github.com/Asyncinnovator/hallint&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With AI Code (That ESLint Won't Help You With)
&lt;/h2&gt;

&lt;p&gt;Traditional linters were designed for human-written code. They're great at catching things humans commonly get wrong — unused variables, missing semicolons, incorrect type usage.&lt;/p&gt;

&lt;p&gt;But AI assistants fail differently.&lt;/p&gt;

&lt;p&gt;Here are the patterns I kept seeing over and over:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Hardcoded secrets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI assistants will happily write &lt;code&gt;const API_KEY = "sk-abc123..."&lt;/code&gt; in your source code. It passes every lint check. It works perfectly in dev. And then you push to GitHub and your key is live in a public repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. SQL injection&lt;/strong&gt;&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 generates this and it looks totally fine&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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`SELECT * FROM users WHERE id = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No ESLint rule will flag this. But it's a textbook SQL injection vector. Every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Missing auth on route handlers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI is great at generating CRUD routes. It's not great at remembering to add authentication middleware. You end up with a perfectly structured Express router where half the routes are completely unprotected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Permissive CORS&lt;/strong&gt;&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&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 a one-liner fix for the CORS errors you see in development. AI assistants suggest it constantly. It's also a wildly bad idea in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. innerHTML with unsanitized strings&lt;/strong&gt;&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;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userInput&lt;/span&gt; &lt;span class="c1"&gt;// XSS waiting to happen&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LLMs write this pattern a lot. It's the obvious, readable way to set content — and it's a cross-site scripting vulnerability.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;hallint&lt;/strong&gt; is a TypeScript library and CLI tool that scans your codebase for these AI-specific failure patterns.&lt;/p&gt;

&lt;p&gt;It uses three detection layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Regex pattern matching&lt;/strong&gt; — fast first pass for known bad patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AST analysis&lt;/strong&gt; — structural checks that understand code, not just text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM review&lt;/strong&gt; (optional) — uses Ollama or another provider to do deeper semantic analysis
The idea is simple: it's a linter that knows what AI gets wrong, not just what humans get wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Getting started
&lt;/h3&gt;

&lt;p&gt;No install needed — just run it with npx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @asyncinnovator/hallint-cli ./src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or scan a specific glob pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @asyncinnovator/hallint-cli &lt;span class="s2"&gt;"./src/**/*.ts"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only care about serious issues? Filter by severity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @asyncinnovator/hallint-cli ./src &lt;span class="nt"&gt;--min-severity&lt;/span&gt; high
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It exits with code &lt;code&gt;1&lt;/code&gt; on any critical or high finding, which makes it easy to use as a CI gate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use it as a library
&lt;/h3&gt;

&lt;p&gt;If you want to integrate it into your own tooling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @asyncinnovator/hallint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;scan&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@asyncinnovator/hallint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;scan&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/**/*.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;recommended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;minSeverity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ruleId&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filePath&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;line&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  fix: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fix&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also scan a string directly without touching the filesystem:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;scanSource&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@asyncinnovator/hallint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scanSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`const apiKey = "sk-abc123def456"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;example.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Rules Shipping
&lt;/h2&gt;

&lt;p&gt;Right now hallint ships with eight rules, all targeting the patterns I described above:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hardcoded-secret&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;critical&lt;/td&gt;
&lt;td&gt;API keys, tokens, passwords in source code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sql-injection&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;critical&lt;/td&gt;
&lt;td&gt;User input interpolated into SQL queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unsafe-eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;critical&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;eval()&lt;/code&gt; or &lt;code&gt;new Function()&lt;/code&gt; with dynamic input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;missing-auth-check&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;td&gt;Route handlers with no auth middleware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;xss-innerHTML&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;td&gt;Unsanitized strings assigned to &lt;code&gt;innerHTML&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;permissive-cors&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cors({ origin: '*' })&lt;/code&gt; in route handlers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;async-no-catch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;async&lt;/code&gt; functions with no &lt;code&gt;try/catch&lt;/code&gt; or &lt;code&gt;.catch()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;http-not-https&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;td&gt;Hardcoded &lt;code&gt;http://&lt;/code&gt; URLs in fetch or axios calls&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  How It's Different From Running a Regular Linter
&lt;/h2&gt;

&lt;p&gt;ESLint with the right plugins will catch &lt;em&gt;some&lt;/em&gt; of this. But there are a few important differences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Existing linters weren't built with AI failure modes in mind.&lt;/strong&gt; They weren't designed around the question "what does an AI assistant get wrong?" They were designed around the question "what do humans get wrong?" Those are different questions with different answers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;hallint is designed to be AI-aware.&lt;/strong&gt; The rules target the specific intersection of "AI generates this confidently" and "this is actually a problem." The goal isn't to be comprehensive — it's to be precise about the failure modes that matter most for AI-generated code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The LLM layer is something ESLint can't do.&lt;/strong&gt; When you enable the optional LLM review, hallint can flag issues that don't match a known regex or AST pattern — things like "this auth flow looks structurally correct but has a logical flaw." That's a different category of analysis entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Real Example
&lt;/h2&gt;

&lt;p&gt;Here's the kind of thing it catches. Say you take some AI-generated Express code:&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;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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="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="s1"&gt;/users/:id&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`SELECT * FROM users WHERE id = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&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;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="nx"&gt;res&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="nx"&gt;user&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;hallint flags it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[critical] sql-injection: User input interpolated directly into SQL query.
  Use parameterized queries: db.query('SELECT * FROM users WHERE id = $1', [req.params.id])
  → src/routes/users.ts:6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, direct, tells you exactly what to fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Drop It Into CI in 5 Lines
&lt;/h2&gt;

&lt;p&gt;One of the things I wanted from day one was a zero-friction GitHub Actions integration. hallint exits with code &lt;code&gt;1&lt;/code&gt; on critical or high findings, so this just works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx @asyncinnovator/hallint-cli ./src --min-severity high&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also add it as a pre-commit hook via husky if you want to catch issues before they even get pushed.&lt;/p&gt;




&lt;h2&gt;
  
  
  It's Open Source — Contributions Welcome
&lt;/h2&gt;

&lt;p&gt;The whole thing is MIT licensed and lives at &lt;strong&gt;&lt;a href="https://github.com/Asyncinnovator/hallint" rel="noopener noreferrer"&gt;github.com/Asyncinnovator/hallint&lt;/a&gt;&lt;/strong&gt;. Issues and PRs are open.&lt;/p&gt;

&lt;p&gt;I think this kind of tooling should exist in public, not behind a paywall. If AI-generated code has specific vulnerability patterns, the entire ecosystem benefits from shared, community-maintained detection rules.&lt;/p&gt;

&lt;p&gt;The rule-writing surface is intentionally small — each rule is a single file (~30 lines) with a &lt;code&gt;bad.ts&lt;/code&gt; / &lt;code&gt;good.ts&lt;/code&gt; fixture. If you've seen an AI-specific pattern that hallint doesn't catch yet, the path from "I noticed this" to "I shipped a fix" is genuinely short. Issues labeled &lt;strong&gt;good first issue&lt;/strong&gt; are pre-scoped and ready to pick up.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;hallint is MIT licensed. Free to use in personal and commercial projects.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;npm: &lt;a href="https://www.npmjs.com/package/@asyncinnovator/hallint" rel="noopener noreferrer"&gt;@asyncinnovator/hallint&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/@asyncinnovator/hallint-cli" rel="noopener noreferrer"&gt;@asyncinnovator/hallint-cli&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>security</category>
      <category>programming</category>
    </item>
    <item>
      <title>Stop Your Render Backend From Sleeping Every 15 Minutes (100% Free Fix)</title>
      <dc:creator>Rishu</dc:creator>
      <pubDate>Sun, 05 Jul 2026 08:39:51 +0000</pubDate>
      <link>https://dev.to/asyncinnovator/my-flask-backend-was-falling-asleep-every-15-minutes-heres-how-i-fixed-it-completely-free-ln5</link>
      <guid>https://dev.to/asyncinnovator/my-flask-backend-was-falling-asleep-every-15-minutes-heres-how-i-fixed-it-completely-free-ln5</guid>
      <description>&lt;p&gt;I recently built a small Flask backend for a side project. I deployed it on Render’s free tier, got that shiny HTTPS URL, and felt that classic developer rush of seeing my code live.&lt;/p&gt;

&lt;p&gt;Proud of my work, I sent the link to a friend for feedback.&lt;/p&gt;

&lt;p&gt;His response? &lt;em&gt;"Bro, it's not loading."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I clicked the link myself. It &lt;em&gt;did&lt;/em&gt; load, but only after staring at a blank screen for nearly a full minute. If you've ever deployed a passion project, you know that a 60-second loading screen is an absolute conversion killer.&lt;/p&gt;

&lt;p&gt;Here is exactly why that happened, and how I fixed it permanently without spending a single dime.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Render’s Free Tier "Cold Start"
&lt;/h2&gt;

&lt;p&gt;After some digging, I realized this wasn't a bug—it’s a feature of Render’s free tier.&lt;/p&gt;

&lt;p&gt;To save server resources, Render automatically spins down your service if it receives zero traffic for &lt;strong&gt;15 minutes&lt;/strong&gt;. When a new request finally comes in, the server has to boot back up before it can process anything. This "cold start" can take anywhere from 30 to 60 seconds.&lt;/p&gt;

&lt;p&gt;If I'm testing the API from my laptop, I know what's happening. But to a real user opening the link cold, the site just looks broken.&lt;/p&gt;

&lt;p&gt;Since the project was in its early stages, I didn't want to shell out money for a paid hosting plan yet. But I also needed my app to be snappy. I needed a workaround.&lt;/p&gt;

&lt;h2&gt;
  
  
  My First Thought: GitHub Actions (And Why It Failed)
&lt;/h2&gt;

&lt;p&gt;My immediate instinct was to write a quick GitHub Actions cron job. The logic was simple: ping the server with a &lt;code&gt;curl&lt;/code&gt; request every 14 minutes. If the server keeps getting hit before the 15-minute timer runs out, it never falls asleep.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*/14&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ping&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl https://green-spoon-backend.onrender.com/api/health&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Then I did the math.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub gives you &lt;strong&gt;2,000 free Actions minutes&lt;/strong&gt; per month. Pinging the server every 14 minutes equals roughly 103 runs per day—which comes out to over &lt;strong&gt;3,000 runs per month.&lt;/strong&gt; That completely blows past the free limit. I definitely didn't want a surprise bill for a side project, so I scrapped the idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ultimate Fix: UptimeRobot
&lt;/h2&gt;

&lt;p&gt;The core concept was still solid: I needed a tool to automatically ping the server. I just needed one that did it for free.&lt;/p&gt;

&lt;p&gt;Enter &lt;strong&gt;UptimeRobot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;UptimeRobot is a standard website monitoring service. You give it a URL, and it checks it periodically to ensure your site is online. The best part? Their free plan allows checks every &lt;strong&gt;5 minutes&lt;/strong&gt;—more than frequent enough to keep a Render instance wide awake.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a Lightweight Endpoint
&lt;/h3&gt;

&lt;p&gt;Before setting up the monitor, I didn't want to ping my root &lt;code&gt;/&lt;/code&gt; route. Root routes often trigger heavier logic or database calls, which is a waste of resources.&lt;/p&gt;

&lt;p&gt;Instead, I added a dead-simple &lt;code&gt;/health&lt;/code&gt; endpoint to my Flask app:&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="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/health&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;health&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&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;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;There are no database queries and no heavy business logic here. It just returns a &lt;code&gt;200 OK&lt;/code&gt; status confirming the server is alive. &lt;em&gt;(Pro tip: Having a health check endpoint is a standard best practice in backend engineering anyway!)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Set Up the Ping
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;I went to UptimeRobot and made a free account.&lt;/li&gt;
&lt;li&gt;I created a new &lt;strong&gt;HTTP monitor&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;I pointed it to my new endpoint: &lt;a href="https://green-spoon-backend.onrender.com/api/health" rel="noopener noreferrer"&gt;https://green-spoon-backend.onrender.com/api/health&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;I set the check interval to 5 minutes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It took me less than two minutes to set up, and my Flask server hasn't fallen asleep since.&lt;/p&gt;

&lt;h2&gt;
  
  
  Won't I Get Spammed with Emails?
&lt;/h2&gt;

&lt;p&gt;This was my biggest worry. I didn't want an email hitting my inbox every 5 minutes saying, &lt;em&gt;"Hey, your site is still up!"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Thankfully, that’s not how UptimeRobot works. The pings happen silently in the background. You only ever get an email if your server actually &lt;strong&gt;goes down&lt;/strong&gt;, and another when it comes back online. You get the benefit of keeping your server awake, bundled with professional-grade uptime monitoring for free.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have you run into the free-tier sleep issue before?&lt;/strong&gt; If you have a different setup or a cool alternative tool you use to keep your side projects awake, drop it in the comments below!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>python</category>
      <category>api</category>
    </item>
    <item>
      <title>What Is Steganography? How It Works, Types, and Why It Matters</title>
      <dc:creator>Rishu</dc:creator>
      <pubDate>Fri, 03 Jul 2026 04:55:13 +0000</pubDate>
      <link>https://dev.to/asyncinnovator/what-is-steganography-how-it-works-types-and-why-it-matters-1c44</link>
      <guid>https://dev.to/asyncinnovator/what-is-steganography-how-it-works-types-and-why-it-matters-1c44</guid>
      <description>&lt;p&gt;I stumbled across steganography while researching privacy tools. The concept immediately caught me — not just encrypting data, but hiding the fact that data exists at all.&lt;/p&gt;

&lt;p&gt;Cryptography says &lt;em&gt;"there's a secret here, but you can't read it."&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Steganography says &lt;em&gt;"there's nothing here."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That distinction matters more than it sounds.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Steganography?
&lt;/h2&gt;

&lt;p&gt;Steganography is the practice of concealing information within an ordinary, non-secret file or message. The word comes from Greek — &lt;em&gt;steganos&lt;/em&gt; (covered) + &lt;em&gt;graphein&lt;/em&gt; (writing). Covered writing.&lt;/p&gt;

&lt;p&gt;Unlike encryption, which protects the &lt;strong&gt;content&lt;/strong&gt; of a message, steganography protects the &lt;strong&gt;existence&lt;/strong&gt; of a message. The goal isn't to make data unreadable — it's to make it undetectable.&lt;/p&gt;

&lt;p&gt;The two techniques are often combined. Hide the data with steganography, encrypt it too, and you've got a system where an attacker can't read the message &lt;em&gt;and&lt;/em&gt; can't even prove a message exists.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Brief History
&lt;/h2&gt;

&lt;p&gt;Steganography is ancient. The Greeks shaved a slave's head, tattooed a message on the scalp, waited for the hair to grow back, and sent the messenger on his way. The recipient shaved the head again.&lt;/p&gt;

&lt;p&gt;The Romans wrote between lines of text using invisible ink made from fruit juice or milk. Heat would reveal the message.&lt;/p&gt;

&lt;p&gt;During World War II, German spies used microdots — shrinking entire pages of text to the size of a period, hiding them in ordinary letters. The FBI called it "the enemy's masterpiece of espionage."&lt;/p&gt;

&lt;p&gt;Digital steganography follows the same principle. Different medium, same idea.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Digital Steganography Works
&lt;/h2&gt;

&lt;p&gt;Digital files — images, audio, video — contain far more data than human perception can use. Our eyes and ears are lossy receivers. We can't detect small changes in color values, barely-audible frequency shifts, or tiny timing differences.&lt;/p&gt;

&lt;p&gt;Steganography exploits that gap. It hides data in the parts of a file that humans can't perceive, while keeping the file looking (or sounding) completely normal.&lt;/p&gt;
&lt;h2&gt;
  
  
  Types of Steganography
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Image Steganography
&lt;/h3&gt;

&lt;p&gt;The most common form. Data is hidden in the pixel values of an image.&lt;/p&gt;

&lt;p&gt;The standard technique is &lt;strong&gt;LSB (Least Significant Bit) steganography&lt;/strong&gt;. Every pixel in an RGB image stores three values — red, green, blue — each as an 8-bit number between 0 and 255.&lt;/p&gt;

&lt;p&gt;The last bit of each value contributes almost nothing to the final color. Changing it shifts the value by 1 out of 255 — imperceptible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original byte:  11001010  →  202
Modified byte:  11001011  →  203
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By replacing the LSB of each byte across an entire image, you can embed a continuous stream of hidden data. A 1920×1080 PNG can carry ~777 KB this way, invisibly.&lt;/p&gt;

&lt;p&gt;One important detail: this only works with &lt;strong&gt;lossless formats like PNG&lt;/strong&gt;. JPEG's lossy compression discards small variations on every save — including your embedded bits. JPEG destroys LSB data.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Audio Steganography
&lt;/h3&gt;

&lt;p&gt;Audio files work similarly. Sound is sampled thousands of times per second, each sample stored as a number. LSB modification of audio samples produces changes below the threshold of human hearing.&lt;/p&gt;

&lt;p&gt;Another technique is &lt;strong&gt;phase coding&lt;/strong&gt; — altering the phase of audio segments to encode data. Phase differences between segments are inaudible to humans but detectable by software.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Video Steganography
&lt;/h3&gt;

&lt;p&gt;Video extends image steganography across frames. The sheer volume of data in a video file makes it an extremely high-capacity carrier. A short clip can hide gigabytes.&lt;/p&gt;

&lt;p&gt;The challenge is that video is often re-encoded after recording, which can destroy embedded data — similar to the JPEG problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Text Steganography
&lt;/h3&gt;

&lt;p&gt;Text has less redundancy than images or audio, but it's still possible. Techniques include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Whitespace steganography&lt;/strong&gt; — encoding bits using spaces and tabs at line endings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unicode steganography&lt;/strong&gt; — using visually identical Unicode characters that differ at the byte level&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line/word shifting&lt;/strong&gt; — subtly adjusting spacing in printed documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Text steganography is generally lower capacity and more fragile than image-based methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Network Steganography
&lt;/h3&gt;

&lt;p&gt;Data can also be hidden in network traffic — in packet timing, unused header fields, or the ordering of packets. This is used in covert channel attacks and advanced persistent threats.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steganalysis — The Other Side
&lt;/h2&gt;

&lt;p&gt;Steganalysis is the detection of hidden data. If steganography is the attack, steganalysis is the defense.&lt;/p&gt;

&lt;p&gt;The core idea: natural images have statistical properties. Pixel values aren't random — they follow patterns. LSB modification disrupts those patterns. Statistical analysis can flag images where the LSB distribution looks abnormal.&lt;/p&gt;

&lt;p&gt;Tools like &lt;strong&gt;zsteg&lt;/strong&gt;, &lt;strong&gt;StegExpose&lt;/strong&gt;, and &lt;strong&gt;Stegdetect&lt;/strong&gt; automate this. A chi-squared test on LSB distributions is often enough to detect naive implementations.&lt;/p&gt;

&lt;p&gt;This is why encryption matters even in steganography. Even if steganalysis detects that &lt;em&gt;something&lt;/em&gt; is hidden, strong encryption ensures the payload is still unreadable. Defense in depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Legitimate Uses
&lt;/h2&gt;

&lt;p&gt;Steganography has a reputation problem — people assume it's only used for hiding illegal content. The reality is broader:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Digital watermarking&lt;/strong&gt; — embedding invisible ownership metadata in images and audio&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Journalistic source protection&lt;/strong&gt; — passing documents inside ordinary images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copyright protection&lt;/strong&gt; — tracking unauthorized distribution of media&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Covert communication&lt;/strong&gt; — used historically by intelligence agencies and dissidents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security research&lt;/strong&gt; — understanding covert channels and detection methods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to see LSB image steganography in practice, I built &lt;strong&gt;&lt;a href="https://stegoimage.pages.dev" rel="noopener noreferrer"&gt;Stego.Image&lt;/a&gt;&lt;/strong&gt; — a free, open-source browser tool that hides any file inside a PNG using DEFLATE compression + AES-256 encryption + LSB embedding. Everything runs client-side, nothing touches a server.&lt;/p&gt;

&lt;p&gt;Source on &lt;a href="https://github.com/50RISHU/Stego.Image" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. MIT licensed.&lt;/p&gt;

</description>
      <category>security</category>
      <category>privacy</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Built an Image Steganography Tool — Hide Any File Inside a PNG with AES-256 Encryption</title>
      <dc:creator>Rishu</dc:creator>
      <pubDate>Wed, 01 Jul 2026 08:06:52 +0000</pubDate>
      <link>https://dev.to/asyncinnovator/i-built-an-image-steganography-tool-hide-any-file-inside-a-png-with-aes-256-encryption-4chd</link>
      <guid>https://dev.to/asyncinnovator/i-built-an-image-steganography-tool-hide-any-file-inside-a-png-with-aes-256-encryption-4chd</guid>
      <description>&lt;p&gt;I've been fascinated by steganography for a while — the idea that you can hide a file inside an image, and nobody would ever know it's there. Not just encrypted, but completely invisible.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://stegoimage.pages.dev" rel="noopener noreferrer"&gt;Stego.Image&lt;/a&gt;. A free, open-source tool that does exactly that — hide any file inside a PNG image, entirely in your browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is steganography?
&lt;/h2&gt;

&lt;p&gt;Steganography is hiding data inside another file so that the existence of the hidden data is concealed. It's different from encryption — encryption makes data unreadable, steganography makes data invisible.&lt;/p&gt;

&lt;p&gt;The most common digital technique is &lt;strong&gt;LSB steganography&lt;/strong&gt; — hiding bits of data in the least significant bits of image pixels. Changing the last bit of a pixel's color value shifts it by just 1 out of 255. Completely imperceptible to the human eye.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original:  11001010  →  202
Modified:  11001011  →  203  ← you cannot see this difference
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 1920×1080 image has over 2 million pixels. At 3 bits per pixel across RGB channels, that's enough to hide ~777 KB of data — invisibly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;Most steganography tools I found were either desktop-only, outdated, or required uploading your file to a server. That last part kills the entire point — if your secret file touches someone else's server, it's not secret anymore.&lt;/p&gt;

&lt;p&gt;I wanted something that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runs entirely in the browser&lt;/li&gt;
&lt;li&gt;uses proper modern encryption&lt;/li&gt;
&lt;li&gt;works on any device, no install&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built it.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Stego.Image works
&lt;/h2&gt;

&lt;p&gt;The pipeline has three stages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Compress&lt;/strong&gt;&lt;br&gt;
The file is compressed using DEFLATE before anything else. Encrypted data is incompressible (it looks like random noise), so compression must happen first. This reduces payload size and increases how much you can hide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Encrypt&lt;/strong&gt;&lt;br&gt;
Compressed data is encrypted with AES-256. The key is derived using PBKDF2-SHA256 at 100,000 iterations with a randomly generated salt — so your password never becomes the key directly. This makes brute-force attacks computationally expensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Embed&lt;/strong&gt;&lt;br&gt;
Encrypted bits are written into the LSBs of each RGB pixel channel using the HTML5 Canvas API. The output is saved as PNG — always PNG, because JPEG's lossy compression would destroy the embedded data on save.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hide a file:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload a PNG carrier image&lt;/li&gt;
&lt;li&gt;Upload the file you want to hide (any format — PDF, ZIP, image, document, anything)&lt;/li&gt;
&lt;li&gt;Set a password&lt;/li&gt;
&lt;li&gt;Download the output image&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The output looks identical to the original. Nobody can tell the difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extract a file:&lt;/strong&gt;&lt;br&gt;
Upload the stego image → enter the password → download your file.&lt;/p&gt;

&lt;p&gt;That's it. No account, no install, no server. Try it at &lt;a href="https://stegoimage.pages.dev" rel="noopener noreferrer"&gt;stego.image&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The security problem I fixed mid-build
&lt;/h2&gt;

&lt;p&gt;The first version used crypto-js's default &lt;code&gt;EvpKDF&lt;/code&gt; for key derivation — MD5-based, fast, and completely wrong for a security tool. It's the kind of thing that passes a quick test but fails the moment someone runs a brute-force attack.&lt;/p&gt;

&lt;p&gt;I replaced it with PBKDF2-SHA256 at 100k iterations with a random salt. Files encoded with the old version are intentionally incompatible with the new one. Security over backward compatibility — no exceptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  What you can use it for
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Sending sensitive documents without revealing what's being transferred&lt;/li&gt;
&lt;li&gt;Embedding invisible ownership metadata in images you distribute&lt;/li&gt;
&lt;li&gt;Security research — understanding how LSB steganalysis works by building the thing it's trying to detect&lt;/li&gt;
&lt;li&gt;Anywhere you need plausible deniability about a file's existence&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;React 19, Vite, crypto-js, pako, Bootstrap 5, deployed on Cloudflare Pages.&lt;/p&gt;

&lt;p&gt;The entire thing is static — Cloudflare serves the build, your browser does all the work.&lt;/p&gt;




&lt;p&gt;🌐 &lt;strong&gt;Live:&lt;/strong&gt; &lt;a href="https://stegoimage.pages.dev" rel="noopener noreferrer"&gt;https://stegoimage.pages.dev&lt;/a&gt;&lt;br&gt;&lt;br&gt;
🐙 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/50RISHU/Stego.Image" rel="noopener noreferrer"&gt;https://github.com/50RISHU/Stego.Image&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT licensed. If you find a bug or want to contribute, PRs are open.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>opensource</category>
      <category>security</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Image Steganography tool</title>
      <dc:creator>Rishu</dc:creator>
      <pubDate>Sat, 20 Jun 2026 06:57:12 +0000</pubDate>
      <link>https://dev.to/rishu50/-5h5</link>
      <guid>https://dev.to/rishu50/-5h5</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/rishu50/i-built-a-free-tool-to-hide-files-inside-images-aes-256-fully-client-side-1ioh" class="crayons-story__hidden-navigation-link"&gt;Image Steganography Tool: Hide Any File Inside a PNG — AES-256 &amp;amp; Fully Client-Side&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/rishu50" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3948651%2F4d41437e-a334-4cef-a4da-c2a08124d9e5.jpg" alt="rishu50 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/rishu50" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Rishu
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Rishu
                
              
              &lt;div id="story-author-preview-content-3839827" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/rishu50" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3948651%2F4d41437e-a334-4cef-a4da-c2a08124d9e5.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Rishu&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/rishu50/i-built-a-free-tool-to-hide-files-inside-images-aes-256-fully-client-side-1ioh" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 7&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/rishu50/i-built-a-free-tool-to-hide-files-inside-images-aes-256-fully-client-side-1ioh" id="article-link-3839827"&gt;
          Image Steganography Tool: Hide Any File Inside a PNG — AES-256 &amp;amp; Fully Client-Side
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/showdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;showdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/security"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;security&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/rishu50/i-built-a-free-tool-to-hide-files-inside-images-aes-256-fully-client-side-1ioh" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/rishu50/i-built-a-free-tool-to-hide-files-inside-images-aes-256-fully-client-side-1ioh#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
  </channel>
</rss>
