{"slug": "i-built-a-linter-that-catches-the-security-bugs-ai-assistants-keep-writing", "title": "I Built a Linter That Catches the Security Bugs AI Assistants Keep Writing", "summary": "A developer built hallint, an open-source static analysis tool designed to catch security vulnerabilities and logic bugs commonly introduced by AI coding assistants like Copilot, Claude, and ChatGPT. The tool detects patterns such as hardcoded secrets, SQL injection, missing authentication, permissive CORS, and XSS via innerHTML, which traditional linters often miss. Hallint is available as a TypeScript library and CLI tool on GitHub.", "body_md": "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.\n\nBut here's the thing nobody talks about enough: **AI-generated code has a specific category of bugs that normal linters don't catch.**\n\nNot 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.\n\nI got burned enough times that I built something about it. It's called **hallint**, and it's a free, open-source static analysis tool designed specifically for AI-generated code. You can find it on GitHub: [github.com/Asyncinnovator/hallint](https://github.com/Asyncinnovator/hallint)\n\nTraditional linters were designed for human-written code. They're great at catching things humans commonly get wrong — unused variables, missing semicolons, incorrect type usage.\n\nBut AI assistants fail differently.\n\nHere are the patterns I kept seeing over and over:\n\n**1. Hardcoded secrets**\n\nAI assistants will happily write `const API_KEY = \"sk-abc123...\"`\n\nin 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.\n\n**2. SQL injection**\n\n``` js\n// AI generates this and it looks totally fine\nconst user = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`)\n```\n\nNo ESLint rule will flag this. But it's a textbook SQL injection vector. Every time.\n\n**3. Missing auth on route handlers**\n\nAI 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.\n\n**4. Permissive CORS**\n\n```\napp.use(cors({ origin: '*' }))\n```\n\nThis 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.\n\n**5. innerHTML with unsanitized strings**\n\n```\nelement.innerHTML = userInput // XSS waiting to happen\n```\n\nLLMs write this pattern a lot. It's the obvious, readable way to set content — and it's a cross-site scripting vulnerability.\n\n**hallint** is a TypeScript library and CLI tool that scans your codebase for these AI-specific failure patterns.\n\nIt uses three detection layers:\n\nNo install needed — just run it with npx:\n\n```\nnpx @asyncinnovator/hallint-cli ./src\n```\n\nOr scan a specific glob pattern:\n\n```\nnpx @asyncinnovator/hallint-cli \"./src/**/*.ts\"\n```\n\nOnly care about serious issues? Filter by severity:\n\n```\nnpx @asyncinnovator/hallint-cli ./src --min-severity high\n```\n\nIt exits with code `1`\n\non any critical or high finding, which makes it easy to use as a CI gate.\n\nIf you want to integrate it into your own tooling:\n\n```\nnpm install @asyncinnovator/hallint\njs\nimport { scan } from '@asyncinnovator/hallint'\n\nconst result = await scan({\n  files: ['./src/**/*.ts'],\n  rules: 'recommended',\n  minSeverity: 'high',\n})\n\nresult.findings.forEach(f => {\n  console.log(`[${f.severity}] ${f.ruleId} ${f.filePath}:${f.line}`)\n  console.log(`  ${f.message}`)\n  console.log(`  fix: ${f.fix}`)\n})\n```\n\nYou can also scan a string directly without touching the filesystem:\n\n``` js\nimport { scanSource } from '@asyncinnovator/hallint'\n\nconst findings = scanSource(`const apiKey = \"sk-abc123def456\"`, 'example.ts')\n```\n\nRight now hallint ships with eight rules, all targeting the patterns I described above:\n\n| Rule | Severity | What it catches |\n|---|---|---|\n`hardcoded-secret` |\ncritical | API keys, tokens, passwords in source code |\n`sql-injection` |\ncritical | User input interpolated into SQL queries |\n`unsafe-eval` |\ncritical |\n`eval()` or `new Function()` with dynamic input |\n`missing-auth-check` |\nhigh | Route handlers with no auth middleware |\n`xss-innerHTML` |\nhigh | Unsanitized strings assigned to `innerHTML`\n|\n`permissive-cors` |\nhigh |\n`cors({ origin: '*' })` in route handlers |\n`async-no-catch` |\nmedium |\n`async` functions with no `try/catch` or `.catch()`\n|\n`http-not-https` |\nmedium | Hardcoded `http://` URLs in fetch or axios calls |\n\nESLint with the right plugins will catch *some* of this. But there are a few important differences.\n\n**Existing linters weren't built with AI failure modes in mind.** 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.\n\n**hallint is designed to be AI-aware.** 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.\n\n**The LLM layer is something ESLint can't do.** 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.\n\nHere's the kind of thing it catches. Say you take some AI-generated Express code:\n\n``` python\nimport express from 'express'\nimport db from './db'\n\nconst app = express()\n\napp.get('/users/:id', async (req, res) => {\n  const user = await db.query(\n    `SELECT * FROM users WHERE id = ${req.params.id}`\n  )\n  res.json(user)\n})\n```\n\nhallint flags it:\n\n```\n[critical] sql-injection: User input interpolated directly into SQL query.\n  Use parameterized queries: db.query('SELECT * FROM users WHERE id = $1', [req.params.id])\n  → src/routes/users.ts:6\n```\n\nClean, direct, tells you exactly what to fix.\n\nOne of the things I wanted from day one was a zero-friction GitHub Actions integration. hallint exits with code `1`\n\non critical or high findings, so this just works:\n\n```\n- uses: actions/checkout@v4\n- uses: actions/setup-node@v4\n  with:\n    node-version: 20\n- run: npx @asyncinnovator/hallint-cli ./src --min-severity high\n```\n\nYou can also add it as a pre-commit hook via husky if you want to catch issues before they even get pushed.\n\nThe whole thing is MIT licensed and lives at ** github.com/Asyncinnovator/hallint**. Issues and PRs are open.\n\nI 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.\n\nThe rule-writing surface is intentionally small — each rule is a single file (~30 lines) with a `bad.ts`\n\n/ `good.ts`\n\nfixture. 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 **good first issue** are pre-scoped and ready to pick up.\n\n*hallint is MIT licensed. Free to use in personal and commercial projects.*", "url": "https://wpnews.pro/news/i-built-a-linter-that-catches-the-security-bugs-ai-assistants-keep-writing", "canonical_source": "https://dev.to/ri5hu/i-built-a-linter-that-catches-the-security-bugs-ai-assistants-keep-writing-58m8", "published_at": "2026-07-10 07:11:18+00:00", "updated_at": "2026-07-10 07:42:54.636528+00:00", "lang": "en", "topics": ["developer-tools", "ai-safety", "ai-tools", "artificial-intelligence"], "entities": ["hallint", "GitHub", "Copilot", "Claude", "ChatGPT", "Asyncinnovator"], "alternates": {"html": "https://wpnews.pro/news/i-built-a-linter-that-catches-the-security-bugs-ai-assistants-keep-writing", "markdown": "https://wpnews.pro/news/i-built-a-linter-that-catches-the-security-bugs-ai-assistants-keep-writing.md", "text": "https://wpnews.pro/news/i-built-a-linter-that-catches-the-security-bugs-ai-assistants-keep-writing.txt", "jsonld": "https://wpnews.pro/news/i-built-a-linter-that-catches-the-security-bugs-ai-assistants-keep-writing.jsonld"}}