Software Supply Chain Security

Secure Package Publishing Checklist for Open Source Maintainers

Publishing a package to a public registry makes your code part of thousands of supply chains. This checklist covers the security controls that responsible maintainers implement before and during publication.

Michael
Application Security Engineer
7 min read

When you publish a package to npm, PyPI, crates.io, or any public registry, your code becomes a dependency for other software. A security failure in your publishing process does not just affect your users -- it affects every application that depends on your package, directly or transitively. The event-stream incident compromised a single maintainer account and affected thousands of downstream projects.

This checklist is not aspirational. Every item is practical and implementable by solo maintainers and small teams. Start with the critical items and work through the rest as your project matures.

Account Security

Your registry account is the most valuable target. If an attacker compromises your account, they can publish malicious code under your trusted package name.

Enable multi-factor authentication. Every major package registry supports MFA. Enable it. Use a hardware security key (YubiKey, Titan) or a TOTP authenticator app. SMS-based MFA is better than nothing but vulnerable to SIM swapping. npm, PyPI, and crates.io all support hardware keys.

Use a dedicated email for registry accounts. Your registry account email receives password reset links. If that email is compromised, the account is compromised. Use a dedicated email with its own MFA, not the same email you use for social media or mailing lists.

Use strong, unique passwords. This is basic but still not universal among maintainers. Use a password manager. Generate a random password of at least 20 characters. Do not reuse passwords across registries.

Audit account access regularly. Review which machines, tokens, and CI/CD systems have publishing access to your registry account. Revoke tokens you no longer use. If you used a token on a machine that has been decommissioned, that token should not still be valid.

Use scoped or granular tokens. npm supports granular access tokens that restrict publishing to specific packages with expiration dates. Use them instead of full-access tokens. A CI/CD token should only be able to publish the specific package it builds, not every package your account owns.

Pre-Publication Code Review

The code you publish should be the code you intended to publish.

Review the publish output. Before publishing, run the dry-run command (npm publish --dry-run, python setup.py sdist then inspect the tarball). Verify that only intended files are included. Common mistakes: publishing .env files, test fixtures with credentials, build artifacts with source maps, or the entire .git directory.

Use .npmignore or files in package.json. Explicitly define which files are included in the published package. The files field in package.json is a whitelist -- only listed files and directories are published. This is safer than .npmignore, which is a blacklist that can miss files you forgot to exclude.

Check for secrets in the codebase. Run a secret scanning tool (trufflehog, gitleaks, detect-secrets) against the code before publishing. Embedded API keys, test credentials, and private keys in published packages are depressingly common.

Ensure tests pass. Run the full test suite before every release. Automated CI should be a gate for publication -- if tests fail, the release does not happen. Tests are not just for functionality; they verify that the code behaves as expected, which is a security property.

Build Integrity

The package you publish should be built from the source code in your repository, not from a developer's laptop with unknown modifications.

Build in CI/CD. Publish from your CI/CD pipeline, not from a local machine. A local machine may have modified files, additional tools, or a compromised environment. The CI/CD pipeline starts from a known state.

Pin build tool versions. Your build tools (compilers, bundlers, minifiers) affect the output. Pin their versions in your CI configuration to ensure reproducible builds.

Generate and publish provenance. npm supports --provenance flag that generates SLSA build provenance attestations linking the published package to the source code and build system that produced it. GitHub Actions supports this natively. Use it.

Verify the tarball contents. After building and before publishing, extract the tarball and verify its contents match expectations. Automate this verification in CI.

Release Signing

Sign your releases so that consumers can verify they came from you.

Sign git tags. Use GPG or SSH to sign the git tag for each release. This creates a verifiable link between the release and your identity. Configure git tag -s as the default.

Sign packages where supported. npm provenance provides attestation. Python packages can be signed with Sigstore. Rust crates on crates.io are published from authenticated accounts. Use whatever signing mechanism your registry supports.

Publish signing keys. Make your public keys available so consumers can verify signatures. Publish them on your project's security page, on key servers, or through your registry profile.

Dependency Management

Your package's dependencies become your consumers' transitive dependencies. Manage them responsibly.

Minimize dependencies. Every dependency you add increases the attack surface of every project that depends on you. Before adding a dependency, ask whether the functionality justifies the supply chain risk. Can you implement it in 50 lines of code instead of adding a package?

Pin dependency versions. Use exact versions or tight ranges in your package.json (or equivalent). Loose ranges like ^1.0.0 allow your consumers to pull in versions you have not tested. Exact versions ensure they get the dependencies you verified.

Audit your dependency tree. Run npm audit, pip-audit, or cargo audit regularly. Fix or document known vulnerabilities. Your consumers inherit your vulnerability posture.

Monitor for compromised dependencies. If a dependency of your package is compromised, your package is a distribution vector for the compromise. Monitor advisories for your dependencies and release updates that address compromised transitive dependencies.

Vulnerability Disclosure

Prepare for the inevitability that someone will find a vulnerability in your package.

Create a security policy. Add a SECURITY.md file to your repository that explains how to report vulnerabilities. Include an email address or a link to a private reporting mechanism. GitHub's private vulnerability reporting feature works well for this.

Respond to reports promptly. Acknowledge reports within 48 hours. Provide an estimated timeline for a fix. Keep the reporter informed. Responsible disclosure works when maintainers are responsive.

Coordinate disclosure. Work with the reporter to agree on a disclosure timeline. Publish the fix before or simultaneously with the advisory. Give consumers time to update before exploitation details are public.

Request a CVE. For significant vulnerabilities, request a CVE through your registry's security team or through MITRE directly. CVEs enable automated vulnerability scanning to detect the issue in consumer projects.

Post-Publication Monitoring

Your responsibility does not end at npm publish.

Monitor for typosquatting. Check periodically for packages with names similar to yours that might be impersonating your package. Report typosquatting packages to the registry's security team.

Watch for unauthorized forks. Forks are normal, but forks that republish your package under a different name with modifications may be distributing modified versions of your code.

Track downstream impact. Understand how many projects depend on your package. This helps you assess the urgency of security fixes and communicate risk effectively.

How Safeguard.sh Helps

Safeguard.sh supports the consumer side of this checklist by monitoring the packages your organization depends on for the security practices described here. It checks whether dependencies are published with provenance, whether maintainers use MFA, whether packages include unnecessary files, and whether known vulnerabilities have been addressed. For maintainers, knowing that downstream consumers use tools like Safeguard.sh provides motivation to implement these practices -- your package's security posture is visible to your consumers.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.