
Protege contra ataques de cadena de suministro, slopsquatting y typosquatting provenientes de dependencias y código.
cargo install sloppy-joe
El ataque a la cadena de suministro de LiteLLM (marzo de 2026) comprometió un paquete con 97 millones de descargas mensuales. Los atacantes robaron credenciales de publicación, lanzaron versiones maliciosas que recolectaron claves SSH, credenciales en la nube y secretos de K8s. La compuerta de edad de versión predeterminada de 72 horas de sloppy-joe habría bloqueado ambas versiones envenenadas: fueron descubiertas en cuestión de horas, mucho antes de que la compuerta se abriera. Si ejecutas
sloppy-joe checken CI, este ataque falla. Análisis completo
Los generadores de código con IA alucinan nombres de paquetes ~20% de las veces. Los atacantes registran esos nombres y esperan. sloppy-joe los detecta en CI antes de que se ejecuten npm install o 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
Modos de escaneo:
sloppy-joe check ejecuta el guardarraíl local rápido. Siempre impone el análisis del manifiesto, lockfile/sync, procedencia y la política de fuentes no soportadas. Si el estado de las dependencias o la política cambió, o si el último escaneo completo exitoso tiene más de 24 horas, recomienda sloppy-joe check --full.sloppy-joe check --full ejecuta el escaneo estricto en línea y refresca el estado registrado del escaneo completo exitoso.sloppy-joe check --ci ejecuta la misma cobertura estricta que --full, con intención orientada a CI.sloppy-joe check evalúa el perfil runtime por defecto. Si existen dependencias con ámbito, advierte y te indica que pases explícitamente las banderas --python-groups, --python-extras, --python-platform y/o --python-version para paridad con CI/build.sloppy-joe check siempre recuerda usar o para CI y control de producción.Códigos de salida: 0 = no se encontraron problemas bloqueantes en el modo seleccionado, 1 = se encontraron problemas bloqueantes, 2 = error de ejecución.
Soporta: JavaScript (npm, pnpm, Yarn, Bun), Python, Rust, Go, Ruby, PHP, JVM (Gradle/Maven) y .NET — detectados automáticamente a partir de los archivos de manifiesto.
Guías de ecosistemas: consulta docs/ecosystems/README.md para conocer el modelo de confianza actual, las funciones compatibles y los límites de fallo cerrado para cada ecosistema.
Fuentes de configuración: ruta de archivo local, URL HTTPS o variable de entorno SLOPPY_JOE_CONFIG. La configuración nunca se lee desde el directorio del proyecto (ver CONFIG.md para saber por qué).
Incorporación: usa el modo de arranque que coincida con el repositorio:
sloppy-joe init --greenfield --ecosystem <eco> imprime una política de inicio específica del ecosistema para nuevos proyectos. Actualmente, los ajustes predefinidos de greenfield están implementados para npm, pypi y cargo; otros ecosistemas fallan con un error "not supported yet". Añade --register para escribirlo fuera del repositorio y registrarlo de forma segura.sloppy-joe init --from-current inspecciona el repositorio actual e imprime sugerencias de arranque solo para revisión. Actualmente, --from-current solo está implementado para repositorios cuyo código propio es npm y/o cargo; otros ecosistemas fallan de forma cerrada con un error "not implemented yet". Añade --register para escribir y registrar la configuración generada.sloppy-joe init sin modo imprime una plantilla manual neutra.Un solo binario. 8 ecosistemas. 16 tipos de ataque. Cero falsos positivos en comprobaciones generativas. Configuración que los agentes de IA no pueden manipular.
La mayoría de las herramientas de seguridad de dependencias verifican una o dos cosas — existencia o distancia de edición. sloppy-joe comprueba 16 vectores de ataque en una sola pasada: paquetes alucinados, 10 tipos de typosquatting (homoglifos, squatting de ámbito, caracteres repetidos, confusión de separadores, reordenación de palabras, intercambios adyacentes, caracteres omitidos, formas confusas, variantes de mayúsculas/minúsculas, sufijos de versión), aplicación canónica, control de antigüedad de versión, amplificación de scripts de instalación, explosión de dependencias, cambios de mantenedor y vulnerabilidades conocidas mediante OSV.dev.
Se ejecuta como un único binario Rust sin dependencias de ejecución. Soporta los 8 ecosistemas principales de paquetes. Y su configuración está diseñada para la seguridad: nunca se lee desde el directorio del proyecto, se puede cargar desde una URL para CI, con mensajes de error claros cuando algo está mal.
🔶 = beta/experimental
El ataque: La IA genera import ai_json_helper. El paquete no existe. Un atacante registra ai-json-helper en PyPI con malware. La próxima vez que alguien ejecute pip install, obtendrá el paquete malicioso.
Cómo lo bloquea sloppy-joe: La comprobación de existencia consulta la API de PyPI y recibe un 404. Compilación bloqueada.``` 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. Typosquatting (verificaciones generativas + recurso de distancia de edición)
**El ataque:** Un atacante registra `expresz` en npm — a un carácter de `express`. La IA lo genera, o un desarrollador lo escribe mal. El paquete existe, pasa la verificación de existencia e instala malware.
**Cómo sloppy-joe lo bloquea:** sloppy-joe ejecuta 10 verificaciones generativas antes de recurrir a la distancia de edición. Cada verificación generativa produce una mutación específica del nombre de la dependencia (intercambiar caracteres, colapsar repeticiones, eliminar sufijos, reordenar palabras, normalizar separadores, reemplazar homóglifos, verificar ámbitos) y prueba una coincidencia exacta con paquetes populares conocidos. Este enfoque, inspirado en la biblioteca [Typomania de la Rust Foundation](https://github.com/rustfoundation/typomania), tiene casi cero falsos positivos porque solo se activa con coincidencias exactas después de la mutación.
La distancia de edición de Levenshtein se ejecuta al final como una red de seguridad para mutaciones novedosas que ninguna verificación específica anticipó. Juntos, cubren tanto patrones de ataque conocidos (con precisión) como desconocidos (de manera amplia).```
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.
El ataque: expresss (s extra) o reeact (e extra). Estos son patrones comunes de alucinación de IA: el modelo genera nombres de apariencia plausible con caracteres repetidos.
Cómo lo bloquea sloppy-joe: La verificación de caracteres repetidos elimina un duplicado a la vez y comprueba si el resultado coincide con un paquete conocido. expresss → eliminar una s → express → coincidencia.```
ERROR expresss [similarity/repeated-chars]
'expresss' matches 'express' after removing a repeated character.
Fix: Use 'express' — remove the repeated characters.
### 4. Confusión de separadores
**El ataque:** `python-dateutil` vs `python_dateutil` vs `pythondateutil`. En algunos registros, estos son paquetes diferentes. Un atacante registra la variante.
**Cómo sloppy-joe lo bloquea:** Normaliza todos los separadores (`-`, `_`, `.`) antes de la comparación. Si la forma normalizada coincide con un paquete conocido, se marca.```
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.
El ataque: parse-json vs json-parse. La distancia de Levenshtein es 8 — invisible para las comprobaciones de distancia de edición. Pero un atacante puede registrar el nombre reordenado.
Cómo lo bloquea sloppy-joe: Divide en separadores, genera todas las permutaciones de los segmentos y verifica cada una contra el corpus. parse-json → permutar → json-parse → coincidencia.```
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. Intercambios de caracteres adyacentes
**El ataque:** `reqeust` en lugar de `request`. Dos caracteres adyacentes transpuestos — un error tipográfico común que los atacantes aprovechan.
**Cómo sloppy-joe lo bloquea:** Genera todas las variantes de intercambio adyacente del nombre de la dependencia y verifica cada una contra el corpus.```
ERROR reqeusts [similarity/char-swap]
'reqeusts' matches 'requests' with two adjacent characters swapped.
Fix: Use 'requests' — two characters are transposed.
El ataque: reqests (falta la u) en lugar de requests. La IA omite un carácter y el resultado es un nombre de apariencia válida.
Cómo lo bloquea sloppy-joe: Inserta cada carácter de la a a la z en cada posición del nombre y verifica si algún resultado coincide con un paquete conocido. reqests + u en la posición 3 → requests → coincidencia.```
ERROR reqests [similarity/omitted-char]
'reqests' matches 'requests' with one character inserted.
Fix: Use 'requests' — a character appears to be missing.
### 8. Homoglifos (similitudes visuales)
**El ataque:** `rеquests` con una `е` cirílica (U+0435) en lugar de la `e` latina (U+0065). Visualmente idéntico. El nombre del paquete se ve exactamente como `requests` pero se resuelve a un paquete malicioso diferente.
**Cómo sloppy-joe lo bloquea:** Reemplaza 17 caracteres de homoglifos conocidos (cirílicos, de ancho completo, variantes de script) con sus equivalentes latinos y comprueba si el resultado coincide con un paquete conocido.```
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.
El ataque: py-utils vs python-utils. En PyPI, estos son paquetes diferentes. La IA genera uno cuando querías el otro. Similarmente, github.com vs gitlab.com en módulos Go.
Cómo sloppy-joe lo bloquea: Aplica reglas de sustitución específicas del ecosistema (py↔python para PyPI, github↔gitlab para Go) y verifica si alguna variante coincide con un paquete conocido.``` ERROR py-flask [similarity/confused-form] 'py-flask' is a confused form of 'flask'. Fix: Use the canonical name 'flask'.
### 10. Ataques de variantes de mayúsculas/minúsculas (registros sensibles a mayúsculas/minúsculas)
**El ataque:** En Go, Maven y Ruby, `Rails` y `rails` son paquetes diferentes. Un atacante registra la variante en mayúsculas.
**Cómo lo bloquea sloppy-joe:** En registros sensibles a mayúsculas/minúsculas, cualquier variante de mayúsculas/minúsculas de un paquete conocido se marca como un error. En registros insensibles a mayúsculas/minúsculas (npm, PyPI, Cargo, NuGet, PHP), las variantes de mayúsculas/minúsculas son seguras y se omiten.```
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.
El ataque: requests2 o lodash-4. La IA añade un número de versión al nombre del paquete en lugar de especificar la versión correctamente.
Cómo lo bloquea sloppy-joe: Elimina los dígitos y separadores finales y comprueba si el nombre base coincide con un paquete conocido.``` 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. Suplantación de alcance (npm, PHP, Go, JVM)
**El ataque:** Un atacante registra `@typos/lodash` en npm — a un carácter de `@types/lodash`. O `larvael/framework` en Packagist — a dos caracteres de `laravel/framework`. O `github.com/gooogle/protobuf` en Go — una `o` extra. El alcance parece legítimo a simple vista. El paquete se resuelve. El malware se instala.
Esto es raro pero plausible — y "raro pero plausible" es exactamente para lo que existe sloppy-joe. El incidente de `ua-parser-js` en 2021 estaba relacionado con el alcance. Si puede ocurrirle a un paquete con millones de descargas semanales, puede ocurrirle al tuyo.
**Cómo lo bloquea sloppy-joe:** Extrae el alcance/namespace del nombre de la dependencia y lo compara contra una lista de alcances conocidos como seguros usando la distancia de edición. Funciona en npm (`@scope`), PHP (`vendor/`), Go (`github.com/org`) y 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.
wifi_eap_hammer.sh --bssid 22:6a:7d:4f:c2:4a --interface wlan0 --essid "SecureWifi" --wordlist rockyou.txt --daemon
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. Paquetes no canónicos (no es un ataque — una puerta de consistencia)
**El ataque:** No es un ataque — es un problema de consistencia. La IA elige `moment` porque era popular en los datos de entrenamiento, pero tu equipo usa `dayjs`. Diferentes equipos usando diferentes paquetes para la misma tarea genera deuda de mantenimiento e hinchazón de dependencias.
**Cómo lo bloquea sloppy-joe:** Tu configuración asigna cada paquete canónico a sus alternativas rechazadas. Si una dependencia coincide con una alternativa, la compilación falla.```
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.
El ataque: Un atacante compromete la cuenta de un mantenedor de paquetes (o un mantenedor se vuelve malintencionado) y publica una versión de parche maliciosa. Parece una actualización normal. Si tu CI la instala inmediatamente, estás comprometido antes de que nadie se dé cuenta.
Cómo lo bloquea sloppy-joe: La puerta de antigüedad de versión bloquea cualquier dependencia cuya versión se haya publicado hace menos de min_version_age_hours (predeterminado: 72 horas). Esto le da tiempo a la comunidad, Socket.dev y otros escáneres para marcar versiones maliciosas.```
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. Paquetes completamente nuevos
**El ataque:** Un paquete creado ayer con 3 descargas que tiene un nombre similar a un paquete popular. Alta probabilidad de ser un typosquat o un marcador de posición para un ataque futuro.
**Cómo lo bloquea sloppy-joe:** Marca cualquier paquete creado hace menos de 30 días.```
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.
El ataque: Un paquete con 12 descargas que resulta estar a un carácter de diferencia de requests. Casi con total seguridad un typosquat.
Cómo sloppy-joe lo bloquea: Marca los paquetes con menos de 100 descargas (donde el registro proporciona datos de descargas — actualmente npm, crates.io, RubyGems).``` ERROR requsets [metadata/low-downloads] 'requsets' has only 12 downloads. Fix: Verify 'requsets' is the package you intend to use.
## Ecosistemas compatibles
| Ecosistema | Manifiesto | Política de archivo de bloqueo | Existencia | Metadatos | Control de antigüedad |
|-----------|----------|-----------------|:---------:|:--------:|:--------:|
| npm | package.json | `package-lock.json` o `npm-shrinkwrap.json` requerido | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| PyPI | `pyproject.toml`, `requirements*.txt`, `Pipfile`, `setup.cfg`, `setup.py` | Poetry es confiable con `poetry.lock`, uv es confiable con `uv.lock`, pip-tools con bloqueo de hash completo es confiable solo cuando el grafo de requisitos comprometido vincula `--index-url` y valores exactos permitidos de `--extra-index-url`, y los índices personalizados de Poetry/uv visibles en el repositorio solo se pueden confiar mediante la lista de permitidos exacta `trusted_indexes.pypi`; los manifiestos heredados advierten en cada ejecución a menos que `python_enforcement` sea `poetry_only` | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Cargo | Cargo.toml | `Cargo.lock` requerido | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Go | go.mod | `go.sum` requerido para dependencias externas; no requerido para stdlib-only o `replace` totalmente local | :white_check_mark: | :x: | :x: |
| Ruby | Gemfile | `Gemfile.lock` requerido | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| PHP | composer.json | `composer.lock` requerido | :white_check_mark: | :x: | :x: |
| JVM (Gradle) | build.gradle / build.gradle.kts | `gradle.lockfile` requerido | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| JVM (Maven) | pom.xml | solo advertencia: sin aplicación estricta de archivo de bloqueo | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| .NET | *.csproj | `packages.lock.json` requerido | :white_check_mark: | :x: | :x: |
Todos los ecosistemas reciben verificaciones de existencia, similitud y canónicas. Los metadatos y el control de antigüedad dependen de lo que expone la API del registro. El soporte de archivos de bloqueo permite el escaneo de dependencias transitivas y la resolución exacta de versiones donde el ecosistema proporciona un modelo de archivo de bloqueo local de proyecto confiable.
## Inicio Rápido```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
| Código | Significado |
|---|---|
0 | Todas las comprobaciones superadas |
1 | Problemas encontrados |
2 | Error de ejecución |
{ "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`** — las claves son paquetes aprobados; los valores son alternativas rechazadas.
**`internal`** — paquetes de su organización. Omitir TODAS las comprobaciones. Cambian constantemente.
**`allowed`** — paquetes externos verificados. Omitir existencia + similitud, pero aún sujetos a la puerta de antigüedad de versión.
**`similarity_exceptions`** — supresiones exactas de paquete/candidato/generador para falsos positivos de similitud revisados. Úselo cuando un borde de similitud específico sea incorrecto pero aún desee comprobaciones normales en el paquete.
**`metadata_exceptions`** — supresiones exactas de metadatos revisados. Actualmente solo admite `metadata/maintainer-change` y requiere una coincidencia exacta de paquete/versión/publicador-anterior/publicador-actual.
Use `sloppy-joe check --review-exceptions` cuando necesite revisar bloqueos de cambio de mantenedor. El escaneo aún bloquea normalmente, pero la salida humana agrega una sección `REVIEW EXCEPTIONS` con propietarios, URL del repositorio y un fragmento `metadata_exceptions` listo para pegar. `--json` incluye los mismos datos en un campo de nivel superior `review_candidates`.
**`min_version_age_hours`** — bloquear cualquier versión publicada hace menos de esta cantidad de horas. Predeterminado: 72 (3 días). Establecer en 0 para deshabilitar. Los paquetes internos están exentos.
**`allow_legacy_npm_v1_lockfile`** — permitir `lockfileVersion: 1` archivos de bloqueo npm de npm v5/v6 en modo de confianza reducida. Predeterminado: `false`. Manténgalo desactivado a menos que esté intencionalmente atascado en npm heredado y acepte advertencias fuertes más cobertura transitiva npm de confianza reducida.
**`python_enforcement`** — controla la política de confianza de Python. `prefer_poetry` (predeterminado) confía en proyectos Poetry y proyectos uv, confía en requisitos de pip-tools completamente bloqueados por hash solo cuando el gráfico de requisitos comprometido vincula `--index-url` y cualquier valor `--extra-index-url` que no sea PyPI exactamente, y de lo contrario degrada pip-tools a confianza reducida. Manifiestos heredados como `requirements*.txt` sin hash, `Pipfile`, `setup.cfg`, `setup.py` y `pyproject.toml` que no sean de Poetry/uv advierten en cada ejecución. `poetry_only` bloquea esos flujos de trabajo Python no Poetry y requiere Poetry.
### Seguridad de la Configuración
La configuración **nunca se lee desde el directorio del proyecto**. Un agente de IA con acceso al shell podría reescribir una configuración en el repositorio para permitir lo que quiera.
Resolución de la configuración:
1. `--config /path/to/config.json` — archivo local (bandera CLI, prioridad máxima)
2. `--config https://example.com/config.json` — obtener desde URL
3. `SLOPPY_JOE_CONFIG=...` — variable de entorno (ruta de archivo o URL)
4. Sin configuración = solo comprobaciones de existencia + similitud + metadatos
Las configuraciones mal formadas **fallan duramente** con mensajes de error procesables: una configuración rota nunca retrocede silenciosamente a ninguna protección.
Consulte [CONFIG.md](https://github.com/brennhill/sloppy-joe/blob/HEAD/CONFIG.md) para obtener la referencia de formato completa, patrones de integración CI y ejemplos.
Configuración de arranque:```bash
sloppy-joe init --greenfield --ecosystem npm
sloppy-joe init --from-current
sloppy-joe init --from-current --register
sloppy-joe init --register
La forma más rápida de agregar sloppy-joe a tu pipeline de CI — descarga un binario precompilado desde GitHub Releases (no requiere 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
#### Entradas de acción
| Entrada | Descripción | Predeterminado |
|---------|-------------|----------------|
| `config` | Ruta de archivo de configuración o URL HTTPS | *(ninguno)* |
| `dir` | Directorio del proyecto a escanear | `.` |
| `type` | Ecosistema (`npm`, `pypi`, `cargo`, `go`, `ruby`, `php`, `jvm`, `dotnet`) | detección automática |
| `deep` | Habilitar comprobaciones de similitud de dependencias transitivas | `false` |
| `paranoid` | Habilitar mutaciones de bitflip | `false` |
| `args` | Argumentos CLI adicionales | *(ninguno)* |
| `version` | versión de sloppy-joe a instalar | `latest` |
#### Ejemplos```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 funciona con el framework [pre-commit](https://pre-commit.com).
Agrégalo a tu `.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
El hook ejecuta sloppy-joe check en cada commit (y opcionalmente en push).
Detecta automáticamente tu ecosistema a partir de los archivos de manifiesto. Pasa argumentos adicionales
a través de args:```yaml
- id: sloppy-joe
args: [--config, "https://example.com/config.json"]
O usa un simple hook de shell sin el framework:```bash
#!/bin/sh
sloppy-joe check || exit 1
sloppy-joe utiliza un enfoque generativo basado en registros para la detección de similitudes. En lugar de comparar cada dependencia contra un corpus estático con distancia de edición (lo que genera falsos positivos), genera mutaciones específicas de cada nombre de dependencia, consulta el registro para verificar si la mutación existe y marca las coincidencias exactas.``` Pipeline (in order):
Similitud ejecuta 4 fases:
- **Fase 0: Suplantación de ámbito** — verificación local, sin red. Compara el ámbito/namespace con ámbitos conocidos mediante distancia de Levenshtein.
- **Fase 1: Intra-manifiesto** — verificación local. Señala cuando dos dependencias en el mismo manifiesto son mutaciones entre sí.
- **Fase 2: Consulta al registro** — genera mutaciones, consulta por lotes al registro para verificar existencia, almacena en caché los resultados (TTL de 7 días).
- **Fase 3: Enriquecimiento de metadatos** — obtiene conteos de descargas y fechas de publicación para coincidencias, añadiendo evidencia a los informes.
Cada generador de mutaciones etiqueta su salida, por lo que el tipo de verificación reportado (ej., `similarity/homoglyph`) es determinista: el generador de mayor severidad gana cuando múltiples generadores producen el mismo candidato.
## Fiabilidad en CI
sloppy-joe está diseñado para pipelines de CI donde fallos inestables son inaceptables.
**Reintento con retroceso.** Todas las llamadas HTTP al registro reintentan 3 veces con retroceso exponencial (200ms, 400ms, 800ms) en fallos transitorios (5xx, tiempos de espera, errores de conexión). Un solo error de red no arruinará tu build.
**Fallo cerrado en errores de consulta.** Si las consultas al registro o a OSV fallan, sloppy-joe emite un error bloqueante `registry-unreachable` en lugar de saltar silenciosamente las verificaciones. El escaneo ya no depende de umbrales por ecosistema o límites de tamaño de muestra antes de bloquear.
**Caché de similitud.** Los resultados de existencia de mutaciones se almacenan en caché durante 7 días. Después del primer escaneo, la mayoría de las consultas se sirven desde la caché sin llamadas de red. Solo las dependencias nuevas activan consultas al registro.
**Resolución consciente del archivo de bloqueo.** Cuando un archivo de bloqueo compatible está presente y es confiable (`package-lock.json`, `npm-shrinkwrap.json`, `Cargo.lock`, `Gemfile.lock`, `poetry.lock` para proyectos Poetry, `uv.lock` para proyectos uv, `composer.lock`, `gradle.lockfile`, `packages.lock.json`), sloppy-joe resuelve versiones exactas a partir de él en lugar de adivinarlas a partir de rangos. Los archivos `requirements*.txt` con hash completo también pueden proporcionar versiones exactas fijadas, y se vuelven completamente confiables cuando el grafo de requerimientos comprometido vincula su propio `--index-url` y valores `--extra-index-url` exactos en lista blanca.
## Pruebas
La suite de pruebas cubre verificaciones de similitud, señales de metadatos, comportamiento de OSV, análisis y validación de configuración, resolución de archivos de bloqueo, política de pre-vuelo para manifiestos y archivos de bloqueo, formato de informes y lógica de reintentos HTTP.```bash
cargo test
Donde otros son más fuertes: Socket.dev realiza un análisis profundo de scripts de instalación con detección conductual que va mucho más allá del enfoque basado en banderas de sloppy-joe. cargo-deny tiene una verificación de cumplimiento de licencias de primer nivel, pero eso queda intencionalmente fuera del alcance de sloppy-joe porque la política de licencias es un problema de cumplimiento, no un control de seguridad de dependencias. npm audit y pip-audit son opciones sin instalación para escaneo de vulnerabilidades de un solo ecosistema.
Donde sloppy-joe es diferente: Es la única herramienta que verifica que los paquetes realmente existan en los registros (detectando alucinaciones de IA), ejecuta 11 generadores de suplantación de escritura con falsos positivos casi nulos, impone opciones de paquetes canónicos y mantiene su configuración fuera del repositorio para que los agentes de IA no puedan debilitar sus propias comprobaciones.
Apache 2.0
--ci--full| Ecosistema | Manifiesto requerido | Lockfile / estado de proyecto confiable |
|---|
| JavaScript / npm | package.json | package-lock.json o npm-shrinkwrap.json; npm v1 heredado bloqueado por defecto |
| 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, o setup.py | La ruta de Poetry confiable usa poetry.lock, la ruta de uv confiable usa uv.lock, y pip-tools con hash completo solo es confiable cuando el grafo de requisitos comprometido vincula exactamente los valores de --index-url y cualquier --extra-index-url; los índices de Python visibles en el repositorio pueden ser permitidos mediante trusted_indexes.pypi; los modos de Python confiables evalúan un perfil de instalación seleccionado a la vez (runtime por defecto, grupos/extras/plataforma/arquitectura/versión explícitos mediante CLI); los manifiestos heredados se permiten con advertencias por defecto |
| Rust | Cargo.toml | Cargo.lock |
| Go | go.mod | go.sum requerido para dependencias externas |
| Ruby | Gemfile | Gemfile.lock |
| PHP / Composer | composer.json | composer.lock |
| JVM / Gradle | build.gradle o build.gradle.kts | gradle.lockfile |
| JVM / Maven | pom.xml | solo advertencia: aún no hay ruta de lockfile local de proyecto confiable |
| .NET / NuGet | .csproj | packages.lock.json |
| sloppy-joe | Socket.dev | GuardDog | Phantom Guard | antislopsquat |
|---|
| Comprobación de existencia | ✅ | ✅ | ❌ | ✅ | ✅ |
| Similitud / typosquat | ✅ | ✅ | ✅ | ✅ | ❌ |
| Detección de homoglifos | ✅ | ❌ | ❌ | ❌ | ❌ |
| Squatting de ámbito | ✅ | ❌ | ❌ | ❌ | ❌ |
| Aplicación canónica | ✅ | ❌ | ❌ | ❌ | ❌ |
| Control de antigüedad de versión | ✅ | ❌ | ❌ | ❌ | ❌ |
| Amplificación de script de instalación | ✅ | ✅ | ❌ | ❌ | ❌ |
| Explosión de dependencias | ✅ | ❌ | ❌ | ❌ | ❌ |
| Cambio de mantenedor | ✅ | ✅ | ❌ | ❌ | ❌ |
| Comprobación de vulnerabilidades OSV | ✅ | ✅ | ❌ | ❌ | ❌ |
| Seguridad de configuración (fuera del repositorio) | ✅ | N/A | ❌ | ❌ | ❌ |
| Listas internas y permitidas | ✅ | ❌ | ❌ | ❌ | ❌ |
| npm | ✅ | ✅ | ✅ | ✅ | ❌ |
| PyPI | ✅ | ✅ | ✅ | ✅ | ✅ |
| Cargo | ✅ | ✅ | ❌ | ✅ | ❌ |
| Go | ✅ | ✅ | ✅ | ❌ | ❌ |
| Ruby | ✅ | ✅ | ✅ | ❌ | ❌ |
| PHP | ✅ | 🔶 | ❌ | ❌ | ❌ |
| JVM (Gradle/Maven) | ✅ | ✅ | ❌ | ❌ | ❌ |
| .NET (NuGet) | ✅ | ✅ | ❌ | ❌ | ❌ |
| Binario único | ✅ | ❌ | ❌ | ❌ | ❌ |
| Código abierto | Apache 2.0 | Commercial | Apache 2.0 | MIT | OSS |
| Lenguaje | Rust | SaaS | Python | Python | Python |
| Característica | sloppy-joe | Socket.dev | cargo-deny | pip-audit | npm audit |
|---|
| Detección de paquetes alucinados | ✅ | ❌ | ❌ | ❌ | ❌ |
| Detección de suplantación de escritura | ✅ 11 generadores | Parcial | ❌ | ❌ | ❌ |
| Imposición de nombre canónico | ✅ | ❌ | ❌ | ❌ | ❌ |
| Escaneo de vulnerabilidades conocidas | ✅ vía OSV | ✅ | ✅ | ✅ | ✅ |
| Análisis de scripts de instalación | Básico (bandera + sin repositorio) | ✅ Análisis profundo | ❌ | ❌ | ❌ |
| Cumplimiento de licencias | Fuera de alcance: cumplimiento, no seguridad | ✅ | ✅ Excelente | Fuera de alcance: cumplimiento, no seguridad | Fuera de alcance: cumplimiento, no seguridad |
| Multi-ecosistema | 8 ecosistemas | npm, PyPI, Go, Ruby, Java, .NET | Solo Rust | Solo Python | Solo npm |
| Seguridad para agentes de IA (configuración externa al repositorio) | ✅ | ❌ | ❌ | ❌ | ❌ |
| Amigable sin conexión/CI | ✅ Funciona en cualquier lugar | Requiere plataforma Socket | ✅ | ✅ | ✅ |
| Gratuito / código abierto | Apache 2.0 | Plan gratuito + pago | Apache 2.0 | Apache 2.0 | Integrado |