
Защищает от атак на цепочку поставок, слопсквоттинга и тайпсквоттинга со стороны зависимостей и кода.
cargo install sloppy-joe
Атака на цепочку поставок LiteLLM (март 2026) скомпрометировала пакет с 97 млн загрузок в месяц. Злоумышленники украли учетные данные для публикации, выложили вредоносные версии, которые собирали SSH-ключи, облачные учетные данные и секреты Kubernetes. Встроенная 72-часовая задержка по возрасту версий в sloppy-joe заблокировала бы обе отравленные версии — они были обнаружены в течение нескольких часов, задолго до того, как заслонка открылась бы. Если вы запускаете
sloppy-joe checkв CI, эта атака проваливается. Полный анализ
Генераторы ИИ-кода галлюцинируют имена пакетов примерно в 20% случаев. Злоумышленники регистрируют эти имена и ждут. sloppy-joe ловит их в CI до того, как будет выполнено npm install или pip install.
cargo install sloppy-joe
sloppy-joe check
sloppy-joe check --full
sloppy-joe check --ci
sloppy-joe check --dir ./my-project
sloppy-joe check --type npm
sloppy-joe check --python-groups dev,test --python-version 3.12 sloppy-joe check --python-extras docs --python-platform linux --python-version 3.12
sloppy-joe check --config /etc/sloppy-joe/config.json
sloppy-joe check --config https://raw.githubusercontent.com/yourorg/security-configs/main/sloppy-joe.json
sloppy-joe check --json
sloppy-joe check --review-exceptions
sloppy-joe init --register
sloppy-joe init --greenfield --ecosystem npm
sloppy-joe init --from-current
sloppy-joe init --from-current --register
sloppy-joe init > /secure/location/sloppy-joe.json
### Nix```bash
nix profile install github:brennhill/sloppy-joe
Режимы сканирования:
sloppy-joe check запускает быстрый локальный барьер (guardrail). Он всегда применяет политики разбора манифеста, блокировки/синхронизации, подтверждения происхождения (provenance) и неподдерживаемых источников. Если изменилось состояние зависимостей или политики, или последняя успешная полная проверка была больше 24 часов назад, выводится рекомендация sloppy-joe check --full.sloppy-joe check --full запускает строгую онлайн-проверку и обновляет запись состояния успешной полной проверки.sloppy-joe check --ci выполняет то же строгое покрытие, что и --full, но с ориентацией на CI.sloppy-joe check по умолчанию оценивает профиль runtime. Если существуют уточнённые зависимости, выводится предупреждение с указанием передать явные флаги --python-groups, --python-extras, --python-platform и/или --python-version для паритета с CI/сборкой.Коды выхода: 0 = в выбранном режиме проблем, блокирующих выполнение, не найдено, 1 = найдены блокирующие проблемы, 2 = ошибка выполнения.
Поддержка: JavaScript (npm, pnpm, Yarn, Bun), Python, Rust, Go, Ruby, PHP, JVM (Gradle/Maven) и .NET — автоматическое определение по файлам манифеста.
Путеводители по экосистемам: см. docs/ecosystems/README.md для текущей модели доверия, поддерживаемых функций и ограничений режима «fail-closed» для каждой экосистемы.
Источники конфигурации: локальный путь к файлу, URL по HTTPS или переменная окружения SLOPPY_JOE_CONFIG. Конфигурация никогда не считывается из каталога проекта (см. CONFIG.md почему).
Начало работы (Onboarding): используйте режим начальной настройки, соответствующий репозиторию:
sloppy-joe init --greenfield --ecosystem <eco> выводит стартовую политику для нового проекта, специфичную для экосистемы. Сегодня заготовки greenfield реализованы для npm, pypi и cargo; для других экосистем команда завершается ошибкой «not supported yet». Добавьте --register, чтобы записать её за пределами репозитория и зарегистрировать безопасно.sloppy-joe init --from-current проверяет текущий репозиторий и выводит предложения начальной настройки только для просмотра (review-only). Сегодня --from-current реализован только для репозиториев, чей собственный код является npm и/или cargo; для других экосистем команда завершается ошибкой «not implemented yet». Добавьте --register, чтобы записать и зарегистрировать сгенерированную конфигурацию.sloppy-joe init без режима выводит нейтральный шаблон для ручной настройки.Один бинарный файл. 8 экосистем. 16 типов атак. Ноль ложных срабатываний на генеративных проверках. Конфигурация, которую AI-агенты не могут подделать.
Большинство инструментов безопасности зависимостей проверяют одну-две вещи — существует ли пакет или расстояние редактирования. sloppy-joe проверяет 16 векторов атак за один проход: галлюцинированные пакеты, 10 типов тайпсквоттинга (гомоглифы, захват области видимости, повторяющиеся символы, путаница с разделителями, перестановка слов, соседние обмены, пропущенные символы, спутанные формы, варианты регистра, суффиксы версий), контроль каноничности, шлюз по возрасту версии, усиление скриптов установки, взрыв зависимостей, смена мейнтейнера и известные уязвимости через OSV.dev.
Он работает как единый Rust-бинарник без зависимостей времени выполнения. Поддерживает все 8 основных экосистем пакетов. А его конфигурация спроектирована для безопасности: никогда не считывается из каталога проекта, может загружаться через URL для CI, с понятными сообщениями об ошибках, когда что-то не так.
🔶 = бета/экспериментальная
Атака: ИИ генерирует import ai_json_helper. Пакета не существует. Злоумышленник регистрирует ai-json-helper на PyPI с вредоносным кодом. В следующий раз, когда кто-то запустит pip install, будет установлен вредоносный пакет.
Как sloppy-joe блокирует: Проверка существования обращается к API PyPI и получает 404. Сборка блокируется.``` ERROR ai-json-helper [existence] Package 'ai-json-helper' does not exist on the pypi registry. It may be hallucinated by an AI code generator. Fix: Remove 'ai-json-helper' from your dependencies.
### 2. Тайпсквоттинг (генеративные проверки + резервное расстояние Левенштейна)
**Атака:** Злоумышленник регистрирует `expresz` в npm — на один символ отличается от `express`. ИИ генерирует его, или разработчик случайно ошибается в наборе. Пакет существует, проходит проверку на существование и устанавливает вредоносное ПО.
**Как sloppy-joe это блокирует:** sloppy-joe запускает 10 генеративных проверок перед переходом к расстоянию редактирования. Каждая генеративная проверка создаёт специфическую мутацию имени зависимости (меняет местами символы, схлопывает повторения, удаляет суффиксы, переупорядочивает слова, нормализует разделители, заменяет гомоглифы, проверяет области видимости) и проверяет точное совпадение с известными популярными пакетами. Этот подход, вдохновлённый библиотекой [Rust Foundation's Typomania](https://github.com/rustfoundation/typomania), имеет почти нулевой уровень ложных срабатываний, поскольку срабатывает только при точных совпадениях после мутации.
Расстояние Левенштейна выполняется последним в качестве страховочной сети для новых мутаций, которые не были предусмотрены ни одной конкретной проверкой. Вместе они покрывают как известные шаблоны атак (точно), так и неизвестные (в целом).```
ERROR expresz [similarity/edit-distance]
'expresz' is 1 character away from 'express'. This could be a typosquat.
Fix: If you meant 'express', fix the name in your manifest.
Атака: expresss (лишняя s) или reeact (лишняя e). Это распространенные шаблоны галлюцинаций ИИ — модель генерирует правдоподобные названия с повторяющимися символами.
Как sloppy-joe блокирует это: Проверка повторяющихся символов удаляет по одному дубликату за раз и проверяет, соответствует ли результат известному пакету. expresss → удаляем одну s → express → совпадение.```
ERROR expresss [similarity/repeated-chars]
'expresss' matches 'express' after removing a repeated character.
Fix: Use 'express' — remove the repeated characters.
### 4. Путаница с разделителями
**Атака:** `python-dateutil` vs `python_dateutil` vs `pythondateutil`. В некоторых реестрах это разные пакеты. Злоумышленник регистрирует вариант.
**Как sloppy-joe блокирует это:** Нормализует все разделители (`-`, `_`, `.`) перед сравнением. Если нормализованная форма совпадает с известным пакетом, он помечается.```
ERROR socket_io [similarity/separator-confusion]
'socket_io' matches 'socket.io' after normalizing separators.
Fix: Use the canonical name 'socket.io' with the correct separators.
Атака: parse-json vs json-parse. Расстояние Левенштейна равно 8 — невидимо для проверок расстояния редактирования. Но злоумышленник может зарегистрировать переставленное имя.
Как sloppy-joe это блокирует: Разделяет по разделителям, генерирует все перестановки сегментов и проверяет каждую по корпусу. parse-json → перестановка → json-parse → совпадение.```
ERROR parse-json [similarity/word-reorder]
'parse-json' is a reordering of 'json-parse'.
Fix: Use 'json-parse' — the segments are in the wrong order.
### 6. Перестановки соседних символов
**Атака:** `reqeust` вместо `request`. Два соседних символа переставлены местами — распространённая опечатка, которую атакующие используют в своих целях.
**Как sloppy-joe блокирует это:** Генерирует все варианты перестановок соседних символов имени зависимости и проверяет каждый по корпусу.```
ERROR reqeusts [similarity/char-swap]
'reqeusts' matches 'requests' with two adjacent characters swapped.
Fix: Use 'requests' — two characters are transposed.
Атака: reqests (пропущен u) вместо requests. ИИ пропускает символ, и результат выглядит как валидное имя.
Как sloppy-joe блокирует это: Вставляет каждый символ a-z на каждую позицию в имени и проверяет, соответствует ли какой-либо результат известному пакету. reqests + u на позиции 3 → requests → совпадение.```
ERROR reqests [similarity/omitted-char]
'reqests' matches 'requests' with one character inserted.
Fix: Use 'requests' — a character appears to be missing.
### 8. Омоглифы (визуально похожие символы)
**Атака:** `rеquests` с кириллической `е` (U+0435) вместо латинской `e` (U+0065). Визуально идентично. Имя пакета выглядит в точности как `requests`, но ссылается на другой, вредоносный пакет.
**Как sloppy-joe блокирует это:** Заменяет 17 известных омоглифов (кириллица, полноширинные символы, варианты прописных) на их латинские эквиваленты и проверяет, совпадает ли результат с известным пакетом.```
ERROR rеquests [similarity/homoglyph]
'rеquests' contains characters that look identical to 'requests'
but are different Unicode codepoints (homoglyphs).
Fix: Replace the lookalike characters with standard ASCII.
Атака: py-utils против python-utils. На PyPI это разные пакеты. ИИ генерирует один, когда вы имели в виду другой. Аналогично, github.com против gitlab.com в модулях Go.
Как sloppy-joe блокирует это: Применяет специфические для экосистемы правила подстановки (py↔python для PyPI, github↔gitlab для Go) и проверяет, не соответствует ли какой-либо вариант известному пакету.``` ERROR py-flask [similarity/confused-form] 'py-flask' is a confused form of 'flask'. Fix: Use the canonical name 'flask'.
### 10. Атаки с использованием вариантов регистра (чувствительные к регистру реестры)
**Атака:** В Go, Maven и Ruby пакеты `Rails` и `rails` считаются разными. Злоумышленник регистрирует вариант с заглавной буквы.
**Как sloppy-joe блокирует это:** В реестрах, чувствительных к регистру, любой вариант регистра известного пакета помечается как ошибка. В реестрах, не чувствительных к регистру (npm, PyPI, Cargo, NuGet, PHP), варианты регистра безопасны и пропускаются.```
ERROR Rails [similarity/case-variant]
'Rails' differs from 'rails' only in letter casing.
On case-sensitive registries (ruby) these resolve to different packages.
Fix: Use the exact casing 'rails' in your manifest.
Атака: requests2 или lodash-4. ИИ добавляет номер версии к имени пакета вместо правильного указания версии.
Как sloppy-joe блокирует это: Удаляет конечные цифры и разделители и проверяет, соответствует ли базовое имя известному пакету.``` ERROR requests2 [similarity/version-suffix] 'requests2' looks like 'requests' with a version suffix appended. Fix: Use 'requests' and specify the version in your manifest's version field.
### 12. Сквоттинг области видимости (npm, PHP, Go, JVM)
**Атака:** Злоумышленник регистрирует `@typos/lodash` в npm — на одну букву от `@types/lodash`. Или `larvael/framework` на Packagist — на две буквы от `laravel/framework`. Или `github.com/gooogle/protobuf` в Go — одна лишняя `o`. Область видимости с первого взгляда выглядит легитимной. Пакет устанавливается. Вредоносное ПО внедряется.
Это редко, но возможно — и именно для таких случаев существует sloppy-joe. Инцидент с `ua-parser-js` в 2021 году был связан с областью видимости. Если это может случиться с пакетом с миллионами еженедельных загрузок, это может случиться и с вашим.
**Как sloppy-joe это блокирует:** Извлекает область видимости/пространство имен из имени зависимости и сравнивает его со списком известных доверенных областей с помощью расстояния редактирования. Работает для npm (`@scope`), PHP (`vendor/`), Go (`github.com/org`) и JVM (`com.group`).```
ERROR @typos/lodash [similarity/scope-squatting]
Scope '@typos' is 1 character away from the known scope '@types'.
Scope squatting is a known supply chain attack vector.
Fix: If you meant '@types/lodash', fix the scope in your manifest.
| --discover-timeout | No | При назначении портов целям время ожидания в миллисекундах до истечения тайм-аута. 0 означает бесконечное ожидание. | 0 |
| --max-depth | No | Максимальная глубина поиска шаблонов файлов. Поддерживаются отрицательные числа. -1 означает отсутствие ограничений. | -1 |
| --stats | No | Отображение статистики выполняемого сканирования | false |
| --stats-json | No | Запись статистики в файл в формате JSONL(ines) | false |
| --stats-interval | No | Интервал в секундах для отображения/записи статистики. 0 отключает. | 5 |
| --metrics | No | Предоставлять метрики nucleus на указанном порту. | |
| | No | Порт, на котором предоставлять метрики nucleus. | |
| | No | Отключить вывод HTTP-ответа, показывать только результаты и вывод в STDOUT | |
| | No | Включить пассивную обработку HTTP-ответов | |
| | No | Разрешить использование переменных окружения в шаблоне | |
| | No | Показать дамп переменных для отладки. | |
| | No | Включить загрузку шаблонов, основанных на протоколе code. | |
| | No | Принудительно использовать HTTP2 для всех HTTP-запросов. | |
| | No | Отключить обработку стандартного ввода. | |
| | No | Настроить клиентский сертификат для аутентификации с целевым объектом сканирования. Поддерживаются форматы , , . | |
| | No | Настроить центр сертификации для аутентификации с целевым объектом сканирования. Поддерживаются форматы , , . | |
| | No | Использовать библиотеку ZTLS с автоматическим переходом на стандартную для расширенной обработки TLS. | |
| | No | Использовать библиотеку ZTLS с возможностью перехода на стандартную библиотеку TLS. | |
| | No | (Экспериментальный) Принудительно использовать пользовательский SNI для TLS-соединений через ZTLS. | `` |```
ERROR github.com/gooogle/protobuf [similarity/scope-squatting]
Scope 'github.com/gooogle' is 1 character away from 'github.com/google'.
Fix: If you meant 'github.com/google/protobuf', fix the org name.
### 13. Неканонические пакеты (не атака — шлюз согласованности)
**The attack:** Не атака — проблема согласованности. ИИ выбирает `moment`, потому что он был популярен в обучающих данных, но ваша команда использует `dayjs`. Разные команды, использующие разные пакеты для одной и той же задачи, создают долг по обслуживанию и разбухание зависимостей.
**How sloppy-joe blocks it:** Ваша конфигурация сопоставляет каждый канонический пакет с его отклоненными альтернативами. Если зависимость совпадает с альтернативой, сборка завершается неудачей.```
ERROR moment [canonical]
'moment' is not the approved package for this purpose.
Your team uses 'dayjs'.
Fix: Replace 'moment' with 'dayjs' in your manifest file.
Атака: Злоумышленник взламывает учетную запись мейнтейнера пакета (или мейнтейнер становится мошенником) и публикует вредоносную версию патча. Она выглядит как обычное обновление. Если ваш CI сразу же установит её, вы будете скомпрометированы до того, как кто-либо заметит.
Как sloppy-joe блокирует это: Шлюз возраста версии блокирует любую зависимость, версия которой была опубликована менее min_version_age_hours часов назад (по умолчанию: 72 часа). Это дает сообществу, Socket.dev и другим сканерам время пометить вредоносные версии.```
ERROR react [metadata/version-age]
Version '^19.0.0' of 'react' was published 6 hours ago (minimum: 72 hours).
New versions need time for the community and security scanners to review them.
Fix: Wait until the version is at least 72 hours old, or pin to an older version.
### 15. Совершенно новые пакеты
**Атака:** Пакет, созданный вчера, с 3 загрузками, имя которого похоже на популярный пакет. Высокая вероятность того, что это тайпсквот или заполнитель для будущей атаки.
**Как sloppy-joe блокирует это:** Помечает любой пакет, созданный менее 30 дней назад.```
ERROR sketchy-lib [metadata/new-package]
'sketchy-lib' was first published 2 days ago.
New packages are higher risk.
Fix: Verify 'sketchy-lib' at its registry page and source repository.
Атака: Пакет с 12 загрузками, который отличается на один символ от requests. Почти наверняка это опечатка (typosquat).
Как sloppy-joe блокирует это: Помечает пакеты с количеством загрузок менее 100 (там, где реестр предоставляет данные о загрузках — в настоящее время npm, crates.io, RubyGems).``` ERROR requsets [metadata/low-downloads] 'requsets' has only 12 downloads. Fix: Verify 'requsets' is the package you intend to use.
---
## Поддерживаемые экосистемы
| Экосистема | Манифест | Политика лок-файлов | Существование | Метаданные | Возрастной шлюз |
|-----------|----------|-----------------|:---------:|:--------:|:--------:|
| npm | package.json | Требуется `package-lock.json` или `npm-shrinkwrap.json` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| PyPI | `pyproject.toml`, `requirements*.txt`, `Pipfile`, `setup.cfg`, `setup.py` | Poetry доверен с `poetry.lock`, uv доверен с `uv.lock`, полностью хэш-блокированный pip-tools доверен только когда зафиксированный граф зависимостей привязывает `--index-url` и точные разрешённые значения `--extra-index-url`, а видимые в репозитории пользовательские индексы Poetry/uv можно доверять только по точному разрешённому списку `trusted_indexes.pypi`; устаревшие манифесты предупреждают при каждом запуске, если `python_enforcement` не установлен в `poetry_only` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Cargo | Cargo.toml | Требуется `Cargo.lock` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Go | go.mod | `go.sum` требуется для внешних зависимостей; не требуется для стандартной библиотеки или полностью локальных `replace` | :white_check_mark: | :x: | :x: |
| Ruby | Gemfile | Требуется `Gemfile.lock` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| PHP | composer.json | Требуется `composer.lock` | :white_check_mark: | :x: | :x: |
| JVM (Gradle) | build.gradle / build.gradle.kts | Требуется `gradle.lockfile` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| JVM (Maven) | pom.xml | только предупреждение: строгого контроля лок-файлов нет | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| .NET | *.csproj | Требуется `packages.lock.json` | :white_check_mark: | :x: | :x: |
Все экосистемы проходят проверки существования, сходства и каноничности. Метаданные и возрастной шлюз зависят от того, что предоставляет API реестра. Поддержка лок-файлов обеспечивает сканирование транзитивных зависимостей и точное разрешение версий в тех экосистемах, где существует надёжная модель проектного лок-файла.
## Быстрый старт```bash
# Install
cargo install sloppy-joe
# Check current project (auto-detects ecosystem)
sloppy-joe check
# Check with canonical enforcement and age gate
sloppy-joe check --config /etc/sloppy-joe/config.json
# Output as JSON for CI
sloppy-joe check --json
| Код | Значение |
|---|---|
0 | Все проверки пройдены |
1 | Обнаружены проблемы |
2 | Ошибка выполнения |
{ "canonical": { "npm": { "lodash": ["underscore", "ramda", "lazy.js"], "dayjs": ["moment", "luxon"], "axios": ["request", "got", "node-fetch", "superagent"] }, "pypi": { "httpx": ["urllib3", "requests"], "ruff": ["flake8", "pylint"] } }, "internal": { "go": ["github.com/yourorg/"], "npm": ["@yourorg/"] }, "allowed": { "npm": ["some-vetted-external-pkg"] }, "similarity_exceptions": { "cargo": [ { "package": "serde_json", "candidate": "serde", "generator": "segment-overlap" } ] }, "metadata_exceptions": { "cargo": [ { "package": "colored", "check": "metadata/maintainer-change", "version": "2.2.0", "previous_publisher": "kurtlawrence", "current_publisher": "hwittenborn" } ] }, "min_version_age_hours": 72, "allow_legacy_npm_v1_lockfile": false, "python_enforcement": "prefer_poetry" }
**`canonical`** — ключи — это одобренные пакеты; значения — отвергнутые альтернативы.
**`internal`** — пакеты вашей организации. Пропускать ВСЕ проверки. Они постоянно меняются.
**`allowed`** — проверенные внешние пакеты. Пропускать проверки на существование и сходство, но всё ещё подвержены проверке возраста версии.
**`similarity_exceptions`** — точные подавления package/candidate/generator для проверенных ложных срабатываний сходства. Используйте это, когда одно конкретное сходство неверно, но вы всё ещё хотите нормальные проверки для пакета.
**`metadata_exceptions`** — точные проверенные подавления метаданных. В настоящее время поддерживает только `metadata/maintainer-change` и требует точного совпадения package/version/previous-publisher/current-publisher.
Используйте `sloppy-joe check --review-exceptions`, когда нужно просмотреть блокировки смены мейнтейнера. Сканирование по-прежнему блокирует нормально, но в выводе для человека добавляется раздел `REVIEW EXCEPTIONS` с владельцами, URL репозитория и готовым к вставке фрагментом `metadata_exceptions`. `--json` включает те же данные в поле `review_candidates` верхнего уровня.
**`min_version_age_hours`** — блокировать любую версию, опубликованную менее этого количества часов назад. По умолчанию: 72 (3 дня). Установите 0, чтобы отключить. Внутренние пакеты освобождены.
**`allow_legacy_npm_v1_lockfile`** — разрешить lock-файлы npm `lockfileVersion: 1` от npm v5/v6 в режиме пониженного доверия. По умолчанию: `false`. Оставьте выключенным, если вы не застряли на устаревшем npm и не принимаете громкие предупреждения плюс сниженное покрытие доверенных транзитивных зависимостей npm.
**`python_enforcement`** — управляет политикой доверия Python. `prefer_poetry` (по умолчанию) доверяет проектам Poetry и uv, полностью доверяет требованиям pip-tools с хеш-локом только когда зафиксированный граф требований привязывает `--index-url` и любые значения `--extra-index-url` не из PyPy точно, в противном случае понижает pip-tools до пониженного доверия. Устаревшие манифесты, такие как `requirements*.txt` без хешей, `Pipfile`, `setup.cfg`, `setup.py` и `pyproject.toml` без Poetry/uv, предупреждают при каждом запуске. `poetry_only` блокирует такие не-Poetry Python-процессы и требует Poetry.
### Безопасность конфигурации
Конфигурация **никогда не читается из директории проекта**. AI-агент с доступом к оболочке мог бы перезаписать конфигурацию внутри репозитория, чтобы разрешить всё, что угодно.
Разрешение конфигурации:
1. `--config /path/to/config.json` — локальный файл (флаг CLI, наивысший приоритет)
2. `--config https://example.com/config.json` — загрузка по URL
3. `SLOPPY_JOE_CONFIG=...` — переменная окружения (путь к файлу или URL)
4. Нет конфигурации = только проверки существования + сходства + метаданных
Некорректные конфигурации **жёстко падают** с информативными сообщениями об ошибках — сломанная конфигурация никогда не возвращается молча к отсутствию защиты.
См. [CONFIG.md](https://github.com/brennhill/sloppy-joe/blob/HEAD/CONFIG.md) для полного справочника по формату, шаблонов интеграции CI и примеров.
Конфигурация начальной загрузки (bootstrap):```bash
sloppy-joe init --greenfield --ecosystem npm
sloppy-joe init --from-current
sloppy-joe init --from-current --register
sloppy-joe init --register
Самый быстрый способ добавить sloppy-joe в ваш CI-конвейер — загружает предварительно собранный бинарный файл из GitHub Releases (не требуется Rust toolchain):```yaml
name: Dependency Check on: [push, pull_request]
jobs: sloppy-joe: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: brennhill/[email protected] with: config: https://raw.githubusercontent.com/yourorg/configs/main/sloppy-joe.json
#### Входные данные действия
| Вход | Описание | По умолчанию |
|-------|-------------|---------|
| `config` | Путь к конфигурационному файлу или URL HTTPS | *(нет)* |
| `dir` | Каталог проекта для сканирования | `.` |
| `type` | Экосистема (`npm`, `pypi`, `cargo`, `go`, `ruby`, `php`, `jvm`, `dotnet`) | автоопределение |
| `deep` | Включить проверки сходства транзитивных зависимостей | `false` |
| `paranoid` | Включить мутации с переворотом битов | `false` |
| `args` | Дополнительные аргументы CLI | *(нет)* |
| `version` | Версия sloppy-joe для установки | `latest` |
#### Примеры```yaml
# Minimal — CI-oriented scan, auto-detect ecosystem, no config
- uses: brennhill/[email protected]
# With org config from a URL
- uses: brennhill/[email protected]
with:
config: https://raw.githubusercontent.com/yourorg/configs/main/sloppy-joe.json
# Deep scan with paranoid mode
- uses: brennhill/[email protected]
with:
config: ${{ secrets.SLOPPY_JOE_CONFIG }}
deep: true
paranoid: true
# Scan a subdirectory, pin to a specific version
- uses: brennhill/[email protected]
with:
dir: ./packages/api
version: '1.1.0'
dependency-guard: script: - cargo install sloppy-joe - sloppy-joe check --ci --config $SLOPPY_JOE_CONFIG
### pre-commit
sloppy-joe работает с фреймворком [pre-commit](https://pre-commit.com).
Добавьте его в свой `.pre-commit-config.yaml`:```yaml
# .pre-commit-config.yaml
repos:
- repo: https://github.com/brennhill/sloppy-joe
rev: v1.1.0
hooks:
- id: sloppy-joe
Хук запускает sloppy-joe check при каждом коммите (и опционально при пуше).
Он автоматически определяет вашу экосистему по манифест-файлам. Передавайте дополнительные аргументы
через args:```yaml
- id: sloppy-joe
args: [--config, "https://example.com/config.json"]
Или используйте простой хук оболочки без фреймворка:```bash
#!/bin/sh
sloppy-joe check || exit 1
sloppy-joe использует генеративный подход на основе реестра для обнаружения сходства. Вместо сравнения каждой зависимости со статическим корпусом с помощью редакционного расстояния (что приводит к ложным срабатываниям), он генерирует специфические мутации имени каждой зависимости, запрашивает реестр для проверки существования мутации и помечает точные совпадения.``` Pipeline (in order):
Поиск сходства выполняется в 4 этапа:
- **Этап 0: Сквоттинг области видимости** — локальная проверка, без сети. Сравнивает область/пространство имён с известными корректными областями через расстояние Левенштейна.
- **Этап 1: Внутриманифестная проверка** — локальная проверка. Помечает, когда две зависимости в одном манифесте являются мутациями друг друга.
- **Этап 2: Запрос к реестру** — генерирует мутации, пакетно опрашивает реестр на существование, кэширует результаты (TTL 7 дней).
- **Этап 3: Обогащение метаданными** — получает количество загрузок и даты публикации для совпадений, чтобы добавить доказательства в отчёты.
Каждый генератор мутаций помечает свой вывод, поэтому отображаемый тип проверки (например, `similarity/homoglyph`) детерминирован — генератор с наибольшей серьёзностью побеждает, когда несколько генераторов выдают одного и того же кандидата.
## Надёжность CI
sloppy-joe разработан для CI-конвейеров, где неприемлемы нестабильные сбои.
**Повтор с экспоненциальной задержкой.** Все HTTP-вызовы к реестру повторяются 3 раза с экспоненциальной задержкой (200ms, 400ms, 800ms) при временных сбоях (5xx, тайм-ауты, ошибки соединения). Одиночный сетевой сбой не приведёт к сбою вашей сборки.
**Блокировка при ошибках запроса.** Если запросы к реестру или OSV завершаются сбоем, sloppy-joe выдаёт блокирующую ошибку `registry-unreachable` вместо того, чтобы молча пропускать проверки. Сканирование больше не полагается на пороговые значения для каждой экосистемы или ограничения по размеру выборки перед блокировкой.
**Кэш сходства.** Результаты существования мутаций кэшируются на 7 дней. После первого сканирования большинство запросов обслуживаются из кэша без сетевых вызовов. Только новые зависимости инициируют запросы к реестру.
**Разрешение зависимостей с учётом lock-файла.** Когда присутствует и является надёжным поддерживаемый lock-файл (`package-lock.json`, `npm-shrinkwrap.json`, `Cargo.lock`, `Gemfile.lock`, `poetry.lock` для проектов Poetry, `uv.lock` для проектов uv, `composer.lock`, `gradle.lockfile`, `packages.lock.json`), sloppy-joe определяет точные версии из него, а не угадывает по диапазонам. Полностью хэш-заблокированные `requirements*.txt` также могут предоставлять точные закреплённые версии, и они становятся полностью доверенными, когда зафиксированный граф требований связывает собственный `--index-url` и точные разрешённые значения `--extra-index-url`.
## Тесты
Набор тестов охватывает проверки сходства, сигналы метаданных, поведение OSV, разбор и валидацию конфигурации, разрешение lock-файлов, политику предварительной проверки манифестов и lock-файлов, форматирование отчётов и логику повторных HTTP-запросов.```bash
cargo test
Где другие сильнее: Socket.dev выполняет глубокий анализ скриптов установки с поведенческим обнаружением, что выходит далеко за рамки флагового подхода sloppy-joe. cargo-deny обладает лучшим в своём классе контролем соответствия лицензиям, но это намеренно выходит за рамки sloppy-joe, потому что лицензионная политика — это проблема соответствия, а не средство безопасности зависимостей. npm audit и pip-audit — это варианты без установки для сканирования уязвимостей в одной экосистеме.
Чем sloppy-joe отличается: Это единственный инструмент, который проверяет, существуют ли пакеты в реестрах (выявляя галлюцинации ИИ), запускает 11 генераторов опечаток с почти нулевым уровнем ложных срабатываний, обеспечивает канонический выбор пакетов и хранит свою конфигурацию вне репозитория, чтобы агенты ИИ не могли ослабить собственные проверки.
Apache 2.0
sloppy-joe check--ci--full| Экосистема | Обязательный манифест | Доверенный lockfile / состояние проекта |
|---|
| JavaScript / npm | package.json | package-lock.json или npm-shrinkwrap.json; устаревший npm v1 блокируется по умолчанию |
| JavaScript / pnpm | package.json | pnpm-lock.yaml |
| JavaScript / Yarn | package.json | yarn.lock |
| JavaScript / Bun | package.json | bun.lock |
| Python | pyproject.toml, requirements*.txt, Pipfile, setup.cfg или setup.py | доверенный путь Poetry использует poetry.lock, доверенный путь uv использует uv.lock, а полностью хеш-блокированный pip-tools доверяется только тогда, когда зафиксированный граф требований (requirements graph) точно привязывает значения --index-url и любые --extra-index-url; видимые в репозитории индексы Python могут быть добавлены в белый список через trusted_indexes.pypi; доверенные режимы Python оценивают один выбранный профиль установки за раз (runtime по умолчанию, явные группы/extras/платформа/архитектура/версия через CLI); устаревшие манифесты допускаются с предупреждениями по умолчанию |
| Rust | Cargo.toml | Cargo.lock |
| Go | go.mod | go.sum требуется для внешних зависимостей |
| Ruby | Gemfile | Gemfile.lock |
| PHP / Composer | composer.json | composer.lock |
| JVM / Gradle | build.gradle или build.gradle.kts | gradle.lockfile |
| JVM / Maven | pom.xml | только предупреждение: доверенного локального lockfile проекта пока нет |
| .NET / NuGet | .csproj | packages.lock.json |
| sloppy-joe | Socket.dev | GuardDog | Phantom Guard | antislopsquat |
|---|
| Проверка существования | ✅ | ✅ | ❌ | ✅ | ✅ |
| Похожесть / тайпсквоттинг | ✅ | ✅ | ✅ | ✅ | ❌ |
| Обнаружение гомоглифов | ✅ | ❌ | ❌ | ❌ | ❌ |
| Захват области видимости | ✅ | ❌ | ❌ | ❌ | ❌ |
| Каноническое принуждение | ✅ | ❌ | ❌ | ❌ | ❌ |
| Шлюз по возрасту версии | ✅ | ❌ | ❌ | ❌ | ❌ |
| Усилитель скриптов установки | ✅ | ✅ | ❌ | ❌ | ❌ |
| Взрыв зависимостей | ✅ | ❌ | ❌ | ❌ | ❌ |
| Смена мейнтейнера | ✅ | ✅ | ❌ | ❌ | ❌ |
| Проверка уязвимостей через OSV | ✅ | ✅ | ❌ | ❌ | ❌ |
| Безопасность конфигурации (вне репозитория) | ✅ | N/A | ❌ | ❌ | ❌ |
| Внутренние + разрешённые списки | ✅ | ❌ | ❌ | ❌ | ❌ |
| npm | ✅ | ✅ | ✅ | ✅ | ❌ |
| PyPI | ✅ | ✅ | ✅ | ✅ | ✅ |
| Cargo | ✅ | ✅ | ❌ | ✅ | ❌ |
| Go | ✅ | ✅ | ✅ | ❌ | ❌ |
| Ruby | ✅ | ✅ | ✅ | ❌ | ❌ |
| PHP | ✅ | 🔶 | ❌ | ❌ | ❌ |
| JVM (Gradle/Maven) | ✅ | ✅ | ❌ | ❌ | ❌ |
| .NET (NuGet) | ✅ | ✅ | ❌ | ❌ | ❌ |
| Один бинарник | ✅ | ❌ | ❌ | ❌ | ❌ |
| Открытый исходный код | Apache 2.0 | Коммерческий | Apache 2.0 | MIT | OSS |
| Язык | Rust | SaaS | Python | Python | Python |
false--metrics-port9092--silent-anfalse--passivefalse--env-varsfalse--show-var-dumpfalse--codefalse--force-http2false--disable-stdinfalse--client-certpemcrtp12| | `--client-key` | No | Настроить клиентский ключ для аутентификации с целевым объектом сканирования. Поддерживаются форматы `pem`, `crt`, `p12`. |--client-capemcrtp12| | `--client-asn` | No | Настроить аутентификацию с использованием определённого ASN для клиентского сертификата. |--ztlsfalse--ztls-fallbackfalse--ztls-sni| Feature | sloppy-joe | Socket.dev | cargo-deny | pip-audit | npm audit |
|---|
| Обнаружение галлюцинированных пакетов | ✅ | ❌ | ❌ | ❌ | ❌ |
| Обнаружение опечаток | ✅ 11 генераторов | Частично | ❌ | ❌ | ❌ |
| Принуждение к каноническим именам | ✅ | ❌ | ❌ | ❌ | ❌ |
| Сканирование известных уязвимостей | ✅ через OSV | ✅ | ✅ | ✅ | ✅ |
| Анализ скриптов установки | Базовый (флаг + без репозитория) | ✅ Глубокий анализ | ❌ | ❌ | ❌ |
| Соответствие лицензиям | Вне области: соблюдение, не безопасность | ✅ | ✅ Отлично | Вне области: соблюдение, не безопасность | Вне области: соблюдение, не безопасность |
| Мультиэкосистема | 8 экосистем | npm, PyPI, Go, Ruby, Java, .NET | Только Rust | Только Python | Только npm |
| Безопасность ИИ-агентов (конфигурация вне репозитория) | ✅ | ❌ | ❌ | ❌ | ❌ |
| Удобство для офлайн/CI | ✅ Работает везде | Требует платформу Socket | ✅ | ✅ | ✅ |
| Бесплатно / с открытым исходным кодом | Apache 2.0 | Бесплатный уровень + платный | Apache 2.0 | Apache 2.0 | Встроенный |