Frequently Asked Question
A supply-chain attack is a compromise that reaches a target indirectly, through a trusted supplier, dependency, build tool, update channel, source repository, package registry, or developer workflow rather than by attacking the final application directly.
In software development, this usually means malicious or compromised third-party code is introduced into a project through normal dependency management. Because modern applications often depend on hundreds or thousands of external packages, a single poisoned dependency can affect many systems very quickly.
What a supply-chain attack looks like
Common examples include:
- A maintainer account on a package registry is stolen and a malicious update is published.
- A legitimate package is sold, abandoned, or transferred to a new maintainer who introduces hostile code.
- A typo-squatted package is uploaded with a name similar to a popular one, hoping developers install the wrong package.
- A dependency deep in the tree is compromised, affecting applications that never knowingly selected it.
- A build script, install hook, or post-install step downloads and executes additional code.
- A source repository such as GitHub is compromised, or a release tag is altered before packaging.
- A CI/CD pipeline or signing key is compromised, causing trusted releases to be replaced with malicious ones.
The danger is not limited to obviously malicious code. A compromised package may:
- Exfiltrate secrets such as API keys, database credentials, SSH keys, or cloud tokens
- Open backdoors into production systems
- Alter business logic subtly
- Add cryptocurrency miners or botnet functions
- Corrupt builds or inject hidden payloads only in production
- Sabotage future updates
Why ecosystems such as npm, RubyGems, crates.io and GitHub are attractive targets
These platforms are widely used, trusted by default, and heavily automated. Developers often install directly from them during local development, CI builds, container builds, and production deployments. That creates an efficient attack path.
npm
The Node ecosystem is particularly exposed because:
- Projects often have very large dependency trees
- Small utility libraries are used everywhere
- Install scripts can execute automatically
- Developers frequently trust semver ranges and automatic updates
A single compromised npm package can spread into thousands of builds within hours.
RubyGems
RubyGems has similar risks:
- Gems may include install-time or runtime code with broad access
- Applications and deployment tooling often fetch directly from public registries
- Legacy applications may rely on old, unmaintained gems
A compromise in a transitive gem can affect production applications even when the top-level dependency list appears limited.
crates.io
Rust’s package ecosystem is generally more conservative, but it is not immune:
- Applications may still depend on many crates indirectly
- Build scripts can run during compilation
- Trust is often placed in crate maintainers and automated build pipelines
The language being memory-safe does not make the supply chain safe.
GitHub
GitHub is not just a hosting site; it is part of many organisations’ software supply chain:
- Source code is often pulled directly from Git repositories
- Release archives are trusted without independent verification
- GitHub Actions workflows may run third-party action code
- Maintainer account compromise can affect source, tags, releases, and CI pipelines
Even when a project is not installed from npm or RubyGems, developers may still pull code directly from a GitHub repository, which carries similar trust risks.
Other registries and repositories
The same principles apply to:
- PyPI
- Maven Central
- NuGet
- Packagist
- Composer repositories
- Docker Hub and other container registries
- OS package repositories
- Helm charts and Terraform modules
Any shared repository that allows widespread reuse becomes a high-value target.
Why direct reliance on public repositories is risky
Public repositories are useful distribution platforms, but they should not be treated as trusted internal infrastructure.
Key risks include:
- Loss of control: a package can change without warning
- Silent updates: version ranges may pull in new code automatically
- Account compromise: trusted maintainers can be impersonated
- Dependency sprawl: few teams fully review the entire transitive tree
- Availability risk: outages, takedowns, or deleted packages can break builds
- Reproducibility problems: the same build may produce different results at different times
- Policy mismatch: public packages may not meet internal security, legal, or support standards
In short, public repositories are not the right place to make the final trust decision for production software.
Why third-party modules should be staged on internal servers
Staging third-party modules internally means downloading, vetting, approving, storing, and serving dependencies from systems your organisation controls rather than allowing developers and build pipelines to fetch directly from the internet.
This approach improves security, governance, and reliability.
Security benefits
- Prevents uncontrolled direct installation from public sources
- Allows malware scanning and policy checks before approval
- Makes it possible to pin exact approved artefacts
- Reduces exposure to malicious updates and typosquatting
- Supports cryptographic verification and checksum tracking
- Gives time for human review before a package enters production use
Operational benefits
- Improves build reproducibility
- Protects builds from public repository outages
- Reduces bandwidth and external dependency
- Preserves copies of dependencies even if upstream removes them
- Enables rollback to previously approved versions
Governance benefits
- Creates an audit trail of what was approved, when, and by whom
- Supports licence checking and compliance review
- Allows blocking of banned packages, maintainers, or versions
- Enforces consistent dependency policy across teams
Why code review matters even when staging is used
Internal staging is necessary, but not sufficient. A package can be clean at the infrastructure level yet still unsuitable or dangerous at the code level. The march of AI into repositories has meant that there is much less internal review, and of course AI has been responsible for some of the worlds most notable data breaches in recent years.
Code review helps identify:
- Obfuscated or unnecessary functionality
- Data exfiltration behaviour
- Dangerous install scripts
- Weak cryptography or insecure defaults
- Hidden network calls
- Suspicious dependency chains
- Unmaintained or poor-quality code
- Licence issues
- Packages that are far larger or more complex than the requirement justifies
Review should cover both:
- The package being introduced
- The impact on the full dependency tree
A harmless-looking top-level module can pull in dozens of opaque transitive dependencies.
Recommended approach for staging third-party modules
A practical and defensible process usually includes the following steps.
1. Prohibit direct internet dependency installs in production pipelines
Build systems should not fetch dependencies directly from public registries during controlled builds or deployments.
Use internal mirrors, proxies, or private registries instead.
Examples of tools commonly used for this purpose include:
- npm proxy registries
- private RubyGems mirrors
- internal Cargo registries
- repository managers such as Nexus, Artifactory, or similar
- internally managed Git mirrors
2. Pin exact versions
Avoid loose version specifications wherever possible and prefer exact versions and lockfiles so the approved dependency set is deterministic.
Where possible, keep immutable internal copies rather than dynamically proxying the latest upstream state.
3. Review before approval
For new dependencies and major updates, review should include:
- What problem the package solves
- Whether a standard library or existing approved package already covers the need
- Maintainer reputation and activity
- Release history
- Dependency count and complexity
- Presence of install scripts or generated code
- Any suspicious network, filesystem, or process activity
- Licence compatibility
For high-risk modules, review the source itself rather than relying on the project description.
4. Re-review updates
Approval should not be permanent. Each new version should be treated as new code, especially if:
- The maintainer changed
- Release cadence changes abruptly
- New install hooks appear
- Dependency count increases sharply
- The package begins making network calls
- Minified or obfuscated files are introduced
5. Mirror Git dependencies carefully
Dependencies pulled directly from Git repositories are especially risky. Instead, mirror and approve specific commits or signed release tags, and store internal copies of the source reviewed.
Good dependency hygiene for developers
Developers should adopt the following habits:
- Prefer fewer dependencies
- Avoid tiny single-purpose modules unless clearly justified
- Remove unused packages promptly
- Treat every dependency as executable third-party code
- Review lockfile changes in pull requests
- Be cautious of packages added only for convenience
- Challenge unexpected transitive dependency growth
- Avoid running install scripts from untrusted sources
- Keep development, CI, and production dependency policy aligned
A useful rule is this: if a package is not trusted enough to be copied onto an internal server and reviewed, it is not trusted enough to run inside the build or production environment.
Common objections and why they are weak
“Popular packages are safe”
Popularity increases scrutiny, but also increases attacker interest. Popular projects are prime targets because one compromise gives broad reach.
“We only use well-known repositories”
Well-known repositories are distribution channels, not guarantees of code quality or trustworthiness.
“We have antivirus and vulnerability scanning”
That may catch known threats, but it will not reliably detect a novel malicious update or a subtle backdoor.
“Reviewing everything is too slow”
The cost of review is far lower than the cost of compromise, incident response, credential rotation, legal exposure, service outage, and reputational damage.
“It is only a development dependency”
Development dependencies often run in CI, on developer workstations, and during build steps. That can expose source code, signing keys, cloud credentials, and deployment tokens.
Bottom line
A supply-chain attack works by abusing trust in upstream software sources. Package registries and source hosting platforms such as npm, RubyGems, crates.io and GitHub are convenient, but they are also common attack surfaces.
For any serious development or production environment, third-party modules should be staged on internal servers, approved before use, pinned to exact reviewed versions, and re-reviewed on change. Public repositories are suitable as upstream sources for evaluation, not as the final authority for what enters a trusted system.
GENSoftware follows this rigorously, stages everything, code reviews everything and never deploys third party code unvented and we've been doing this we wrote code in assembler for CP/M.
