We are all using Copilot, Cursor, or ChatGPT to write code faster. They save time, tackle boilerplate, and help us think through complex logic. But there is a massive blind spot that the industry isn't talking about enough: AI coding assistants generate the exact same class of security bugs, over and over again, with total confidence.
Traditional linters like ESLint are fantastic, but they were designed for human-written code. They catch unused variables and missing semicolons. They aren't built to catch the subtle, plausible-looking security holes that LLMs naturally default to.
That is why I built hallint. It is a free, open-source static analysis tool specifically tuned to catch the failure modes of AI code generation before they reach your production environment.
AI assistants fail differently than humans do. When you ask an LLM to generate an Express route or a database query, it tends to take the path of least resistance. It writes code that passes casual review, runs perfectly in local development, and creates devastating vulnerabilities once deployed.
Here are a few classic AI failure patterns:
Hardcoded Secrets: AI will happily spit out const API_KEY = "sk-abc123..."
. It passes linting, but pushing it to a public repo is a disaster.
SQL Injection by Default: AI loves template literals. db.query("SELECT * FROM users WHERE id = ${req.params.id}")
looks completely fine at a glance, but it is a textbook SQL injection vector.
Missing Authentication: Generating CRUD routes is easy. Remembering to apply auth middleware to every single one of them? AI forgets constantly.
Auth Masking: This is a particularly insidious AI habit. When asked to add error handling to auth middleware, an LLM will often generate a try/catch
block that catches a token error but still calls next()
, silently allowing unauthenticated requests through.
Permissive CORS: Setting cors({ origin: '*' })
is the AI's favorite one-liner to "fix" your CORS errors.
hallint
? hallint is a TypeScript library and CLI tool that scans your JavaScript, TypeScript, and Python codebases for these specific AI-generated security and quality issues.
Instead of trying to be a general-purpose linter, it uses three detection layers to target AI-specific code smells:
hallint
currently ships with 11 targeted rules:
| Rule | Severity | What it catches |
|---|---|---|
hardcoded-secret |
||
| Critical | ||
API keys, tokens, and known prefixes (ghp_ , sk- ). |
||
sql-injection |
||
| Critical | ||
| User input directly interpolated into SQL queries. | ||
unsafe-eval |
||
| Critical | ||
eval() or new Function() using dynamic input. |
||
auth-masking |
||
| Critical | ||
| Catch blocks that swallow auth errors, making failures silently pass. | ||
missing-auth-check |
||
| High | ||
| Route handlers missing authentication middleware. | ||
xss-innerHTML |
||
| High | ||
Unsanitized strings assigned directly to .innerHTML . |
||
permissive-cors |
||
| High | ||
cors({ origin: '*' }) left in route handlers. |
||
jwt-in-localstorage |
||
| High | ||
JWTs or auth tokens being stored in localStorage . |
||
swallowed-error |
||
| High | ||
| Empty or comment-only catch blocks. | ||
http-not-https |
||
| Medium | ||
Hardcoded http:// URLs in fetch/axios requests. |
||
async-no-catch |
||
| Medium | ||
async functions with no error handling at all. |
hallint
You can use hallint
as a quick CLI scanner or integrate it deeply into your Node.js tooling.
You don't even need to install it to try it out. Just point it at your source directory using npx
:
npx @asyncinnovator/hallint-cli ./src
Example Output:
hallint scanning ./src...
src/routes/users.ts
users.ts:4 CRITICAL [hardcoded-secret]
Hardcoded secret detected — API key, token, or password in source code
> 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
> 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])
Summary: 2 issue(s) in 1 file(s) — 12ms
2 critical
If you are building your own testing pipelines, pre-commit hooks, or editor plugins, you can import the core library directly:
npm install @asyncinnovator/hallint
js
import { scan, scanSource } from '@asyncinnovator/hallint'
// Scan your file system
const result = await scan({
files: ['./src/**/*.ts'],
rules: 'recommended',
minSeverity: 'high',
})
// Or scan raw strings directly without touching the filesystem
const findings = scanSource(
`const key = "sk-abc123abc123abc123abc"`,
'virtual.ts'
)
If you want to integrate the CLI directly into your CI/CD pipelines (like GitHub Actions) as a hard merge gate, configure ESLint-style inline rule suppressions, set up public route allowlists to reduce noise, or enable the optional LLM-powered plain-English explanations, you will find everything you need in our official documentation.
We want to keep this blog post focused on the core problem, so for advanced configurations, architecture details, and guides on how to write your own custom rules in under 30 lines of code, head over to our repository.
👉 Read the full documentation on GitHub
AI is changing how we write code, but we need tools that adapt to the new mistakes we are making. hallint
is MIT licensed, community-driven, and highly extensible. If you've seen an AI-specific vulnerability pattern that we don't catch yet, pull requests are always welcome!