{"course":{"slug":"how-software-changed","title":"How Software Development Changed","url":"https://addaly.com/learn/how-software-changed","level":"intermediate","track":"work"},"lesson":{"slug":"packages-that-do-not-exist","title":"The package that did not exist until an attacker made it","url":"https://addaly.com/learn/how-software-changed/packages-that-do-not-exist","position":69,"of":87,"minutes":9,"answer":"Invented package names repeat across runs, which turns a hallucination into a predictable target an attacker can pre-register.","keyIdea":"Invented package names repeat across runs, which turns a hallucination into a predictable target an attacker can pre-register, so the defence is verifying a package exists and has history before installing rather than trusting that a failed install will catch it."},"body":{"format":"markdown","text":"## A new door, opened by a specific failure\n\nAsk for code that parses a config format and you may get an import for a library that has never existed. The model produced a name of the right shape (plausible prefix, plausible suffix, the naming convention of that ecosystem) because that is what it does. The name is fiction.\n\nOn its own that is a broken build, and broken builds are cheap. The problem is what happens next. An attacker who knows which names get invented can **register them**. Then the fictional import resolves, the install script runs, and code the attacker wrote is executing on a developer machine with that developer's credentials.\n\nThe name that stuck for this is **slopsquatting**, by analogy with typosquatting.\n\n## Why it is exploitable rather than merely annoying\n\nThe attack needs the hallucinations to repeat. If every invented name were unique noise, an attacker would have nothing to pre-register.\n\nA 2025 study presented at USENIX Security examined around 576,000 generated code samples across a range of models and found that roughly a fifth of the package references did not exist. The finding that matters for security was the second one: a large fraction of those invented names came back **again on repeated runs of the same prompt**. That repetition is the exploitable property. It converts a random error into a predictable target list that anybody can harvest by running prompts.\n\nTreat those figures as a snapshot. Models change, rates move, and the paper measured particular models at a particular time. The mechanism is what persists: a generator is optimising for plausibility, and a plausible package name is exactly what a squatter needs.\n\n## The older cousin, still live\n\nDependency confusion, demonstrated by Alex Birsan in 2021, is the related trick. Your build refers to an internal package: `acme-billing-utils`, which exists only on your private registry. An attacker publishes a package with that name to the **public** registry with a higher version number. Many default resolvers prefer the higher version wherever they find it, so the public one wins.\n\nGenerated code makes this worse in an unglamorous way: it invents internal-sounding names freely, and those names end up committed and visible in public repositories, which is the reconnaissance step done for free.\n\n## What to actually do, in order of value\n\n**Check before installing, not after.** Thirty seconds, and it is the whole defence.\n\n```bash\nnpm view left-pad-config          # does it exist, who published it, when\npip index versions requests-toml  # same question for PyPI\n```\n\nLook at four things: publication date, download counts, whether the repository link resolves to a real project, and the maintainer's other packages. A package first published last Tuesday with forty downloads and no repository is not a library, whatever it is called.\n\n**Install from a lockfile with hashes, always.** `npm ci` uses the lockfile and its integrity hashes; `npm install` may resolve something new. In Python, `pip install --require-hashes -r requirements.txt` refuses anything whose hash does not match. Container images by digest, never by `:latest`.\n\n**Scope your internal packages.** Publishing under an organisation scope (`@acme/billing-utils`) makes the public namespace collision impossible. Configure the resolver so scoped names only ever resolve to your registry.\n\n**Scan continuously.** `osv-scanner`, `trivy`, `npm audit`, `cargo audit`, `pip-audit` are all free and open source, and all of them run in a pipeline in seconds.\n\n**Treat every new dependency in a generated diff as its own review item.** Module two covered how to review a dependency change; the addition here is narrower. Before anything else, ask the dumb question: *does this package exist, and did it exist last month?*\n\n## What does not work\n\nAsking the model whether the package is real. It will answer confidently either way, and the answer comes from the same distribution that produced the name.\n\nRelying on the install failing. Once a squatter has registered the name, the install succeeds: that is the point of the attack.\n\nA blanket ban on new dependencies. It sounds decisive, it survives about a week, and it pushes people into copying source into the repository, which is worse because it never gets patched.\n\n## The limitation\n\nNone of this catches a compromised version of a package you already trust, which has been the mechanism behind several of the largest supply-chain incidents. Lockfiles pin what you had; they do not tell you the maintainer's account was taken over last night. That class of attack is older than generated code and is not made worse by it, but it is also not addressed by anything in this lesson.\n\n> The check is not \"is this a good library\". It is \"is this a library\". That question used to be answered for free by the fact that a human had heard of it."},"check":{"question":"Why does the repetition of hallucinated package names across runs matter more for security than the hallucination rate itself?","options":[{"text":"Repeated names indicate the model is more confident about them, so developers are less likely to check.","correct":false,"tempting":"Ascribes a confidence signal to repetition; the risk does not depend on how the developer feels about the name."},{"text":"Repetition means an attacker can harvest a stable target list and register those names in advance.","correct":true},{"text":"A high hallucination rate is caught by the build failing, so only repeated names ever reach a lockfile at all.","correct":false,"tempting":"Any hallucinated name reaches the lockfile once registered; repetition is about the attacker's economics, not the build."},{"text":"Repeated names are more likely to collide with real internal package names used inside companies.","correct":false,"tempting":"Describes dependency confusion, which is a separate attack with a different mechanism."}]},"prev":{"slug":"roles-that-grew-and-shrank","title":"Where the work moved, and what is genuinely unknown","url":"https://addaly.com/learn/how-software-changed/roles-that-grew-and-shrank"},"next":{"slug":"vulnerabilities-that-arrive-by-default","title":"Insecure defaults, inherited","url":"https://addaly.com/learn/how-software-changed/vulnerabilities-that-arrive-by-default"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'The package that did not exist until an attacker made it', in How Software Development Changed. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/how-software-changed/packages-that-do-not-exist (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"The package that did not exist until an attacker made it","courseTitle":"How Software Development Changed","module":{"index":8,"slug":"provenance-security-and-law","title":"Provenance, security and the unsettled law"},"answer":null,"keyIdea":"Invented package names repeat across runs, which turns a hallucination into a predictable target an attacker can pre-register, so the defence is verifying a package exists and has history before installing rather than trusting that a failed install will catch it.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"a-new-door-opened-by-a-specific-failure","runs":[{"text":"A new door, opened by a specific failure"}]},{"t":"p","runs":[{"text":"Ask for code that parses a config format and you may get an import for a library that has never existed. The model produced a name of the right shape (plausible prefix, plausible suffix, the naming convention of that ecosystem) because that is what it does. The name is fiction."}]},{"t":"p","runs":[{"text":"On its own that is a broken build, and broken builds are cheap. The problem is what happens next. An attacker who knows which names get invented can "},{"text":"register them","b":true},{"text":". Then the fictional import resolves, the install script runs, and code the attacker wrote is executing on a developer machine with that developer’s credentials."}]},{"t":"p","runs":[{"text":"The name that stuck for this is "},{"text":"slopsquatting","b":true},{"text":", by analogy with typosquatting."}]},{"t":"h2","id":"why-it-is-exploitable-rather-than-merely-annoying","runs":[{"text":"Why it is exploitable rather than merely annoying"}]},{"t":"p","runs":[{"text":"The attack needs the hallucinations to repeat. If every invented name were unique noise, an attacker would have nothing to pre-register."}]},{"t":"p","runs":[{"text":"A 2025 study presented at USENIX Security examined around 576,000 generated code samples across a range of models and found that roughly a fifth of the package references did not exist. The finding that matters for security was the second one: a large fraction of those invented names came back "},{"text":"again on repeated runs of the same prompt","b":true},{"text":". That repetition is the exploitable property. It converts a random error into a predictable target list that anybody can harvest by running prompts."}]},{"t":"p","runs":[{"text":"Treat those figures as a snapshot. Models change, rates move, and the paper measured particular models at a particular time. The mechanism is what persists: a generator is optimising for plausibility, and a plausible package name is exactly what a squatter needs."}]},{"t":"h2","id":"the-older-cousin-still-live","runs":[{"text":"The older cousin, still live"}]},{"t":"p","runs":[{"text":"Dependency confusion, demonstrated by Alex Birsan in 2021, is the related trick. Your build refers to an internal package: "},{"text":"acme-billing-utils","code":true},{"text":", which exists only on your private registry. An attacker publishes a package with that name to the "},{"text":"public","b":true},{"text":" registry with a higher version number. Many default resolvers prefer the higher version wherever they find it, so the public one wins."}]},{"t":"p","runs":[{"text":"Generated code makes this worse in an unglamorous way: it invents internal-sounding names freely, and those names end up committed and visible in public repositories, which is the reconnaissance step done for free."}]},{"t":"h2","id":"what-to-actually-do-in-order-of-value","runs":[{"text":"What to actually do, in order of value"}]},{"t":"p","runs":[{"text":"Check before installing, not after.","b":true},{"text":" Thirty seconds, and it is the whole defence."}]},{"t":"code","lang":"bash","label":"Bash","text":"npm view left-pad-config          # does it exist, who published it, when\npip index versions requests-toml  # same question for PyPI"},{"t":"p","runs":[{"text":"Look at four things: publication date, download counts, whether the repository link resolves to a real project, and the maintainer’s other packages. A package first published last Tuesday with forty downloads and no repository is not a library, whatever it is called."}]},{"t":"p","runs":[{"text":"Install from a lockfile with hashes, always.","b":true},{"text":" "},{"text":"npm ci","code":true},{"text":" uses the lockfile and its integrity hashes; "},{"text":"npm install","code":true},{"text":" may resolve something new. In Python, "},{"text":"pip install --require-hashes -r requirements.txt","code":true},{"text":" refuses anything whose hash does not match. Container images by digest, never by "},{"text":":latest","code":true},{"text":"."}]},{"t":"p","runs":[{"text":"Scope your internal packages.","b":true},{"text":" Publishing under an organisation scope ("},{"text":"@acme/billing-utils","code":true},{"text":") makes the public namespace collision impossible. Configure the resolver so scoped names only ever resolve to your registry."}]},{"t":"p","runs":[{"text":"Scan continuously.","b":true},{"text":" "},{"text":"osv-scanner","code":true},{"text":", "},{"text":"trivy","code":true},{"text":", "},{"text":"npm audit","code":true},{"text":", "},{"text":"cargo audit","code":true},{"text":", "},{"text":"pip-audit","code":true},{"text":" are all free and open source, and all of them run in a pipeline in seconds."}]},{"t":"p","runs":[{"text":"Treat every new dependency in a generated diff as its own review item.","b":true},{"text":" Module two covered how to review a dependency change; the addition here is narrower. Before anything else, ask the dumb question: "},{"text":"does this package exist, and did it exist last month?","i":true}]},{"t":"h2","id":"what-does-not-work","runs":[{"text":"What does not work"}]},{"t":"p","runs":[{"text":"Asking the model whether the package is real. It will answer confidently either way, and the answer comes from the same distribution that produced the name."}]},{"t":"p","runs":[{"text":"Relying on the install failing. Once a squatter has registered the name, the install succeeds: that is the point of the attack."}]},{"t":"p","runs":[{"text":"A blanket ban on new dependencies. It sounds decisive, it survives about a week, and it pushes people into copying source into the repository, which is worse because it never gets patched."}]},{"t":"h2","id":"the-limitation","runs":[{"text":"The limitation"}],"role":"limit"},{"t":"p","runs":[{"text":"None of this catches a compromised version of a package you already trust, which has been the mechanism behind several of the largest supply-chain incidents. Lockfiles pin what you had; they do not tell you the maintainer’s account was taken over last night. That class of attack is older than generated code and is not made worse by it, but it is also not addressed by anything in this lesson."}]},{"t":"quote","runs":[{"text":"The check is not “is this a good library”. It is “is this a library”. That question used to be answered for free by the fact that a human had heard of it."}]}],"check":{"question":[{"text":"Why does the repetition of hallucinated package names across runs matter more for security than the hallucination rate itself?"}],"options":[{"text":[{"text":"Repeated names indicate the model is more confident about them, so developers are less likely to check."}],"correct":false,"tempting":[{"text":"Ascribes a confidence signal to repetition; the risk does not depend on how the developer feels about the name."}]},{"text":[{"text":"Repetition means an attacker can harvest a stable target list and register those names in advance."}],"correct":true,"tempting":null},{"text":[{"text":"A high hallucination rate is caught by the build failing, so only repeated names ever reach a lockfile at all."}],"correct":false,"tempting":[{"text":"Any hallucinated name reaches the lockfile once registered; repetition is about the attacker’s economics, not the build."}]},{"text":[{"text":"Repeated names are more likely to collide with real internal package names used inside companies."}],"correct":false,"tempting":[{"text":"Describes dependency confusion, which is a separate attack with a different mechanism."}]}],"order":[3,1,0,2],"why":null},"prev":{"slug":"roles-that-grew-and-shrank","title":"Where the work moved, and what is genuinely unknown","minutes":9},"next":{"slug":"vulnerabilities-that-arrive-by-default","title":"Insecure defaults, inherited","minutes":10}}}