← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

Dependency Confusion Attack

security PHP 5.0+ Advanced
debt(d5/e5/b5/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). The term's detection_hints.tools lists semgrep, snyk, and dependabot — these are specialist security scanners that can detect missing private registry pinning or namespace scoping issues. Standard linters won't catch this; it requires supply chain security tooling.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor' (e5). The quick_fix mentions setting private registry configuration, using namespace scoping for internal packages, and adding repository forcing in composer.json. While each individual fix is small, remediating across a codebase requires updating package manager configs, renaming internal packages to use vendor namespaces, and potentially modifying CI/CD pipelines — a significant cross-component effort.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). The term applies to all PHP contexts (web, cli) and the fix requires ongoing discipline: every new internal package must use proper namespacing, registry configs must be maintained, and developers must understand the resolution rules. This creates a persistent process tax that shapes how all internal packages are created and managed.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap - contradicts similar concept elsewhere' (t7). The misconception field states developers believe private registries are safe because they are not public, but the attack exploits package manager resolution order — public registries are checked even for private package names. This directly contradicts the intuitive mental model that 'private means isolated,' making the obvious assumption dangerously wrong.

About DEBT scoring →

Also Known As

namespace confusion dependency hijacking supply chain attack

TL;DR

An attacker publishes a public package with the same name as a private internal package — package managers may fetch the malicious public version instead of the private one.

Explanation

Alex Birsan's 2021 research showed that when a company uses private packages (e.g. company-utils on a private registry), an attacker can publish company-utils on the public npm/PyPI/Packagist with a higher version number. Many package managers check public registries first or alongside private ones — the higher-versioned malicious package gets installed instead. Mitigations: namespace all private packages (e.g. @company/utils), configure the package manager to only use the private registry for internal packages, and pin exact versions.

Common Misconception

Private registries are safe because they are not public — the attack exploits package manager resolution order, not the registry's access controls; public registries are checked even for nominally private package names.

Why It Matters

Several major companies (Apple, Microsoft, PayPal) were compromised by this attack — a single dependency confusion vulnerability gives attackers code execution in CI/CD pipelines and developer machines.

Common Mistakes

  • Internal Composer packages without a vendor namespace — company-utils is registerable on Packagist.
  • Package manager configured to check both private and public registries without preference rules.
  • Not pinning exact versions in composer.lock — allows higher malicious versions to resolve.
  • Not scanning composer.lock for unexpected public packages matching private names.

Code Examples

✗ Vulnerable
// composer.json with unnamespaced private package:
{
    "require": {
        "internal-utils": "^1.0"  // Attacker publishes internal-utils 99.0 on Packagist
    },
    "repositories": [
        {"type": "composer", "url": "https://private.registry.example.com"}
    ]
    // Public Packagist also checked — malicious v99 wins!
}
✓ Fixed
// Namespaced + private-only resolution:
{
    "require": {
        "company/internal-utils": "^1.0"  // Namespaced — harder to squat
    },
    "repositories": [
        {"type": "composer", "url": "https://private.registry.example.com",
         "only": ["company/*"]}  // Only fetch company/* from private registry
    ],
    "config": {
        "secure-http": true
    }
}

Added 16 Mar 2026
Edited 22 Mar 2026
Views 28
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings F 0 pings S 2 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 2 pings S 0 pings M 0 pings T 0 pings W 3 pings T 0 pings F 1 ping S 2 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 2 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S
No pings yet today
No pings yesterday
Amazonbot 8 Perplexity 5 Google 3 Ahrefs 2 ChatGPT 2 SEMrush 2 Majestic 1
crawler 20 crawler_json 3
DEV INTEL Tools & Severity
🔴 Critical ⚙ Fix effort: Medium
⚡ Quick Fix
Set a private registry as the sole package source in .npmrc/.pip.conf; use namespace scoping for internal packages; add composer.json 'repositories' to force private source
📦 Applies To
PHP 5.0+ web cli
🔗 Prerequisites
🔍 Detection Hints
Internal package names without private registry pinning; no repository config forcing private source in composer.json
Auto-detectable: ✓ Yes semgrep snyk dependabot
⚠ Related Problems
🤖 AI Agent
Confidence: High False Positives: Low ✗ Manual fix Fix: Medium Context: File
CWE-427 CWE-830

✓ schema.org compliant