<?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 (@ri5hu).</description>
    <link>https://dev.to/ri5hu</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/ri5hu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ri5hu"/>
    <language>en</language>
    <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/ri5hu/i-built-a-linter-that-catches-the-security-bugs-ai-assistants-keep-writing-58m8</link>
      <guid>https://dev.to/ri5hu/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>My Flask Backend Was Falling Asleep Every 15 Minutes — Here's How I Fixed It Completely Free</title>
      <dc:creator>Rishu</dc:creator>
      <pubDate>Sun, 05 Jul 2026 08:39:51 +0000</pubDate>
      <link>https://dev.to/ri5hu/my-flask-backend-was-falling-asleep-every-15-minutes-heres-how-i-fixed-it-completely-free-ln5</link>
      <guid>https://dev.to/ri5hu/my-flask-backend-was-falling-asleep-every-15-minutes-heres-how-i-fixed-it-completely-free-ln5</guid>
      <description>&lt;p&gt;I built a small Flask backend for one of my side projects, deployed it on Render's free tier, and felt that usual satisfaction of seeing it live with an HTTPS URL and everything. Then I shared the link with a friend to get some feedback.&lt;/p&gt;

&lt;p&gt;His reply: &lt;em&gt;"bro it's not loading."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I opened it myself. It did load — but after sitting there for almost a minute on a blank screen. Not a great first impression.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Was Actually Happening
&lt;/h2&gt;

&lt;p&gt;After a bit of digging I found out this is just how Render's free tier works. If your service gets no traffic for &lt;strong&gt;15 minutes&lt;/strong&gt;, Render shuts it down to save resources. The moment a new request comes in, it has to boot the server back up before it can respond — and that cold start can take anywhere from 30 seconds to a full minute.&lt;/p&gt;

&lt;p&gt;For me sitting at my laptop knowing what's happening, it's fine. For anyone else opening the link cold, it just looks like the site is down or broken.&lt;/p&gt;

&lt;p&gt;I didn't want to pay for hosting yet — the project was still in early stages and didn't need production-level reliability. But I also didn't want every first impression to be a 60 second loading screen. So I started looking for a workaround.&lt;/p&gt;

&lt;h2&gt;
  
  
  First Thought: GitHub Actions
&lt;/h2&gt;

&lt;p&gt;My first idea was to write a simple GitHub Actions workflow that runs every 14 minutes and sends a &lt;code&gt;curl&lt;/code&gt; request to my server. If something keeps hitting it before the timer runs out, it never goes to sleep. Simple enough in theory.&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;But then I did the math. GitHub's free tier gives you &lt;strong&gt;2,000 Actions minutes per month.&lt;/strong&gt; Pinging every 14 minutes means roughly 103 runs per day, which comes out to around &lt;strong&gt;3,000+ runs per month.&lt;/strong&gt; That exceeds the free limit by a lot, and I didn't want surprise charges on a side project I wasn't even monetizing. Crossed that off the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Worked
&lt;/h2&gt;

&lt;p&gt;The core idea was still the same — something needs to ping the server regularly so it never hits the 15 minute idle limit. I just needed a tool that could do it for free without consuming GitHub minutes.&lt;/p&gt;

&lt;p&gt;I came across &lt;strong&gt;UptimeRobot.&lt;/strong&gt; It's primarily a monitoring service — you give it a URL, it checks that URL every few minutes, and sends you an alert if it goes down. The free plan checks every &lt;strong&gt;5 minutes&lt;/strong&gt;, which is more than frequent enough to prevent Render from sleeping.&lt;/p&gt;

&lt;p&gt;But before setting that up, I wanted a proper endpoint to ping — not just the root &lt;code&gt;/&lt;/code&gt; which might have heavier logic attached. I added a dead simple &lt;code&gt;/health&lt;/code&gt; route 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;No database queries, no business logic, nothing that could fail. Just a response that says the server is alive. This is actually a good practice for any backend — having a health endpoint is useful beyond just keep-alive purposes, like when you want to confirm a deployment went through or check if a service is up during debugging.&lt;/p&gt;

&lt;p&gt;Then I went to UptimeRobot, created a free account, and added a new &lt;strong&gt;HTTP monitor&lt;/strong&gt; pointing to &lt;code&gt;https://green-spoon-backend.onrender.com/api/health&lt;/code&gt; with a 5 minute check interval. Took about 2 minutes to set up.&lt;/p&gt;

&lt;p&gt;That's genuinely it. The server has stayed awake ever since.&lt;/p&gt;

&lt;h2&gt;
  
  
  Something I Was Worried About
&lt;/h2&gt;

&lt;p&gt;My first concern was that I'd get flooded with emails — a notification every 5 minutes saying "your site is up" would be unbearable.&lt;/p&gt;

&lt;p&gt;But that's not how it works. UptimeRobot pings your URL silently in the background. You only get notified when something actually goes wrong — when the server goes &lt;strong&gt;down&lt;/strong&gt;, and again when it comes back &lt;strong&gt;up.&lt;/strong&gt; During normal operation you hear nothing at all. It's actually a nice bonus to have real uptime monitoring on top of the keep-alive behavior.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you've dealt with this before and found a better way to handle it, drop it in the comments — always curious how others approach this.&lt;/em&gt;&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/ri5hu/what-is-steganography-how-it-works-types-and-why-it-matters-1c44</link>
      <guid>https://dev.to/ri5hu/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/ri5hu/i-built-an-image-steganography-tool-hide-any-file-inside-a-png-with-aes-256-encryption-4chd</link>
      <guid>https://dev.to/ri5hu/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>
