Some time ago I was chatting with a friend about OSS supply chain security. During the conversation I mentioned that I'd prefer having my bank account compromised compared to my GitHub or PyPI accounts.
Due to the sheer number of downloads urllib3 and other foundational packages receive per day, any compromise would we widespread and devastating.
It's scary to think about! 😱
We've taken many precautions both individually and as a team to ensure this scenario is unlikely to occur. I hope that some of the knowledge I've gained along the way can help you secure your own packages as well as inspiring some adversarial security-minded thinking.
You could 0wn so many machines with so little effort...— Christian Heimes (@ChristianHeimes) April 18, 2020
Implementing security practices like the ones described in this article takes time, energy, and sometimes money! If your organization consumes open source software but doesn't care about the security practices of dependencies and maintainers you are gambling your organizations' security.
Financial support for maintainers goes a long way towards ensuring your dependencies are maintained, kept up to date, and security best practices are used. There are many options to support maintainers like GitHub Sponsors, Tidelift, and Open Collective.
If a project is especially critical to your business you might also consider hiring one or more maintainers and allocate some of their time to maintaining the project. This is my current arrangement with Elastic and urllib3.
This article focuses on securing Accounts and Package Repositories:
I plan on releasing a future article and example project on GitHub which covers how to configure a project and deployment pipeline securely. If you want to notified when that article is published you can subscribe via email or RSS.
If you're a package maintainer your accounts are likely very privileged, including access to pushing code to the project, publishing new versions of the package, or managing account access on multiple platforms. For this reason account security should be at the top of your mind.
Even if you're not a package maintainer a lot of this advice applies to you too! Better security is a good thing for everyone.
The email address you use for an account on a platform is important because of password resets, to the point where ownership of an email address is nearly synonymous with ownership of the accounts using the email address.
I do not recommend using personal domains for email. Personal domain names won't last forever, if you stop paying your registrar the domain will go back onto the market which means whoever purchases the domain can receive your password resets for accounts tied to them. I'm much more confident in
gmail.com being around longer than any personal domain name.
If you have a personal domain but want to use that domain as a "vanity address" and forward emails to your Gmail account, for example, that's fine! But then don't use your personal domain email address for your accounts, instead use the email address that's being forwarded to directly.
Two-factor authentication (abbreviated 2FA) gives a huge security boost to your account security by requiring more than only your username and password to login, typically a smartphone or other physical device. When done right 2FA makes it almost impossible for an adversary to login to your accounts without physical access to your 2FA device, even with your password!
There are many 2FA methods available, I personally use and recommend Google Authenticator.
I firmly believe that every account that is involved with committing code or publishing a package should have at a minimum non-SMS 2FA enabled. That includes your accounts for your email provider, project host, and package repository. To do otherwise is forgoing one of the best improvements to account security in recent history.
The push towards required 2FA for accounts is already underway: NPM recently announced that all publishers of the top 500 packages must use enforced 2FA. I sincerely hope that Python and other ecosystems follow suit and continue pushing for 2FA as a requirement to publish code to platforms.
Knowing your passwords is so 2000s! — Using a password manager is the easiest way to ensure your passwords are unique and secure. Get in the habit of using one for all accounts for open source work.
I recognize that it can be a little daunting to make the switch, especially because you're likely used to "knowing" your password. Try it for a few hours, I promise you won't miss typing in your passwords in after a few logins. These days password managers are much more user-friendly and integrated into mobile devices and browsers.
My preference having tried multiple different password managers is BitWarden which is open source and supports a free tier with cloud synchronization. There are other free alternatives like KeepassXC which is also open source and LastPass which I have used in the past.
Most mobile 2FA apps use the Time-based One-time Password (TOTP) algorithm which is essentially a shared secret between the server and the 2FA device. There's another algorithm called Webauthn which is typically used via a hardware key. Webauthn uses asymmetric secrets instead of shared symmetric secrets to authenticate.
Comparing TOTP and Webauthn:
Both are "good enough" for passive account security but due to the previous reasons targetted phishing is more likely to succeed against TOTP 2FA. If you're interested more in the differences between 2FA methods you can read this article from the developers who implemented 2FA for PyPI.
The only downside to hardware keys is they cost money, so if you have some extra cash you can invest in a hardware key. I use and recommend Yubikey for hardware keys, there are tons of options to pick from depending on your use-cases. I personally use a Yubikey 5 NFC which costs ~$50, another option is a hardware key from Google for $30.
SMS 2FA is a 2FA method where you receive an SMS text with a code to authenticate. There are two reasons I don't recommend using SMS 2FA:
SIM-jacking: On multiple occurrences malicious actors have used social engineering to get a phone number reassigned to another SIM card. This is called "SIM-jacking" and it circumvents SMS 2FA by allowing attackers to receive the texts containing your 2FA code.
Convenience: Personally I find using an authentication app is much better experience than SMS. You don't have to wait for the text message to arrive and then manually copy the code. Much easier to open the app, click, and paste.
Remember that if you've previously used SMS 2FA on your account you need to explicitly disable SMS 2FA because services treat all methods of 2FA as equally valid.
When you first enable 2FA you're often offered "recovery codes" which are essentially a one-time token to login to your account without 2FA in case your phone or 2FA device is lost, inaccessible, or destroyed. These codes are important and should be treated with the same security as you would your phone or 2FA device.
In an ideal world these codes shouldn't be stored in the same place as your password. If possible store them in a secure physical location like a safe, otherwise being stored as a secure note in your password manager works too.
If you suspect your account has been compromised you should immediately reset your password and notify the security team for the platform.
When you reset your password the platform should log your account off from all devices and require reauthentication with the new password. You may want to hold off on storing this password in your password manager until speaking with the security team for the platform.
Every platform will have a different security team you can reach out to. For the Python ecosystem this procedure is outlined in the PyPI security disclosure page which recommends emailing the Python Security Response Team at
email@example.com and the PyPI admins at
The rest of this article focuses on security of platforms. I use the following terminology for the different platforms and their roles:
The examples I'll be using are from my own experience which is primarily using GitHub and the Python Package Index (PyPI). However the recommendations are likely applicable to other project hosts like GitLab or BitBucket and other package repositories like NPM and RubyGems.
This article is written from the perspective of having a separate platform for the project host and package repository, but this isn't always the case!
For example in the Go and PHP+Composer ecosystems the project host and package repository are usually the same platform and use git tags to signal new releases. You should consult more specific documentation for securing your project host, deployment pipeline, and packages if your ecosystem has this overlap.
What does it mean to be a maintainer of an open source package? The word maintainer is used for a large variety of contexts ranging from committing code regularly to being in charge of future project direction.
To avoid this ambiguity I'll be using the terminology Reviewer (abbreviated
REV), Release manager (
RMGR), and Owner (
OWNER). Each of these roles is described in detail below.
The diagrams identify 3 resources which an account can have write access:
The distinction between source code and deployment pipeline code is because deployment pipeline code is sometimes stored in to the same code repository as the rest of the source code (e.g. GitHub Actions). If this is the case this code should be protected via a mechanism like CODEOWNERs on GitHub to prevent direct write access by Reviewers without the approval of a Release Manager or Owner.
This role is also known as a collaborator or developer. The phrase commit-bit is sometimes used to describe this role because they have write access to the source code.
The defining feature of the "Reviewer" role is that they can review and merge pull requests on their own. This means that they can be an autonomous within the project and triage pull requests. Having many members in the reviewer role is great for a project as this increases a projects ability to quickly ship features and fix bugs.
The defining feature of the "Release manager" role is that they can publish packages either directly to the package repository or indirectly by running the automated deployment pipeline.
In many projects, particularly in smaller projects, there are no team members that are in the release manager role. Instead the owner of the project fulfills all the duties of a release manager.
For larger projects I would favor having multiple release managers instead of more than two owners.
Finally the defining feature of an "Owner" is they have access to assign and modify the permissions of other roles within both the project host and package repository. There should not be many accounts with the Owner role for a package, stick to 3 or fewer, but the optimal number of Owners per package is 2.
Every platform has different terminology for the different roles that can be configured. When you're applying advice from this article be careful that you're selecting the right ones. If you're not sure about a role for a given platform I suggest carefully reading their documentation.
For some platforms you won't have a fine-grained enough role to exactly match one that's listed here. Notice how there is only one role on RubyGems so there's no difference between an Owner and a Release Manager on RubyGems. Keep this in mind.
Be careful who you select to be new Release Managers (or Owners). Ideally these individuals will be a public figure with a history of contributing to the project or other projects. Unfortunately, trust isn't something that's easy to define in explicit terms, so proceed with caution. Be especially wary of newcomers to a project who aren't public figures asking for package ownership.
Sometimes people move on from open source or working on a project stops being fun. That's totally fine! However if this is the case and you're in the role of "Owner" for this project you have some tough decisions ahead.
If you're planning on somewhat sticking around but not being an active contributor sometimes it's not necessary to reassign ownership. This scenario is true of urllib3 with the original author Andrey Petrov sticking around as a "meta owner" while release managers (myself and Quentin Pradet) make decisions about the direction of the project.
If you plan on completely moving on and your project has one or more release managers or reviewers, consider elevating one of them to the role of owner in your place.
Another route if you're in the situation of having many users but not many trusted contributors is to offer the project up to a known organization of package maintainers like Jazzband for Python.
If you're at a loss for what to do after this you can alert your users either via an issue or note in the changelog that the project is at risk of being unmaintained. This can sometimes generate interest from users to either fork or continue maintaining your project.
If you've exhausted all other possibilities the final option is also the easiest: do nothing. Remember that creating open source code doesn't mean you owe anyone replies, support, or time. If your package repository or project host supports "archiving" your package you may consider doing so to mark the project as inactive.
The only accounts that should have access to the package repository are active Release Managers and Owner accounts. Emphasis on the active, keeping accounts that are inactive on the package repository (beyond the optimal 2 Owners) is only increasing the attack surface without benefit.
I empathize with the feeling of wanting to keep the original author or previous owners of a package around, but remember that in the end access to the package repository is an operational concern. Celebrations of authorship and historical ownership can be kept elsewhere without impacting security.
It may be tempting to remove yourself completely from a package after you transfer ownership. The situation of only having one owner on a package can sometimes lead to the package becoming orphaned if the new owner unexpectedly leaves without transferring ownership to someone else.
This is an unfortunate situation for everyone and for this reason I recommend having 2 Owners on a package, even if one of the Owners isn't actively developing the package anymore.
There's a reason that doing nothing is the best final option: deleting your package is a bad idea! Some package repositories don't allow you to delete your own packages. There are two reasons for this:
If someone was depending on your package and then upon installing later received a different malicious package with the same name, that's a nightmare scenario! It's much better to leave the package as-is and walk away.
Some package repositories support publishing packages via a scoped API token or key instead of using your username and password. When you're configuring automated deployments you should take advantage of this feature.
Your username and password combination has the potential to publish to all of the packages you maintain, whereas the scoped API token can only publish to the one package in scope for the token. This means if your publishing credentials are exposed somehow then only one package is at risk instead of many packages.
Another reason to favor tokens is when rotating credentials. Ideally you should be able to reset your password any time you want without thinking about how it would impact your deployment configuration. If you're using your username and password across multiple projects you'll have to update each one every time. Contrast that to when using an API token per project where you'd only have to update one project's credentials.
Wow, you made it this far! Thanks for staying so long, now we can talk about some other practices I use but don't necessarily recommend for everyone to achieve a secure configuration.
I won't give a full guide here but you should invest time into setting up signed commits via GPG. Generating a GPG key and configuring git to use the key only requires a few steps and reduces the chances that your commits are spoofed.
Because this only protects against malicious commits being proposed via a pull request and still requires an approving review to do any damage I don't consider signed commits to be critical for "good enough" security, however it's something to think about doing.
Signed commits would have a lot more teeth if it were practical to force all committers to use them, however this would likely have an impact on the number of new contributors to your projects.
Current operating systems support encrypting all the data on your disk automatically. Your disk can have information that you wouldn't want anyone to have access to (session cookies, terminal history, API tokens, SSH keys). Because it's fairly trivial to enable I recommend doing so for all devices.
When traveling there are often checkpoints you need to go through where you're separated from your devices. To avoid any chance of sensitive information falling into the wrong hands many password managers support temporarily removing vaults from your devices until you've crossed the checkpoint. Sometimes this process is a manual one where you remove individual vaults (or the entire app) from your phone and some password managers offer "travel mode" as a feature.