<?xml version="1.0" encoding="UTF-8" ?> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel> <title>The Python Package Index Blog</title><description>The official blog of the Python Package Index</description><link>https://blog.pypi.org/</link><atom:link href="https://blog.pypi.org/feed_rss_updated.xml" rel="self" type="application/rss+xml" /> <docs>https://github.com/pypi/warehouse</docs><language>en</language> <pubDate>Fri, 16 Oct 2026 16:51:17 -0000</pubDate> <lastBuildDate>Fri, 16 Oct 2026 16:51:17 -0000</lastBuildDate> <ttl>1440</ttl> <generator>MkDocs RSS plugin - v1.17.9</generator> <image> <url>https://blog.pypi.org/assets/logo.png</url> <title>The Python Package Index Blog</title> <link>https://blog.pypi.org/</link> </image> <item> <title>Checking email reputation, and allowing Outlook again</title> <author>Mike Fiedler</author> <category>security</category> <category>transparency</category> <description>&lt;p&gt;PyPI improves on checks for the reputation of an email address when used to register an account or add to an existing account, and rejects addresses identified as disposable.&lt;/p&gt; &lt;p&gt;With the new check in place, &lt;code&gt;outlook.com&lt;/code&gt; and &lt;code&gt;hotmail.com&lt;/code&gt; addresses can once again be used on PyPI.&lt;/p&gt; &lt;!-- more --&gt; &lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt; &lt;p&gt;In June 2024 we &lt;a href=&#34;../2024-06-16-prohibiting-msn-emails/&#34;&gt;prohibited Outlook email domains&lt;/a&gt; after a malware campaign registered accounts in bulk with &lt;code&gt;outlook.com&lt;/code&gt; and &lt;code&gt;hotmail.com&lt;/code&gt; addresses.&lt;/p&gt; &lt;p&gt;Until now, PyPI has blocked disposable email addresses with two lists: the public &lt;a href=&#34;https://github.com/disposable-email-domains/disposable-email-domains&#34;&gt;disposable-email-domains&lt;/a&gt; list, and our own internal list of prohibited domains. Both are static domain lists, and neither can tell a throwaway address apart from a real one on a large provider like Outlook.&lt;/p&gt; &lt;p&gt;The downside of this approach was that we had to apply a broad prohibition to entire domains, blocking new account creation from legitimate users on those domains. This also relied heavily on investigation and decisioning from staff or volunteer administrators, which led to slower remediation times.&lt;/p&gt; &lt;h2 id=&#34;checking-email-reputation&#34;&gt;Checking email reputation&lt;/h2&gt; &lt;p&gt;When an email address passes the existing domain checks, PyPI now asks an email reputation service whether the address is disposable. This covers disposable providers that register new domains faster than any list can track, as well as throwaway aliases on public email providers.&lt;/p&gt; &lt;p&gt;When the service reports that a whole domain is a disposable provider, PyPI adds that domain to its prohibited list, so the next attempt is rejected without another lookup.&lt;/p&gt; &lt;p&gt;This process happens automatically, and no longer requires any volunteer or staff time to add to the internal prohibited list. This is more important in the new age of autonomous exploits, as the time to react is critical.&lt;/p&gt; &lt;p&gt;As of today, &lt;code&gt;outlook.com&lt;/code&gt; and &lt;code&gt;hotmail.com&lt;/code&gt; addresses can once again be used to register a PyPI account or added to an existing account.&lt;/p&gt; &lt;h2 id=&#34;results-so-far&#34;&gt;Results so far&lt;/h2&gt; &lt;p&gt;Over the past month since I added email reputation checking, the overwhelming volume of results are positive, which is a good signal.&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;EmailReputationChecks_full&#34; src=&#34;../../assets/2026-10-16-email-reputation/EmailReputationChecks_full.png&#34; /&gt;&lt;/p&gt; &lt;p&gt;Removing the Valid responses shows a more detailed picture:&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;EmailReputationChecks_disposable&#34; src=&#34;../../assets/2026-10-16-email-reputation/EmailReputationChecks_disposable.png&#34; /&gt;&lt;/p&gt; &lt;p&gt;We have since blocked disposable 62 domains from being used to register for user accounts on PyPI, and disallowed over 120 attempts at using a disposable address from an otherwise valid domain.&lt;/p&gt; &lt;p&gt;If you run into problems registering or adding an email address, please open an issue on the &lt;a href=&#34;https://github.com/pypi/support&#34;&gt;support tracker&lt;/a&gt;. If none of the Issue categories match, email support at pypi dot org with complete details.&lt;/p&gt; &lt;h2 id=&#34;thanks&#34;&gt;Thanks&lt;/h2&gt; &lt;p&gt;Thanks to &lt;a href=&#34;https://www.usercheck.com/&#34;&gt;UserCheck&lt;/a&gt; for their donation of the service to aid with defending the Python ecosystem. Find them, and our other sponsors, on the &lt;a href=&#34;https://pypi.org/sponsors/&#34;&gt;PyPI Sponsors page&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;&lt;em&gt;Mike&#39;s work on PyPI is thanks to continued support from &lt;a href=&#34;https://alpha-omega.dev/&#34;&gt;Alpha-Omega&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2026-10-16-email-reputation-and-outlook/</link> <pubDate>Fri, 16 Oct 2026 12:35:23 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2026-10-16-email-reputation-and-outlook/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2026-10-16-email-reputation-and-outlook.png" type="image/png" length="73207" /> </item> <item> <title>Welcome to the PyPI Blog</title> <author>Ee Durbin</author> <category>blogs-about-blogs</category> <category>meta</category> <category>welcome</category> <description>&lt;p&gt;Today, we&#39;re excited to launch &lt;a href=&#34;https://blog.pypi.org&#34;&gt;blog.pypi.org&lt;/a&gt;, the official blog of the Python Package Index.&lt;/p&gt; &lt;p&gt;One of the most common refrains I hear from Python community members, irrespective of if they have been around for days or years, is &lt;em&gt;&#34;I didn&#39;t realize that PyPI...&#34;&lt;/em&gt;. Followed by something along the lines of:&lt;/p&gt; &lt;!-- more --&gt; &lt;ul&gt; &lt;li&gt;Could &lt;strong&gt;do&lt;/strong&gt; that&lt;/li&gt; &lt;li&gt;Is operated with &lt;strong&gt;so&lt;/strong&gt; few resources&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt; &lt;li&gt;Only had &lt;strong&gt;3&lt;/strong&gt; administrators&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt; &lt;li&gt;Needs &lt;strong&gt;more&lt;/strong&gt; help&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;That is certainly in part due to the fact that the people&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; who make PyPI &#34;happen&#34; are &lt;em&gt;humble&lt;/em&gt;, and do so with the limited time they have to commit to the project among their other responsibilities. But its also because writing is hard. Harder still when there is not a convenient tool at hand. We&#39;re hoping that a modern and extensible stack&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:4&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; for this blog will make writing about and sharing our work more frictionless.&lt;/p&gt; &lt;p&gt;The PyPI team will use this space to communicate with PyPI users, provide announcements about new features and updates, interesting technology, as well as share information and context around PyPI and related efforts of the &lt;a href=&#34;https://www.python.org/psf-landing/&#34;&gt;Python Software Foundation&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Stay tuned for more, we&#39;re very glad you&#39;re here! 💜&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Ee Durbin is the Director of Infrastructure at the Python Software Foundation. They have been contributing to keeping PyPI online, available, and secure since 2013.&lt;/em&gt;&lt;/p&gt; &lt;div class=&#34;footnote&#34;&gt; &lt;hr /&gt; &lt;ol&gt; &lt;li id=&#34;fn:1&#34;&gt; &lt;p&gt;We&#39;re thrifty and picky about what we run, but &lt;a href=&#34;https://www.fastly.com/fast-forward&#34;&gt;Fastly&lt;/a&gt; is the single most important factor allowing us to get by with comparatively few resources for our backends.&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; title=&#34;Jump back to footnote 1 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li id=&#34;fn:2&#34;&gt; &lt;p&gt;That was until we welcomed Mike Fiedler as an admin in 2023.&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; title=&#34;Jump back to footnote 2 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li id=&#34;fn:3&#34;&gt; &lt;p&gt;The PyPI Administrator team is &lt;a href=&#34;https://github.com/dstufft&#34;&gt;Donald Stufft&lt;/a&gt;, &lt;a href=&#34;https://github.com/di&#34;&gt;Dustin Ingram&lt;/a&gt;, &lt;a href=&#34;https://github.com/miketheman&#34;&gt;Mike Fiedler&lt;/a&gt;, and &lt;a href=&#34;https://github.com/ewdurbin&#34;&gt;Ee Durbin&lt;/a&gt;. The PyPI Moderator team is &lt;a href=&#34;https://github.com/yeraydiazdiaz&#34;&gt;Yeray Díaz&lt;/a&gt;, &lt;a href=&#34;https://github.com/cmaureir&#34;&gt;Cristián Maureira-Fredes&lt;/a&gt;, &lt;a href=&#34;https://github.com/ewjoachim&#34;&gt;Joachim Jablon&lt;/a&gt;, &lt;a href=&#34;https://github.com/jamadden&#34;&gt;Jason Madden&lt;/a&gt;, &lt;a href=&#34;https://github.com/pradyunsg&#34;&gt;Pradyun Gedam&lt;/a&gt;, and &lt;a href=&#34;https://github.com/theacodes&#34;&gt;Stargirl Flowers&lt;/a&gt;.&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; title=&#34;Jump back to footnote 3 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li id=&#34;fn:4&#34;&gt; &lt;p&gt;Shout-out: This blog is hosted on &lt;a href=&#34;https://readthedocs.org/&#34;&gt;Read the Docs&lt;/a&gt;, using the &lt;a href=&#34;https://pypi.org/project/mkdocs/&#34;&gt;MkDocs&lt;/a&gt; static site generator, and the &lt;a href=&#34;https://pypi.org/project/mkdocs-material/&#34;&gt;Material for MkDocs&lt;/a&gt; theme. Many thanks to the maintainers of those projects for your contributions, we&#39;re very excited to have our blog contents live side-by-side with our other documentation and code in &lt;a href=&#34;https://github.com/pypi/warehouse&#34;&gt;pypi/warehouse&lt;/a&gt;!&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:4&#34; title=&#34;Jump back to footnote 4 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;/div&gt;</description> <link>https://blog.pypi.org/posts/2023-03-21-welcome-to-the-pypi-blog/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-03-21-welcome-to-the-pypi-blog/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-03-21-welcome-to-the-pypi-blog.png" type="image/png" length="50338" /> </item> <item> <title>Introducing &#39;Trusted Publishers&#39;</title> <author>Dustin Ingram</author> <category>oidc</category> <category>publishing</category> <category>security</category> <description>&lt;p&gt;Starting today, PyPI package maintainers can adopt a new, more secure publishing method that does not require long-lived passwords or API tokens to be shared with external systems.&lt;/p&gt; &lt;!-- more --&gt; &lt;h3 id=&#34;about-trusted-publishing&#34;&gt;About trusted publishing&lt;/h3&gt; &lt;p&gt;&#34;Trusted publishing&#34; is our term for using the &lt;a href=&#34;https://openid.net/connect/&#34;&gt;OpenID Connect (OIDC)&lt;/a&gt; standard to exchange short-lived identity tokens between a trusted third-party service and PyPI. This method can be used in automated environments and eliminates the need to use username/password combinations or manually generated API tokens to authenticate with PyPI when publishing.&lt;/p&gt; &lt;p&gt;Instead, PyPI maintainers can configure PyPI to trust an identity provided by a given OpenID Connect Identity Provider (IdP). This allows allows PyPI to verify and delegate trust to that identity, which is then authorized to request short-lived, tightly-scoped API tokens from PyPI. These API tokens never need to be stored or shared, rotate automatically by expiring quickly, and provide a verifiable link between a published package and its source.&lt;/p&gt; &lt;h3 id=&#34;using-trusted-publishing-with-github-actions&#34;&gt;Using trusted publishing with GitHub Actions&lt;/h3&gt; &lt;p&gt;PyPI currently supports trusted publishing with GitHub Actions, using &lt;a href=&#34;https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect&#34;&gt;their support for OpenID Connect&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;After configuring PyPI to trust a given GitHub repository and workflow, users of the PyPA&#39;s &lt;a href=&#34;https://github.com/marketplace/actions/pypi-publish&#34;&gt;&#39;pypi-publish&#39; GitHub Action&lt;/a&gt; can adopt trusted publishing by removing the &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; fields from their workflow configuration, and adding permissions to generate an identity token:&lt;/p&gt; &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;jobs: &lt;span class=&#34;w&#34;&gt; &lt;/span&gt; pypi-publish: &lt;span class=&#34;w&#34;&gt; &lt;/span&gt; name: upload release to PyPI &lt;span class=&#34;w&#34;&gt; &lt;/span&gt; runs-on: ubuntu-latest &lt;span class=&#34;gi&#34;&gt;+ permissions:&lt;/span&gt; &lt;span class=&#34;gi&#34;&gt;+ # IMPORTANT: this permission is mandatory for trusted publishing&lt;/span&gt; &lt;span class=&#34;gi&#34;&gt;+ id-token: write&lt;/span&gt; &lt;span class=&#34;w&#34;&gt; &lt;/span&gt; steps: &lt;span class=&#34;w&#34;&gt; &lt;/span&gt; # retrieve your distributions here &lt;span class=&#34;w&#34;&gt; &lt;/span&gt; - name: Publish package distributions to PyPI &lt;span class=&#34;w&#34;&gt; &lt;/span&gt; uses: pypa/gh-action-pypi-publish@release/v1 &lt;span class=&#34;gd&#34;&gt;- with:&lt;/span&gt; &lt;span class=&#34;gd&#34;&gt;- username: __token__&lt;/span&gt; &lt;span class=&#34;gd&#34;&gt;- password: ${{ secrets.PYPI_TOKEN }}&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Using the PyPA&#39;s GitHub action is strongly recommended, but not required. More details on how to manually exchange tokens are available &lt;a href=&#34;https://docs.pypi.org/trusted-publishers/using-a-publisher/#the-manual-way&#34;&gt;in our documentation&lt;/a&gt;.&lt;/p&gt; &lt;h3 id=&#34;additional-security-hardening-is-available&#34;&gt;Additional security hardening is available&lt;/h3&gt; &lt;p&gt;PyPI package maintainers can further increase the security of their release workflows by configuring trusted publishers to only release from a specific &lt;a href=&#34;https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment&#34;&gt;GitHub Actions environment&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Configuring an environment is optional, but strongly recommended: with a GitHub environment, you can apply additional restrictions to your trusted GitHub Actions workflow, such as requiring manual approval on each run by a trusted subset of repository maintainers.&lt;/p&gt; &lt;h3 id=&#34;unblocking-future-security-improvements&#34;&gt;Unblocking future security improvements&lt;/h3&gt; &lt;p&gt;In addition to making publishing more secure now, the availability of trusted publishers unblocks additional future security improvements for PyPI.&lt;/p&gt; &lt;p&gt;Configuring and using a trusted publisher provides a &#39;strong link&#39; between a project and its source repository, which can allow PyPI to verify related metadata, like the URL of a source repository for a project&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. Additionally, publishing with a trusted publisher allows PyPI to correlate more information about where a given file was published from in a verifiable way.&lt;/p&gt; &lt;p&gt;Finally, although trusted publishers is currently limited to GitHub Actions, much of the underlying work that went into making this feature possible is generalizable and not specific to a single publisher. We&#39;re interested in supporting the ability to publish from additional services that provide OpenID Connect identities.&lt;/p&gt; &lt;h3 id=&#34;get-started-today&#34;&gt;Get started today&lt;/h3&gt; &lt;p&gt;To get started with using trusted publishers on PyPI, see our documentation here: &lt;a href=&#34;https://docs.pypi.org/trusted-publishers/&#34;&gt;https://docs.pypi.org/trusted-publishers/&lt;/a&gt;.&lt;/p&gt; &lt;h3 id=&#34;acknowledgements&#34;&gt;Acknowledgements&lt;/h3&gt; &lt;p&gt;Funding for this work was provided by the Google Open Source Security Team, and much of the development work was performed by &lt;a href=&#34;https://www.trailofbits.com/&#34;&gt;Trail of Bits&lt;/a&gt;, with special thanks to contributor &lt;a href=&#34;https://github.com/woodruffw&#34;&gt;William Woodruff&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Many thanks as well to &lt;a href=&#34;https://github.com/webknjaz&#34;&gt;Sviatoslav Sydorenko&lt;/a&gt;, maintainer of the PyPA&#39;s &lt;a href=&#34;https://github.com/marketplace/actions/pypi-publish&#34;&gt;&#39;pypi-publish&#39; GitHub Action&lt;/a&gt; for his quick and timely work to add support for trusted publishers in the action.&lt;/p&gt; &lt;p&gt;Finally, we want to thank all our beta testers, including GitHub staff, for working with us to ensure this feature is intuitive and useful, and for providing valuable feedback to improve this feature along the way.&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Dustin Ingram is a maintainer of the Python Package Index.&lt;/em&gt;&lt;/p&gt; &lt;div class=&#34;footnote&#34;&gt; &lt;hr /&gt; &lt;ol&gt; &lt;li id=&#34;fn:1&#34;&gt; &lt;p&gt;Currently, information such as this are provided by the uploader and are not verified as accurate by PyPI.&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; title=&#34;Jump back to footnote 1 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;/div&gt;</description> <link>https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-04-20-introducing-trusted-publishers.png" type="image/png" length="54661" /> </item> <item> <title>Introducing PyPI Organizations</title> <author>Ee Durbin</author> <category>organizations</category> <category>sustainability</category> <description>&lt;p&gt;Today, we are rolling out the first step in our plan to build financial support and long-term sustainability of the Python Packaging Index (PyPI), while simultaneously giving our users one of our most requested features: organization accounts.&lt;/p&gt; &lt;!-- more --&gt; &lt;h3 id=&#34;introducing-organizations&#34;&gt;Introducing Organizations&lt;/h3&gt; &lt;p&gt;Organizations on PyPI are self-managed teams, with their own exclusive branded web addresses. Our goal is to make PyPI easier to use for large community projects, organizations, or companies who manage multiple sub-teams and multiple packages. We’re making organizations available to community projects for free, forever, and to corporate projects for a small fee. Additional priority support agreements will be available to all paid subscribers, and all revenue will go right back into PyPI to continue building better support and infrastructure for all our users.&lt;/p&gt; &lt;h3 id=&#34;increasing-sustainability-and-support&#34;&gt;Increasing sustainability and support&lt;/h3&gt; &lt;p&gt;In the last year PyPI served 235.7 billion downloads for the 448,941 projects hosted there. This means that since the previous period, PyPI saw a 57% annual growth in download counts and bandwidth alike. Having more people using and contributing to Python every year is an fantastic problem to have, but it is one we must increase organizational capacity to accommodate.&lt;/p&gt; &lt;p&gt;Increased revenue for PyPI allows it to become a staffed platform that can respond to support requests and attend to issues in a timeframe that is significantly faster than what our excellent (but thinly spread) largely volunteer team could reasonably handle.&lt;/p&gt; &lt;h3 id=&#34;organizations-are-opt-in&#34;&gt;Organizations are opt-in&lt;/h3&gt; &lt;p&gt;We want to be very clear—these new features are completely optional. If features for larger projects don&#39;t sound like something that would be useful to you as a PyPI maintainer, then there is no obligation to create an organization and absolutely nothing about your PyPI experience will change for you.&lt;/p&gt; &lt;h3 id=&#34;a-basis-for-future-features&#34;&gt;A basis for future features&lt;/h3&gt; &lt;p&gt;We look forward to discussing what other features PyPI users would like to see tackled next. We know there are lots of ideas out there around safety, security and usability and we’re looking forward to hearing about anything that you think would benefit the community. And in the spirit of open source, &lt;a href=&#34;https://github.com/pypi/warehouse/issues&#34;&gt;we welcome your feedback&lt;/a&gt; on what&#39;s on offer so far.&lt;/p&gt; &lt;h3 id=&#34;get-started-today&#34;&gt;Get started today&lt;/h3&gt; &lt;p&gt;Both community projects (non-profits, NGO’s, hobbyists, etc) and corporate teams can &lt;a href=&#34;https://pypi.org/manage/organizations/&#34;&gt;sign up to request their organization name starting today&lt;/a&gt;. Submissions will begin seeing review and approval in the coming weeks, and corporate teams will be able to finalize their signup with billing details in May.&lt;/p&gt; &lt;h3 id=&#34;acknowledgements&#34;&gt;Acknowledgements&lt;/h3&gt; &lt;p&gt;Organization feature development was approved by the &lt;a href=&#34;https://wiki.python.org/psf/PackagingWG&#34;&gt;Packaging Working Group&lt;/a&gt; and funded through the &lt;a href=&#34;https://www.python.org/psf-landing/&#34;&gt;Python Software Foundation&lt;/a&gt;&#39;s sponsorship program -- thanks to our &lt;a href=&#34;https://pypi.org/sponsors/&#34;&gt;sponsors&lt;/a&gt; for making this work possible.&lt;/p&gt; &lt;p&gt;We especially want to thank &lt;a href=&#34;https://www.bloomberg.com/company/values/tech-at-bloomberg/?ea-publisher=psf&#34;&gt;Bloomberg&lt;/a&gt; for funding our Packaging Project Manager, Shamika Mohanan.&lt;/p&gt; &lt;p&gt;We are also grateful for the many generous PyPI users who shared their perspectives with Shamika, which laid the foundation for these new features.&lt;/p&gt; &lt;p&gt;And thanks as well to our beta testers, including the following ✨new✨ PyPI organizations:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Python Cryptographic Authority (&lt;a href=&#34;https://pypi.org/org/pyca/&#34;&gt;https://pypi.org/org/pyca/&lt;/a&gt;)&lt;/li&gt; &lt;li&gt;The Pallets Project (&lt;a href=&#34;https://pypi.org/org/pallets/&#34;&gt;https://pypi.org/org/pallets/&lt;/a&gt;)&lt;/li&gt; &lt;li&gt;certifi (&lt;a href=&#34;https://pypi.org/org/certifi&#34;&gt;https://pypi.org/org/certifi&lt;/a&gt;)&lt;/li&gt; &lt;/ul&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Ee Durbin is the Director of Infrastructure at the Python Software Foundation. They have been contributing to keeping PyPI online, available, and secure since 2013.&lt;/em&gt;&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-04-23-introducing-pypi-organizations/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-04-23-introducing-pypi-organizations/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-04-23-introducing-pypi-organizations.png" type="image/png" length="57343" /> </item> <item> <title>Announcing the PyPI Safety &amp; Security Engineer role</title> <author>Ee Durbin</author> <category>hiring</category> <category>security</category> <description>&lt;p&gt;We are pleased to announce Amazon Web Services (AWS) as the inaugural Security Sponsor for PyPI, investing $144,000 over one year to fund key enhancements to PyPI infrastructure and operations, including the creation of a new “PyPI Safety &amp;amp; Security Engineer” role. &lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;This role builds on our existing long term partnership with AWS as one of the top sponsors of the Python Software Foundation for the last five years, which has included in-kind donations of cloud computing infrastructure and services to support PyPI. The role will complement our previously announced role for a &lt;a href=&#34;https://pyfound.blogspot.com/2023/01/the-psf-is-hiring-security-developer-in.html&#34;&gt;PSF Security Developer in Residence&lt;/a&gt; and will work closely with the person hired for that role (to be announced soon).&lt;/p&gt; &lt;p&gt;This funding also builds on &lt;a href=&#34;https://dustingram.com/articles/2021/04/14/powering-the-python-package-index-in-2021/#project-funding&#34;&gt;previously successful project-focused funding efforts&lt;/a&gt;, such as the 2018 full-stack rewrite of PyPI, the introduction of internationalization and localization for PyPI, as well as 2FA and WebAuthn support.&lt;/p&gt; &lt;p&gt;We expect this partnership to tangibly improve the experience for all PyPI users, from consumers downloading packages, to package maintainers, to large corporate teams. Some of the outcomes we are working toward over the next year include increased support for package maintainers including multi-maintainer projects, improvements to reporting infrastructure for malicious projects, as well as a reduced response time for malware reports and account recovery requests.&lt;/p&gt; &lt;p&gt;&lt;a href=&#34;https://jobs.pyfound.org/apply/CKEONredws/PyPI-Safety-Security-Engineer&#34;&gt;The job posting can be found here&lt;/a&gt;, and applications for the role are open until June 1st. Similar to existing developer-in-residence roles, the contract for this role will be for a one year period, and the PSF will be actively engaging with our sponsors and supporters to renew funding for the role in subsequent years.&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Ee Durbin is the Director of Infrastructure at the Python Software Foundation. They have been contributing to keeping PyPI online, available, and secure since 2013.&lt;/em&gt;&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-05-09-announcing-pypi-safety-and-security-engr-role/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-05-09-announcing-pypi-safety-and-security-engr-role/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-05-09-announcing-pypi-safety-and-security-engr-role.png" type="image/png" length="69077" /> </item> <item> <title>Removing PGP from PyPI</title> <author>Donald Stufft</author> <category>security</category> <description>&lt;p&gt;If you are someone who is currently uploading signatures, your package uploads will continue to succeed, but any PGP signatures will be silently ignored. If you are someone who is currently downloading PGP signatures, existing signatures &lt;em&gt;SHOULD&lt;/em&gt; continue to be available &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, but no new signatures will be made available. The related API fields such as &lt;code&gt;has_sig&lt;/code&gt; have all been hardcoded to always be &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;Historically, PyPI has supported uploading PGP signatures alongside the release artifacts in an attempt to provide some level of package signing. However, the approach used had long standing, &lt;a href=&#34;https://caremad.io/posts/2013/07/packaging-signing-not-holy-grail/&#34;&gt;documented issues&lt;/a&gt; which had previously lead us to deemphasize the support for PGP signatures over time by removing them from the PyPI web user interface.&lt;/p&gt; &lt;p&gt;PyPI has continued to support uploading these signatures in the hope that there might be some systems out there that found them useful. Recently though, &lt;a href=&#34;https://blog.yossarian.net/2023/05/21/PGP-signatures-on-PyPI-worse-than-useless&#34;&gt;an examination of the signatures on PyPI&lt;/a&gt; has revealed to us that the current support for PGP signatures is not proving useful.&lt;/p&gt; &lt;p&gt;In the last 3 years, about 50k signatures had been uploaded to PyPI by 1069 unique keys. Of those 1069 unique keys, about 30% of them were not discoverable on major public keyservers, making it difficult or impossible to meaningfully verify those signatures. Of the remaining 71%, nearly half of them were unable to be meaningfully verified at the time of the audit (2023-05-19) &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt; &lt;p&gt;In other words, out of all of the unique keys that had uploaded signatures to PyPI, only 36% of them were capable of being meaningfully verified &lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; at the time of audit. Even if &lt;em&gt;all&lt;/em&gt; of those signatures uploaded in that 3 year period of time were made by one of those 36% of keys that are able to be meaningfully verified, that would still represent only 0.3% of all of those files.&lt;/p&gt; &lt;p&gt;Given all of this, the continued support of uploading PGP signatures to PyPI is no longer defensible. While it doesn&#39;t represent a &lt;em&gt;massive&lt;/em&gt; operational burden to continue to support it, it does require any new features that touch the storage of files to be made aware of and capable of handling these PGP signatures, which is a non zero cost on the maintainers and contributors of PyPI.&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Donald Stufft is a PyPI administrator and maintainer of the Python Package Index since 2013.&lt;/em&gt;&lt;/p&gt; &lt;div class=&#34;footnote&#34;&gt; &lt;hr /&gt; &lt;ol&gt; &lt;li id=&#34;fn:1&#34;&gt; &lt;p&gt;For now, but they may be removed in the future.&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; title=&#34;Jump back to footnote 1 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li id=&#34;fn:2&#34;&gt; &lt;p&gt;These could be because the original signature was made incorrectly and never had a binding signature to a associated key identity, or because the signature was present but had since expired.&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; title=&#34;Jump back to footnote 2 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li id=&#34;fn:3&#34;&gt; &lt;p&gt;We use meaningfully verified to mean that the signature was valid and the key that made it was not expired and had binding identify information that could tell us if this key was the correct key.&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; title=&#34;Jump back to footnote 3 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;/div&gt;</description> <link>https://blog.pypi.org/posts/2023-05-23-removing-pgp/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-05-23-removing-pgp/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-05-23-removing-pgp.png" type="image/png" length="53446" /> </item> <item> <title>PyPI was subpoenaed</title> <author>Ee Durbin</author> <category>compliance</category> <category>transparency</category> <description>&lt;p&gt;In March and April 2023, the Python Software Foundation (PSF) received three (3) subpoenas for PyPI user data. All three subpoenas were issued by the United States Department of Justice. The PSF was not provided with context on the legal circumstances surrounding these subpoenas. In total, user data related to five (5) PyPI usernames were requested.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;The data request was:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;&#34;Names (including subscriber names, user names, and screen names);&#34;&lt;/li&gt; &lt;li&gt;&#34;Addresses (including mailing, residential addresses, business addresses, and email addresses);&#34;&lt;/li&gt; &lt;li&gt;&#34;Connection records;&#34;&lt;/li&gt; &lt;li&gt;&#34;Records of session times and durations, and the temporarily assigned network address (such as Internet Protocol addresses) associated with those sessions;&#34;&lt;/li&gt; &lt;li&gt;&#34;Length of service (including start date) and type of services utilized;&#34;&lt;/li&gt; &lt;li&gt;&#34;Telephone or instrument numbers (including the registration Internet Protocol address);&#34;&lt;/li&gt; &lt;li&gt;&#34;Means and source of payment of any such services (including any credit card or bank account number) and billing records;&#34;&lt;/li&gt; &lt;li&gt;&#34;Records of all Python Package Index (PyPI) packages uploaded by...&#34; given usernames&lt;/li&gt; &lt;li&gt;&#34;IP download logs of any Python Package Index (PyPI) packages uploaded by...&#34; given usernames&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;The privacy of PyPI users is of utmost concern to PSF and the PyPI Administrators, and we are committed to protecting user data from disclosure whenever possible. In this case, however, PSF determined with the advice of counsel that our only course of action was to provide the requested data. I, as Director of Infrastructure of the Python Software Foundation, fulfilled the requests in consultation with PSF&#39;s counsel.&lt;/p&gt; &lt;p&gt;We have waited for the string of subpoenas to subside, though we were committed from the beginning to write and publish this post as a matter of transparency, and as allowed by the lack of a non-disclosure order associated with the subpoenas received in March and April 2023.&lt;/p&gt; &lt;h2 id=&#34;next-steps&#34;&gt;Next Steps&lt;/h2&gt; &lt;p&gt;PyPI and the PSF are committed to the freedom, security, and privacy of our users.&lt;/p&gt; &lt;p&gt;This process has offered time to revisit our current data and privacy standards, which are minimal, to ensure they take into account the varied interests of the Python community. Though we collect very little personal data from PyPI users, any unnecessarily held data are still subject to these kinds of requests in addition to the baseline risk of data compromise via malice or operator error.&lt;/p&gt; &lt;p&gt;As a result we are currently developing new data retention and disclosure policies. These policies will relate to our procedures for future government data requests, how and for what duration we store personally identifiable information such as user access records, and policies that make these explicit for our users and community.&lt;/p&gt; &lt;p&gt;Please continue to watch this blog for related announcements as policies are finalized, published, and implemented.&lt;/p&gt; &lt;hr /&gt; &lt;h2 id=&#34;details&#34;&gt;Details&lt;/h2&gt; &lt;p&gt;In order to provide as much transparency as possible, the following will detail the shape of and extent of the data that was contained in the responses to these subpoenas.&lt;/p&gt; &lt;p&gt;We will not be releasing the usernames involved publicly or to the users themselves.&lt;/p&gt; &lt;h3 id=&#34;1-names-including-subscriber-names-user-names-and-screen-names&#34;&gt;1) Names (including subscriber names, user names, and screen names);&lt;/h3&gt; &lt;p&gt;These were confirmed via our database records&lt;/p&gt; &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;username&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;users&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;username&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;{USERNAME}&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Returning: &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;id | UUID for USERNAME username | PyPI username name | Display name for user &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt; &lt;p&gt;And are also publicly available at &lt;code&gt;https://pypi.org/user/{USERNAME}/&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;PyPI allows users to delete their accounts.&lt;/p&gt; &lt;p&gt;PyPI does not allow for the &lt;code&gt;username&lt;/code&gt; field to be changed without admin intervention, and no such intervention has occurred for the users in question. If they had, we would have provided records of those changes.&lt;/p&gt; &lt;p&gt;PyPI does allow for user to update their display names and keeps no record of the history of these changes.&lt;/p&gt; &lt;h3 id=&#34;2-addresses-including-mailing-residential-addresses-business-addresses-and-email-addresses&#34;&gt;2) Addresses (including mailing, residential addresses, business addresses, and email addresses);&lt;/h3&gt; &lt;p&gt;PyPI only stores email addresses for individual users. No physical addresses are stored. Organization accounts who have signed up for billing (not yet live) will be required to provide a billing address that validates to their payment method.&lt;/p&gt; &lt;p&gt;These were sourced from our database records and is private to PyPI.&lt;/p&gt; &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_emails&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;{UUID for USERNAME}&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Returning: &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;email | An email address user_id | UUID for USERNAME &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt; &lt;p&gt;PyPI allows for users to add and remove email addresses without admin intervention. Records of these changes are kept, and no such changes were observed in our records for the usernames in question. If they had, we would have provided records of those changes.&lt;/p&gt; &lt;h3 id=&#34;3-connection-records&#34;&gt;3. Connection records&lt;/h3&gt; &lt;h4 id=&#34;3a-project-events&#34;&gt;3a. Project events&lt;/h4&gt; &lt;p&gt;PyPI retains records of all changes to projects on the index, and has since &lt;code&gt;2002-11-01 17:11:36 UTC&lt;/code&gt;.&lt;/p&gt; &lt;p&gt;These were confirmed via our database records&lt;/p&gt; &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;journals&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;submitted_by&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;{USERNAME}&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;by&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;submitted_date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Returning: &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;id | An auto incrementing integer representing the &amp;quot;Serial&amp;quot; name | Name of a PyPI Project version | Version of a PyPI Release if relevant action | Description of the action taken against the Project/Release submitted_date | ISO-8601 datetime in UTC submitted_by | PyPI Username submitted_from | IP Address &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt; &lt;p&gt;and with the exception of the &lt;code&gt;submitted_by&lt;/code&gt; (PyPI username) and &lt;code&gt;submitted_from&lt;/code&gt; (IP Address) columns are publicly available via our &lt;a href=&#34;https://warehouse.pypa.io/api-reference/xml-rpc.html#changelog-since-with-ids-false&#34;&gt;XMLRPC API&lt;/a&gt;.&lt;/p&gt; &lt;h4 id=&#34;3b-user-events&#34;&gt;3b. User events&lt;/h4&gt; &lt;p&gt;PyPI retains records of critical user events including account creation, emails sent, email address changes, logins, and login failures. See &lt;a href=&#34;https://github.com/pypi/warehouse/blob/9738ebb2ffcee91a935a6a11b224575aaf02a878/warehouse/events/tags.py#L61-L106&#34;&gt;this list&lt;/a&gt; for the comprehensive set of events recorded.&lt;/p&gt; &lt;p&gt;These were sourced from our database records&lt;/p&gt; &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;user_events&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;source_id&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;{UUID for USERNAME}&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;order&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;by&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;desc&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Returning: &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;id | UUID of the event source_id | UUID for USERNAME tag | EventTag time | ISO-8601 datetime in UTC ip_address_string | IP Address additional | JSONB metadata about the event ip_address_id | UUID of associated IPAddress object &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt; &lt;p&gt;and are private to PyPI.&lt;/p&gt; &lt;h3 id=&#34;4-records-of-session-times-and-durations-and-the-temporarily-assigned-network-address-such-as-internet-protocol-addresses-associated-with-those-sessions&#34;&gt;4. Records of session times and durations, and the temporarily assigned network address (such as Internet Protocol addresses) associated with those sessions;&lt;/h3&gt; &lt;p&gt;PyPI does not record session durations.&lt;/p&gt; &lt;p&gt;Session creation (Login) times were provided as a synopsis of the data in 3b.&lt;/p&gt; &lt;p&gt;Sessions are not created for uploads, but the associated login events for project uploads were provided as a synopsis of the data in 3a.&lt;/p&gt; &lt;h3 id=&#34;5-length-of-service-including-start-date-and-type-of-services-utilized&#34;&gt;5. Length of service (including start date) and type of services utilized;&lt;/h3&gt; &lt;p&gt;PyPI retains records of the date that a user account was created, as well as the last time it was successfully logged in by any method (web UI or command line tool for upload).&lt;/p&gt; &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;date_joined&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;last_login&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;users&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;username&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;err&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;USERNAME&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;}&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;Returning: &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;date_joined | ISO-8601 datetime in UTC last_login | ISO-8601 datetime in UTC &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/p&gt; &lt;p&gt;These were sourced from our database records and are private to PyPI.&lt;/p&gt; &lt;p&gt;Types of service utilized are &#34;standard&#34; to PyPI and include the ability to create projects, releases, and distribution files for downloads.&lt;/p&gt; &lt;h3 id=&#34;6-telephone-or-instrument-numbers-including-the-registration-internet-protocol-address&#34;&gt;6. Telephone or instrument numbers (including the registration Internet Protocol address);&lt;/h3&gt; &lt;p&gt;A synopsis of all IP Addresses for each username from previous records were shared.&lt;/p&gt; &lt;p&gt;These were sourced from our database records and are private to PyPI.&lt;/p&gt; &lt;h3 id=&#34;7-means-and-source-of-payment-of-any-such-services-including-any-credit-card-or-bank-account-number-and-billing-records&#34;&gt;7. Means and source of payment of any such services (including any credit card or bank account number) and billing records;&lt;/h3&gt; &lt;p&gt;PyPI has no cost to use for individual users and no such payment records or billing records exist.&lt;/p&gt; &lt;h3 id=&#34;8-records-of-all-python-package-index-pypi-packages-uploaded-by-the-given-usernames&#34;&gt;8. Records of all Python Package Index (PyPI) packages uploaded by the given usernames&lt;/h3&gt; &lt;p&gt;A list of all past and any current projects associated with each username was provided.&lt;/p&gt; &lt;p&gt;These were sourced from our database records and for past projects, are private to PyPI.&lt;/p&gt; &lt;h3 id=&#34;9-ip-download-logs-of-any-python-package-index-pypi-packages-uploaded-by-the-given-usernames&#34;&gt;9. IP download logs of any Python Package Index (PyPI) packages uploaded by the given usernames&lt;/h3&gt; &lt;p&gt;PyPI does not retain download logs for packages which include IP addresses. Download logs are processed by a pipeline which includes GeoIP information reported by our CDN only.&lt;/p&gt; &lt;p&gt;These records were sourced from the &lt;a href=&#34;https://docs.pypi.org/api/bigquery/&#34;&gt;Google BigQuery Public dataset&lt;/a&gt; with the following queries:&lt;/p&gt; &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class=&#34;k&#34;&gt;SELECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bigquery&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;public&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pypi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;file_downloads&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;`&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;WHERE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;project&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;IN&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;LIST&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;OF&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;PROJECT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;NAMES&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;FROM&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;AND&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;timestamp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;{START OF PERIOD IN QUESTION}&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Ee Durbin is the Director of Infrastructure at the Python Software Foundation. They have been contributing to keeping PyPI online, available, and secure since 2013.&lt;/em&gt;&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-05-24-pypi-was-subpoenaed/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-05-24-pypi-was-subpoenaed/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-05-24-pypi-was-subpoenaed.png" type="image/png" length="60856" /> </item> <item> <title>Securing PyPI accounts via Two-Factor Authentication</title> <author>Donald Stufft</author> <category>2fa</category> <category>security</category> <description>&lt;p&gt;One of the key security promises that PyPI makes is that when you&#39;re downloading something, that only the people associated with that project are going to be able to upload, delete, or otherwise modify a project. That when you look at that project and see that it is owned by someone that you trust, that you can be assured that nobody else is making changes to that package on PyPI.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;This promise is predicated on the security of each and every individual account on PyPI used to create and maintain a Python project. In the past we&#39;ve taken steps to safeguard these accounts by &lt;a href=&#34;https://caremad.io/posts/2018/08/pypi-compromised-passwords/&#34;&gt;blocking compromised passwords&lt;/a&gt;, strong 2FA support using &lt;a href=&#34;https://github.com/pypi/warehouse/pull/5567&#34;&gt;TOTP&lt;/a&gt; and &lt;a href=&#34;https://github.com/pypi/warehouse/pull/5795&#34;&gt;WebAuthN&lt;/a&gt;, &lt;a href=&#34;https://pypi.org/help/#apitoken&#34;&gt;support for API tokens with offline attenuation&lt;/a&gt;, &lt;a href=&#34;https://pypi.org/security-key-giveaway/&#34;&gt;enrolling the most downloaded projects into mandatory 2FA&lt;/a&gt;, and &lt;a href=&#34;https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/&#34;&gt;enabling short lived tokens for upload&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Today, as part of that long term effort to secure the Python ecosystem, we are announcing that &lt;em&gt;every&lt;/em&gt; account that maintains any project or organization on PyPI will be required to enable 2FA on their account by the end of 2023.&lt;/p&gt; &lt;p&gt;Between now and the end of the year, PyPI will begin gating access to certain site functionality based on 2FA usage. In addition, we may begin selecting certain users or projects for early enforcement.&lt;/p&gt; &lt;h2 id=&#34;what-can-i-do-to-prepare&#34;&gt;What can I do to prepare?&lt;/h2&gt; &lt;p&gt;The most important things you can do to prepare are to enable 2FA for your account as soon as possible, either with a &lt;a href=&#34;https://pypi.org/help/#utfkey&#34;&gt;security device&lt;/a&gt; (preferred) or an &lt;a href=&#34;https://pypi.org/help/#totp&#34;&gt;authentication app&lt;/a&gt; and to switch to using either &lt;a href=&#34;https://docs.pypi.org/trusted-publishers/&#34;&gt;Trusted Publishers&lt;/a&gt; (preferred) or &lt;a href=&#34;https://pypi.org/help/#apitoken&#34;&gt;API tokens&lt;/a&gt; to upload to PyPI.&lt;/p&gt; &lt;h2 id=&#34;why-use-2fa&#34;&gt;Why Use 2FA?&lt;/h2&gt; &lt;p&gt;Account takeover attacks typically stem from someone using an insecure password: perhaps it was easy to guess, or it was reused and appeared in a breach. With that insecure password, an attacker is able to gain control over a maintainers account and begin to take actions as if they were that user.&lt;/p&gt; &lt;p&gt;This is particularly problematic on a site like PyPI, where the actions that a person can take include releasing software that might be used by people world wide, allowing an attacker to install and execute software on unsuspecting user&#39;s computers. &lt;a href=&#34;https://python-security.readthedocs.io/pypi-vuln/index-2022-05-24-ctx-domain-takeover.html&#34;&gt;Account takeover attacks have been previously used to compromise PyPI users in this way&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Two-factor authentication immediately neutralizes the risk associated with a compromised password. If an attacker has someone&#39;s password, that is no longer enough to give them access to that account.&lt;/p&gt; &lt;h2 id=&#34;why-every-project-or-organization-maintainer&#34;&gt;Why every project or organization maintainer?&lt;/h2&gt; &lt;p&gt;There&#39;s two ways to think about this question:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Why every project and organization maintainer instead of just some subset of them (based on popularity, purpose, whether that user uses their account, etc)?&lt;/li&gt; &lt;li&gt;Why only maintainers and not every single user?&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Not every account on PyPI has the same value to an attacker. An account with access to the most downloaded project on PyPI can be used to attack far more people than an account with access to the least downloaded project.&lt;/p&gt; &lt;p&gt;However, it only takes one compromised project in someone&#39;s dependency set to compromise their computer. The attacker doesn&#39;t care if they get you from a widely used or a niche project, just that they got you. Even worse, once compromised, an attacker can extend that attack to attack &lt;em&gt;other&lt;/em&gt; systems, including other projects on PyPI that the now compromised person maintains.&lt;/p&gt; &lt;p&gt;Given that it only takes one compromised project, no matter how many downloads it gets &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:1&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, to compromise someone we want to ensure that every project is being protected by 2FA.&lt;/p&gt; &lt;p&gt;On the flipside, an account without access to any project cannot be used to attack anyone &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:2&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; so it is a very low value target.&lt;/p&gt; &lt;p&gt;We recognize that enabling 2FA for an account imposes a non zero cost both for the owner of that account &lt;em&gt;and&lt;/em&gt; for PyPI &lt;sup id=&#34;fnref:3&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:3&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, so forcing that on accounts that cannot affect anyone but themselves is not an effective use of limited resources. In addition, from a practical view, the standard 2FA flow that most people are used to and that PyPI implements also involves adding a 2FA token to an existing account rather than forcing it to be part of the registration flow.&lt;/p&gt; &lt;p&gt;Our expectation is that for users who currently are not a project maintainer or organization member, the ultimate experience will be whenever they attempt to take some action that would require them to add 2FA (creating a new project, accepting an invite, making an organization, etc) they will be prompted to add 2FA to their account before they can proceed.&lt;/p&gt; &lt;h2 id=&#34;why-now&#34;&gt;Why now?&lt;/h2&gt; &lt;p&gt;The direction of many projects in or serving the Open Source community in the last 5-10 years has been an increasing focus on supply chain security, preventing attacks that are being delivered through the &#34;supply chain&#34;, namely the services and tooling used to create and consume software.&lt;/p&gt; &lt;p&gt;In July of 2022, we announced &lt;a href=&#34;https://pypi.org/security-key-giveaway/&#34;&gt;a security key giveway&lt;/a&gt; in conjunction with a plan to begin mandating 2FA for the top 1% of projects on PyPI by download count.&lt;/p&gt; &lt;p&gt;The initial announcement of that mandate to the top 1% of projects and the related giveaway was met with mixed reactions, ranging from people applauding the effort to people deciding to distribute their code on places other than PyPI. We planned to limit this to the projects in the top 1% of downloads because those are likely he highest value targets for an attacker, and because we were concerned about the impact of such a mandate on both the maintainers of projects on PyPI, and on the support burden of the PyPI team itself.&lt;/p&gt; &lt;p&gt;At that time last year, we did not have any plans or expectations on widening our net of users that would fall under that mandate, other than would occur naturally as projects rose in popularity to be part the top 1%.&lt;/p&gt; &lt;p&gt;However, in the year since then a few things have changed.&lt;/p&gt; &lt;ul&gt; &lt;li&gt;We&#39;ve gotten a lot more confident in our 2FA implementation and in what the impact to enabling it is for both people publishing to PyPI, and to the PyPI team itself.&lt;/li&gt; &lt;li&gt;We&#39;ve shipped features like &lt;a href=&#34;https://docs.pypi.org/trusted-publishers/&#34;&gt;Trusted Publishing&lt;/a&gt; that help remove some of the overheard of 2FA has on publishing (by far the most common action users do).&lt;/li&gt; &lt;li&gt;GitHub has furthered it&#39;s &lt;a href=&#34;https://github.blog/2022-05-04-software-security-starts-with-the-developer-securing-developer-accounts-with-2fa/&#34;&gt;plans to mandate 2FA for all contributors&lt;/a&gt; on their platform, making more people already (or soon will be) prepared to cope with the requirements of having 2FA.&lt;/li&gt; &lt;li&gt;The PSF has received funding to hire a &lt;a href=&#34;https://blog.pypi.org/posts/2023-05-09-announcing-pypi-safety-and-security-engr-role/&#34;&gt;PyPI Safety and Security Engineer&lt;/a&gt;. While the role is not meant to purely handle support requests, having dedicated focus on the overall security posture, as well as the implementation specifics will improve PyPI as a whole for both users and project maintainers, and will help alleviate some of the pressures on PyPI&#39;s volunteers.&lt;/li&gt; &lt;li&gt;The workload to support end users relies heavily on a very small group of volunteers. When an user account report is seen by our trusted admins, we have to take time to properly investigate. These are often reported as an emergency, red-alert-level urgency. By mandating 2FA for project maintainers, the likelihood of account takeovers drop significantly, reserving the emergency status for truly extraordinary circumstances. Account recovery becomes part of normal routine support efforts instead of admin-level urgency.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;All of these together help lead us to the conclusion that we can widen our mandate to &lt;em&gt;all&lt;/em&gt; project maintainers on PyPI, while minimizing the impact on both project maintainers and PyPI administrators, and to do so in a way that improves the sustainability of PyPI and the wider Python ecosystem.&lt;/p&gt; &lt;h2 id=&#34;isnt-supply-chain-security-a-corporate-concern&#34;&gt;Isn&#39;t supply chain security a corporate concern?&lt;/h2&gt; &lt;p&gt;There are some people who believe that efforts to improve supply chain security benefits only corporate or business users, and that individual developers should not be asked to take on a uncompensated burden for their benefit.&lt;/p&gt; &lt;p&gt;We believe this is shortsighted.&lt;/p&gt; &lt;p&gt;A compromise in the supply chain can be used to attack individual developers the same as it able to attack corporate and business users. In fact, we believe that individual developers, are in a &lt;em&gt;more&lt;/em&gt; vulnerable position than corporate and business users. While businesses are generally able to hire staff and devote resources to vetting their dependencies, individual developers generally are not, and must expend their own limited free time to do so &lt;sup id=&#34;fnref:4&#34;&gt;&lt;a class=&#34;footnote-ref&#34; href=&#34;#fn:4&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt; &lt;p&gt;To make matters worse for the individual, in the case of a compromise a business is more likely going to have experts available to them to detect and remediate the compromise, while the individual has to do this on their own. At the extreme ends, businesses often have insurance to compensate them for any losses incurred while the individual almost always does not.&lt;/p&gt; &lt;p&gt;We recognize that supply chain security effects &lt;em&gt;everyone&lt;/em&gt;, no matter how big or how small they are, and we are dedicated to protecting &lt;em&gt;all&lt;/em&gt; our users.&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Donald Stufft is a PyPI administrator and maintainer of the Python Package Index since 2013.&lt;/em&gt;&lt;/p&gt; &lt;div class=&#34;footnote&#34;&gt; &lt;hr /&gt; &lt;ol&gt; &lt;li id=&#34;fn:1&#34;&gt; &lt;p&gt;&lt;em&gt;Technically&lt;/em&gt; a project with 0 downloads is effectively the same as a non-existent project, but it&#39;s easier to draw the line between non-existent and existent than it is to draw the line between 0 and 1 downloads. This is particularly true on PyPI, where a large network of mirrors and scanners mean that &lt;em&gt;no&lt;/em&gt; projects truly get downloaded exactly 0 times.&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:1&#34; title=&#34;Jump back to footnote 1 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li id=&#34;fn:2&#34;&gt; &lt;p&gt;Except maybe the account owner themselves, by denying them access to their account.&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:2&#34; title=&#34;Jump back to footnote 2 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li id=&#34;fn:3&#34;&gt; &lt;p&gt;For end users it forces them to purchase some kind of hardware token &lt;em&gt;OR&lt;/em&gt; to use some sort of TOTP application. In both cases it forces them to keep track of something else besides their password and changes the login flow from what they are used to. For PyPI it increases the chance that someone may get locked out of their account, requiring intervention by administrators.&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:3&#34; title=&#34;Jump back to footnote 3 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;li id=&#34;fn:4&#34;&gt; &lt;p&gt;Not for nothing, but PyPI is also an Open Source project, run largely by volunteers, and cleaning up after a compromise on PyPI is something that affects those volunteers significantly.&amp;#160;&lt;a class=&#34;footnote-backref&#34; href=&#34;#fnref:4&#34; title=&#34;Jump back to footnote 4 in the text&#34;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;/div&gt;</description> <link>https://blog.pypi.org/posts/2023-05-25-securing-pypi-with-2fa/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-05-25-securing-pypi-with-2fa/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-05-25-securing-pypi-with-2fa.png" type="image/png" length="71926" /> </item> <item> <title>Reducing Stored IP Data in PyPI</title> <author>Mike Fiedler</author> <category>security</category> <category>transparency</category> <description>&lt;p&gt;Hi there! I&#39;m Mike, the newest member of the PyPI admin team. Nice to meet you!&lt;/p&gt; &lt;h2 id=&#34;tldr&#34;&gt;TL;DR&lt;/h2&gt; &lt;p&gt;We&#39;ve been working on reducing the amount of IP address data we store, and we&#39;re making progress.&lt;/p&gt; &lt;!-- more --&gt; &lt;h2 id=&#34;whats-this-about&#34;&gt;What&#39;s this about?&lt;/h2&gt; &lt;p&gt;If you&#39;ve read some of the other blogs here, you may have noticed that we&#39;ve been working on a number of security and privacy improvements.&lt;/p&gt; &lt;p&gt;A few months ago we started exploring what it would take to remove the concept of IP addresses from our stack, and retain the ability to safely manage the platform.&lt;/p&gt; &lt;p&gt;Some places of where IP data was historically used:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;web access logs&lt;/li&gt; &lt;li&gt;user events (login,. logout, password reset, et al)&lt;/li&gt; &lt;li&gt;project events (creation, new releases, new file uploads, yanks, et al)&lt;/li&gt; &lt;li&gt;organization/team membership events (&lt;a href=&#34;../2023-04-23-introducing-pypi-organizations/&#34;&gt;new!&lt;/a&gt;)&lt;/li&gt; &lt;li&gt;journal entries (private to PyPI admins)&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Security is a spectrum - where on one extreme, it&#39;s the wild west, no security. On the other extreme, everything is locked down, impossible to use. We constantly have to balance the needs and desires of our users with the needs of running a sustainable, trusted platform.&lt;/p&gt; &lt;p&gt;A similar mindset can be applied to privacy - where we must strike the balance between providing a manageable service, and protecting the privacy of our users.&lt;/p&gt; &lt;p&gt;The two main approaches we&#39;ve pursued in the short term are:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Evaluate whether we need to store IP data at all&lt;/li&gt; &lt;li&gt;Whenever possible, use alternatives to IP data&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;I&#39;ll provide a couple of examples demonstrating the above.&lt;/p&gt; &lt;h2 id=&#34;do-we-need-the-ip-data&#34;&gt;Do we need the IP data?&lt;/h2&gt; &lt;p&gt;As we evaluated the different places we stored IP data, we learned that our Journal entries (similar to an append-only transaction log) were never exposed beyond admin users, and even then, used for admin display only.&lt;/p&gt; &lt;p&gt;We never share that via API, or used it for operational purposed. So we audited the code, removed calls to stop writing the column, and dropped it.&lt;/p&gt; &lt;p&gt;Woohoo!&lt;/p&gt; &lt;p&gt;Other places where we currently still need IP data include rate limiting, and fallbacks until we have backfilled the IP data with hashes and geo data. Our modern approach has evolved from using the IP data at display time to find the relevant geo data, to storing the geo data directly in the database.&lt;/p&gt; &lt;p&gt;Another use case is for handling abuse - we need to be able to identify the source of abuse, and take action to prevent it. We&#39;re thinking about how to manage that without storing IP data, but we&#39;re not there yet.&lt;/p&gt; &lt;h2 id=&#34;alternatives-to-ip-data&#34;&gt;Alternatives to IP data&lt;/h2&gt; &lt;p&gt;For the other places where we stored IP data, we asked ourselves - could we use an alternative?&lt;/p&gt; &lt;p&gt;We can&#39;t store what we don&#39;t see, so we explored what we could do to reduce the amount of IP data we see.&lt;/p&gt; &lt;p&gt;Pretty much every request to PyPI is served via our CDN partner, Fastly. They provide a number of features, including the ability to &lt;a href=&#34;https://docs.fastly.com/en/guides/adding-or-modifying-headers-on-http-requests-and-responses&#34;&gt;add custom headers&lt;/a&gt;. We leverage this ability already for a number of things, like informing the warehouse code of the inbound encoding of the request or language.&lt;/p&gt; &lt;p&gt;We explored whether we could use this to add a hash of the IP address, and use that instead of the IP address itself. Fastly can also provide some basic geographic info about the IP address, which served our purpose of showing the user where they had connected from.&lt;/p&gt; &lt;p&gt;Using this approach, we have Fastly pass along an already-hashed IP address, as well as the geographic data, to our backend, and store those for later use.&lt;/p&gt; &lt;p&gt;Another spot we identified was web access logs. We don&#39;t need real IP addresses there, as we rarely use them for anything other than low-level debugging, so a stable, hashed value serves the purpose of correlating requests.&lt;/p&gt; &lt;p&gt;For the requests that Fastly doesn&#39;t serve, we&#39;re already hashing the IP address ourselves prior to storage, so we could &#34;see&#34; the IP address briefly, but we try to avoid storing it. We don&#39;t get get the geo data for these requests, we&#39;re thinking of creative and sustainable solutions already.&lt;/p&gt; &lt;h2 id=&#34;questions-and-answers&#34;&gt;Questions and Answers&lt;/h2&gt; &lt;p&gt;I tried to think up some questions you might have, and answer them here. If you have more, please feel free to reach out to us!&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Is a hashed IP address secure?&lt;/p&gt; &lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; It&#39;s more secure than a plain IP address, for sure. We apply a &lt;a href=&#34;https://en.wikipedia.org/wiki/Salt_(cryptography)&#34;&gt;salt&lt;/a&gt; (known value) to the IP address before hashing it. It&#39;s not a perfect solution, but it&#39;s a step in the right direction.&lt;/p&gt; &lt;p&gt;The hash is non-reversible, but since the known address space is relatively small, it&#39;s possible to brute force the hash to determine the original IP address. By applying a salt, we require someone to possess &lt;strong&gt;both&lt;/strong&gt; the salt and the hashed IP addresses to brute force the value. Our salt is not stored in the database while the hashed IP addresses are, we protect against leaks revealing this information.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Is this a response to the subpoenas?&lt;/p&gt; &lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; No, we started exploring this &lt;a href=&#34;https://github.com/pypi/warehouse/issues/8158&#34;&gt;back in 2020&lt;/a&gt;, with a long term goal to increase end-user security, while retaining the ability to effectively steer the platform. We picked it up recently as we explored our CDN partner&#39;s options for geo IP data.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; Are we done?&lt;/p&gt; &lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Nope! Security is an ongoing journey, and we&#39;re making strong strides to accomplish this goal. We still have some work to do to replace IP data in our models, after we&#39;ve backfilled our models with the hashed IP data and relevant geo data, and clean up some of the code.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt; What&#39;s next?&lt;/p&gt; &lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; I can&#39;t predict every future step we&#39;re likely to take, but some things we&#39;re considering:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Reevaluate the need for IP data in Event history &lt;strong&gt;forever&lt;/strong&gt;, remove it after a period of time&lt;/li&gt; &lt;li&gt;Explore whether we can use a CDN for all requests&lt;/li&gt; &lt;li&gt;Determine if there&#39;s a better mechanism than Journal Entries, and replace them&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;We believe the steps we&#39;re taking are in the right direction, and we&#39;re excited to share our progress with you. Hopefully this enriches your understanding of the work we&#39;re doing, in support of maintaining a secure, trusted platform.&lt;/p&gt; &lt;p&gt;Thanks for reading!&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Mike Fiedler is a PyPI administrator and maintainer of the Python Package Index since 2022.&lt;/em&gt;&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-05-26-reducing-stored-ip-data/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-05-26-reducing-stored-ip-data/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-05-26-reducing-stored-ip-data.png" type="image/png" length="59649" /> </item> <item> <title>Enforcement of 2FA for upload.pypi.org begins today</title> <author>Ee Durbin</author> <category>2fa</category> <category>security</category> <description>&lt;p&gt;Beginning today, all uploads from user accounts with 2FA enabled will be required to use an &lt;a href=&#34;https://pypi.org/help/#apitoken&#34;&gt;API Token&lt;/a&gt; or &lt;a href=&#34;https://docs.pypi.org/trusted-publishers/&#34;&gt;Trusted Publisher&lt;/a&gt; configuration in place of their password.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;This change has &lt;a href=&#34;https://github.com/pypi/warehouse/issues/7265&#34;&gt;been planned&lt;/a&gt; since 2FA was rolled out in 2019. In &lt;a href=&#34;https://github.com/pypi/warehouse/pull/10836&#34;&gt;February of 2022&lt;/a&gt; we began notifying users on upload that this change was coming.&lt;/p&gt; &lt;p&gt;If you have 2FA enabled and have been using only your password to upload, the following email is likely familiar to you:&lt;/p&gt; &lt;figure&gt; &lt;p&gt;&lt;img alt=&#34;Sample notice email&#34; src=&#34;../../assets/2023-06-01-2fa-notice-email.png&#34; /&gt; &lt;/p&gt; &lt;figcaption&gt; A sample notice email sent when users with 2FA enabled upload using only their password. &lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;Initially, we intended for this notice to live for six months before we began enforcement.&lt;/p&gt; &lt;p&gt;However, some valid concerns were raised regarding the use of user-scoped API tokens for new project creation.&lt;/p&gt; &lt;p&gt;With the &lt;a href=&#34;../2023-04-20-introducing-trusted-publishers/&#34;&gt;introduction of Trusted Publishers&lt;/a&gt; PyPI now provides a way for users to publish &lt;strong&gt;new&lt;/strong&gt; projects without provisioning a user-scoped token, and to continue publishing without ever provisioning a long lived API token whatsoever.&lt;/p&gt; &lt;p&gt;Given this, and our &lt;a href=&#34;../2023-05-25-securing-pypi-with-2fa/&#34;&gt;commitment to further rolling out 2FA across PyPI&lt;/a&gt;, we are now enforcing this policy.&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Ee Durbin is the Director of Infrastructure at the Python Software Foundation. They have been contributing to keeping PyPI online, available, and secure since 2013.&lt;/em&gt;&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-06-01-2fa-enforcement-for-upload/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-06-01-2fa-enforcement-for-upload/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-06-01-2fa-enforcement-for-upload.png" type="image/png" length="76445" /> </item> <item> <title>Announcing the launch of PyPI Malware Reporting and Response project</title> <author>Shamika Monahan</author> <category>security</category> <description>&lt;p&gt;We are pleased to announce that the PSF has received funding from the &lt;a href=&#34;https://cset.georgetown.edu/&#34;&gt;Center for Security and Emerging Technology&lt;/a&gt; (CSET) to develop and improve the infrastructure for malware reporting and response on PyPI. This project will be executed over the coming year.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;Currently, malware reports are submitted to PyPI admins by email before being manually triaged and responded to. There is an opportunity for improvement in streamlining the report submission process and the tools used to triage and respond to them. The current process cannot scale easily or handle duplication of reports. It is not easy to measure time to remediation and is currently impossible to implement automated takedown of threats.&lt;/p&gt; &lt;p&gt;This project has the following aims:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Develop an API that allows malware reporting&lt;/li&gt; &lt;li&gt;Extend PyPI admin tools to view, collate and handle security reports&lt;/li&gt; &lt;li&gt;Collect metadata as required and identify trusted reporters&lt;/li&gt; &lt;li&gt;Define metrics that allow us to define good reporting practices and time to handle a security issue&lt;/li&gt; &lt;li&gt;Define the criteria for automated consensus based takedown and soft-deletes of packages&lt;/li&gt; &lt;li&gt;Highlight trusted reporters and report quality&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;As PyPI is an integral part of the Python ecosystem, this project is crucial in ensuring the security of over 450,000 packages that are trusted by millions of Python developers. Over the next few weeks, we will be working with security reporters to identify key elements that should be supported by the API and useful metrics that would add value to PyPI security reporting. If you or your colleagues are currently performing malware analysis of PyPI uploads, we would love to hear from you at &lt;a href=&#34;https://forms.gle/ixRoNJEPVNekFN7H7&#34;&gt;https://forms.gle/ixRoNJEPVNekFN7H7&lt;/a&gt;.&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Shamika Mohanan is the Packaging Project Manager at the PSF since 2021.&lt;/em&gt;&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-06-22-malware-detection-project/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-06-22-malware-detection-project/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-06-22-malware-detection-project.png" type="image/png" length="60440" /> </item> <item> <title>Deprecation of bdist_egg uploads to PyPI</title> <author>Ee Durbin</author> <category>deprecation</category> <description>&lt;p&gt;&lt;a href=&#34;https://peps.python.org/pep-0715/&#34;&gt;PEP 715&lt;/a&gt;, deprecating &lt;code&gt;bdist_egg&lt;/code&gt;/&lt;code&gt;.egg&lt;/code&gt; uploads to PyPI has been &lt;a href=&#34;https://discuss.python.org/t/pep-715-disabling-bdist-egg-distribution-uploads-on-pypi/27610/13&#34;&gt;accepted&lt;/a&gt;. We&#39;ll begin the process of implementing this today.&lt;/p&gt; &lt;p&gt;Please note that this does &lt;strong&gt;NOT&lt;/strong&gt; remove any existing uploaded eggs from PyPI.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;The deprecation timeline is as follows:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Today, June 26, 2023: All maintainers of projects which have uploaded one or more eggs since January 1, 2023 will receive a one-time email informing them of this change.&lt;/li&gt; &lt;li&gt;Today, June 26, 2023: Each upload of an egg to PyPI will result in a notice being sent to all Owners and Maintainers for the project.&lt;/li&gt; &lt;li&gt;August 1, 2023: Uploads of eggs will be &lt;a href=&#34;https://i.kym-cdn.com/photos/images/original/001/402/192/398.jpg&#34;&gt;&lt;strong&gt;rejected&lt;/strong&gt;&lt;/a&gt; by PyPI.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;You can read more detailed rationale in &lt;a href=&#34;https://peps.python.org/pep-0715/#rationale&#34;&gt;PEP 715&lt;/a&gt;. Thanks to contributor &lt;a href=&#34;https://blog.yossarian.net&#34;&gt;William Woodruff&lt;/a&gt; for his work to author and propose PEP 715, as well as support the rollout of the implementation.&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Ee Durbin is the Director of Infrastructure at the Python Software Foundation. They have been contributing to keeping PyPI online, available, and secure since 2013.&lt;/em&gt;&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-06-26-deprecate-egg-uploads/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-06-26-deprecate-egg-uploads/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-06-26-deprecate-egg-uploads.png" type="image/png" length="62490" /> </item> <item> <title>PyPI hires a Safety &amp; Security Engineer</title> <author>Mike Fiedler</author> <category>hiring</category> <description>&lt;p&gt;👋 Hi there! I&#39;m Mike Fiedler (&lt;a href=&#34;https://github.com/miketheman&#34;&gt;@miketheman&lt;/a&gt;) I&#39;ve been a Python Package Index (&lt;a href=&#34;https://pypi.org/&#34;&gt;PyPI&lt;/a&gt;) contributor since early 2021, and became a maintainer in 2022. Now I&#39;m joining the &lt;a href=&#34;https://www.python.org/psf-landing/&#34;&gt;PSF&lt;/a&gt; to work on PyPI full-time as the first PyPI Safety &amp;amp; Security Engineer.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;What is that, you ask? We had &lt;a href=&#34;../2023-05-09-announcing-pypi-safety-and-security-engr-role/&#34;&gt;posted about this opening in May&lt;/a&gt;, and I&#39;m happy to be joining the team to help improve the safety and security of PyPI.&lt;/p&gt; &lt;p&gt;What does that mean, Mike? As safety and security are pretty broad topics, it boils down to working on any initiatives that we believe will increase the safety and security of the Package Index for all users - end users, package publishers, maintainers, and PyPI moderators and administrators. That&#39;s a huge audience!&lt;/p&gt; &lt;p&gt;All of us deserve to have a safe and secure experience when using PyPI, and I&#39;m excited to be able to work on this full-time.&lt;/p&gt; &lt;p&gt;Major thanks to Amazon Web Services (AWS) for their role in funding this position, and to the PSF for their continued support of the global Python community.&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Mike Fiedler is the inaugural PyPI Safety &amp;amp; Security Engineer since 2023.&lt;/em&gt;&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-08-04-pypi-hires-safety-engineer/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-08-04-pypi-hires-safety-engineer/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-08-04-pypi-hires-safety-engineer.png" type="image/png" length="61666" /> </item> <item> <title>2FA Enforcement for New User Registrations</title> <author>Mike Fiedler</author> <category>2fa</category> <category>security</category> <description>&lt;h2 id=&#34;whats-changing&#34;&gt;What&#39;s changing?&lt;/h2&gt; &lt;p&gt;Starting today, &lt;strong&gt;newly registered users must enable 2FA before they can perform any management actions on PyPI&lt;/strong&gt;. This change comes after we&#39;ve also added a rule for accounts to have a verified, &lt;em&gt;primary&lt;/em&gt; email address for the same set of management actions.&lt;/p&gt; &lt;p&gt;As a reminder, PyPI has supported adding 2FA since 2019.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;This change is continuing along the path of enforcing 2FA for all users. In May of this year we &lt;a href=&#34;../2023-05-25-securing-pypi-with-2fa/&#34;&gt;announced&lt;/a&gt; that by the end of 2023 PyPI will require all users to enable Two-Factor Authentication (2FA). That post has a wealth of information on what enforcement means, and how folks can prepare for the change before end of year.&lt;/p&gt; &lt;h2 id=&#34;what-are-management-actions&#34;&gt;What are management actions?&lt;/h2&gt; &lt;p&gt;Management actions may include any of the following:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Creating/managing Projects&lt;/li&gt; &lt;li&gt;Adding/removing API Tokens&lt;/li&gt; &lt;li&gt;Uploading/removing Releases&lt;/li&gt; &lt;li&gt;Adding/removing Collaborators&lt;/li&gt; &lt;li&gt;Requesting/managing &lt;a href=&#34;../2023-04-23-introducing-pypi-organizations/&#34;&gt;Organizations&lt;/a&gt;&lt;/li&gt; &lt;li&gt;Adding/managing &lt;a href=&#34;../2023-04-20-introducing-trusted-publishers/&#34;&gt;Trusted Publishers&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;This is not an exhaustive list, but should provide a good idea of the actions we&#39;re talking about.&lt;/p&gt; &lt;h2 id=&#34;how-does-this-affect-me&#34;&gt;How does this affect me?&lt;/h2&gt; &lt;p&gt;If you only need to browse, download, and install packages from PyPI then a PyPI account isn&#39;t needed so this change doesn&#39;t affect you.&lt;/p&gt; &lt;p&gt;If you&#39;ve already enabled 2FA on your PyPI account, this change will not affect you. Thanks for doing your part to keep the Python ecosystem safe!&lt;/p&gt; &lt;p&gt;If you recently registered a new PyPI account, you are required to enable 2FA before you can perform any management actions. When attempting to perform a management action, you may see a red banner flash at the top of the page, and be redirected to the 2FA setup page for your account.&lt;/p&gt; &lt;p&gt;You will still be able to log in, browse, and download packages without 2FA. But to perform any management actions, you&#39;ll need to enable 2FA.&lt;/p&gt; &lt;h2 id=&#34;is-this-the-end&#34;&gt;Is this the end?&lt;/h2&gt; &lt;p&gt;As a reminder, we will enforce the 2FA requirement for all PyPI users at the end of 2023.&lt;/p&gt; &lt;p&gt;These changes intend to mitigate scenarios like account takeovers, where an attacker may be able to gain access to a user&#39;s account using only an email and password (either via phishing or credential stuffing). If a user&#39;s email account access is compromised, and the attacker is able to successfully request a password reset, the attacker would still need to bypass the 2FA requirement.&lt;/p&gt; &lt;p&gt;The more users using a Two Factor Authentication methods available, the safer we all are. Today PyPI offers both &lt;a href=&#34;https://en.wikipedia.org/wiki/Time-based_one-time_password&#34;&gt;Time-based One-Time Passwords (TOTP)&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/WebAuthn&#34;&gt;WebAuthn&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Security is a spectrum. As long as we continue to make incremental progress, we&#39;ll improve the overall posture for &lt;strong&gt;all users&lt;/strong&gt; of PyPI.&lt;/p&gt; &lt;hr /&gt; &lt;p&gt;&lt;em&gt;Mike Fiedler is the PyPI Safety &amp;amp; Security Engineer since 2023.&lt;/em&gt;&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-08-08-2fa-enforcement-for-new-users/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-08-08-2fa-enforcement-for-new-users/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-08-08-2fa-enforcement-for-new-users.png" type="image/png" length="67997" /> </item> <item> <title>GitHub now scans public issues for PyPI secrets</title> <author>Mike Fiedler</author> <category>integrations</category> <category>security</category> <description>&lt;p&gt;Back in 2019 we &lt;a href=&#34;https://github.com/pypi/warehouse/issues/6051&#34;&gt;kicked off efforts&lt;/a&gt; to integrate with GitHub secret scanning. Due to the complexity in nature, the completed integration launched in 2021, with the volunteer-led effort by Joachim Jablon (&lt;a href=&#34;https://github.com/ewjoachim&#34;&gt;@ewjoachim&lt;/a&gt;) and the GitHub team.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;PyPI didn&#39;t have a blog back then, but GitHub did! Here&#39;s &lt;a href=&#34;https://github.blog/changelog/2021-03-22-the-python-package-index-is-now-a-github-secret-scanning-integrator/&#34;&gt;a link their post&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;The completed integration increased security for all PyPI users. If a user accidentally made their PyPI token public by committing it and pushing it to a GitHub public repository, GitHub would notify us and we would automatically revoke the token to prevent any misuse. This process often completes within seconds.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Cool, Mike, that was two years ago, so what?&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;GitHub &lt;a href=&#34;https://github.blog/changelog/2023-08-16-secret-scanning-detects-secrets-in-issues-for-free-public-repositories/&#34;&gt;announced yesterday&lt;/a&gt; that they will now notify users about any secrets exposed in:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;... issue&#39;s title, description, or comments, including historical revisions ...&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;This is a great enhancement to their existing secret scanning capabilities, previously only scanning &lt;strong&gt;code&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;And even better, we don&#39;t have to change anything, as we continue to receive the same notifications from our existing integration with GitHub. 🎉&lt;/p&gt; &lt;p&gt;When implementing the integration, we set up some metrics to track the number of tokens revoked. Here&#39;s a visualization showing the amount of inbound notifications from GitHub, and the number of tokens we&#39;ve revoked &lt;em&gt;(click for larger image)&lt;/em&gt;:&lt;/p&gt; &lt;figure&gt; &lt;p&gt;&lt;a href=&#34;../../assets/dd-gh-token-scanning_2023-08-16_18.12.26.png&#34;&gt;&lt;img alt=&#34;GitHub secret scanning metrics&#34; src=&#34;../../assets/dd-gh-token-scanning_2023-08-16_18.12.26.png&#34; /&gt;&lt;/a&gt; &lt;/p&gt; &lt;figcaption&gt;Bar charts of values over period of 1 year, and total counts of: received (5.87k), valid (537), processed (535)&lt;/figcaption&gt; &lt;/figure&gt; &lt;p&gt;We show 15 months of data, thanks to our infrastructure sponsor &lt;a href=&#34;https://www.datadoghq.com/&#34;&gt;Datadog&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;The large discrepancy between &#34;received&#34; and &#34;valid&#34; can be due to GitHub reporting the same token multiple times. This can happen if the token is used in multiple repositories, forks, etc.&lt;/p&gt; &lt;p&gt;The small discrepancy between &#34;valid&#34; and &#34;processed&#34; is due to that we may raise an exception when attempting to email the owner of a token that has been revoked, after which we don&#39;t mark it as &#34;processed&#34; when exception is raised. The second time the task runs, the token is now invalid, and doesn&#39;t increment the &#34;valid&#34; count.&lt;/p&gt; &lt;p&gt;Read &lt;a href=&#34;https://github.blog/changelog/2023-08-16-secret-scanning-detects-secrets-in-issues-for-free-public-repositories/&#34;&gt;GitHub&#39;s notice on The Changelog&lt;/a&gt; for more details.&lt;/p&gt; &lt;p&gt;If your organization performs this kind of secret scanning, we&#39;d love to hear from you and integrate with your tooling.&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-08-17-github-token-scanning-for-public-repos/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-08-17-github-token-scanning-for-public-repos/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-08-17-github-token-scanning-for-public-repos.png" type="image/png" length="72393" /> </item> <item> <title>Inbound Malware Volume Report</title> <author>Mike Fiedler</author> <category>security</category> <category>transparency</category> <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt; &lt;p&gt;The current &lt;a href=&#34;https://pypi.org/security/&#34;&gt;PyPI security reporting procedure&lt;/a&gt; directs reporters to send an email to &lt;a href=&#34;&amp;#109;&amp;#97;&amp;#105;&amp;#108;&amp;#116;&amp;#111;&amp;#58;&amp;#115;&amp;#101;&amp;#99;&amp;#117;&amp;#114;&amp;#105;&amp;#116;&amp;#121;&amp;#64;&amp;#112;&amp;#121;&amp;#112;&amp;#105;&amp;#46;&amp;#111;&amp;#114;&amp;#103;&#34;&gt;&amp;#115;&amp;#101;&amp;#99;&amp;#117;&amp;#114;&amp;#105;&amp;#116;&amp;#121;&amp;#64;&amp;#112;&amp;#121;&amp;#112;&amp;#105;&amp;#46;&amp;#111;&amp;#114;&amp;#103;&lt;/a&gt; with details. &lt;code&gt;security@&lt;/code&gt; was previously an email alias for &lt;code&gt;admin@&lt;/code&gt;, a Google Group that contains all current PyPI Administrators (4 people).&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;I&#39;ll refer to the &lt;code&gt;security@&lt;/code&gt; address as the &lt;strong&gt;Security Inbox&lt;/strong&gt; herein, despite it not being a traditional inbox, and what changes we&#39;ve made to it.&lt;/p&gt; &lt;p&gt;The inbound reporting workflow was roughly:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Admins receive an email to the Security Inbox&lt;/li&gt; &lt;li&gt;Any admin reads the email&lt;/li&gt; &lt;li&gt;Admin inspects the indicators of compromise (IOC), package and user history. Often using &lt;a href=&#34;https://inspector.pypi.io/&#34;&gt;inspector.pypi.io&lt;/a&gt; to investigate package contents&lt;/li&gt; &lt;li&gt;Admin takes an action (oftentimes to remove as malware)&lt;/li&gt; &lt;li&gt;Admin responds to reporter and CCs the Security Inbox for tracking&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Here&#39;s a rough sequence diagram demonstrating the notification flow:&lt;/p&gt; &lt;pre class=&#34;mermaid&#34;&gt;&lt;code&gt;sequenceDiagram participant External participant googleMail as Google Mail Routing participant googleGroup as Security Inbox&amp;lt;br/&amp;gt;(Google Group) participant pypiAdmins as PyPI Admins External-&amp;gt;&amp;gt;googleMail: sends an email report googleMail-&amp;gt;&amp;gt;googleGroup: sends an email report googleGroup-&amp;gt;&amp;gt;+pypiAdmins: sends an email report pypiAdmins--&amp;gt;&amp;gt;+External: Hi, thanks for your report... pypiAdmins--&amp;gt;&amp;gt;-googleGroup: CC response to group title Previous Inbound Flow&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;I wanted to answer a couple of questions, so as to have some data to work with:&lt;/p&gt; &lt;ol&gt; &lt;li&gt;How many inbound malware reports do we receive? (daily/weekly/monthly)&lt;/li&gt; &lt;li&gt;How long does it take for a response from an administrator to remove the reported malware?&lt;/li&gt; &lt;/ol&gt; &lt;p&gt;Answering these questions with the email-based system will not be 100% accurate, as there are some conditions that lead to inaccuracies, but since we&#39;re looking at large volumes of records, it&#39;s unlikely that the inaccuracies will lead to material differences in the numbers. Some examples:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;A reporter submits multiple reports on a single message&lt;/li&gt; &lt;li&gt;A reporter replies to their original email for a new report, instead of starting a new thread/conversation&lt;/li&gt; &lt;li&gt;Folks contacting admins for support may email the Security Inbox&lt;/li&gt; &lt;li&gt;Security-related issues that are not malware reports&lt;/li&gt; &lt;li&gt;Spam&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;This was largely to establish a baseline of where we are today. With this measurement, we can then observe whether changes to our process have a positive or negative impact on the volume and response times.&lt;/p&gt; &lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: This analysis is not an accurate measurement of distinct malware packages reported, as multiple researchers may report the same package, which will show up as distinct conversation threads. We could try to normalize the packages, however since the emails are unstructured, that may take more effort than is worthwhile. In any case, admins respond to duplicates, so there&#39;s still non-zero effort being done.&lt;/p&gt; &lt;p&gt;Earlier this year one of our admins posted some removals stats on Twitter (before we had a blog!):&lt;/p&gt; &lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;in 2022, the &lt;a href=&#34;https://twitter.com/pypi?ref_src=twsrc%5Etfw&#34;&gt;@pypi&lt;/a&gt; team removed &amp;gt;12,000 unique projects. each were instances of spam, typosquatting, dependency confusion, exfiltration and/or malware.&lt;br&gt;&lt;br&gt;2022: ~12K (mostly malware)&lt;br&gt;2021: ~27K (mostly dep confusion)&lt;br&gt;2020: ~500&lt;br&gt;2019: 65&lt;br&gt;2018: 137&lt;br&gt;2017: 38&lt;/p&gt;&amp;mdash; Dustin Ingram (@di_codes) &lt;a href=&#34;https://twitter.com/di_codes/status/1610781657128108033?ref_src=twsrc%5Etfw&#34;&gt;January 4, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt; &lt;h2 id=&#34;what-is-the-frequency-of-inbound-malware-reports&#34;&gt;What is the frequency of inbound malware reports?&lt;/h2&gt; &lt;p&gt;To determine this answer, I elected to use the emails themselves, since that&#39;s the feed we have of details. While this is an imperfect data source, it should be good enough to make some early assertions. We can also leverage any statistics generated thus far to assist in measuring future progress.&lt;/p&gt; &lt;p&gt;My PyPI Google Workspace account was created on 2023-03-02 and began receiving &lt;a href=&#34;&amp;#109;&amp;#97;&amp;#105;&amp;#108;&amp;#116;&amp;#111;&amp;#58;&amp;#115;&amp;#101;&amp;#99;&amp;#117;&amp;#114;&amp;#105;&amp;#116;&amp;#121;&amp;#64;&amp;#112;&amp;#121;&amp;#112;&amp;#105;&amp;#46;&amp;#111;&amp;#114;&amp;#103;&#34;&gt;&amp;#115;&amp;#101;&amp;#99;&amp;#117;&amp;#114;&amp;#105;&amp;#116;&amp;#121;&amp;#64;&amp;#112;&amp;#121;&amp;#112;&amp;#105;&amp;#46;&amp;#111;&amp;#114;&amp;#103;&lt;/a&gt; emails after that. Google Groups does not surface any APIs I could find that allows an authenticated user to list/read conversations directly from the group.&lt;/p&gt; &lt;p&gt;This approach provides a cleaner signal-to-noise ratio, as I often delete the random emails that are sent to the Security Inbox if they are not useful (spam, marketing, etc). We can either accept this approach of data collection for the past few months, or pursue other methods for collecting longer-term data from older accounts/mailing lists, all subject to different data quality issues.&lt;/p&gt; &lt;p&gt;Using the data based on 1,303 email threads sent to the Security Inbox by 2023-08-14, we can produce this chart:&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Inbound Malware Reports by Date&#34; src=&#34;../../assets/2023-09-18-inbound-malware-reporting/inbound-by-date.png&#34; /&gt;&lt;/p&gt; &lt;p&gt;The same data, grouped by week number:&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Inbound Malware Reports by Week&#34; src=&#34;../../assets/2023-09-18-inbound-malware-reporting/inbound-by-week.png&#34; /&gt;&lt;/p&gt; &lt;p&gt;One observation is that post-&lt;a href=&#34;https://status.python.org/incidents/qy2t9mjjcc7g&#34;&gt;PyPI Weekend Suspension&lt;/a&gt; in May (Week 20), the overall volume drops for a while. There&#39;s no hard evidence as to why, but it&#39;s interesting that a brief disruption reduced some of the toil maintainers currently handle.&lt;/p&gt; &lt;p&gt;Form completeness, here&#39;s the monthly view:&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Inbound Malware Reports by Month&#34; src=&#34;../../assets/2023-09-18-inbound-malware-reporting/inbound-by-month.png&#34; /&gt;&lt;/p&gt; &lt;hr /&gt; &lt;h2 id=&#34;how-long-does-it-take-to-respond&#34;&gt;How long does it take to respond?&lt;/h2&gt; &lt;p&gt;Why is response time interesting? The longer a malicious package is available for end users to install, the more people and systems it may affect. This is further complicated to any package mirrors that capture the malware, and may not remove it as quickly as PyPI admins. We have received anecdotal evidence from reporters that PyPI admins are already quite fast at handling inbound reports (&lt;code&gt;#humblebrag&lt;/code&gt;), but let&#39;s see if we can get data out of the same emails.&lt;/p&gt; &lt;p&gt;Again, since the nature of email isn&#39;t 100% accurate in this case, we&#39;ll rely on calculating the duration of time (in minutes) between the first message of a thread and the last message of a thread. This doesn&#39;t account for the occasional behavior of a reporter reusing the same thread to report more packages, nor does it reflect any other back-and-forth communication between admins and reporters. As such, removing any threads that have more than 4 total messages helps remove outliers from the analysis.&lt;/p&gt; &lt;p&gt;Inbound reports come in at any time of day, and can also be automatically generated by reporters. It&#39;s common for reports to wait to be handled while we’re asleep. Weekends and holidays often have longer response times as well.&lt;/p&gt; &lt;p&gt;On occasion an inbound report may get overlooked, something we&#39;re trying to solve with a new system, more on this later.&lt;/p&gt; &lt;p&gt;A higher response time may indicate that the mostly-volunteer admins missed responding to it the first time around. For the purpose of this analysis, I&#39;ve excluded 7 total response times that exceed 14 days (20,160 minutes) to remove those outliers.&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Response Times by Date&#34; src=&#34;../../assets/2023-09-18-inbound-malware-reporting/med-response-times-by-date.png&#34; /&gt;&lt;/p&gt; &lt;p&gt;Using median values over averages helps us account for outliers at either end of the spectrum.&lt;/p&gt; &lt;p&gt;Applying a linear trend line to the collected data shows that response times are generally decreasing over time, which is a good thing.&lt;/p&gt; &lt;p&gt;Here&#39;s a distribution of response times for the data collected since March 2023:&lt;/p&gt; &lt;p&gt;&lt;img alt=&#34;Response Times Histogram&#34; src=&#34;../../assets/2023-09-18-inbound-malware-reporting/hist-resp-time-minutes.png&#34; /&gt;&lt;/p&gt; &lt;p&gt;This chart informs us that most responses to reports are completed in under ~485 minutes (~8 hours), and almost all are done within a few days of the report, with a long tail.&lt;/p&gt; &lt;p&gt;All said, that doesn&#39;t capture the full picture of response times, which is why we&#39;ve been working on a new system to help us respond faster, and produce better reports.&lt;/p&gt; &lt;h2 id=&#34;whats-changed-as-a-result&#34;&gt;What&#39;s changed as a result?&lt;/h2&gt; &lt;p&gt;Part of the PyPI Malware Reporting and Response project is to explore ways to decrease the response times even further, while reducing the toil on maintainers, and increasing visibility to reporters.&lt;/p&gt; &lt;p&gt;As a result of this analysis, one change we&#39;ve made so far is to leverage a shared inbox system &lt;a href=&#34;https://www.helpscout.com/&#34;&gt;Help Scout&lt;/a&gt; to receive inbound emails and allow us to tag, assign, and close out reports. This helps is by not missing reports, and preventing duplicate responses from admins.&lt;/p&gt; &lt;p&gt;Here&#39;s how we updated our Google Workspace flow so we can continue to receive inbound emails, as well as copy the conversations from Help Scout for long-term archival into Google Groups.&lt;/p&gt; &lt;pre class=&#34;mermaid&#34;&gt;&lt;code&gt;sequenceDiagram participant External participant googleMail as Google Mail Routing participant googleGroup as Google Group&amp;lt;br/&amp;gt;Archive participant helpScout as Security Inbox&amp;lt;br/&amp;gt;(Help Scout) participant pypiAdmins as PyPI Admins External-&amp;gt;&amp;gt;googleMail: sends an email report googleMail-&amp;gt;&amp;gt;+helpScout: sends an email report googleMail-&amp;gt;&amp;gt;googleGroup: archive email helpScout-&amp;gt;&amp;gt;+pypiAdmins: inbound notification pypiAdmins--&amp;gt;&amp;gt;-helpScout: Hi, thanks for your report... helpScout-&amp;gt;&amp;gt;googleGroup: archive via auto-bcc helpScout--&amp;gt;&amp;gt;-External: Hi, thanks for your report... title Updated Inbound Flow&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;There&#39;s no change to end users, and we hope our change keeps us on track to continue to respond to reports in a timely fashion.&lt;/p&gt; &lt;p&gt;Here&#39;s an initial look at the response times since we started using Help Scout, (2023-09-05) using their reporting. With a total of 31 conversations in the time period since we started using Help Scout, and comparing to the final two weeks of data from the previous chart (59 conversations), we can see that the response times have improved:&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;Response time bucket&lt;/th&gt; &lt;th&gt;% of total&lt;/th&gt; &lt;th&gt;pre-Help Scout&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;&amp;lt; 15 min&lt;/td&gt; &lt;td&gt;40%&lt;/td&gt; &lt;td&gt;53%&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;15-30 min&lt;/td&gt; &lt;td&gt;20%&lt;/td&gt; &lt;td&gt;10%&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;30-60 min&lt;/td&gt; &lt;td&gt;20%&lt;/td&gt; &lt;td&gt;5%&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;1-2 hours&lt;/td&gt; &lt;td&gt;10%&lt;/td&gt; &lt;td&gt;7%&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;2-12 hours&lt;/td&gt; &lt;td&gt;10%&lt;/td&gt; &lt;td&gt;15%&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;12+ hours&lt;/td&gt; &lt;td&gt;0%&lt;/td&gt; &lt;td&gt;10%&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;We can now happily report that 80% all reports are responded to &lt;strong&gt;within 60 minutes of receipt&lt;/strong&gt;, with 100% are responded to within 12 hours.&lt;/p&gt; &lt;p&gt;We will continue to monitor our response times and volumes, and make adjustments as needed.&lt;/p&gt; &lt;h2 id=&#34;whats-next&#34;&gt;What&#39;s next?&lt;/h2&gt; &lt;p&gt;We&#39;re working on designing a new system to help us respond faster, based in inbound reports, and provide better outcomes. It&#39;s still very early, and we&#39;re incorporating a lot of ideas in the design based on collective experience of PyPI admins, external researchers and reporters.&lt;/p&gt; &lt;p&gt;We invite you to engage in the conversation on a more machine-readable format for reporting malware in &lt;a href=&#34;https://github.com/pypi/warehouse/issues/14503&#34;&gt;this GitHub Issue&lt;/a&gt;, and consider sending pull requests where appropriate.&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-09-18-inbound-malware-reporting/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-09-18-inbound-malware-reporting/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-09-18-inbound-malware-reporting.png" type="image/png" length="60018" /> </item> <item> <title>PyPI has completed its first security audit</title> <author>Dustin Ingram</author> <category>security</category> <category>transparency</category> <description>&lt;p&gt;&lt;em&gt;This is part one in a three-part series. See &lt;a href=&#34;../2023-11-14-2-security-audit-remediation-warehouse/&#34;&gt;part two here&lt;/a&gt;, and &lt;a href=&#34;../2023-11-14-3-security-audit-remediation-cabotage/&#34;&gt;part three here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt; &lt;p&gt;We are proud to announce that PyPI has completed its first ever external security audit. This work was funded in partnership with the &lt;a href=&#34;https://www.opentech.fund/&#34;&gt;Open Technology Fund&lt;/a&gt; (OTF), &lt;a href=&#34;https://www.opentech.fund/results/supported-projects/pypi-improvements/&#34;&gt;a previous supporter&lt;/a&gt; of security-related improvements to PyPI.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;The Open Technology Fund selected &lt;a href=&#34;https://www.trailofbits.com/&#34;&gt;Trail of Bits&lt;/a&gt;, an industry-leading cybersecurity firm with significant open-source and Python experience, to perform the audit. Trail of Bits spent a total of 10 engineer-weeks of effort identifying issues, presenting those findings to the PyPI team, and assisting us as we remediated the findings.&lt;/p&gt; &lt;h2 id=&#34;scope&#34;&gt;Scope&lt;/h2&gt; &lt;p&gt;The audit was focused on &#34;Warehouse&#34;, the open-source codebase that powers &lt;a href=&#34;https://pypi.org&#34;&gt;https://pypi.org&lt;/a&gt;, and on &#34;cabotage&#34;, the custom open-source container orchestration framework we use to deploy Warehouse. It included code review of both codebases, prioritizing areas that accept user input, provide APIs and other public surfaces. The audit also covered the continuous integration / continuous deployment (CI/CD) configurations for both codebases.&lt;/p&gt; &lt;h2 id=&#34;findings&#34;&gt;Findings&lt;/h2&gt; &lt;p&gt;Overall, the auditors determined the Warehouse codebase &#34;was adequately tested and conformed to widely accepted best practices for secure Python and web development,&#34; and that while the cabotage codebase lacks the same level of testing, they did not identify any high severity issues in either codebase.&lt;/p&gt; &lt;h2 id=&#34;results-impact&#34;&gt;Results &amp;amp; Impact&lt;/h2&gt; &lt;p&gt;As a result of the audit, Trail of Bits detailed 29 different advisories discovered across both codebases. When evaluating severity level of each advisory, 14 were categorized as &#34;informational&#34;, 6 as &#34;low&#34;, 8 as &#34;medium&#34; and zero as &#34;high&#34;. At the time of writing, the PyPI team has remediated all advisories that posed a significant risk in both codebases where possible, and has worked with third-party teams to unblock additional remediations where necessary.&lt;/p&gt; &lt;h2 id=&#34;more-details&#34;&gt;More details&lt;/h2&gt; &lt;p&gt;In the interest of transparency, today we are publishing the &lt;a href=&#34;https://github.com/trailofbits/publications#technology-product-reviews&#34;&gt;full results of the audit&lt;/a&gt;, as prepared by Trail of Bits. You can read more about the audit from their perspective in their &lt;a href=&#34;https://blog.trailofbits.com/2023/11/14/our-audit-of-pypi/&#34;&gt;accompanying blog post&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;Additionally, in two additional blog posts published today, Mike Fiedler (PyPI Security &amp;amp; Safety Engineer) goes into detail about &lt;a href=&#34;../2023-11-14-2-security-audit-remediation-warehouse/&#34;&gt;how we remediated these findings in Warehouse&lt;/a&gt; and Ee Durbin (Python Software Foundation Director of Infrastructure) &lt;a href=&#34;../2023-11-14-3-security-audit-remediation-cabotage/&#34;&gt;similarly details remediation&#39;s in cabotage&lt;/a&gt;.&lt;/p&gt; &lt;h2 id=&#34;acknowledgements&#34;&gt;Acknowledgements&lt;/h2&gt; &lt;p&gt;We would like to thank the Open Technology Fund for their continued support of PyPI and specifically for this significant security milestone for the Python ecosystem. We would also like to thank Trail of Bits for being a dependable, thorough and thoughtful partner throughout the process.&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-11-14-1-pypi-completes-first-security-audit/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-11-14-1-pypi-completes-first-security-audit/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-11-14-1-pypi-completes-first-security-audit.png" type="image/png" length="61950" /> </item> <item> <title>Security Audit Remediation: Warehouse</title> <author>Mike Fiedler</author> <category>security</category> <category>transparency</category> <description>&lt;p&gt;&lt;em&gt;This is part two in a three-part series. See &lt;a href=&#34;../2023-11-14-1-pypi-completes-first-security-audit/&#34;&gt;part one here&lt;/a&gt;, and &lt;a href=&#34;../2023-11-14-3-security-audit-remediation-cabotage/&#34;&gt;part three here&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;This post is a deeper dive into the remediation of the security audit findings for the Warehouse - the main codebase for &lt;a href=&#34;https://pypi.org&#34;&gt;PyPI.org&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;The audit report can be found &lt;a href=&#34;../2023-11-14-1-pypi-completes-first-security-audit/&#34;&gt;here&lt;/a&gt;. I highly recommend reading that for the fullest context first.&lt;/p&gt; &lt;!-- more --&gt; &lt;h2 id=&#34;findings&#34;&gt;Findings&lt;/h2&gt; &lt;p&gt;The audit report identified 18 findings for Warehouse, along with some code quality suggestions. This post will focus on the findings and their remediation. Some of the code quality suggestions were implemented, others deferred.&lt;/p&gt; &lt;p&gt;Here&#39;s a table of the items that are relevant to warehouse, and their status:&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;ID&lt;/th&gt; &lt;th&gt;Title&lt;/th&gt; &lt;th&gt;Severity&lt;/th&gt; &lt;th&gt;Difficulty&lt;/th&gt; &lt;th&gt;Status&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-1&lt;/td&gt; &lt;td&gt;Unsafe input handling in &#34;Combine PRs&#34; workflow&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/pypi/warehouse/pull/14528&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-2&lt;/td&gt; &lt;td&gt;Weak signatures used in AWS SNS verification&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;Undetermined&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/pypi/warehouse/pull/14387&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-4&lt;/td&gt; &lt;td&gt;Lack of rate limiting on endpoints that send email&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;Accepted&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-5&lt;/td&gt; &lt;td&gt;Account status information leak for frozen and disabled accounts&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/pypi/warehouse/pull/14449&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-6&lt;/td&gt; &lt;td&gt;Potential race conditions in search locking&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/pypi/warehouse/pull/14640&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-7&lt;/td&gt; &lt;td&gt;Use of multiple distinct URL parsers&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;Undetermined&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/pypi/warehouse/pull/14497&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-8&lt;/td&gt; &lt;td&gt;Overly permissive CSP headers on XML views&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/pypi/warehouse/pull/14452&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-9&lt;/td&gt; &lt;td&gt;Missing Permissions-Policy&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/pypi/infra/pull/160&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-10&lt;/td&gt; &lt;td&gt;Domain separation in file digests&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/pypi/warehouse/pull/14492&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-11&lt;/td&gt; &lt;td&gt;Object storage susceptible to TOC/TOU due to temporary files&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;Accepted&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-12&lt;/td&gt; &lt;td&gt;HTTP header is silently trusted if token mismatches&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/pypi/warehouse/pull/14499&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-13&lt;/td&gt; &lt;td&gt;Bleach library is deprecated&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;Undetermined&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/pypi/warehouse/pull/14526&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-14&lt;/td&gt; &lt;td&gt;Weak hashing in storage backends&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;Accepted&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-15&lt;/td&gt; &lt;td&gt;Uncaught exception with crafted README&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;Accepted&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-16&lt;/td&gt; &lt;td&gt;ReDoS via zxcvbn-python dependency&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;Accepted&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-23&lt;/td&gt; &lt;td&gt;Insecure XML processing in XMLRPC server&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/pypi/warehouse/pull/14491&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-27&lt;/td&gt; &lt;td&gt;Denial-of-service risk on tar.gz uploads&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;Accepted&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-29&lt;/td&gt; &lt;td&gt;Unescaped values in LIKE SQL queries&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;Accepted&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;&lt;em&gt;IDs are non-consecutive, as the audit report included findings for cabotage as well.&lt;/em&gt;&lt;/p&gt; &lt;p&gt;For some of the Remediated entries and all the Accepted ones, I&#39;ll go into more detail below.&lt;/p&gt; &lt;h2 id=&#34;details&#34;&gt;Details&lt;/h2&gt; &lt;p&gt;Now that you&#39;ve had a chance to read the original audit report, and can see that we&#39;ve remediated most of the findings, I wanted to take some time to dig into some specifics of particular findings.&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-2-weak-signatures-used-in-aws-sns-verification&#34;&gt;TOB-PYPI-2: Weak signatures used in AWS SNS verification&lt;/h3&gt; &lt;p&gt;PyPI uses AWS SES to send emails to users. The SES configuration is set to use a &lt;a href=&#34;https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html#mailing-list-notification&#34;&gt;Message Delivery Status&lt;/a&gt; topic, which sends a notification to an AWS SNS topic, which then sends a notification to our application.&lt;/p&gt; &lt;p&gt;This is useful for things like &#34;Accepted/Delivered&#34;, but more importantly &#34;Bounced&#34; and &#34;Complaint&#34; notifications, which change the status of user accounts. We don&#39;t want to send more emails to a known bad address, and we don&#39;t want to send emails to users who have marked us as spam.&lt;/p&gt; &lt;p&gt;Since PyPI receives a webhook from AWS SNS, it needs to verify the signature of the message.&lt;/p&gt; &lt;p&gt;Verifying inbound SNS messages has generally been left up to the user. &lt;a href=&#34;https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html&#34;&gt;The AWS SNS docs&lt;/a&gt; are clear about that.&lt;/p&gt; &lt;p&gt;We had previously implemented signature verification for version 1, which uses the SHA1 hash algorithm, as that is what existed when we implemented it.&lt;/p&gt; &lt;p&gt;As time evolved, and AWS SNS added support for SHA256, the path to upgrade was still left in the hands of the user. SNS still defaults to SHA1 (&lt;code&gt;SignatureVersion: &#39;1&#39;&lt;/code&gt;), and there&#39;s no Python SDK function to call to validate the signature for you.&lt;/p&gt; &lt;p&gt;This is &lt;a href=&#34;https://github.com/boto/boto3/issues/2508&#34;&gt;also an outstanding request from boto3 users&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;In September 2022, AWS SNS added support for SHA256 signatures, and shared the details in &lt;a href=&#34;https://aws.amazon.com/blogs/security/sign-amazon-sns-messages-with-sha256-hashing-for-http-subscriptions/&#34;&gt;this blog post&lt;/a&gt;. They also added support for verification in some of the client-side SDKs, but sadly Python is not one of them yet.&lt;/p&gt; &lt;p&gt;While we were already validating SignatureVersion 1, we took this opportunity to add support for SignatureVersion 2, update our settings, and now only accept SHA256 signatures.&lt;/p&gt; &lt;p&gt;As an &lt;a href=&#34;https://aws.amazon.com/developer/community/heroes/&#34;&gt;AWS Hero&lt;/a&gt;, I reached out to Farrah Campbell who heads up Modern Compute Community at AWS, and she quickly connected me with the AWS SNS service team for a chat. We discussed some of the challenges, as well as some ideas for the path forward.&lt;/p&gt; &lt;p&gt;I&#39;m hopeful that sometime in the future we will see two big things:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;message validation in &lt;code&gt;boto3&lt;/code&gt; for both signature versions This would enable us to remove &lt;a href=&#34;https://github.com/pypi/warehouse/blob/256d3e374ff8c2c3b29ac1b3040c88ddfb7e1d76/warehouse/utils/sns.py&#34;&gt;&lt;code&gt;MessageVerifier&lt;/code&gt; we added to warehouse&lt;/a&gt;, and benefit from any future enhancements to the validation process.&lt;/li&gt; &lt;li&gt;update AWS SNS to default to &lt;code&gt;SignatureVersion: 2&lt;/code&gt; (SHA256). This could be a breaking change for users who have not updated their settings, but would be a good step forward for security. This make take some time, as the new signature version was only added a year ago. I&#39;ll leave that up to the SNS service team.&lt;/li&gt; &lt;/ul&gt; &lt;h3 id=&#34;tob-pypi-4-lack-of-rate-limiting-on-endpoints-that-send-email&#34;&gt;TOB-PYPI-4: Lack of rate limiting on endpoints that send email&lt;/h3&gt; &lt;p&gt;We accepted this finding, as we need to send emails to unverified users as part of the account creation process, and we don&#39;t want to block that.&lt;/p&gt; &lt;p&gt;The finding details that PyPI doesn&#39;t apply blanket rate limiting, which is correct. The endpoints that send emails to &lt;strong&gt;unverified&lt;/strong&gt; addresses are protected via rate limiting.&lt;/p&gt; &lt;p&gt;PyPI has compensating controls in place to prevent abuse, such as preventing too many password reset emails from being sent to a single user.&lt;/p&gt; &lt;p&gt;Since the risk here was to the cost and reputation of the email service, we decided to accept this finding. At some future point we may revisit the rate limiting strategy for sending email.&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-6-potential-race-conditions-in-search-locking&#34;&gt;TOB-PYPI-6: Potential race conditions in search locking&lt;/h3&gt; &lt;p&gt;This is another case of &#34;we implemented something that was good at the time, and as time went on a better solution became available&#34;.&lt;/p&gt; &lt;p&gt;We had written a context manager to handle locking the search index when performing updates, to prevent multiple processes from trying to update the search index at the same time. The implementation wasn&#39;t tied to the underlying Redis lock expiration, so could lead to the Redis-lock expiring, but Python believing it was still locked.&lt;/p&gt; &lt;p&gt;Here we updated our implementation to use a context manager that &lt;code&gt;redis-py&lt;/code&gt; now provides, instead of crafting our own.&lt;/p&gt; &lt;p&gt;A solid reminder to check back on your libraries and services now and then, to see if there&#39;s new features that can help you out.&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-11-object-storage-susceptible-to-toctou-due-to-temporary-files&#34;&gt;TOB-PYPI-11: Object storage susceptible to TOC/TOU due to temporary files&lt;/h3&gt; &lt;p&gt;This is a complex timing attack, which requires a level of access to the system that would allow for a more direct attack. The finding itself details that if an attacker could execute this, they are more likely to do other kinds of damage.&lt;/p&gt; &lt;p&gt;The complexity of navigating between our various storage backends/client APIs does not appear to be worth the resulting defense in depth, given the required access level to exploit.&lt;/p&gt; &lt;p&gt;We have a &lt;a href=&#34;https://github.com/pypi/warehouse/pull/14568&#34;&gt;draft PR&lt;/a&gt; with a start of implementation should we decide to pursue this.&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-14-weak-hashing-in-storage-backends&#34;&gt;TOB-PYPI-14: Weak hashing in storage backends&lt;/h3&gt; &lt;p&gt;This is specifically about the Backblaze B2 storage backend, one of PyPI&#39;s current object storage providers, which does not currently support SHA-256 checksums. They do support SHA-1 which is useful for detecting data corruption in transit, but is insufficient for non-colliding checksums - we have to use MD5 for that.&lt;/p&gt; &lt;p&gt;During the audit, we reached out to the Backblaze team to discuss and determined it&#39;s on their roadmap, and when they implement it, we&#39;ll update our usage accordingly.&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-15-uncaught-exception-with-crafted-readme&#34;&gt;TOB-PYPI-15: Uncaught exception with crafted README&lt;/h3&gt; &lt;p&gt;This finding discovered a bug in &lt;code&gt;docutils&lt;/code&gt;, which PyPI uses via the &lt;code&gt;readme_renderer&lt;/code&gt; library to render project descriptions from reStructuredText and Markdown to HTML.&lt;/p&gt; &lt;p&gt;The bug is &lt;a href=&#34;https://sourceforge.net/p/docutils/bugs/474/&#34;&gt;tracked here&lt;/a&gt;, and has yet to see a response from the maintainers.&lt;/p&gt; &lt;p&gt;It only applies to client-side behavior when using reStructuredText for a README, so we&#39;ve accepted this finding. Additionally, any user performing &lt;code&gt;twine check&lt;/code&gt; prior to upload will surface this issue.&lt;/p&gt; &lt;p&gt;Once the bug is fixed, we&#39;ll update!&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-16-redos-via-zxcvbn-python-dependency&#34;&gt;TOB-PYPI-16: ReDoS via &lt;code&gt;zxcvbn-python&lt;/code&gt; dependency&lt;/h3&gt; &lt;p&gt;Direct from the audit:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;This finding is purely informational. We believe that it has virtually no impact, like many ReDoS vulnerabilities, due to Warehouse’s deployment architecture.&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;Enough said.&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-23-insecure-xml-processing-in-xmlrpc-server&#34;&gt;TOB-PYPI-23: Insecure XML processing in XMLRPC server&lt;/h3&gt; &lt;p&gt;The audit began when the Warehouse deployment was on Debian 11 &lt;code&gt;bullseye&lt;/code&gt;, and as part of normal maintenance we upgraded to &lt;a href=&#34;https://github.com/pypi/warehouse/pull/14491&#34;&gt;Debian 12 &lt;code&gt;bookworm&lt;/code&gt;&lt;/a&gt; while the audit was in progress.&lt;/p&gt; &lt;p&gt;Python XMLRPC uses &lt;a href=&#34;https://libexpat.github.io/&#34;&gt;&lt;code&gt;expat&lt;/code&gt;&lt;/a&gt; for XML parsing. The version of &lt;code&gt;expat&lt;/code&gt; in Debian &lt;code&gt;bullseye&lt;/code&gt; was &lt;code&gt;2.2.10&lt;/code&gt;, which was vulnerable to the specific attack detailed in the audit report.&lt;/p&gt; &lt;p&gt;With &lt;code&gt;bookworm&lt;/code&gt;, the version of &lt;a href=&#34;https://packages.debian.org/bookworm/libexpat1&#34;&gt;&lt;code&gt;expat&lt;/code&gt; is &lt;code&gt;2.5.0&lt;/code&gt;&lt;/a&gt;, which is not vulnerable. (Generally considered &lt;a href=&#34;https://github.com/python/cpython/pull/26945&#34;&gt;fixed as of 2.4.1.&lt;/a&gt;)&lt;/p&gt; &lt;p&gt;This was a tricky one to track down, as once the report came in I was unable to reproduce the issue locally, as I had already upgraded.&lt;/p&gt; &lt;p&gt;Using some &lt;code&gt;git bisect&lt;/code&gt; magic, I was able to track down the exact commit that fixed the issue (the &lt;code&gt;bookworm&lt;/code&gt; upgrade), and then it was a matter of figuring out which library had changed.&lt;/p&gt; &lt;p&gt;After figuring it out, I worked with the auditors to update their recommendations to reflect the upgrade. Until now, the general recommendation was to adopt &lt;code&gt;defusedxml&lt;/code&gt;, which might have proven harder as we delegate the majority of our XML parsing to &lt;code&gt;pyramid-rpc&lt;/code&gt;, which uses &lt;code&gt;xmlrpc.client&lt;/code&gt; from the standard library.&lt;/p&gt; &lt;p&gt;If you want to check your own installation, you can run the following:&lt;/p&gt; &lt;div class=&#34;highlight&#34;&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;python&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;-c&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;import pyexpat; print(pyexpat.EXPAT_VERSION)&amp;quot;&lt;/span&gt; &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; &lt;p&gt;On &lt;code&gt;bookworm&lt;/code&gt;, we get &lt;code&gt;expat_2.5.0&lt;/code&gt;, which is not affected by the vulnerability.&lt;/p&gt; &lt;p&gt;This was remediated by underlying OS update to &lt;code&gt;bookworm&lt;/code&gt;. Debian distributions pin to a specific version of libraries for the duration of that distribution version.&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-27-denial-of-service-risk-on-targz-uploads&#34;&gt;TOB-PYPI-27: Denial-of-service risk on tar.gz uploads&lt;/h3&gt; &lt;p&gt;This is a tricky one, as it&#39;s a tradeoff between usability and security.&lt;/p&gt; &lt;p&gt;The audit report details a specific attack vector, where a malicious user could upload a tarball with a highly-compressed file, which would cause the server to spend a lot of time decompressing it.&lt;/p&gt; &lt;p&gt;Since we accept uploads from the general public, we have to take precautions whenever possible to prevent abuse. When it comes to ZIP files (which all &lt;code&gt;.whl&lt;/code&gt; or &#34;wheel&#34; files are), we already have a mechanism to detect decompression bombs, and reject them.&lt;/p&gt; &lt;p&gt;However, since &lt;code&gt;.tar.gz&lt;/code&gt; files do not advertise file sizes as metadata, in order to detect a decompression bomb we would have to decompress the entire file anyhow.&lt;/p&gt; &lt;p&gt;As the report notes, our deployment architecture compensates for this behavior, where we have a dedicated worker pool for handling uploads.&lt;/p&gt; &lt;p&gt;We may apply additional restrictions at the system level in the future, but for now we&#39;ve accepted this finding.&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-29-unescaped-values-in-like-sql-queries&#34;&gt;TOB-PYPI-29: Unescaped values in &lt;code&gt;LIKE SQL&lt;/code&gt; queries&lt;/h3&gt; &lt;p&gt;The risk here is that a query could &#34;walk the table&#34; and not take advantage of any indexes, leading to higher resource usage.&lt;/p&gt; &lt;p&gt;The majority of the places where we use unescaped &lt;code&gt;LIKE&lt;/code&gt; queries is in PyPI admin-only interface, where want to allow admins to search for users, packages, etc.&lt;/p&gt; &lt;p&gt;For the one place where we allow public-facing &lt;code&gt;LIKE&lt;/code&gt; queries, there are already rate limits in place to prevent abuse. The table in question is also smaller than 1M rows, so walking an un-indexed column would not be a significant resource usage, and takes a handful of extra milliseconds.&lt;/p&gt; &lt;p&gt;The potential higher resource usage would be limited to malicious internal actors, and if we can&#39;t trust each other, we&#39;ve got bigger problems to deal with.&lt;/p&gt; &lt;p&gt;We&#39;ve accepted this finding, and will continue to monitor all of relevant resources.&lt;/p&gt; &lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt; &lt;p&gt;Working with the folks at &lt;a href=&#34;https://www.trailofbits.com/&#34;&gt;Trail of Bits&lt;/a&gt; was a pleasure, and I&#39;m thankful for their thoroughness and professionalism.&lt;/p&gt; &lt;p&gt;While the audit was funded through the &lt;a href=&#34;https://opentech.fund/&#34;&gt;Open Technology Fund&lt;/a&gt;, my work on remediation would not have been as timely if not funded by &lt;a href=&#34;https://aws.amazon.com/&#34;&gt;Amazon Web Services&lt;/a&gt; to work as the &lt;a href=&#34;../2023-05-09-announcing-pypi-safety-and-security-engr-role/&#34;&gt;PyPI Safety and Security Engineer&lt;/a&gt;. I am grateful for the continued support of both organizations in making PyPI a safer place for all Python users.&lt;/p&gt; &lt;p&gt;&lt;em&gt;Mike Fiedler is the inaugural PyPI Safety &amp;amp; Security Engineer.&lt;/em&gt;&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-11-14-2-security-audit-remediation-warehouse/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-11-14-2-security-audit-remediation-warehouse/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-11-14-2-security-audit-remediation-warehouse.png" type="image/png" length="70101" /> </item> <item> <title>Security Audit Remediation: cabotage</title> <author>Ee Durbin</author> <category>infrastructure</category> <category>security</category> <category>transparency</category> <description>&lt;p&gt;&lt;em&gt;This is part three in a three-part series. See &lt;a href=&#34;../2023-11-14-1-pypi-completes-first-security-audit/&#34;&gt;part one here&lt;/a&gt;, and &lt;a href=&#34;../2023-11-14-2-security-audit-remediation-warehouse/&#34;&gt;part two here&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt; &lt;p&gt;This post is a deeper dive into the remediation of the security audit findings for cabotage - the &lt;a href=&#34;https://github.com/cabotage/cabotage-app&#34;&gt;codebase&lt;/a&gt; that deploys &lt;a href=&#34;https://pypi.org&#34;&gt;PyPI&lt;/a&gt; and its supporting services such as &lt;a href=&#34;https://github.com/pypi/conveyor&#34;&gt;conveyor&lt;/a&gt;, &lt;a href=&#34;https://github.com/pypi/camo&#34;&gt;camo&lt;/a&gt;, and &lt;a href=&#34;https://github.com/pypi/inspector&#34;&gt;inspector&lt;/a&gt;.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;Relative to the &lt;a href=&#34;https://github.com/pypi/warehouse&#34;&gt;warehouse codebase&lt;/a&gt; that &lt;em&gt;is&lt;/em&gt; &lt;a href=&#34;https://pypi.org&#34;&gt;PyPI&lt;/a&gt;, cabotage is not as widely known. The goals of cabotage are to provide a seamless and secure way of deploying arbitrary services into a &lt;a href=&#34;https://kubernetes.io&#34;&gt;Kubernetes&lt;/a&gt; cluster in a &#34;&lt;a href=&#34;https://12factor.net&#34;&gt;Twelve-Factor&lt;/a&gt;&#34; style. There are also a number of firm opinions baked into cabotage that provide end-to-end TLS, protection against recovering secrets through the web UI, and isolation between tenants inside the cluster.&lt;/p&gt; &lt;p&gt;cabotage was initially developed in 2018 as part of the &lt;a href=&#34;https://pyfound.blogspot.com/2017/11/the-psf-awarded-moss-grant-pypi.html&#34;&gt;Mozilla Open Source Support Award&lt;/a&gt; that enabled the &lt;a href=&#34;https://python.org/psf/&#34;&gt;Python Software Foundation&lt;/a&gt; (PSF) to fund a team of contracted developers and a project manager to complete the development and deployment of warehouse and sunset the &lt;a href=&#34;https://github.com/pypi/legacy&#34;&gt;original PyPI codebase&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;A primary goal of cabotage is to reduce the PSF Infrastructure&#39;s dependence on a specific provider for running PyPI, while providing self-service of configuration for project administrators and fully automated deployments. It is in-effect a &#34;Platform as a Service&#34; that deploys applications into bog-standard Kubernetes clusters, no YAML required.&lt;/p&gt; &lt;p&gt;To date, cabotage has deployed 3,901 releases to PyPI since 2018, and 7,377 releases in total across its current services &#34;fleet&#34;.&lt;/p&gt; &lt;p&gt;The audit report can be found &lt;a href=&#34;../2023-11-14-1-pypi-completes-first-security-audit/&#34;&gt;here&lt;/a&gt;. Reading that &lt;em&gt;before&lt;/em&gt; you dive in will provide the fullest context.&lt;/p&gt; &lt;h2 id=&#34;findings&#34;&gt;Findings&lt;/h2&gt; &lt;p&gt;Eleven findings resulted from the audit along with twelve code quality suggestions. This post will focus on the findings and their remediation. Some of the code quality suggestions were implemented, others deferred.&lt;/p&gt; &lt;p&gt;Here&#39;s a table of the items that are relevant to cabotage, and their status:&lt;/p&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;ID&lt;/th&gt; &lt;th&gt;Title&lt;/th&gt; &lt;th&gt;Severity&lt;/th&gt; &lt;th&gt;Difficulty&lt;/th&gt; &lt;th&gt;Status&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-3&lt;/td&gt; &lt;td&gt;Vulnerable dependencies in cabotage&lt;/td&gt; &lt;td&gt;Undetermined&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/cabotage/cabotage-app/compare/ad532ea0dcb7c3dd5228ee8b1312ddad9c70c6af...3e045eea677dd4af71239aad682c4b8ab3a39d3a&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-17&lt;/td&gt; &lt;td&gt;Use of shell=True in subprocesses&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/36&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-18&lt;/td&gt; &lt;td&gt;Use of HMAC with SHA1 for GitHub webhook payload validation&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/37&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-19&lt;/td&gt; &lt;td&gt;Potential container image manipulation through malicious Procfile&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/39&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-20&lt;/td&gt; &lt;td&gt;Repository confusion during image building&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/46&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-21&lt;/td&gt; &lt;td&gt;Brittle X.509 certificate rewriting&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;Undetermined&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/38&#34;&gt;Accepted&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-22&lt;/td&gt; &lt;td&gt;Unused dependencies in cabotage&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;Undetermined&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/35&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-24&lt;/td&gt; &lt;td&gt;Missing resource integrity check of third-party resources&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/40&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-25&lt;/td&gt; &lt;td&gt;Brittle secret filtering in logs&lt;/td&gt; &lt;td&gt;Medium&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/47&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-26&lt;/td&gt; &lt;td&gt;Routes missing access controls&lt;/td&gt; &lt;td&gt;Low&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/41&#34;&gt;Remediated&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt;TOB-PYPI-28&lt;/td&gt; &lt;td&gt;Deployment hook susceptible to race condition due to temp files&lt;/td&gt; &lt;td&gt;Informational&lt;/td&gt; &lt;td&gt;High&lt;/td&gt; &lt;td&gt;Remediated &lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/45&#34;&gt;1&lt;/a&gt;, &lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/42&#34;&gt;2&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;p&gt;&lt;em&gt;IDs are non-consecutive, as the audit report included findings for Warehouse as well.&lt;/em&gt;&lt;/p&gt; &lt;h2 id=&#34;details&#34;&gt;Details&lt;/h2&gt; &lt;h3 id=&#34;tob-pypi-3-vulnerable-dependencies-in-cabotage&#34;&gt;TOB-PYPI-3: Vulnerable dependencies in cabotage&lt;/h3&gt; &lt;p&gt;The maintenance of cabotage has been primarily driven by the need for new features or to mitigate issues raised. As a result dependency management and upgrades have often been done as a byproduct of other changes.&lt;/p&gt; &lt;p&gt;During review, there were a number of dependencies with known vulnerabilities found. Of the nine vulnerabilities noted, only &lt;a href=&#34;https://github.com/advisories/GHSA-cg8c-gc2j-2wf7&#34;&gt;GHSA-cg8c-gc2j-2wf7&lt;/a&gt; was determined impact cabotage and was remediated by migrating to the latest release of the maintained fork of flask-security, flask-security-too (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/compare/3f3e2ba7753f8e4434b562d226118a0dfe873498...3e045eea677dd4af71239aad682c4b8ab3a39d3a&#34;&gt;diff&lt;/a&gt;).&lt;/p&gt; &lt;p&gt;In order to avoid falling behind in this kind of maintenance, automated dependency management was added along with updates to all of known vulnerable dependencies (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/compare/ad532ea0dcb7c3dd5228ee8b1312ddad9c70c6af...3f3e2ba7753f8e4434b562d226118a0dfe873498&#34;&gt;diff&lt;/a&gt;).&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-17-use-of-shelltrue-in-subprocesses&#34;&gt;TOB-PYPI-17: Use of &lt;code&gt;shell=True&lt;/code&gt; in subprocesses&lt;/h3&gt; &lt;p&gt;An attack vector was identified in the way that cabotage calls out to &lt;a href=&#34;https://github.com/moby/buildkit/tree/master/cmd/buildctl&#34;&gt;&lt;code&gt;buildctl&lt;/code&gt;&lt;/a&gt; when running container builds in development mode. A specifically crafted user-input has the ability to run arbitrary shell commands on the application host.&lt;/p&gt; &lt;p&gt;Ultimately this was not determined to be exploitable in the production instance of cabotage, since the shell commands were only used when building containers in local development mode. The use of &lt;code&gt;shell=True&lt;/code&gt; was removed none-the-less as a matter of hygiene (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/36/files&#34;&gt;diff&lt;/a&gt;).&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-18-use-of-hmac-with-sha1-for-github-webhook-payload-validation&#34;&gt;TOB-PYPI-18: Use of HMAC with SHA1 for GitHub webhook payload validation&lt;/h3&gt; &lt;p&gt;Similar to the SNS verification finding in TOB-PYPI-2 for warehouse, the endpoint that received webhook payloads from GitHub for automated deployments was using SHA1 HMAC signatures to validate authenticity when SHA256 HMAC signatures were available.&lt;/p&gt; &lt;p&gt;The remediation of this finding was much more direct than the SNS finding, as GitHub began sending the SHA256 signature in the header, does not require any changes to the configuration of the webhook, and uses standard HMAC signing supported by the Python standard library (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/37/files&#34;&gt;diff&lt;/a&gt;).&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-19-potential-container-image-manipulation-through-malicious-procfile&#34;&gt;TOB-PYPI-19: Potential container image manipulation through malicious Procfile&lt;/h3&gt; &lt;p&gt;Along the same lines as TOB-PYPI-17, some user-supplied values had the ability to alter the cabotage controlled Dockerfile that specifies how release containers are built, which should not be modifiable. Through specifically crafted process names in the Procfile, a user could alter the resulting Dockerfile by injecting newlines.&lt;/p&gt; &lt;p&gt;Remediation was straightforward by adding additional validation of user supplied process names (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/39/files&#34;&gt;diff&lt;/a&gt;).&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-20-repository-confusion-during-image-building&#34;&gt;TOB-PYPI-20: Repository confusion during image building&lt;/h3&gt; &lt;p&gt;Due to a quirk in GitHub&#39;s API for fetching references, a given reference may return a concrete SHA/commit that belongs to a repository other than the one specified in the API call. In this case by providing a reference that resolves to a commit on a fork of the configured repository, a user of cabotage had the ability to intentionally (or mistakenly) configure cabotage to deploy code from a repository other than the one defined.&lt;/p&gt; &lt;p&gt;By adding additional validation inspired by &lt;a href=&#34;https://www.chainguard.dev&#34;&gt;Chainguard&lt;/a&gt;&#39;s &lt;a href=&#34;https://github.com/chainguard-dev/clank/blob/a5c8412f4e8fb128d2c0919ec02d89f086afdd24/main.go#L242-L272&#34;&gt;clank&lt;/a&gt; tool, cabotage now verifies that the resulting SHA for a given reference belongs to the configured repository (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/46/files&#34;&gt;diff&lt;/a&gt;).&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-21-brittle-x509-certificate-rewriting&#34;&gt;TOB-PYPI-21: Brittle X.509 certificate rewriting&lt;/h3&gt; &lt;p&gt;All containers built and deployed by cabotage are done so using short-lived authentication tokens for an internally deployed Docker &lt;a href=&#34;https://hub.docker.com/_/registry&#34;&gt;registry&lt;/a&gt; instance. The cabotage application itself provides this authentication and must publish a public key that the registry can use to validate tokens.&lt;/p&gt; &lt;p&gt;In order to avoid handling private-key material in the application, cabotage relies heavily on &lt;a href=&#34;https://www.vaultproject.io&#34;&gt;Hashicorp Vault&lt;/a&gt;. The transit backend for vault &lt;a href=&#34;https://github.com/hashicorp/vault/issues/3845&#34;&gt;did not support publishing the required x509 certificate&lt;/a&gt; that Docker registry required when cabotage was originally developed in 2018, so some clever use of the cryptography library was employed to create the necessary file &lt;a href=&#34;https://github.com/cabotage/cabotage-app/blob/f01b75222280699dcaa99aff3ec60e1d1d1830fc/cabotage/utils/cert_hacks.py&#34;&gt;ref&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;In the audit it was determined that this work around was brittle in the event that an attacker had the ability to alter the length of the signature, resulting in an invalid x509 certificate and broken authentication for registry clients.&lt;/p&gt; &lt;p&gt;In practice, this has not been observed in the five and a half years that it has been in production and the result of a successful attack would only lead to deployments being halted. As such, we have accepted this finding for the time being and will investigate the newly released x509 support in vault 1.15 and adopt it if able (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/issues/43&#34;&gt;issue&lt;/a&gt;).&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-22-unused-dependencies-in-cabotage&#34;&gt;TOB-PYPI-22: Unused dependencies in cabotage&lt;/h3&gt; &lt;p&gt;Similar to TOB-PYPI-3, dependency management for cabotage was lacking. This led to a handful of dependencies being installed that could be additional exposure to vulnerabilities or attacks.&lt;/p&gt; &lt;p&gt;By adopting &lt;a href=&#34;https://pypi.org/project/pip-tools/&#34;&gt;&lt;code&gt;pip-tools&lt;/code&gt;&lt;/a&gt; to compile and pin dependencies, only the projects necessary are installed (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/35/files&#34;&gt;diff&lt;/a&gt;).&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-24-missing-resource-integrity-check-of-third-party-resources&#34;&gt;TOB-PYPI-24: Missing resource integrity check of third-party resources&lt;/h3&gt; &lt;p&gt;When adding support for a new feature, third party JavaScript was added without subresource integrity information being added. This addition guards against malicious replacement of JavaScript an is good practice when loading any third party code.&lt;/p&gt; &lt;p&gt;Remediation was simple, by ensuring that all CDN loaded JavaScript had the correct value set (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/40/files&#34;&gt;diff&lt;/a&gt;).&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-25-brittle-secret-filtering-in-logs&#34;&gt;TOB-PYPI-25: Brittle secret filtering in logs&lt;/h3&gt; &lt;p&gt;There was a brief period where cabotage supported building from private GitHub repositories, which necessitated filtering build logs and removing the plaintext authentication tokens.&lt;/p&gt; &lt;p&gt;This filtering was naive, but also no longer required. Remediation was removal of the filtering code, and a comment directing a future developer to the correct way of providing such authentication for builds in the future, should building from private GitHub repositories be supported (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/47/files&#34;&gt;diff&lt;/a&gt;).&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-26-routes-missing-access-controls&#34;&gt;TOB-PYPI-26: Routes missing access controls&lt;/h3&gt; &lt;p&gt;Another vestigial piece of code that allowed for the build context necessary for container builds was identified as allowing for potentially non-public information to be leaked if a release id (UUIDv4) was guessed or surmised.&lt;/p&gt; &lt;p&gt;This route was unauthenticated as a shortcut rather than adding a new authentication method to cabotage itself.&lt;/p&gt; &lt;p&gt;This code was made defunct when cabotage began building from contexts pulled directly from GitHub and supplied via Kubernetes secrets. Remediation was again, a simple removal of the code (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/41/files&#34;&gt;diff&lt;/a&gt;).&lt;/p&gt; &lt;h3 id=&#34;tob-pypi-28-deployment-hook-susceptible-to-race-condition-due-to-temp-files&#34;&gt;TOB-PYPI-28: Deployment hook susceptible to race condition due to temp files&lt;/h3&gt; &lt;p&gt;A final vestigial piece of code was also flagged as part of the audit which was created to fetch and re-package source code from GitHub for deployments. This had the very outside potential of being exploitable if an attacker gained access to the filesystem that the cabotage app uses for temporary files.&lt;/p&gt; &lt;p&gt;This was similarly made defunct when cabotage began building from contexts pulled directly from GitHub. Remediation was a final simple removal of code (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/45/files&#34;&gt;diff-0&lt;/a&gt;), and a refactor of how temporary files are created and opened (&lt;a href=&#34;https://github.com/cabotage/cabotage-app/pull/42/files&#34;&gt;diff-1&lt;/a&gt;).&lt;/p&gt; &lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt; &lt;p&gt;In addition to the specific findings, the Trail of Bits team also made a number of &#34;Code Quality Recommendations&#34; and analyzed the overall maturity of the codebase. Those sections of the report highlight one of the two themes I see in the report regarding cabotage:&lt;/p&gt; &lt;ol&gt; &lt;li&gt; &lt;p&gt;Overall the development experience and continuous integration environment for cabotage is lacking.&lt;/p&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;There are countless minutiae that one must consider when writing code with security in mind.&lt;/p&gt; &lt;/li&gt; &lt;/ol&gt; &lt;p&gt;In the end, no show stopping or easily exploitable security issues were found, which is a relief! Many of the most interesting security findings were only exploitable by a malicious internal actor who already had configuration permissions in cabotage, was deploying their app there in the first place, or had access to the underlying systems.&lt;/p&gt; &lt;p&gt;The takeaway I have as the sole author and maintainer of cabotage is pretty resounding, and addresses both themes from the report:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;Projects with solo maintainers do not benefit from the accountability that comes with collaborative development, are prone to deprioritizing critical improvements to developer experience and testing, and don&#39;t have the extra sets of eyes that often assist in spotting small bugs or improper handling of security sensitive software.&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;So if you&#39;re interested in infrastructure and projects that make deploying software securely and reliably more straightforward, I&#39;d love to talk more. Swing by the &lt;a href=&#34;https://github.com/cabotage/cabotage-app&#34;&gt;cabotage repo&lt;/a&gt; and consider helping build the software that deploys PyPI, and will soon be deploying more and more of the &lt;a href=&#34;https://python.org/psf/&#34;&gt;Python Software Foundation&lt;/a&gt;&#39;s infrastructure as we migrate from previously gratis PaaS hosting providers.&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-11-14-3-security-audit-remediation-cabotage/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-11-14-3-security-audit-remediation-cabotage/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-11-14-3-security-audit-remediation-cabotage.png" type="image/png" length="68687" /> </item> <item> <title>Incident Report: User Account Takeover</title> <author>Mike Fiedler</author> <category>2fa</category> <category>security</category> <category>transparency</category> <description>&lt;h1 id=&#34;incident-report-user-account-takeover&#34;&gt;Incident Report: User Account Takeover&lt;/h1&gt; &lt;h2 id=&#34;what-happened&#34;&gt;What happened?&lt;/h2&gt; &lt;p&gt;A PyPI user&#39;s account was taken over and used to remove the user&#39;s ownership of 4 projects. This was not a malfunction of PyPI or using any vulnerability, rather the user&#39;s account was not sufficiently protected against account takeover.&lt;/p&gt; &lt;p&gt;The attacker added themselves as a collaborator to these projects, and removed the original owner. None of the projects had any modifications made to them other than ownership changes.&lt;/p&gt; &lt;!-- more --&gt; &lt;p&gt;After the attacker completed ownership changes, they deleted the user&#39;s account.&lt;/p&gt; &lt;p&gt;PyPI Admins were informed by the user via email, blocked the attacker&#39;s access to the account, and restored the user&#39;s ownership of their projects.&lt;/p&gt; &lt;p&gt;No further compromise was made, and &lt;strong&gt;existing packages were not modified&lt;/strong&gt;.&lt;/p&gt; &lt;p&gt;Read on for more details.&lt;/p&gt; &lt;h2 id=&#34;timeline&#34;&gt;Timeline&lt;/h2&gt; &lt;p&gt;&lt;em&gt;all times are in UTC&lt;/em&gt;&lt;/p&gt; &lt;p&gt;System timings are from project ownership changes and email notifications. When a user&#39;s account is deleted, the account&#39;s data is removed immediately, thus we do not have timing of when the attacker gained access to &lt;code&gt;meisnate12&lt;/code&gt;&#39;s account.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;Date: 2023-11-22&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;08:42:33 new user account &lt;code&gt;dvolk&lt;/code&gt; created&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;em&gt;(this is the first of 4 packages, only describing one here)&lt;/em&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;08:44:55 &lt;code&gt;meisnate12&lt;/code&gt; account invites &lt;code&gt;dvolk&lt;/code&gt; to be a collaborator on &lt;code&gt;arrapi&lt;/code&gt;&lt;/li&gt; &lt;li&gt;08:47:25 &lt;code&gt;dvolk&lt;/code&gt; accepts the invitation&lt;/li&gt; &lt;li&gt;08:47:35 &lt;code&gt;dvolk&lt;/code&gt; removes &lt;code&gt;meisnate12&lt;/code&gt; as a collaborator on &lt;code&gt;arrapi&lt;/code&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;em&gt;(repeated for &lt;code&gt;tmdbapis&lt;/code&gt;, &lt;code&gt;nagerapi&lt;/code&gt;, &lt;code&gt;pmmutils&lt;/code&gt;)&lt;/em&gt;&lt;/p&gt; &lt;ul&gt; &lt;li&gt;08:50:27 &lt;code&gt;meisnate12&lt;/code&gt; account is deleted&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;No further actions by &lt;code&gt;dvolk&lt;/code&gt; were observed after this point.&lt;/p&gt; &lt;ul&gt; &lt;li&gt; &lt;p&gt;14:33, admin@pypi.org receives an email from the address associated with &lt;code&gt;meinstate12&lt;/code&gt;&#39;s account:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;I was removed as the owner of arrapi, tmdbapis, nagerapi, and pmmutils. Then my account was deleted so I have no way to log in anymore. My username is meisnate12. What are my next steps here.&lt;/p&gt; &lt;/blockquote&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;14:46 A PyPI Admin responds with:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;Thanks for the report. We&#39;ve frozen the account in question while we look into this.&lt;/p&gt; &lt;/blockquote&gt; &lt;/li&gt; &lt;li&gt; &lt;p&gt;14:44 PyPI Admins discuss options in PyPI Admins Slack channel with two other PyPI Admins&lt;/p&gt; &lt;/li&gt; &lt;li&gt;14:46 A PyPI Admin disables the &lt;code&gt;dvolk&lt;/code&gt; account&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;After investigation, PyPI Admins determine that the account was compromised, but the attacker did not change any of the projects&#39; data.&lt;/p&gt; &lt;p&gt;We decide the correct course of action is to manually restore the deleted user account, and restore the user&#39;s ownership of their projects.&lt;/p&gt; &lt;ul&gt; &lt;li&gt;15:53 PyPI Admins close the incident&lt;/li&gt; &lt;/ul&gt; &lt;h2 id=&#34;what-can-we-learn-from-this-incident-and-improve&#34;&gt;What can we learn from this incident and improve?&lt;/h2&gt; &lt;h3 id=&#34;an-ounce-of-prevention-is-worth-a-pound-of-cure&#34;&gt;An ounce of prevention is worth a pound of cure&lt;/h3&gt; &lt;p&gt;We integrate with HaveIBeenPwned to &lt;a href=&#34;https://github.com/pypi/warehouse/blob/6d4b6a3d35e0ce0d0b6d4e6484808e1a9f6e4852/warehouse/accounts/forms.py#L389-L401&#34;&gt;check a user&#39;s password&lt;/a&gt; against their service on every login. Unfortunately, the password in question had not previously surfaced in a breach that HaveIBeenPwned knows about, so this didn&#39;t prevent the attack.&lt;/p&gt; &lt;p&gt;One of the best tools we have to date to prevent account takeover is &lt;a href=&#34;https://pypi.org/help/#twofa&#34;&gt;Two-Factor Authentication&lt;/a&gt; (2FA, or MFA - multifactor auth).&lt;/p&gt; &lt;p&gt;If this user account had 2FA enabled, the attacker would not have been able to take over the account without access to the user&#39;s second factor (TOTP app, WebAuthN device, or passkeys).&lt;/p&gt; &lt;p&gt;We have already taken steps to &lt;a href=&#34;https://github.com/pypi/warehouse/issues/14010&#34;&gt;ratchet down usage of PyPI without 2FA enabled&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;&lt;strong&gt;This encouragement will turn into &lt;a href=&#34;https://blog.pypi.org/posts/2023-05-25-securing-pypi-with-2fa/&#34;&gt;enforcement at the end of 2023&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;We still have a use case where an infrequent user of PyPI, with an account created long before 2FA was available, is still able to log into their account without 2FA, in order to enroll a 2FA device. &lt;a href=&#34;https://github.com/pypi/warehouse/issues/11850&#34;&gt;Read more about this issue&lt;/a&gt; and some ideas for resolution.&lt;/p&gt; &lt;h3 id=&#34;deleting-things-can-have-unintended-consequences&#34;&gt;Deleting things can have unintended consequences&lt;/h3&gt; &lt;p&gt;When a user account is deleted, all the account&#39;s data is removed. We have a mechanism in place today that prevents a user account from being deleted if they are the sole owner of any projects, to prevent orphaning projects.&lt;/p&gt; &lt;p&gt;In this case, the attacker was able to remove the user&#39;s ownership of their projects, and then delete the account.&lt;/p&gt; &lt;p&gt;We have previously entertained ideas on how to prevent full deletions without PyPI Admin intervention, but have yet to settle on a solution. See &lt;a href=&#34;https://github.com/pypi/warehouse/issues/6091&#34;&gt;this tracking issue&lt;/a&gt; and for more details. It&#39;s not as simple as it sounds.&lt;/p&gt; &lt;p&gt;Even if we continue to delete resources immediately, we should entertain what we can do to preserve some of the details we might need to investigate further. &lt;a href=&#34;https://github.com/pypi/warehouse/issues/6451&#34;&gt;This issue&lt;/a&gt; tracks that effort.&lt;/p&gt; &lt;h3 id=&#34;if-you-see-something-say-something&#34;&gt;If you see something, say something&lt;/h3&gt; &lt;p&gt;When collaborators are added or removed to a project, or an account is deleted, we email the address on file.&lt;/p&gt; &lt;p&gt;We were informed of the incident by the impacted user, who still had access to the email account associated with their PyPI username.&lt;/p&gt; &lt;p&gt;Here are a few things you can do to help protect your PyPI account: &lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://pypi.org/help/#twofa&#34;&gt;Add 2FA now&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt; &lt;li&gt;Use a unique and sufficiently complex password for your PyPI account.&lt;/li&gt; &lt;li&gt;Keep your account &lt;a href=&#34;https://pypi.org/help/#verified-email&#34;&gt;email addresses up to date&lt;/a&gt;, and ensure you maintain access.&lt;/li&gt; &lt;li&gt;If you receive an email from PyPI that you did not expect, please contact us via email &lt;a href=&#34;&amp;#109;&amp;#97;&amp;#105;&amp;#108;&amp;#116;&amp;#111;&amp;#58;&amp;#115;&amp;#101;&amp;#99;&amp;#117;&amp;#114;&amp;#105;&amp;#116;&amp;#121;&amp;#64;&amp;#112;&amp;#121;&amp;#112;&amp;#105;&amp;#46;&amp;#111;&amp;#114;&amp;#103;&#34;&gt;&amp;#115;&amp;#101;&amp;#99;&amp;#117;&amp;#114;&amp;#105;&amp;#116;&amp;#121;&amp;#64;&amp;#112;&amp;#121;&amp;#112;&amp;#105;&amp;#46;&amp;#111;&amp;#114;&amp;#103;&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;This user&#39;s diligence in reporting the incident to us helped prevent further compromise.&lt;/p&gt;</description> <link>https://blog.pypi.org/posts/2023-12-04-account-takeover/</link> <pubDate>Tue, 06 Oct 2026 21:31:21 +0000</pubDate> <source url="https://blog.pypi.org/feed_rss_updated.xml">The Python Package Index Blog</source><guid isPermaLink="true">https://blog.pypi.org/posts/2023-12-04-account-takeover/</guid> <enclosure url="https://blog.pypi.org/assets/images/social/posts/2023-12-04-account-takeover.png" type="image/png" length="61883" /> </item> </channel> </rss>