hallint: линтер, который ловит баги безопасности от AI-ассистентов
AI-ассистенты вроде Copilot, Cursor и ChatGPT пишут код быстрее человека — но у них есть системная слепая зона: они воспроизводят одни и те же классы уязвимостей снова и снова, причём с абсолютной уверенностью. Традиционные линтеры вроде ESLint проектировались для человеческого кода и не ловят эти паттерны. Инструмент hallint создан именно для этого — это бесплатный open-source статический анализатор, заточенный под характерные ошибки AI-генерации.
Почему ESLint не справляется с AI-кодом
AI-ассистенты ошибаются не так, как люди. Когда вы просите LLM сгенерировать Express-роут или запрос к базе данных, модель идёт по пути наименьшего сопротивления. Код проходит поверхностное ревью, работает в локальном окружении — и создаёт критические уязвимости в продакшене. Вот типичные паттерны:
-
Захардкоженные секреты — AI с готовностью выдаст
const API_KEY = "sk-abc123...". ESLint пропускает, а пуш в публичный репозиторий превращает это в катастрофу. -
SQL-инъекции по умолчанию — AI обожает template literals:
db.query(\SELECT * FROM users WHERE id = ${req.params.id}`)`. На вид безобидно, но это хрестоматийный вектор SQL-инъекции. -
Пропущенная аутентификация — сгенерировать CRUD-роуты легко, а вот применить auth middleware к каждому из них AI забывает постоянно.
-
Маскировка ошибок аутентификации — особенно коварный паттерн. Когда AI просят добавить обработку ошибок в auth middleware, он часто генерирует
try/catch, который перехватывает ошибку токена, но всё равно вызываетnext(), молча пропуская неаутентифицированные запросы. -
Разрешительный CORS —
cors({ origin: '*' })— любимый однострочник AI для «исправления» ошибок CORS.
Что такое hallint
hallint — это TypeScript-библиотека и CLI-инструмент, который сканирует JavaScript-, TypeScript- и Python-кодовые базы на специфические для AI проблемы безопасности и качества. Вместо того чтобы быть универсальным линтером, он использует три слоя детекции:
- Regex-паттерны — молниеносный первый проход по известным плохим шаблонам.
- AST-анализ — структурные проверки, понимающие логику кода, а не только его текст.
- LLM-ревью (опционально) — семантический анализ логических потоков через Anthropic, OpenAI или локальный Ollama.
Набор правил
На текущий момент hallint включает 11 целевых правил:
| Правило | Критичность | Что обнаруживает |
|---|---|---|
hardcoded-secret |
Critical | API-ключи, токены, известные префиксы (ghp_, sk-) |
sql-injection |
Critical | Пользовательский ввод, напрямую интерполированный в SQL-запросы |
unsafe-eval |
Critical | eval() или new Function() с динамическим вводом |
auth-masking |
Critical | Блоки catch, которые проглатывают ошибки аутентификации, делая отказ невидимым |
missing-auth-check |
High | Обработчики роутов без middleware аутентификации |
xss-innerHTML |
High | Несанитизированные строки, присвоенные напрямую в .innerHTML |
permissive-cors |
High | cors({ origin: '*' }) в обработчиках роутов |
jwt-in-localstorage |
High | JWT или auth-токены, сохраняемые в localStorage |
swallowed-error |
High | Пустые или содержащие только комментарий блоки catch |
http-not-https |
Medium | Закодированные http:// URL в fetch/axios-запросах |
async-no-catch |
Medium | async-функции без какой-либо обработки ошибок |
Использование
Запустить hallint можно без установки — через npx:
npx @asyncinnovator/hallint-cli ./src
Пример вывода:
hallint scanning ./src...
src/routes/users.ts
users.ts:4 CRITICAL [hardcoded-secret]
Hardcoded secret detected
> const apiKey = "sk-abc123def456ghi789jkl"
fix: Move to environment variables: process.env.YOUR_SECRET_NAME
users.ts:9 CRITICAL [sql-injection]
Possible SQL injection
> 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
Для интеграции в собственные пайплайны, pre-commit хуки или редакторные плагины можно подключить библиотеку напрямую:
npm install @asyncinnovator/hallint
import { scan, scanSource } from '@asyncinnovator/hallint'
const result = await scan({
files: ['./src/**/*.ts'],
rules: 'recommended',
minSeverity: 'high',
})
const findings = scanSource(
`const key = "sk-abc123abc123abc123abc"`,
'virtual.ts'
)
Почему это важно
Главное преимущество hallint перед LLM-ревью — детерминизм. Правило «строка начинается с sk- и содержит 40 символов — отклонить» даёт гарантию, которую вероятностная модель обеспечить не может. Паттерн маскировки ошибок аутентификации особенно показателен: код с try/catch, который вызывает next() после перехвата ошибки токена, выглядит абсолютно корректно при беглом осмотре, но молча отключает проверку аутентификации. Эту ошибку практически всегда допускает именно AI — человек, пишущий auth-логику с нуля, так не ошибается.
Инструмент распространяется под лицензией MIT, разрабатывается сообществом и легко расширяется. Если вы встречали AI-специфичный паттерн уязвимости, который ещё не покрыт, pull requests приветствуются.
Ссылки:
- Репозиторий: github.com/Asyncinnovator/hallint
- Core library (npm): @asyncinnovator/hallint
- CLI (npm): @asyncinnovator/hallint-cli