Skip to content

Commit 69ef27c

Browse files
author
Kairo Araujo
authored
Merge branch 'main' into rstuf-bootstrap
2 parents 6cb2f33 + da33fa0 commit 69ef27c

File tree

124 files changed

+22478
-11698
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+22478
-11698
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ static_pipeline: .state/docker-build-static
7474
reformat: .state/docker-build-base
7575
docker compose run --rm base bin/reformat
7676

77-
lint: .state/docker-build-base
77+
lint: .state/docker-build-base .state/docker-build-static
7878
docker compose run --rm base bin/lint
7979
docker compose run --rm static bin/static_lint
8080

bin/rtd-docs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ if [ "${READTHEDOCS_PROJECT}" = "docspypiorg" ]; then
1818
mkdir _readthedocs && mv docs/user-site _readthedocs/html
1919
fi
2020

21-
if [ "${READTHEDOCS_PROJECT}" = "blogpypiorg" ]; then
21+
if [ "${READTHEDOCS_PROJECT}" = "blogpypiorg" ] || [ "${READTHEDOCS_PROJECT}" = "psf-warehouse-private" ]; then
2222
pip install -r requirements/docs-blog.txt
2323
asdf reshim
2424
mkdocs build -f docs/mkdocs-blog.yml

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ services:
148148
worker:
149149
image: warehouse:docker-compose
150150
pull_policy: never
151-
command: hupper -m celery -A warehouse worker -B -S redbeat.RedBeatScheduler -l info
151+
command: hupper -m celery -A warehouse worker --beat --scheduler redbeat.RedBeatScheduler -l info
152152
volumes:
153153
- ./warehouse:/opt/warehouse/src/warehouse:z
154154
- packages:/var/opt/warehouse/packages
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
---
2+
title: "Incident Report: Unauthorized User Accounts Access"
3+
description: An attack on PyPI user accounts starting on March 31st, 2024.
4+
authors:
5+
- miketheman
6+
date: 2024-04-03
7+
tags:
8+
- 2fa
9+
- security
10+
- transparency
11+
---
12+
13+
On Sunday, March 31st, 2024, PyPI Admins received emails
14+
about unexpected account activity from PyPI users.
15+
Users received notifications from PyPI that they had
16+
enrolled in two-factor authentication (2FA).
17+
These users claimed that they had not done so themselves.
18+
19+
PyPI Admins have **not found any evidence** of existing package tampering,
20+
or any other malicious activity beyond unauthorized account access and modification.
21+
22+
The main actions post-investigation taken were:
23+
24+
* affected accounts were frozen for further investigation
25+
* email re-verification was required for all accounts not yet enabled in 2FA
26+
27+
Read on for a summary of what happened, how we responded, and what's next.
28+
29+
<!-- more -->
30+
31+
_All times are in Eastern Time (UTC-4), and some are approximated for clarity._
32+
33+
## What Happened
34+
35+
### Sunday, March 31st, 2024
36+
37+
It was a weekend morning.
38+
39+
* 06:20: PyPI Admins receive an influx of emails from users.
40+
41+
These users were responding to an automated notification from PyPI
42+
that they had enrolled in two-factor authentication (2FA).
43+
44+
**As a reminder, [PyPI requires all users](./2024-01-01-2fa-enforced.md) to enable 2FA.**
45+
46+
Users stated they had not enrolled in 2FA themselves.
47+
The accounts in question had not previously enrolled in any form of 2FA,
48+
and the users were surprised by these notifications.
49+
50+
Emails of this nature trickled in over the course of the day,
51+
as users read their email and notice the activity.
52+
53+
* 08:00: A volunteer PyPI Admin saw these notifications and began to investigate.
54+
55+
They found some traffic patterns used for the attack.
56+
The admin then reached out to the rest of the team to discuss their findings,
57+
and to take action on the accounts that were affected.
58+
59+
The main action taken was to immediately freeze any suspicious accounts,
60+
as this is completely reversible and would prevent any further damage
61+
to the known compromised accounts or projects they have access to.
62+
63+
~120 user accounts were frozen due to the analysis of the situation,
64+
based on their characteristics and the traffic patterns observed.
65+
_(A precise count was not recorded as actions were taken in real-time.)_
66+
67+
PyPI Admins began work on a feature which would require re-verification
68+
of email addresses for all accounts not yet enabled in 2FA,
69+
before any other account action can be taken (including enabling 2FA).
70+
71+
* 11:00: the volunteer admin had to step away from the computer for the remainder of the day.
72+
73+
Investigation continues on Monday.
74+
75+
### Monday, April 1st, 2024
76+
77+
Monday morning, investigations continue.
78+
79+
* 08:00: an additional ~54 accounts were frozen based on similar criteria, with better filters
80+
81+
Another investigation to determine if any packages had been tampered with began.
82+
**No evidence of package tampering was found** associated with any of these accounts.
83+
84+
PyPI Admins completed the new feature to now require email address
85+
re-verification for any account that has not yet enabled 2FA.
86+
87+
* 12:20: Account re-verification change went live.
88+
89+
Once the change was in place, PyPI Admins took another action -
90+
invalidated any existing email verifications for accounts **without 2FA enabled**.
91+
This was done to prevent any unauthorized access to accounts that may have been compromised.
92+
Now, if a user who has not enrolled in 2FA logs in,
93+
they will be required to re-verify their email address,
94+
prior to enrollment in 2FA.
95+
This process ensures that someone logging into a pre-existing account
96+
without 2FA controls the password _and_ email address associated with the account.
97+
98+
* 12:30: Email invalidation applied to 370k (56%) email addresses.
99+
100+
The rationale was that these accounts were at risk of unauthorized access,
101+
and this was a proactive measure to prevent further damage.
102+
103+
## Summary
104+
105+
This event has similar characteristics to a
106+
[credential stuffing](https://attack.mitre.org/techniques/T1110/004/) attack,
107+
where attackers use credentials found via other service's leaks or incidents
108+
to attempt to log in to accounts on PyPI.
109+
110+
For the time frame in question we did not observe a higher-than-normal
111+
rate of requests to the PyPI login endpoint.
112+
Authentication rate limits did not show any out-of-the-ordinary activity.
113+
114+
This an attack that 2FA is designed to protect against,
115+
as even if the attackers have the correct username and password,
116+
they would still need to provide a second factor to gain access to the account.
117+
118+
PyPI disallows the use of compromised passwords,
119+
commonly used in credential stuffing attacks,
120+
by [integrating](https://github.com/pypi/warehouse/blob/d16fc21b0d5a57409740f5ebd780ea446df6d95c/warehouse/accounts/services.py#L769-L825)
121+
with the [Have I Been Pwned](https://haveibeenpwned.com/) [API](https://haveibeenpwned.com/API/v3)
122+
to check for compromised passwords on login, but there was no evidence
123+
that these passwords were compromised in known data leaks on that platform
124+
at the time of login.
125+
126+
All PyPI users should take precautions to secure their PyPI account
127+
and associated projects, including, but not only:
128+
129+
* Use a unique password on PyPI
130+
* Verify your email address, add a secondary email address (optional)
131+
* Enable 2FA on your account. **Save your recovery codes somewhere safe!**
132+
* Review your packages for any unauthorized changes
133+
* Review collaborators on your projects
134+
135+
For any users who have been affected by this incident,
136+
and cannot obtain access to their accounts with email re-verification,
137+
Please read the [FAQ on account recovery](https://pypi.org/help/#account-recovery).
138+
139+
We will continue to monitor the situation and take further action as needed.
140+
141+
---
142+
143+
_This analysis, and my role, is made possible with the support from
144+
[Amazon Web Services Open Source](https://aws.amazon.com/opensource/),
145+
[Georgetown CSET](https://cset.georgetown.edu/),
146+
the [PSF](https://www.python.org/psf/) and their sponsors._

docs/dev/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,7 @@ pseudoxml:
175175
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
176176
@echo
177177
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
178+
179+
# https://github.com/sphinx-doc/sphinx-autobuild#using-with-makefile
180+
livehtml:
181+
sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

docs/dev/development/frontend.rst

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,35 @@ serving.
88
All of the static files are located in ``warehouse/static/`` and external
99
libraries are found in ``package.json``.
1010

11+
The static files are compiled and included in the
12+
``warehouse:docker-compose-static`` Docker image.
13+
1114

1215
Building
1316
--------
1417

15-
Static files should be automatically built when ``make serve`` is running;
16-
however, you can trigger a manual build of them by installing
17-
`NodeJS 16.x <https://nodejs.org/en/download/releases/>`_, installing
18-
the dependencies using ``npm install`` and then running ``npm run build``.
18+
Static files should be automatically built when ``make serve`` is
19+
running; however, you can also manually run commands in the ``static``
20+
container:
21+
22+
.. code-block:: console
23+
24+
$ # install dependencies
25+
$ docker compose run --rm static npm install
26+
27+
$ # start a build
28+
$ docker compose run --rm static npm run build
29+
30+
31+
Building outside of Docker
32+
--------------------------
33+
34+
Note: building outside of Docker is **not recommended** as it may
35+
install platform-specific dependencies.
36+
37+
Install `NodeJS 20.x <https://nodejs.org/en/download/releases/>`_,
38+
install the dependencies using ``npm install`` and then run ``npm run
39+
build``.
1940

2041
If you're in a POSIX environment you may find
2142
`NVM <https://github.com/nvm-sh/nvm>`_ useful to have multiple NodeJS

package-lock.json

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@babel/core": "^7.21.3",
3838
"@babel/eslint-parser": "^7.21.3",
3939
"@babel/preset-env": "^7.21.4",
40+
"@stylistic/eslint-plugin-js": "^1.7.0",
4041
"@testing-library/dom": "^9.2.0",
4142
"@testing-library/jest-dom": "^5.16.5",
4243
"babel-jest": "^29.5.0",
@@ -77,24 +78,27 @@
7778
"parserOptions": {
7879
"sourceType": "module"
7980
},
81+
"plugins": [
82+
"@stylistic/js"
83+
],
8084
"rules": {
81-
"comma-dangle": [
85+
"@stylistic/js/comma-dangle": [
8286
"error",
8387
"always-multiline"
8488
],
85-
"indent": [
89+
"@stylistic/js/indent": [
8690
"error",
8791
2
8892
],
89-
"linebreak-style": [
93+
"@stylistic/js/linebreak-style": [
9094
"error",
9195
"unix"
9296
],
93-
"quotes": [
97+
"@stylistic/js/quotes": [
9498
"error",
9599
"double"
96100
],
97-
"semi": [
101+
"@stylistic/js/semi": [
98102
"error",
99103
"always"
100104
]

0 commit comments

Comments
 (0)