
SecureAI-Scan v0.4.0
SecureAI-Scan — это CLI-инструмент для сканирования кодовых баз на TypeScript и JavaScript на предмет уязвимостей, характерных для приложений с ИИ: инъекции промптов, злоупотребление инструментами MCP, отравление данных RAG, нарушение доверия к агентам и другие.
SecureAI-Scan
Офлайн-CLI, который сканирует TypeScript, JavaScript и Python на предмет рисков LLM, MCP, Agent Skill и RAG — доказательства потока данных с разрешением импортов, ноль ложных срабатываний по умолчанию, сопоставлено с OWASP LLM/ASI/MCP Top 10.
Большинство сканеров в этой области сопоставляют ключевое слово по шаблону и называют это находкой. SecureAI-Scan прослеживает фактический путь источник → поток → приёмник через реальный код с разрешением импортов — и сканирование по умолчанию показывает только то, что может доказать. Без аккаунта, без загрузки в облако, ничего не покидает вашу машину.
Охватывает официальные OWASP Top 10 для LLM-приложений 2026, Top 10 для агентных приложений (2026) и MCP Top 10 с первой недели запуска.
Начало работы за 30 секунд```bash
npx --yes [email protected] scan .
Нет необходимости в аккаунте, облачной загрузке, интерпретаторе Python или настройке. TypeScript, JavaScript, Python, MCP-конфигурации и пакеты Agent Skill определяются автоматически.
**Измеренный кандидат в релиз `0.9.0`:** 136/136 тестов · 88.08% покрытие операторов · 12 676 файлов в 9 публичных репозиториях · 0 новых отпечатков уровня по умолчанию относительно проверенного базового уровня. [Доказательства](https://github.com/akanthed/secureai-scan/blob/main/docs/benchmarks/v0.9.0.json) · [методология и ограничения](https://github.com/akanthed/secureai-scan/blob/main/docs/ReleaseAssurance.md)```
▌ HIGH AI001 Prompt injection via user input
PROVEN LLM01:2026 Prompt Injection
source src/chat.ts:8 request data `req.body.input`
flow src/chat.ts:13 passed as `systemPrompt`
sink src/chat.ts:10 openai.chat.completions.create — system role (OpenAI)
fix Keep system prompts static; pass user input as a user-role message.
Это для вас? SecureAI-Scan намеренно ограничен рисками LLM, MCP и RAG/агентов — промпт-инъекциями, отравлением инструментов, небезопасной обработкой вывода, контролем доступа к векторным хранилищам, отравлением навыков агентов. Это не универсальный SAST- или секрет-сканер, и он не пытается им быть; известный вредоносный пакет без полезной нагрузки в форме LLM (например, жёстко заданный адрес эксфильтрации в вызове email API) обнаруживается офлайн-списком рекомендаций (DEP003), а не правилом-паттерном. Если ваша кодовая база взаимодействует с LLM, MCP-сервером, векторным хранилищем или поставляет Agent Skills — этот инструмент создан для вас.
Новое: статическое сканирование конфигурации для LiteLLM Proxy (config.yaml) — жёстко заданные секреты, открытые текстом конечные точки провайдера, отсутствующие защитные механизмы. См. Правила (LLC001–LLC003).
Содержание
- Почему этот сканер отличается
- С чем его сравнить
- Начало работы за 30 секунд
- Посмотрите, как он работает
- Команды
- GitHub Action
- Pre-commit hook
- Правила
- Архитектура
- MCP-сервер (используйте его из Claude)
- Claude Skill
- Гарантии доверия и релиза
- Контракт точности
- Тестирование и бенчмаркинг
- Дорожная карта
- Вклад в проект
Почему этот сканер отличается
- Уровни доказательности, а не шум. Каждая находка —
proven(прослеженный поток данных или установленный факт из конфига),likely(разрешённый приёмник, один эвристический шаг) илиheuristic. Стандартное сканирование показывает только proven + likely. Эвристики включаются опционально через--paranoid. - Детекция с разрешением импортов. Вызов считается «LLM-вызовом», только если он разрешается в реальный импорт SDK (
openai,@anthropic-ai/sdk,ai,@google/genai, LangChain, Bedrock, …). Ваш клиент Google Maps больше никогда не будет помечен как LLM. - Ограничен по точности и протестирован на реальных репозиториях. Тестовый набор утверждает, что каждая уязвимая фикстура срабатывает и каждая безопасная фикстура остаётся чистой — ложное срабатывание на безопасном корпусе ломает сборку. Кроме того,
npm run regressionсканирует реальные публичные репозитории (OpenAI/Anthropic/Vercel AI SDKs, официальные MCP-серверы, LlamaIndex) против зафиксированного, вручную проверенного базового уровня и падает при любой новой находкеproven/likely. См. Тестирование и бенчмаркинг для фактических цифр до/после, или Что мы нашли при сканировании реальных репозиториев для истории за ними — 6/6 уровень обнаружения на размеченном корпусе вредоносных навыков, и почему мы не называем llama_index «уязвимым» из-за честной находки на уровне библиотеки. Обсуждение → - SARIF для GitHub code scanning.
--output report.sarifразмещает находки прямо в pull request и во вкладке Security. - AI-BOM.
secureai-scan bom .строит инвентарь на основе синтаксиса: SDK, ID моделей, векторные хранилища, агентные фреймворки и MCP-серверы, сопоставленные с потребностями документации OWASP LLM Top 10 / EU AI Act. - Сканирование MCP-конфигурации. Разбирает
.mcp.json,claude_desktop_config.json,.cursor/mcp.json: незакреплённыеnpx -yсерверы, встроенные секреты, HTTP-транспорты открытым текстом. - Детекция отравления MCP-инструментов. Ловит паттерн, стоящий за rug-pull WhatsApp MCP и бэкдором postmark-mcp — невидимый Unicode, фразы инъекций, направленные на агента, и затенение между инструментами в именах/описаниях, статически, до того как вы запустите сервер.
- Детекция инъекций команд в MCP. Помечает
command/argsstdio-транспорта MCP, построенные из данных запроса — паттерн, стоящий за раскрытием RCE в MCP STDIO 2026 года. - Детекция отравления Agent Skills. Те же проверки невидимого Unicode, фраз инъекций и затенения применяются к файлам
SKILL.md— Agent Skills загружаются в контекст целиком, поэтому отравленный навык — это отравленное описание инструмента под другим именем. - Устойчивое к обходу сканирование навыков. Пакеты навыков сканируются как каталоги, а не только их
SKILL.md, и каждая проверка содержимого выполняется против деобфусцированных вариантов текста. Это нацелено на опубликованные техники — гомоглифы, разбиение нулевой ширины, полезные нагрузки в.git/илиbuild/, эксфильтрация, скрытая в файле*.test.ts, — которые обошли >90% из девяти сканеров, исследованных в Cloak and Detonate (arXiv:2607.02357). См. Устойчивость к обходу. - Рекомендации по известным уязвимым и вредоносным пакетам с учётом версий. Проверяет каждую зависимость и каждый пакет, запускаемый через MCP, против встроенного снимка рекомендаций — вручную подобранный список задокументированных бэкдоров в дикой природе, плюс рекомендации OSV уровня HIGH/CRITICAL для списка отслеживания LLM/MCP/RAG-пакетов, перегенерируемые
scripts/sync-advisories.js. Работает офлайн при каждом сканировании, без необходимости флага. CVE срабатывает только когда ваша закреплённая версия доказуемо находится в затронутом диапазоне; задокументированный вредоносный пакет срабатывает даже при неоднозначном диапазоне, потому что установка бэкдора необратима. - Локально в первую очередь. Ничего не покидает вашу машину.
С чем его сравнить
SecureAI-Scan не заменяет универсальный SAST-инструмент или сканер контейнеров/IaC — запускайте его вместе с ними, а не вместо. Он создан специально для поверхности атаки LLM/MCP/RAG и делает акцент на доказательности потока данных, а не на плоских находках по ключевым словам.
| SecureAI-Scan | Semgrep (OSS rules) | Trivy | GitHub Advanced Security | |
|---|---|---|---|---|
| Промпт-инъекция (источник→приёмник прослежен) | ✅ поток данных с разрешением импортов | ⚠️ только правила-паттерны, поддерживаются сообществом | ❌ | ⚠️ CodeQL может, но нет набора правил для ИИ |
| Отравление MCP-инструментов / риск конфигурации | ✅ MCP007–010, сканер конфигурации | ❌ | ❌ | ❌ |
Отравление Agent Skills (SKILL.md) | ✅ устойчиво к обходу, с учётом пакетов | ❌ | ❌ | ❌ |
| Неправильная настройка RAG / векторных хранилищ | ✅ VEC001–004 | ❌ | ❌ | ❌ |
| Рекомендации по известным вредоносным ИИ-пакетам | ✅ DEP003, офлайн, с учётом версий | ❌ | ⚠️ общий поток CVE, не для ИИ | ⚠️ Dependabot, общий поток CVE |
| Общий SAST (SQLi, XSS, path traversal) | ❌ вне области действия по дизайну | ✅ | ❌ | ✅ |
| Сканирование контейнеров / IaC | ❌ | ❌ | ✅ | ⚠️ через CodeQL/Actions |
| Уровни доказательности (proven/likely/heuristic) | ✅ | ❌ находки плоские | ❌ | ⚠️ CodeQL имеет некоторые, не настроены под ИИ |
| Вывод SARIF (GitHub code scanning) | ✅ | ✅ | ✅ | нативный |
| Работает офлайн, без аккаунта | ✅ | ✅ (OSS rules) | ✅ | ❌ требует GitHub |
Если вы уже используете Semgrep или GHAS, оставьте их — добавьте SecureAI-Scan для той поверхности риска, которую они вообще не моделируют.
Предпочитаете сначала задать вопросы? Попробуйте бесплатного AI-консультанта по безопасности SecureAI-Scan на ChatGPT.
Собираетесь запустить MCP-сервер, найденный на GitHub или в Twitter? Сначала вставьте его описание инструмента в MCP X-Ray — он проверит его на скрытый Unicode, внедрённые инструкции и известные вредоносные пакеты в вашем браузере, без установки.
Посмотрите, как он работает
secureai-scan scan . от начала до конца, реальный вывод на реальном (маленьком, намеренно уязвимом) файле — исходник:
Формы атак, которые сканер прослеживает от начала до конца:
| Поток данных отравления MCP-инструментов | Поток данных инъекции в контекст RAG |
|---|---|
![]() | ![]() |
Команды
Та, что нужна вам в 95% случаев:```bash secureai-scan scan .
Все остальное доступно, когда понадобится. `secureai-scan scan . --help` показывает всё это в терминале, сгруппированным так же:
**Повседневное использование**
| Флаг | Что делает |
|------|---------------|
| *(нет)* | результаты `proven` + `likely` — по умолчанию, флаги не нужны |
| `--paranoid` | также включает результаты уровня `heuristic` |
| `-s, --severity <уровень>` | показывать только результаты на уровне `low`\|`medium`\|`high`\|`critical` и выше |
| `--output <файл>` | записать полный отчёт — `.sarif` (code scanning GitHub), `.json`, `.md` или `.html` |
**Область применения: какие правила запускать**
| Флаг | Что делает |
|------|---------------|
| `-r, --rules <список>` | запускать только эти ID правил, например `AI001,MCP007` |
| `--only-ai` / `--only-mcp` / `--only-vec` / `--only-skl` | запускать только одну категорию правил |
| `--check-dependencies` | также проверять `package.json`/`requirements.txt` по реестрам npm/PyPI на опечатки и выдуманные пакеты (`DEP001`/`DEP002`). Автоматически включается, если выбрать эти правила напрямую через `-r` — не нужно помнить о передаче обоих флагов. Не требуется для `DEP003` (заведомо вредоносные пакеты), который всегда работает офлайн |
**CI / рабочий процесс**
| Флаг | Что делает |
|------|---------------|
| `--fail-on <серьёзность>` | выход с кодом `1`, если существуют результаты на этом уровне серьёзности и выше |
| `--baseline <файл>` | отслеживать только новые/изменённые проблемы относительно сохранённого базового уровня |
| `--policy <файл>` | загрузить пороги, пропускаемые пути и заблокированные правила из `.secureai-policy.json` (определяется автоматически, если присутствует — `secureai-scan init` создаёт такой файл) |
**Расширенные возможности**
| Флаг | Что делает |
|------|---------------|
| `--min-confidence <0-1>` | более точный, чем `--paranoid`: скрывать результаты ниже точного порога уверенности (`0.9` proven / `0.65` likely / `0.35` heuristic) |
| `--limit <n>` | максимальное количество групп правил, отображаемых в терминале (по умолчанию `10`) — полные детали всегда попадают в `--output` |
| `--debug` | выводить каждый просканированный файл и какие правила выполнялись |
**Сканируйте перед установкой — без клонирования, без настройки:**```bash
secureai-scan skill anthropics/skills # a GitHub "owner/repo" shorthand
secureai-scan skill https://github.com/… # or a full git URL
secureai-scan skill ./some/local/skill-dir # or a local path
secureai-scan mcp some-mcp-server-package # a bare npm package name
secureai-scan mcp owner/mcp-server-repo # or git, same as `skill`
skill и mcp загружают цель и сканируют её, после чего удаляют загруженную копию (--keep, чтобы вместо этого изучить её). Ничто из загруженного никогда не выполняется: npm-цель загружается с помощью npm pack — только tarball, без install, без скриптов жизненного цикла — а git-цель представляет собой обычный git clone --depth 1. Это самый важный момент: до того, как навык попадёт в ~/.claude/skills/ или сервер — в .mcp.json, а не после.
Другие команды:```bash secureai-scan bom . --output AI_BOM.md # AI Bill of Materials secureai-scan explain AI001 # why + exploit + fix example, for any rule secureai-scan threat-model . # THREAT_MODEL.md with the OWASP coverage matrix — example: docs/examples/THREAT_MODEL.example.md secureai-scan init # policy file + CI workflow, one-time setup
Подавить проверенное замечание в коде:```ts
// secureai-ignore AI001: reviewed, input sanitized via allowlist
GitHub Action```yaml
name: SecureAI-Scan on: [pull_request] permissions: contents: read security-events: write jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: akanthed/[email protected] with: scanner-version: 0.10.0 fail-on: high
Находки отображаются в виде встроенных аннотаций в PR и на вкладке Security репозитория. (`secureai-scan init` генерирует эквивалентный workflow с использованием CLI напрямую.)
Сканирование чистое? Добавьте бейдж в свой собственный README:```md
[](https://github.com/akanthed/SecureAI-Scan)
Pre-commit hook
Предпочитаете выявлять находки до того, как они будут отправлены? Добавьте этот репозиторий как источник хука pre-commit вместо GitHub Action или вместе с ним:```yaml repos:
- repo: https://github.com/akanthed/SecureAI-Scan
rev: v0.10.0
hooks:
- id: secureai-scan
The hook scans the whole project on every commit (not just changed files — a dataflow trace into file A can depend on file B, which a partial scan would miss) and blocks the commit on `high`+ severity findings by default. Override the threshold in your own config:```yaml
- id: secureai-scan
args: ["--fail-on", "critical"]
Правила
42 правила, сопоставленные с официальным OWASP Top 10 для LLM-приложений (2026) — а также, где применимо, с OWASP Top 10 для агентных приложений (2026, ASI), OWASP MCP Top 10 (2025) и статьёй EU AI Act. См. версионированное покрытие и ограничения 2026; threat-model выводит матрицу для каждого сканируемого проекта.
| Правило | Что оно доказывает | OWASP |
|---|---|---|
| AI001 | Пользовательский ввод попадает в системный/разработческий промпт (прослеженный путь от источника к приёмнику, включая границы функций/файлов) | LLM01 |
| AI002 | Содержимое промпта или секреты записываются в логи (в файлах, использующих LLM SDK) | LLM02 |
| AI003 | Вызов LLM в обработчике запроса без проверки аутентификации перед ним | LLM06 |
| AI004 | Весь объект пользователя/сессии сериализуется в промпт (выбор отдельных полей не помечается) | LLM02 |
| AI005 | Вывод LLM достигает приёмников eval/exec/SQL/HTML | LLM10 |
| AI006 | Высокорисковые инструменты (delete, pay, deploy, …) доступны без шлюза одобрения | LLM03 |
| AI007 | Извлечённый RAG-контент интерполируется в привилегированные промпты | LLM01 |
| AI008 | Секреты, встроенные в текст системного промпта | LLM08 |
| AI009 | Неограниченный пользовательский ввод / отсутствие лимитов токенов | LLM06 |
| AI010 | Полученный извне контент попадает в промпты | LLM01 |
| AI011 | Вывод агента повышается до системной роли в последующих вызовах | LLM03 |
| AI012 | Вывод LLM разбирается без проверки схемы | LLM10 |
| MCP001 | Метаданные MCP-инструмента достигают системного промпта без валидации | LLM01 |
| MCP002 | URL MCP-сервера формируется из пользовательского ввода | LLM04 |
| MCP003 | Результаты MCP-инструмента повышаются до системной роли | LLM10 |
| MCP004 | MCP-сервер запускается как незакреплённый пакет npx -y | LLM04 |
| MCP005 | Секрет, встроенный в закоммиченную MCP-конфигурацию | LLM02 |
| MCP006 | MCP-сервер по незашифрованному HTTP | LLM04 |
| MCP007 | Невидимый/двунаправленный Unicode, скрытый в именах или описаниях MCP-инструментов | LLM01 · MCP03 |
| MCP008 | Фразы инъекций, направляющих агента, в описаниях MCP-инструментов | LLM01 · MCP03 |
| MCP009 | Описание инструмента, перенаправляющее вызовы на другой инструмент (затенение) | LLM01 · MCP03 |
| MCP010 | Команда/аргументы MCP stdio-сервера, сформированные из пользовательского ввода (RCE) | LLM04 · MCP05 |
| SKL001 | Невидимый/двунаправленный Unicode где-либо в пакете Agent Skill | LLM01 |
| SKL002 | Фразы инъекций, направляющих агента, в описании или теле навыка (обнаруживаются через обфускацию) | LLM01 |
| SKL003 | Содержимое навыка управляет тем, когда/как используется другой навык (затенение) | LLM01 |
| SKL004 | Многоэтапная/самораспаковывающаяся полезная нагрузка: непрозрачный блоб + инструкции по его декодированию и запуску | LLM04 · MCP04 |
| SKL005 | Чтение учётных данных + жёстко заданный внешний исходящий канал в сопутствующем файле пакета | LLM02 · MCP04 |
| SKL006 | Выполнение команд во время загрузки через синтаксис динамической инъекции контекста Claude Code (!`cmd`/```!), до любого шлюза разрешений инструментов | LLM04 · MCP05 |
| SKL007 | Неограниченное разрешение Bash во frontmatter allowed-tools навыка | LLM03 |
| SKL008 | Навык получает инструкции с внешнего URL и направляет агента следовать им («Circus of Skills») | LLM04 |
| SKL009 | Навык сохраняет бэкдор, записывая в другой файл контекста (MEMORY.md/SOUL.md/AGENTS.md/CLAUDE.md) | LLM05 |
| SKL010 | Небезопасный тег десериализации YAML/JSON во frontmatter навыка или в прилагаемом конфигурационном файле | LLM04 |
| VEC001 | Векторный поиск без фильтра арендатора/пользователя | LLM09 |
| VEC002 | Неограниченный или управляемый пользователем лимит поиска | LLM06 |
| VEC003 | Пользовательский контент, загружаемый в общее векторное хранилище | LLM05 |
| VEC004 | Загрузка без тегирования арендатора/пространства имён | LLM09 |
| DEP001 | Имя зависимости не найдено в реестре (опционально --check-dependencies) | LLM04 |
| DEP002 | Имя зависимости на одно изменение от популярного пакета (опционально) | LLM04 |
| DEP003 | Зависимость с задокументированным вредоносным релизом или критической CVE — проверяется офлайн при каждом сканировании, с учётом диапазона версий (postmark-mcp, mcp-remote CVE-2025-6514, …) | LLM04 · MCP04 |
| LLC001 | Жёстко заданный секрет в config.yaml прокси LiteLLM | LLM02 |
| LLC002 | api_base прокси LiteLLM доступен по незашифрованному HTTP | LLM04 |
| LLC003 | В конфигурации прокси LiteLLM нет секции guardrails: (эвристика, только --paranoid) | LLM03 |
secureai-scan explain <RULE_ID> предоставляет описание эксплуатации и пример кода до/после для любого правила.
Архитектура
Три независимые поверхности сканирования питают один объединённый список находок с дедупликацией:``` ┌─────────────────────┐ *.ts / *.js ───▶ │ ts-morph AST rules │───┐ │ (import-resolved │ │ │ sinks + dataflow) │ │ └─────────────────────┘ │ │ ┌─────────────────────┐ │ ┌──────────────┐ ┌─────────────────┐ *.py ───▶ │ tree-sitter AST + │───┼───▶ │ scan.ts │───▶ │ evidence filter │ │ local taint flow │ │ │ merge/dedupe│ │ → confidence │ └─────────────────────┘ │ │ + suppress │ │ → severity │ │ │ (// secure- │ │ → baseline diff │ .mcp.json, ┌─────────────────────┐ │ │ ai-ignore) │ │ → report │ SKILL.md ───▶ │ Config/bundle scan │──┘ └──────────────┘ └─────────────────┘ │ (off-disk, evasion- │ │ │ resistant) │ ▼ └─────────────────────┘ terminal · sarif · json · md · html
package.json, requirements.txt ─▶ dependency-guard.ts (advisories.ts, offline, version-aware)
Каждое правило AST вызывает функцию «LLM-вызовом» только в том случае, если она через реальные импорты разрешается в известный SDK — никогда только по совпадению имени. Полную разбивку каждой поверхности см. в [`docs/Architecture.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/Architecture.md), а о том, как работает контракт уровней доказательств, — в [`docs/DetectionEngine.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/DetectionEngine.md).
## MCP-сервер (используйте его из Claude)
Пакет поставляется с MCP-сервером, предоставляющим `scan_repository`, `explain_rule`, `generate_bom` и `scan_untrusted_target` (получение и сканирование навыка или MCP-сервера, прежде чем Claude порекомендует его установить — то же поведение «получение без выполнения», что и у CLI-команд `skill`/`mcp`):```json
{
"mcpServers": {
"secureai-scan": {
"command": "node",
"args": ["/path/to/secureai-scan/mcp-server/index.js"]
}
}
}
Claude Skill
Для пользователей Claude Code / Claude.ai, skills/secureai-scan/SKILL.md обучает Claude, когда запускать сканирование (при проверке кода AI/LLM или при проверке MCP-сервера/Agent Skill перед его установкой) и как читать результаты — в отличие от MCP-сервера выше, здесь не нужен отдельный процесс. Скопируйте каталог skills/secureai-scan/ в свой .claude/skills/, чтобы использовать его.
Устойчивость к обходу
В июле 2026 года два публичных исследования показали, что сканеры Agent Skill тривиально обходятся.
Cloak and Detonate (Ji, Xu, Li, Gao, Wei, Wang & Cheung — HKUST, arXiv:2607.02357) провели состязательное исследование против девяти опубликованных сканеров навыков, используя 1 613 вредоносных навыков из реального мира. Два семейства атак, оба из которых сохраняют полезную нагрузку в точности:
- Reify — переписывает байты, на которые срабатывает правило, в то время как агент всё равно восстанавливает оригинал во время выполнения. >80% обход большинства статических сканеров.
- Disperse / SFS packing — перемещает полезную нагрузку за пределы области действия правила: в каталог, который сканер пропускает, за переименованным расширением, или архивирует и кодирует её в
.git/, с безобидным прикрытиемSKILL.md, который пересобирает её при первом запуске. ≥90% обход на каждом протестированном сканере, ≥96% на большинстве. У одного гибридного сканера уровень обнаружения упал с ~99% до ~10%.
Отдельно Gecko Security (VentureBeat) разместили полезную нагрузку в файле *.test.ts. Каждый публичный сканер пропустил его — но Jest и Vitest автоматически обнаруживают и выполняют эти файлы при следующем npm test.
SecureAI-Scan обрабатывает каждую опубликованную технику. Каждой строке ниже соответствует тест в test/deobfuscate.test.js или фикстура в test-fixtures/vulnerable/skills/:
| Техника (название из статьи) | Пример | Как обрабатывается |
|---|---|---|
| Вставка нулевой ширины | Ignore all previous… | Текст сопоставляется с вариантами без невидимых символов |
| Схожие глифы | сurl (кириллическая с) | Свёртка гомоглифов в ASCII перед сопоставлением |
| Смена регистра | EvAl | Все шаблоны содержимого нечувствительны к регистру |
| Склейка переменных | 'cu' + 'rl' | Смежные строковые литералы объединяются перед сопоставлением |
| Разбиение по строкам | ev⏎al(x) | Разрывы строк внутри слова объединяются как дополнительный вариант |
| Извлечение по файлам | полезная нагрузка во вспомогательном файле | Сканируется весь пакет, а не только SKILL.md |
| Перемещение каталога | scripts/x.sh → build/x.sh | Нет списка пропускаемых каталогов внутри пакета |
| Переименование расширения | x.sh → x.txt | Файлы сканируются по содержимому, а не по расширению |
| Дополнение размера | дополнение сверх лимита сканера | Чрезмерно большие файлы читаются с начала, никогда не пропускаются |
| SFS packing | полезная нагрузка в .git/skillpack.dat | Любой не-git файл в .git/ пакета помечается как proven (SKL004) |
| Размещение в тестовых файлах | полезная нагрузка в *.test.ts | Сканирование пакета намеренно не понижает тестовые пути (SKL005) |
Это не ослабляет контракт точности
Деобфускация обычно является риском для точности — больше совпадений, больше шума. Здесь логика инвертирована: совпадение, которое появляется только после деобфускации, повышается до proven, а не понижается. Обычная документация не содержит соединителя нулевой ширины внутри «ignore previous instructions» и кириллической с внутри curl. Само сокрытие является утвердительным доказательством намерения.
Сравнение проводится с набором сырых совпадений, а не просто с вопросом «совпал ли сырой текст вообще» — иначе атакующий мог бы замаскировать сигнал, оставив одну безобидную фразу в открытом виде.
Два новых правила пакета срабатывают только на конъюнкции, никогда на отдельное ключевое слово:
- SKL004 требует непрозрачный блоб и директиву распаковки, которая ссылается на этот блоб по имени — упоминание
tar -xв README рядом с несвязанным бинарным ресурсом недостаточно. Настоящие архивы (gzip/zip/png/pdf/wasm — проверяются по магическим байтам, а не по расширению) вообще никогда не являются «непрозрачными», как бы они ни были сжаты. - SKL005 требует конкретный сигнал учётных данных — путь (
~/.aws/credentials, а не слово «token») или массовое перечисление переменных окружения (os.environ.items(), а неos.environ["API_KEY"]) — и исходящий трафик на жёстко заданный нелокальный хост, в пределах 25 строк друг от друга в одном файле, или удалённую загрузку, которая выполняется после переназначения через одно или несколько переименований. Хелпер публикации, который читает~/.npmrcв одной функции и вызывает реестр сорока строками позже, остаётся чистым, а чтение одной именованной переменной окружения для вызова API никогда не помечается — обе формы закреплены как безопасные фикстуры.
Проверено на двух реальных корпусах, а не только на фикстурах, написанных нами самими: 0 находок во всех 18 реальных пакетах навыков в anthropics/skills и во всех 14 в vercel/ai, и 6/6 правильных на размеченном оценочном корпусе cisco-ai-defense/skill-scanner (20 навыков, каждый с вердиктом _expected.json) с нулевым количеством ложных срабатываний на всём, что помечено как безопасное. См. Тестирование и бенчмаркинг.
Чем это не является
Честное ограничение: вывод статьи состоит в том, что детонация во время выполнения превосходит статический анализ, и это верно. Адаптивный противник, знающий эти правила, может написать преобразование, которое они не покрывают. Что здесь меняется — это стоимость обхода: опубликованные, ныне циркулирующие техники больше не работают, а обфускация, необходимая для их преодоления, теперь сама повышает серьёзность находки. Статическое сканирование — это фильтр, а не граница безопасности. Относитесь к непроверенному навыку как к непроверенному коду, независимо от того, что говорит любой сканер.
Доверие и гарантии выпуска
- CI запускается на Linux, Windows и macOS на поддерживаемых версиях Node.
- CodeQL, аудит производственных зависимостей, OpenSSF Scorecard, Dependabot и собственное блокирующее самосканирование этого сканера обеспечивают независимые проверки.
- Каждая ручная публикация npm запускает тесты, минимальные пороги покрытия, проверенный шлюз регрессии на реальных репозиториях и проверку tarball через
prepublishOnly. - GitHub Actions не получает пароль или токен npm и не может опубликовать пакет.
- Гарантии выпуска, управление с одним мейнтейнером, сообщение об уязвимостях и версионированные свидетельства бенчмарков являются публичными.
Это проект с одним мейнтейнером, без контрактного SLA или независимой сертификации. Указанные выше меры контроля снижают риск; они не превращают статическое сканирование в доказательство безопасности.
Контракт точности
Ложные срабатывания убивают сканеры. Механизм правил SecureAI-Scan следует трём жёстким правилам:
- Стоки разрешаются через импорты. Если идентификатор разрешается в модуль, который не является LLM SDK, это определённо не вызов LLM — независимо от его названия.
- Свидетельства помечаются, а не смешиваются. Отслеженный поток данных и совпадение по близости слов — не одно и то же, поэтому они никогда не делят один уровень.
- Корпус безопасных образцов блокирует каждый выпуск.
test-fixtures/safe/содержит шаблоны, которые раньше вызывали ложные срабатывания (редактированные полезные нагрузки PII, клиенты Google Maps, ключи API из переменных окружения рядом с LLM-клиентами, обычное логирование ответов, поля метаданных OAuth,chunksпотоковых ответов, художественный/повествовательный текст промптов). Любая находка там приводит к провалу набора тестов.
Тестирование и бенчмаркинг
Три уровня, потому что одного недостаточно, чтобы доверять заявлениям сканера — точность и полнота — это разные режимы отказа, и проверяются оба.
1. Корпус фикстур — точность + полнота, запускается при каждой сборке.```bash npm test
[`test-fixtures/vulnerable/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/vulnerable) и [`test-fixtures/safe/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/safe) сканируются вместе: каждый уязвимый фикстур должен срабатывать на ожидаемое правило с доказательством `proven`/`likely` (полнота), каждый безопасный фикстур должен давать **ноль** результатов `proven`/`likely` (точность). Быстро и детерминированно — но это лишь доказывает, что сканер ведёт себя корректно на коде, написанном специально для его тестирования.
**2. Регрессионный бенчмарк на реальных проектах — против публичных репозиториев, которые мы не писали.**```bash
npm run regression # scan the full curated repo set
npm run regression -- --fresh # re-clone everything first
npm run regression -- openai-node # scan just one repo by name
npm run regression -- --update-baseline # accept the current findings
scripts/regression-scan.js клонирует подобранный, разнообразный набор реальных публичных репозиториев (OpenAI/Anthropic/Vercel AI SDK, официальные MCP-серверы и TypeScript SDK, LlamaIndex, а также anthropics/skills и cisco-ai-defense/skill-scanner для покрытия skill-бандлов — охватывая TS и Python, примеры кода потребителей SDK и исходники авторов SDK) и сканирует каждый из них с помощью собранного CLI.
Он завершается с ненулевым кодом при любом proven/likely срабатывании, отсутствующем в test/regression-baseline.json — вручную проверенной записи срабатываний, уже сверенных с их исходной строкой. Отпечатки имеют формат repo|rule|file, а не номера строк, поэтому обычные изменения в вышестоящих репозиториях не создают шума. Новый отпечаток — это утверждение, которое сканер должен обосновать: если это не реальная проблема, значит, это баг правила, исправленный в корне и закреплённый как новый фикстур в test-fixtures/safe/. Внесение в базовую линию срабатывания, которое вы не прочитали, сводит на нет весь механизм.
Покрытие skill-бандлов выделено отдельной строкой, потому что корпус evals/ из cisco-ai-defense/skill-scanner размечен — каждый из его 20 фикстуров поставляется с вердиктом _expected.json и находится в каталоге, буквально названном malicious/ или safe/, поэтому он служит двойной проверкой полноты, а не только точности: 6/6 вредоносных фикстуров в области действия срабатывают, 0 срабатываний на всём, что помечено как safe, и 0 срабатываний на всех 18 реальных бандлах в anthropics/skills и всех 14 в vercel/ai. (Остальные категории Cisco — SQL-инъекции, обход пути, исчерпание ресурсов, обобщённый eval() аргумента функции, полезная нагрузка, намеренно разбитая на четыре файла — либо выходят за документированную область LLM/MCP/RAG, либо за пределы анализа конъюнкции в пределах одного файла; см. запись в журнале изменений 0.6.0 с конкретным обоснованием по каждой.)
Исторические данные «до/после» из прогона, который привёл к исходным исправлениям точности (срабатывания на уровне доказательств по умолчанию, без --paranoid):
| Репозиторий | До | После | Что было не так |
|---|---|---|---|
| vercel/ai | 773 | 1 | Каталоги examples/, корневые tests/ и каталоги в стиле ecosystem-tests/ с дефисами не распознавались как пути с более низким уровнем доверия; chunks (распространённая переменная потокового ответа) рассматривалась как однозначное свидетельство RAG |
| openai/openai-node | 47 | 0 | Тот же пробел в определении путей, применённый к собственным examples//ecosystem-tests/ SDK |
| anthropics/anthropic-sdk-typescript | 2 | 0 | Тот же пробел в определении путей для корневого каталога tests/ |
| modelcontextprotocol/typescript-sdk | 3 | 0 | Поля метаданных OAuth в стиле token_endpoint/tokenType помечались как утёкшие секреты |
| run-llama/llama_index | 18 | 15 | Проверка Python помечала любое поле description=, содержащее «system prompt», как proven отравление MCP-инструмента, независимо от контекста. Оставшиеся 15 — это срабатывания VEC001 на собственных обобщённых определениях ретриверов библиотеки — сканирование исходников SDK векторной БД, а не кода приложения, поэтому фильтр для проверки существовать не может; честное, внутренне присущее ограничение, а не баг |
Текущий прогон (2026-08-06) — версионированные свидетельства записаны в docs/benchmarks/v0.9.0.json:
| Репозиторий | Срабатывания | Правила | Статус |
|---|---|---|---|
| openai-node, anthropic-sdk-typescript, anthropic-sdk-python, modelcontextprotocol/typescript-sdk, modelcontextprotocol/servers | 0 | — | чисто |
| anthropics/skills (18 реальных skill-бандлов) | 0 | — | чисто — чистая проверка точности для SKL001–005 |
| vercel/ai (5 691 файл) | 0 | — | было 40 (AI001, AI003, AI005, AI010, MCP002) до триажа — каждое вручную сверено с исходником и подтверждено как ложное срабатывание, прослежено до 3 независимых багов в корневой причине (см. ниже), исправлено и повторно подтверждено как чистое при полном повторном сканировании |
| run-llama/llama_index | 46 | VEC001 | внутренне присущее ограничение, а не баг — собственные обобщённые определения ретриверов библиотеки, где фильтр арендатора для поиска существовать не может |
| cisco-ai-defense/skill-scanner | 7 | SKL001, SKL002, SKL005 | все на фикстурах, помеченных как malicious/ — 6/6 в области действия, 0 на всём, что помечено как safe/ |
Триаж vercel/ai выявил три реальных бага в корневой причине — ни один не специфичен для правил навыков v0.6.0, все находятся в общей логике, используемой многими правилами:
resolveLlmSinkрассматривал любой вызов, разрешённый в модуль LLM SDK, как вызов модели, независимо от имени метода — помечаяisToolUIPart(защитник типа, который пакетaiэкспортирует прямо рядом сgenerateText) как вызов LLM. Только это вызвало 3 из 5 групп срабатываний (AI001, AI003, AI010).DANGEROUS_CALLEESв AI005 включает"query"для приёмников в стиле SQL-инъекций, но"query"также является допустимым глаголом вызова LLM/агента —claudeSdk.query({ prompt, options }), собственный вызов модели Claude Agent SDK, помечался как «вывод LLM, переданный в опасный приёмник» исключительно из-за общего имени метода.REQUEST_SOURCES(идентично продублированный в MCP002, MCP010, VEC003) сопоставлялся с голым"params."— любой параметр функции, традиционно названныйparams, не обязательно данные HTTP-запроса. Валидатор схемы URL (assertOpenLinkParams(params: unknown)) помечался как «URL MCP-сервера из пользовательского ввода».
Все три исправлены в корневой причине (а не в конкретном месте вызова) и закреплены как постоянные фикстуры в test-fixtures/. Полные подробности в CHANGELOG.md.
3. Проверка «уязвимая vs исправленная версия» — доказывает полноту, а не только точность.
Два описанных выше уровня проверяют только то, что сканер остаётся спокойным на безопасном коде. Проверки рекомендаций DEP003 валидируются в обратную сторону: закрепите пакет на документально уязвимой версии и подтвердите, что он помечается, затем закрепите его на исправленной версии и подтвердите, что он не помечается.```bash
node --test test/dependency-guard.test.js
covers: `[email protected]` (CVE-2025-6514, уязвим) помечен / `[email protected]` (исправлен) чист; `[email protected]` (до бэкдора) чист / `[email protected]` (после — для вредоносного пакета легитимного патча не существует) всё ещё помечен; `llama-cpp-python==0.2.71` (CVE-2024-34359, из набора, сгенерированного OSV) помечен / `==0.2.72` (исправлен) чист, в том числе с учётом нормализации имён в PyPI (`llama_cpp_python`); и спецификаторы без пиннинга вида `langchain>=0.1.0`, дающие **ноль** находок в отчёте по умолчанию. Сборка этого теста выявила реальный пробел: `DEP003` раньше сопоставлял уведомления только по имени пакета, никогда фактически не сравнивая заявленную версию с затронутым диапазоном из уведомления — исправлено в [`src/scanner/semver.ts`](https://github.com/akanthed/secureai-scan/blob/main/src/scanner/semver.ts).
Неоднозначность разрешается по-разному для каждого вида уведомлений — намеренно. **Вредоносный** пакет срабатывает, даже если заявленную версию не удаётся разрешить — установка бэкдора необратима, поэтому проверка завершается пометкой. **CVE** срабатывает на уровне `proven` только тогда, когда заявленная версия является точным пином, заведомо попадающим в затронутый диапазон; вариант без пиннинга, но возможно затронутый, понижается до `heuristic` (только с `--paranoid`). Применение правила для вредоносных пакетов к снимку CVE из 162 записей поставило бы критическую находку в каждом репозитории, где объявлен `langchain>=0.1.0` — непригодный к действию шум в масштабе.
## Roadmap
См. [`ROADMAP.md`](https://github.com/akanthed/secureai-scan/blob/main/ROADMAP.md) — что уже выпущено и что запланировано. Оба языковых движка основаны на AST: ts-morph для TypeScript/JavaScript и Tree-sitter для Python. Импорты, вызовы, присваивания, декораторы, области видимости, именованные аргументы, поля словарей и строки в Python являются узлами синтаксиса; целевой код никогда не импортируется и не выполняется, и интерпретатор Python не требуется. Оставшийся пробел Python ограничен глубиной межфункционального/межфайлового потока данных, а не парсингом. Производительность сканирования и известные ограничения задокументированы в [`docs/Performance.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/Performance.md).
## Участие
Вклад приветствуется — см. [`CONTRIBUTING.md`](https://github.com/akanthed/secureai-scan/blob/main/CONTRIBUTING.md) о рабочем процессе и [`docs/WritingRules.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/WritingRules.md) / [`docs/RuleDevelopment.md`](https://github.com/akanthed/secureai-scan/blob/main/docs/RuleDevelopment.md) о том, как добавить правило обнаружения, отвечающее указанной выше планке точности. Каждому новому правилу нужен фикстур как в [`test-fixtures/vulnerable/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/vulnerable), так и в [`test-fixtures/safe/`](https://github.com/akanthed/secureai-scan/blob/main/test-fixtures/safe), запись в `src/scanner/catalog.ts` и кейс в `test/corpus.test.js` — `npm test` обеспечивает соблюдение всех трёх.
## Лицензия
MIT © Akshay Kanthed

