{"slug": "hallint-update-what-we-fixed-what-we-shipped-and-what-s-coming-in-v0-2", "title": "hallint Update: What We Fixed, What We Shipped, and What's Coming in v0.2", "summary": "The hallint project shipped fixes for false positives in its security linter, removing the async-no-catch rule from the recommended set and adding inline suppression markers for intentionally public routes. Version 0.2 will include dual-pass detection for hardcoded secrets and improved scanner dispatch.", "body_md": "*This is a follow-up to I Built a Linter That Catches the Security Bugs AI Assistants Keep Writing. The comments on that post shaped most of what's in this update — thank you.*\n\nThe first post got a lot of honest feedback. Not just \"cool project\" — actual technical pushback on the things that weren't right yet. This post covers what we fixed based on that, what's shipped since, and what's coming in v0.2.\n\nCurrent versions:\n\n`@asyncinnovator/hallint`\n\n— `@asyncinnovator/hallint-cli`\n\n— Two issues came up repeatedly and they were both valid.\n\n**1. async-no-catch was polluting the signal.**\n\n[Nazar Boyko](https://dev.to/nazar-boyko) put it cleanly: the rule breaks the bar of \"a human looking at the flagged line agrees it's wrong.\" Plenty of correct async code has no try/catch because the caller handles it, or because an Express error middleware catches it upstream. Running hallint on a real codebase and getting forty `async-no-catch`\n\nwarnings next to one genuine hardcoded key destroys the signal you worked to build.\n\n**Fix shipped:** `async-no-catch`\n\nis now removed from the `recommended`\n\nruleset. It still exists and still runs if you opt in with `--rules all`\n\n, but it no longer fires by default. The seven security rules that share the \"this line is wrong\" property remain in `recommended`\n\n.\n\n**2. missing-auth-check had a false positive problem on intentionally public routes.**\n\n[Dipankar Sarkar](https://dev.to/dipankar_sarkar) and [Adam Lewis](https://dev.to/nark3d) both flagged this. Health checks, webhooks, public endpoints — they're supposed to have no auth middleware. Flagging them is noise, and noise gets the rule disabled.\n\n**Fix shipped:** hallint now recognizes three inline suppression markers:\n\n```\n// public\n// hallint-public\n/* hallint-public */\n```\n\nAdd any of these to a route and hallint skips the auth check on that handler. Clean, explicit, no magic inference needed.\n\n``` js\napp.get('/health', (req, res) => { // hallint-public\n  res.json({ status: 'ok' })\n})\n```\n\n`hardcoded-secret`\n\nnow runs dual-pass detection.\n\nThe original regex caught generic `api_key = \"...\"`\n\npatterns. It missed token prefixes that are dead giveaways regardless of variable name. The rule now has a second pass targeting known prefixes: `ghp_`\n\n, `sk-`\n\n, `AKIA`\n\n, `xoxb-`\n\n. A line containing any of these is flagged as critical even if the variable name looks innocent. Comments and env-var assignments are excluded.\n\n`sqlInjection`\n\nmessage copy was over-claiming.\n\nThe original message said \"user input flowing into query\" but the detection was pattern-based, not data-flow-based. It couldn't actually prove the variable was user input — just that a template literal was used inside a query call. The message now reflects what the rule actually detects: template literal interpolation in a query call. Accurate scope, no false confidence.\n\n**Scanner dispatch was refactored.**\n\nPreviously, the scanner used `rule.layer`\n\nto decide whether to run regex or AST matching. This meant rules had to set `layer`\n\ncorrectly or they'd be silently skipped. Now `rule.match()`\n\npresence is the dispatch signal — if a rule has a `match()`\n\nfunction it gets AST treatment, otherwise regex. `layer`\n\nis now metadata only, used for documentation and filtering. This makes rule authoring less error-prone.\n\n`asyncNoCatch`\n\ndetection was fragile.\n\nThe brace-counter approach used to find async function boundaries broke on nested objects and template literals with braces. Replaced with a `stripStrings()`\n\nheuristic that removes string content before counting — significantly fewer false positives even before the rule was moved out of `recommended`\n\n.\n\nYou can now gate CI on hallint in five lines:\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\n```\n\nhallint exits `1`\n\non critical or high findings, so this blocks merges on real issues. The LLM layer — when it lands — will never trigger exit code `1`\n\n. Non-deterministic checks don't belong in CI gates.\n\nA few things were promised in the comments and are actively in progress.\n\n**Cross-file router composition tracking.**\n\nRight now `missing-auth-check`\n\nis same-file only. If your middleware is registered in `app.ts`\n\nand your routes live in `routes/users.ts`\n\n, hallint doesn't connect them. This is the honest limitation of regex-based analysis — you can't follow imports. v0.2 introduces tree-sitter for AST analysis, which makes cross-file tracking possible. This is the main structural change in v0.2.\n\n**LLM layer — opt-in only.**\n\nThe optional LLM review pass will land in v0.2 as a strict opt-in: `--llm ollama`\n\nor `--llm anthropic`\n\n. It will never run by default and will never contribute to exit code `1`\n\n. As Kartik noted in the comments, non-determinism in a CI gate is a dealbreaker. The LLM layer surfaces things the regex and AST layers miss — semantic issues, logic flaws, context-blind patterns — but those findings go in a separate output section, not in the main results that block a merge.\n\n**New rules under consideration for v0.2–v0.3:**\n\n`silent-error-swallow`\n\n— bare catch blocks that swallow errors without logging or rethrowing. The pattern Edu Peralta described: looks handled, isn't.`jwt-in-localstorage`\n\n— storing tokens where XSS can reach them`hallucinated-dependency`\n\n— imports of packages that don't exist in package.json (a real AI-specific failure mode)\nEach rule is a single file, around 30 lines, with a `bad.ts`\n\nand `good.ts`\n\nfixture. If you've seen a pattern AI assistants keep producing that hallint doesn't catch yet, the path from \"I noticed this\" to \"I shipped a fix\" is short.\n\nIssues labeled **good first issue** are pre-scoped and ready to pick up.\n\n**GitHub:** [github.com/Asyncinnovator/hallint](https://github.com/Asyncinnovator/hallint)\n\n```\nnpx @asyncinnovator/hallint-cli ./src\n```\n\nMIT licensed. Free for personal and commercial use.", "url": "https://wpnews.pro/news/hallint-update-what-we-fixed-what-we-shipped-and-what-s-coming-in-v0-2", "canonical_source": "https://dev.to/asyncinnovator/hallint-update-what-we-fixed-what-we-shipped-and-whats-coming-in-v02-35l7", "published_at": "2026-07-14 11:36:38+00:00", "updated_at": "2026-07-14 11:58:26.023822+00:00", "lang": "en", "topics": ["developer-tools", "ai-safety", "ai-agents"], "entities": ["hallint", "Nazar Boyko", "Dipankar Sarkar", "Adam Lewis", "@asyncinnovator/hallint", "@asyncinnovator/hallint-cli"], "alternates": {"html": "https://wpnews.pro/news/hallint-update-what-we-fixed-what-we-shipped-and-what-s-coming-in-v0-2", "markdown": "https://wpnews.pro/news/hallint-update-what-we-fixed-what-we-shipped-and-what-s-coming-in-v0-2.md", "text": "https://wpnews.pro/news/hallint-update-what-we-fixed-what-we-shipped-and-what-s-coming-in-v0-2.txt", "jsonld": "https://wpnews.pro/news/hallint-update-what-we-fixed-what-we-shipped-and-what-s-coming-in-v0-2.jsonld"}}