Dependency Confusion y Typosquatting: Defensa Practica para Equipos Dev
Como politicas de registry, lockfiles y scoping bloquean paquetes maliciosos antes del build. Guia tecnica hands-on del equipo Basilisk.

En este artículo
En 2021 un investigador facturo mas de 130 mil dolares en bug bounty publicando paquetes con nombres internos de Apple, Microsoft, PayPal y decenas de empresas en npm y PyPI publicos. El ataque no uso ningun zero-day: solo exploto la precedencia que los gestores de paquetes dan al registry publico cuando el nombre existe en ambos lados. Cinco anos despues, equipos Basilisk siguen encontrando ese vector abierto en siete de cada diez pipelines auditados. Dependency confusion y typosquatting no son curiosidades academicas, son la puerta barata para comprometer un build entero y, por consecuencia, a todo cliente que recibe ese artefacto. Este post desarma ambos vectores, recorre la cadena de ataque paso a paso y te entrega una defensa que puedes commitear a tu CI hoy mismo.
Que significa tecnicamente el dependency confusion#
El mecanismo es simple y cruel. Tienes un paquete interno llamado acme-payments-sdk alojado en un Nexus o Artifactory privado. Un atacante descubre ese nombre en un package.json filtrado, un post de Stack Overflow o una capa docker publica, y publica acme-payments-sdk@99.0.0 en npmjs.com. La proxima vez que el pipeline ejecuta npm install sin un lockfile estricto, el resolver elige la version mayor, porque la config por defecto incluye el registry publico y SemVer prefiere la mayor version compatible. Listo, codigo arbitrario corriendo en tu CI con credenciales AWS, tokens de GitHub y acceso al registry interno. Lo vimos en campo en pruebas parecidas a Pentest de APIs REST y GraphQL: Checklist Tecnico para Bug Bounty Legal, donde la superficie de build era mas explotable que la propia API. La raiz es que npm, pip y otros comparten un namespace plano entre interno y publico y, sin config explicita, no exigen prueba de origen.
Typosquatting como vector propio#
El typosquatting juega en otra liga. En vez de asumir el nombre real, el atacante registra colorss, requets, python-dateutill, lodahs o djanga. El payload vive en un hook postinstall o directo en el modulo importado y corre en cuanto alguien se equivoca al teclear o una IA sugiere un paquete alucinado (el llamado efecto slopsquatting). Investigacion de Phylum en 2024 catalogo mas de once mil paquetes maliciosos en PyPI usando ese patron en doce meses. Los payloads varian: robo de variables de entorno, mineria, instalacion de RAT, exfiltracion via DNS de ~/.aws/credentials. La primera defensa es cultural: code review obligatorio en cualquier cambio de dependencia, sin subir versiones a ciegas. Herramientas como Socket, Snyk Advisor y deps.dev ya senalan paquetes recien publicados, sin historico de mantenedor o con install scripts sospechosos, dandote una firma de riesgo antes del merge.
La cadena de ataque paso a paso#
Entiende al atacante y entiendes la defensa. Fase uno es reconocimiento: el atacante cosecha nombres internos desde busquedas en GitHub por @acme, desde .npmrc filtrados, desde sourcemaps del bundle frontend o desde mensajes de error en logs de CI publicos. Fase dos es publicacion: registra el mismo paquete con una version absurdamente alta en el registry publico y mete un hook preinstall que golpea una URL de callback. Fase tres es detonacion: tu CI baja el paquete publico en cualquier build y el hook corre con los privilegios del runner. Fase cuatro es persistencia y exfiltracion: el codigo lee variables de entorno, tokens OIDC y deploy keys y los saca, muchas veces por DNS o un POST HTTPS de aspecto inocente. Aqui es donde el compromiso de supply chain se funde con el post-explotacion clasico, por eso aplica la misma mentalidad que en respuesta a incidentes.
Lockfiles y hash pinning#
Los lockfiles resuelven el ochenta por ciento del problema si se usan en serio. package-lock.json, yarn.lock, pnpm-lock.yaml, poetry.lock, Cargo.lock y go.sum amarran no solo la version sino el hash de integridad. Configura npm ci, pnpm install --frozen-lockfile, pip install --require-hashes y cargo --locked en los pipelines. Nada de npm install suelto en produccion, porque puede mutar el lockfile. Combinado con un .npmrc que defina registry=https://nexus.interno/repository/npm-group/ y always-auth=true, reduces drasticamente la chance del resolver de ir al registry publico por error. Clave: el lockfile solo protege si el runner de CI lo respeta y no lo regenera. El patron mental se parece al que discutimos en Supply Chain Security: Firma con Sigstore y SBOM Real en CI/CD al hablar de SBOM y atestaciones.
Scoping y registries privados por ecosistema#
El scoping es el arma definitiva contra dependency confusion en JavaScript. Migra todos los paquetes internos a @acme/payments-sdk, @acme/auth, @acme/billing. En el .npmrc, define @acme:registry=https://nexus.interno/. Ahora npm solo resuelve ese scope en el registry interno, aunque alguien publique @acme/payments-sdk en npmjs (Microsoft reserva scopes comunes desde 2021). En Python, usa indexes separados: pip --index-url para internos y --extra-index-url solo cuando de verdad haga falta, porque ambos indexes se mezclan y la version mayor gana. Mejor, espeja todo via devpi o Artifactory y define solo index-url. Para Go, GOPRIVATE=git.acme.com mas disciplina de GONOSUMCHECK bloquea consultas a proxy.golang.org. Este hardening dialoga directo con Hardening de Linux Server: CIS Benchmark Aplicado sin Romper Produccion sobre principio de menor privilegio.
Aislamiento de CI y credenciales efimeras#
En el lado del CI, aisla sin piedad. Cada job de build debe correr con tokens efimeros, sin acceso a produccion, con salida de red filtrada por un egress proxy que permita solo registry interno, github.com y endpoints conocidos. OIDC con credenciales short-lived en GitHub Actions o GitLab CI elimina los secrets largos que un install hook podria robar. Desactiva install scripts donde puedas con npm ci --ignore-scripts y permitelos solo para una allowlist curada. Corre el build en un contenedor no-root, filesystem read-only, sin acceso al socket de docker. Asi hasta un install hook exitoso queda romo: no encuentra secrets largos, no puede telefonear a casa y no sobrevive al fin del job. Esta segmentacion es mas barata que cualquier incidente y tambien protege el caso en que una dependencia legitima sea secuestrada aguas arriba mas tarde.
Verificacion antes de instalar#
Agrega un step de verificacion pre-install: scripts/check-deps.sh ejecuta npm audit signatures, valida que ninguna dependencia fue publicada en los ultimos siete dias sin aprobacion manual (un cooldown contra secuestros frescos), y chequea paquetes contra una allowlist. Suma osv-scanner contra la base OSV y Socket en CI, que muestra diffs de comportamiento entre versiones: nuevas llamadas de red, nuevo acceso a filesystem, install scripts recien agregados. Un paquete que de golpe importa child_process o contacta una IP queda bloqueado y va a revision manual. El equipo de Datadog publico un reporte en 2025 mostrando que sesenta y dos por ciento de los compromisos de supply chain habrian sido bloqueados con esas tres reglas combinadas. Ninguna herramienta sola es bala de plata, pero encadenar cooldown, chequeo de firma y diff de comportamiento cierra los caminos mas comunes.
Monitoreo continuo y proteccion de nombres#
El monitoreo continuo cierra el ciclo. Configura alertas en Sigstore Rekor para cualquier publicacion con tu nombre de organizacion, registra defensivamente los dominios y nombres de paquete tipo-squatting de tu producto (acme-cloud, acme-coud, acmecloud), y manten un inventario de SBOMs firmados via cosign para cada release. Cuando un paquete sospechoso aparezca, ya tienes evidencia para el takedown y datos para el incident response, en el estilo que mostramos en DFIR en Linux: Triaje en Vivo con UAC y Velociraptor. Reserva proactivamente los nombres de tus paquetes internos como placeholders vacios en el registry publico para que nadie los ocupe. Entrena al equipo para reportar cualquier warning de install script nuevo en vez de ignorarlo por reflejo, porque ese warning suele ser la unica senal antes del compromiso.
Errores comunes en la practica#
Cinco pitfalls aparecen en casi toda auditoria. Primero: --extra-index-url como default en Python, que mezcla interno y PyPI y abre el vector de par en par. Segundo: lockfiles que se regeneran en vez de respetarse en CI, porque alguien dejo npm install en lugar de npm ci. Tercero: paquetes internos sin scope, de modo que cualquier namespace publico los ensombrece. Cuarto: install scripts permitidos globalmente aunque el noventa por ciento de los builds no los necesita. Quinto: tokens npm o PyPI de larga vida en CI que un hook exfiltra y que siguen validos por meses. Cada uno solo alcanza para un compromiso; juntos son un porton abierto. La buena noticia es que cada uno se arregla en menos de un dia y ninguno exige cambiar el codigo del producto.
Checklist practico#
Trabaja esta lista hoy. Uno: migra todos los paquetes npm internos a un @scope amarrado al registry interno. Dos: fuerza npm ci, --frozen-lockfile, --require-hashes o --locked en cada pipeline. Tres: en Python usa solo index-url apuntando al mirror interno, sin --extra-index-url. Cuatro: define GOPRIVATE para todos los modulos Go internos. Cinco: desactiva install scripts por defecto, manten allowlist. Seis: OIDC en vez de tokens de registry de larga vida. Siete: egress proxy en CI con allowlist. Ocho: osv-scanner, npm audit signatures y Socket como gate de merge. Nueve: reserva defensivamente nombres internos en el registry publico. Diez: alertas de Rekor sobre el nombre de la organizacion. Marca estos diez y trancaste la puerta barata.
FAQ#
Alcanza con un lockfile contra dependency confusion? No. El lockfile protege dependencias existentes, pero apenas agregas una dependencia interna nueva o regeneras el lockfile, la precedencia del registry vuelve a morder. Amarrar el scope al registry interno y un egress proxy son la solucion estructural; el lockfile es la segunda capa.
El typosquatting es solo problema de npm y PyPI? No. RubyGems, Maven Central, NuGet, Crates e incluso imagenes de Docker Hub estan afectados. El enfoque defensivo es el mismo en todas partes: mirror interno curado, diff de comportamiento entre versiones, cooldown para artefactos recien publicados y revision humana en cada cambio de dependencia.
Conclusion#
Dependency confusion y typosquatting no son ataques exoticos, son la forma mas barata de pasar por al lado de una organizacion sin tocar una sola linea de su codigo real. La defensa es conocida, barata y aplicable hoy: amarre de scope, hash pinning, CI aislado con credenciales efimeras, verificacion antes de instalar y monitoreo continuo. Takeaway practico: hoy mismo, audita tus .npmrc, .pip.conf y go env. Si no puedes responder en treinta segundos de que registry viene cada dependencia, ya estas vulnerable. Los atacantes no necesitan un zero-day; solo esperan que alguien deje el orden del registry sin configurar.


