Skip to content

Commit 60079ff

Browse files
ewdurbinJacobCoffeedi
authored
add transparency report for incident today (#17958)
* add transparency report for incident today * typo's and stuff * Update docs/blog/posts/2025-04-14-incident-report-organization-team-privileges.md Co-authored-by: Jacob Coffee <[email protected]> * Apply suggestions from code review Co-authored-by: Dustin Ingram <[email protected]> * make mergeable, awaiting reporter's consent * credit reporters --------- Co-authored-by: Jacob Coffee <[email protected]> Co-authored-by: Dustin Ingram <[email protected]>
1 parent 8900cb4 commit 60079ff

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
---
2+
title: "Incident Report: Organizations Team privileges"
3+
description: We responded to an incident related to privileges persisting
4+
via Organization Teams after Members are removed from Organizations.
5+
authors:
6+
- ewdurbin
7+
date: 2025-04-14
8+
tags:
9+
- transparency
10+
- security
11+
---
12+
13+
On April 14, 2025 <[email protected]> was notified of a potential security concern
14+
relating to privileges granted to a PyPI User via Organization Teams membership
15+
persisting after the User was removed from the PyPI Organization the Team belongs to.
16+
17+
We validated the report as a true finding, identified all cases where this scenario
18+
had occurred, notified impacted parties, and released a fix.
19+
A full audit determined that all instances were accounted for,
20+
with no unauthorized actions taken as a result of the issue.
21+
22+
<!-- more -->
23+
24+
## Timeline of events
25+
26+
- 2025-04-14 16:37 UTC
27+
A PyPI User who has been testing out our Organizations features noticed the issue
28+
and reported it according to our [Security Policy](https://pypi.org/security/)
29+
30+
- 2025-04-14 17:02 UTC
31+
PyPI Security acknowledges receipt.
32+
- 2025-04-14 17:22 UTC
33+
PyPI Security validates the report as a true finding.
34+
- 2025-04-14 17:58 UTC
35+
PyPI Security validating test and hot fix prepared for internal review.
36+
- 2025-04-14 18:30 UTC
37+
PyPI Security removes invalid Team Membership and notifies the owners of the only
38+
other actively impacted Organization.
39+
[public PR](https://github.com/pypi/warehouse/pull/17957) opened with fix.
40+
- 2025-04-14 18:33 UTC
41+
Hot fix is merged.
42+
- 2025-04-14 18:39 UTC
43+
Hot fix deployed and live on PyPI.
44+
- 2025-04-14 19:06 UTC
45+
Security audit complete, validating that only two instances of this had
46+
occurred, with no unauthorized actions taken as a result of the persisted
47+
privileges.
48+
49+
## Details
50+
51+
PyPI Organizations have been a feature on PyPI since they were first enabled
52+
on April 20, 2023.
53+
This issue was introduced in the initial development of Organizations features,
54+
and was mitigated April 14, 2025.
55+
56+
PyPI Organizations are quickly seeing more use as we (finally) exit our public beta
57+
period. In the last month we have gone from 70 Community Organization beta testers
58+
to 1,935 active Organizations[^1], so it is of little surprise that we are surfacing a few
59+
more issues as a result.
60+
61+
Thanks to PyPI's strong test coverage identifying and validating the issue was rather
62+
trivial, and getting a fix prepared and out the door was straight forward.
63+
64+
In total, this incident was resolved in 2 hours and 2 minutes from the time of report.
65+
66+
## Response
67+
68+
Given that this is an otherwise straightforward bug, I thought I would take a moment
69+
to share how the issue was validated as well as how we audited.
70+
I've replaced the specific organization, team, and user strings below,
71+
but otherwise all of this is copied and pasted from the terminal session used
72+
as I worked this report.
73+
74+
I spun up a local development environment of
75+
[pypi/warehouse](https://github.com/pypi/warehouse)
76+
from the current `main` branch locally and followed the reporter's steps to reproduce:
77+
78+
> The basic reproduce steps were:
79+
>
80+
> 1. Add a user to an organization as a member
81+
> 2. Add that member to a organization team
82+
> 3. Remove the member from the organization
83+
84+
Noting that indeed, the User's team role persisted, and they could continue to act
85+
with those privileges on PyPI.
86+
87+
At that point the reporter and PyPI Administrators team were notified that we had a
88+
finding, and that review would be needed shortly to get a fix merged and deployed.
89+
90+
From there, I added a
91+
[failing test](https://github.com/pypi/warehouse/pull/17957/commits/33707f0ad72e4d2efacf85fd0488e0c42fca47e6)
92+
which further validated the issue, and got to work creating a
93+
[patch](https://github.com/pypi/warehouse/pull/17957/commits/34a40178ee7d0e048e45867a9d8f76497f68da8c)
94+
which turned the test green.
95+
96+
Now, with time to wait while a volunteer PyPI Admin returned I focused on assessing
97+
if this was actively impacting any other organizations:
98+
99+
```
100+
warehouse=> select
101+
o.name as organization,
102+
t.name as team_name,
103+
u.username as user,
104+
tr. role_name as team_role,
105+
ors. role_name as organization role
106+
from
107+
team_roles tr
108+
join teams t
109+
on t.id=tr.team_id
110+
join organizations o
111+
on t.organization_id=o.id
112+
join users u
113+
on u.id=tr.user_id
114+
left outer join organization roles ors
115+
on ors.organization_id=t.organization_id and ors.user_id=tr.user_id
116+
where
117+
ors. role_name is null;
118+
organization | team_name | user | team_role | organization_role
119+
--------------+-------------+-----------+-----------+-------------------
120+
spam | Spam-owners | spamlover | Member |
121+
(1 row)
122+
```
123+
124+
This query showed me that one instance of a User having an Organization Team Role
125+
_without_ being a Member of that Organization still existed on PyPI[^2].
126+
The reporter made clear that they had already resolved the instance from their testing.
127+
128+
I drafted a notice to the five users with role `Owner` on the impacted Organization,
129+
and took a moment to realize that this was our first time emailing Organization
130+
Owners as a group, and that we needed to account for the fact that Users on PyPI
131+
do not necessarily already know one-another's email addresses, as it is not required
132+
to invite them to a Project or Organization. A quick gut-check in the PyPI Moderators
133+
channel validated my plan to `Bcc:` all the Owners rather than `To:` them as a
134+
group.[^3]
135+
136+
By that point, the volunteer PyPI Administrator was available to review the PR and
137+
drafted e-mail. We notified the impacted Organization, and then coordinated to
138+
open the PR publicly and approve/merge it hastily before completing a more in-depth
139+
audit.
140+
141+
Luckily this audit was straightforward using our internal security records
142+
combined with the fact that there has been minimal churn in the Organization membership
143+
in the short time that Organizations has been in broader use.
144+
145+
```
146+
warehouse=> select
147+
o.name, time, tag, u.username
148+
from
149+
organization_events oe
150+
join users u
151+
on (additional->>'target_user_id')::uuid=u.id
152+
join organizations o on oe.source_id=o.id
153+
where
154+
tag in ('organization:team_role:remove', 'organization:organization_role:remove')
155+
order by time;
156+
name | time | tag | username
157+
------------+----------------------------+---------------------------------------+-------------------
158+
lumberjack | 2023-05-02 03:01:18.935901 | organization:organization_role:remove | sirrobin
159+
holygrail | 2023-07-06 12:55:43.261593 | organization:organization_role:remove | blackknight
160+
ni | 2023-09-18 12:07:17.389244 | organization:organization_role:remove | shrubbery
161+
parrot | 2024-02-04 19:23:25.354344 | organization:organization_role:remove | exparrot
162+
spam | 2024-08-24 01:40:22.405746 | organization:organization_role:remove | spamlover
163+
spam | 2025-02-09 18:14:13.891224 | organization:team_role:remove | eggandspam
164+
albatross | 2025-03-07 06:55:29.446617 | organization:organization_role:remove | nudge
165+
albatross | 2025-03-07 06:55:37.271176 | organization:organization_role:remove | wink
166+
cheese | 2025-03-13 18:25:54.650905 | organization:team_role:remove | gorgonzola
167+
cheese | 2025-03-13 18:26:02.525162 | organization:team_role:remove | camembert
168+
ministry | 2025-03-20 07:53:45.616404 | organization:organization_role:remove | sillywalks
169+
argument | 2025-03-31 15:52:18.186223 | organization:organization_role:remove | contradiction
170+
fishslap | 2025-04-14 15:12:14.023183 | organization:organization_role:remove | danceking
171+
fishslap | 2025-04-14 15:24:54.208641 | organization:organization_role:remove | danceking
172+
fishslap | 2025-04-14 15:27:22.954624 | organization:team_role:remove | danceking
173+
```
174+
175+
Here, we see the `spamlover` user being removed from the `spam` Organization
176+
on `2024-08-24`, without being removed from the team, confirming our finding from the
177+
earlier query.
178+
179+
We also see the User `danceking` from the `fishslap` Organization being removed from
180+
the Organization multiple times, before the reporter removed them from their assigned
181+
Team.
182+
183+
This allowed us to confirm that beyond the already identified incidents,
184+
no other Organizations had found this problem before without letting us know.
185+
186+
## Thanks
187+
188+
First and foremost, thanks to our reporters, Matthew Treinish and Jake Lishman
189+
of IBM Quantum for finding and reporting this issue.
190+
191+
We are grateful for the entire community of security researchers and users who
192+
find and report security issues to PyPI in accordance with our
193+
[Security Policy](https://pypi.org/security/).
194+
PyPI relies on the efforts of our community to help us find and resolve issues like
195+
these before they become critical issues.
196+
Cooperation between all parties helps to improve the security of open source,
197+
and none of us could do it alone.
198+
199+
The tools and capabilities we've evolved in PyPI over the past six years have really
200+
come to be an asset in situations like these. I'm grateful to all the contributors
201+
and admins who have helped us to build them 💜.
202+
203+
---
204+
205+
_Ee Durbin is the Director of Infrastructure at
206+
the Python Software Foundation.
207+
They have been contributing to keeping PyPI online, available, and
208+
secure since 2013._
209+
210+
[^1]:
211+
As of writing, there are 6,682 remaining Organization Requests to review.
212+
213+
[^2]:
214+
It also showed me that our modeling could certainly be improved.
215+
In general all the joins are fine, but the fact that a `TeamRole` is directly
216+
related to a `User` rather than to their `OrganizationRole` allowed for this
217+
disconnect in the first place.
218+
219+
[^3]:
220+
Another thing to work on moving forward. We recently added some "in-app" messaging
221+
for PyPI Admins and Support to contact users regarding Organization Requests,
222+
which could be useful for group communication with Organization Owners.

0 commit comments

Comments
 (0)