Dependency-Confusion-Angriffe und wie man sie verhindert
Wie Dependency-Confusion-Angriffe Paketmanager ausnutzen, um Schadcode einzuschleusen — und welche Maßnahmen deine Lieferkette wirklich schützen.

Im Februar 2021 zeigte ein Sicherheitsforscher, dass er Code in den Netzwerken von Apple, Microsoft, PayPal und Dutzenden anderer Unternehmen ausführen konnte, indem er eine Schwachstelle in der Art und Weise ausnutzte, wie Paketmanager Abhängigkeiten auflösen. Die Technik wird Dependency Confusion genannt und funktioniert, weil die meisten Paketmanager öffentliche Registries vor — oder neben — privaten durchsuchen.
Wenn deine Organisation interne Pakete in einer privaten Registry veröffentlicht, bist du verwundbar, es sei denn, du hast explizite Schritte unternommen, um das zu verhindern.
Wie der Angriff funktioniert
Der Angriff zielt auf die Lücke zwischen privaten und öffentlichen Paketregistries. Wenn ein Projekt von einer privaten Registry auf @company/auth-utils angewiesen ist, könnte der Paketmanager auch die öffentliche npm-Registry prüfen. Wenn ein Angreifer ein Paket namens auth-utils (ohne Scope) auf npm mit einer höheren Versionsnummer veröffentlicht, bevorzugen einige Konfigurationen die öffentliche Version.
// ❌ Vulnerable package.json — unscoped private package names
{
"dependencies": {
"auth-utils": "^1.2.0",
"payment-service-sdk": "^3.0.0",
"internal-logger": "^2.1.0"
}
}
// An attacker publishes "auth-utils@99.0.0" to npm
// Package manager sees the higher version on the public registry
// Installs the attacker's malicious package instead// ✅ Scoped packages with registry pinning
{
"dependencies": {
"@yourcompany/auth-utils": "^1.2.0",
"@yourcompany/payment-service-sdk": "^3.0.0",
"@yourcompany/internal-logger": "^2.1.0"
}
}
// Scoped packages (@yourcompany/*) route to your private registry
// Attacker cannot claim your organization's scope on npmDer Angriff beruht auf drei Bedingungen: unscoped interne Paketnamen, ein Paketmanager, der beide Registries abfragt, sowie keine Versionsfixierung oder Integritätsprüfungen. Entferne eine davon, und der Angriff schlägt fehl.
Verteidigungsebene 1: Alle internen Pakete mit einem Scope versehen
Die einfachste und effektivste Verteidigung besteht darin, npm-Scopes für alle internen Pakete zu verwenden. Wenn du den Scope @yourcompany in der öffentlichen npm-Registry besitzt, kann niemand sonst Pakete unter diesem Scope veröffentlichen.
# Claim your organization's scope on npm (even if you never publish there)
npm login --registry=https://registry.npmjs.org
npm org create yourcompany
# Now no attacker can publish @yourcompany/* packages publicly// ❌ Internal package without scope
// package.json of your internal library
{
"name": "feature-flags-sdk",
"version": "1.3.0"
}
// Attacker can publish "feature-flags-sdk@99.0.0" on npm
// ✅ Internal package with scope
{
"name": "@yourcompany/feature-flags-sdk",
"version": "1.3.0"
}
// Only members of @yourcompany npm org can publish thisSelbst wenn du nie Pakete in der öffentlichen Registry veröffentlichst, beanspruche deinen Organisations-Scope defensiv. Es ist kostenlos und blockiert den häufigsten Angriffsvektor.
Verteidigungsebene 2: Registry-Konfiguration
Konfiguriere deinen Paketmanager so, dass interne Scopes an deine private Registry und alles andere an die öffentliche Registry weitergeleitet werden. Lass den Paketmanager niemals in beiden Registries nach demselben Paket "suchen".
# .npmrc — explicit registry routing
# All @yourcompany packages come from your private registry
@yourcompany:registry=https://npm.yourcompany.com/
# Everything else comes from the public registry
registry=https://registry.npmjs.org/
# Optional: require authentication for private registry
//npm.yourcompany.com/:_authToken=${NPM_PRIVATE_TOKEN}# .yarnrc.yml (Yarn Berry / Yarn 2+)
npmScopes:
yourcompany:
npmRegistryServer: "https://npm.yourcompany.com/"
npmAuthToken: "${NPM_PRIVATE_TOKEN}"
npmRegistryServer: "https://registry.npmjs.org/"// ❌ Single registry fallback — dangerous
// .npmrc with only:
// registry=https://npm.yourcompany.com/
// If private registry is down, npm falls back to public registry
// Attacker waits for an outage, or the fallback fetches malicious packages
// ✅ Explicit routing — no fallback ambiguity
// .npmrc with scoped registries as shown above
// @yourcompany/* → private registry (no fallback)
// everything else → public npm (no confusion)Verteidigungsebene 3: Lockfiles und Integritätsprüfungen
Lockfiles fixieren exakte Versionen und enthalten Integritäts-Hashes. Selbst wenn ein Paketmanager eine bösartige Version auflöst, führt ein nicht übereinstimmender Integritäts-Hash zum Scheitern der Installation.
// package-lock.json includes integrity hashes
{
"node_modules/@yourcompany/auth-utils": {
"version": "1.2.0",
"resolved": "https://npm.yourcompany.com/@yourcompany/auth-utils/-/auth-utils-1.2.0.tgz",
"integrity": "sha512-abc123def456..."
}
}# CI should always use frozen lockfile installation
# npm
npm ci # Fails if lock file is out of sync with package.json
# yarn
yarn install --frozen-lockfile
# pnpm
pnpm install --frozen-lockfile# ❌ Running 'npm install' in CI
# This can update the lock file, potentially pulling in new versions
npm install
# ✅ Running 'npm ci' in CI
# This installs exactly what the lock file specifies
# Fails fast if lock file and package.json disagree
npm ciCommitte dein Lockfile in die Versionskontrolle und erzwinge npm ci (oder das Äquivalent) in allen CI-Pipelines. Jeder Entwickler, der npm install ausführt und unerwartete Änderungen am Lockfile erhält, sollte dies vor dem Commit untersuchen.
Verteidigungsebene 4: Automatisierte Audits
Führe Abhängigkeits-Audits als Teil deiner CI-Pipeline durch. Prüfe auf bekannte Schwachstellen und unerwartete Paketquellen.
# GitHub Actions — dependency audit step
name: Security Audit
on:
pull_request:
paths:
- 'package.json'
- 'package-lock.json'
- '.npmrc'
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install with frozen lockfile
run: npm ci
- name: Run npm audit
run: npm audit --audit-level=high
- name: Check package sources
run: |
# Verify all @yourcompany packages resolve to private registry
grep -E '"resolved": "https://npm.yourcompany.com' package-lock.json \
| wc -l
# Verify NO @yourcompany packages resolve to public registry
if grep -E '@yourcompany.*registry.npmjs.org' package-lock.json; then
echo "ERROR: Internal package resolving to public registry!"
exit 1
fi// Custom script to validate package sources
import { readFileSync } from 'fs';
interface PackageLockEntry {
version: string;
resolved: string;
integrity: string;
}
function auditPackageSources(lockfilePath: string): void {
const lockfile = JSON.parse(readFileSync(lockfilePath, 'utf-8'));
const packages = lockfile.packages || {};
for (const [name, meta] of Object.entries<PackageLockEntry>(packages)) {
if (name.includes('@yourcompany/')) {
const isPrivateRegistry = meta.resolved?.startsWith(
'https://npm.yourcompany.com'
);
if (!isPrivateRegistry) {
console.error(
`SECURITY: ${name} resolves to unexpected registry: ${meta.resolved}`
);
process.exit(1);
}
}
}
console.log('All internal packages resolve to private registry.');
}
auditPackageSources('package-lock.json');Zusammenfassung: Verteidigung in der Tiefe
Eine einzelne Verteidigungsmaßnahme reicht nicht. Schichte sie:
defense_layers:
1_scoping:
action: "Scope all internal packages under @yourcompany"
blocks: "Attacker cannot claim your scope on public registry"
2_registry_routing:
action: "Pin scopes to specific registries in .npmrc"
blocks: "Package manager never queries wrong registry"
3_lock_files:
action: "Use npm ci with integrity hashes in CI"
blocks: "Even if resolution is wrong, hash mismatch stops install"
4_auditing:
action: "Automated CI checks for package source URLs"
blocks: "Catches misconfigurations before they reach production"
5_monitoring:
action: "Alert on new packages appearing in lock file diffs"
blocks: "Human review of unexpected dependency changes"Wichtigste Erkenntnisse
- Dependency Confusion nutzt die Auflösungsreihenfolge des Paketmanagers aus — öffentliche Pakete mit höheren Versionen überschreiben private
- Versehe alle internen Pakete mit einem Scope (
@yourcompany/*) und beanspruche den Organisations-Scope defensiv in öffentlichen Registries - Binde gescopte Pakete an deine private Registry in
.npmrc— lass Paketmanager niemals in beiden Registries nach demselben Namen suchen - Verwende
npm ciin CI-Pipelines — eingefrorene Lockfiles mit Integritäts-Hashes erkennen Auflösungsfehler - Automatisiere die Quellenprüfung — verifiziere, dass interne Pakete immer in deiner privaten Registry aufgelöst werden, nicht bei npm
- Schichte deine Verteidigung — Scoping, Registry-Routing, Lockfiles und Audits zusammen bieten robusten Schutz


