Skip to content

Commit de1aded

Browse files
Add incident response plan (aio-libs#12914)
1 parent 83b06e4 commit de1aded

3 files changed

Lines changed: 303 additions & 0 deletions

File tree

CHANGES/12914.contrib.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Added admin documentation on incident response and on running reproducer code
2+
safely, covering security vulnerability handling and supply-chain, account, and
3+
CI/infrastructure compromise -- by :user:`Dreamsorcerer`.

docs/contributing-admins.rst

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@ For regular contributors, return to :doc:`contributing`.
88

99
.. contents::
1010
:local:
11+
:depth: 1
12+
13+
.. highlight:: none
14+
15+
Running reproducer code
16+
-----------------------
17+
18+
.. warning::
19+
20+
When evaluating a bug report or vulnerability report, treat reproducer code as
21+
untrusted. If you don't understand what it does or are unfamiliar with a library
22+
it imports **do not run it** (and ask the reporter to provide a simpler reproducer).
23+
We also recommend that any reproducers you do run are executed in a container.
1124

1225
Creating a new release
1326
----------------------
@@ -60,3 +73,287 @@ If doing a minor release:
6073
#. Update both ``target-branch`` backports for Dependabot to reference the new branch name in ``.github/dependabot.yml``.
6174
#. Delete the older backport label (e.g. backport-3.8): https://github.com/aio-libs/aiohttp/labels
6275
#. Add a new backport label (e.g. backport-3.10).
76+
77+
Incident response
78+
-----------------
79+
80+
This section covers responding to a reported security vulnerability and to
81+
three classes of compromise -- of the supply chain, of a maintainer account,
82+
and of the project infrastructure. It picks up *after* a report or a problem
83+
is in hand.
84+
85+
.. note::
86+
87+
Vulnerability reports arrive through the organization's ``SECURITY.md``
88+
-- GitHub private vulnerability reporting, or email to the security coordinators.
89+
Never triage a suspected vulnerability in a public issue or pull request.
90+
91+
The security coordinator who first triages a report is its *incident lead*,
92+
and may explicitly hand off to another coordinator. The lead owns the GitHub
93+
Security Advisory (GHSA) draft, fix coordination, the release, and notification.
94+
95+
Severity tiers
96+
~~~~~~~~~~~~~~
97+
98+
aiohttp is volunteer-maintained, so there is no response-time commitment. The
99+
tier guides prioritization, the release decision, and how widely the fix is
100+
announced. All security fixes -- regardless of tier -- land on ``master`` and
101+
are backported to the currently supported ``x.y`` branch (and the next
102+
``x.y`` branch, if one is in development).
103+
104+
High
105+
A concrete remote impact an attacker can actually achieve against a
106+
default deployment. Examples: a single request that stops the server from
107+
handling further requests (a server-wide denial of service), reading files
108+
on the server outside the project root, remote code execution,
109+
authentication bypass. Cut a dedicated security release once the fix lands.
110+
111+
Medium
112+
Bounded impact, or impact that needs a non-default option, an unusual
113+
configuration, or a local position. Examples: request smuggling demonstrated
114+
to cause a real issue against a common proxy, or a DoS that use significant server
115+
resources with a low effort sustained attack.
116+
Cut a dedicated security release.
117+
118+
Low
119+
Limited or hard-to-exploit impact, or impact confined to debug or
120+
non-default paths. Examples: minor version or error-message disclosure,
121+
parser leniency with no demonstrated security impact, or request smuggling
122+
that has not been demonstrated to cause a real issue against a common
123+
proxy. May ride the next routine release rather than cutting a dedicated
124+
security release.
125+
126+
Severity is rated by demonstrated attacker impact. A denial of service that
127+
disables further request handling for the whole server from a single request
128+
leans High; one that requires sustained low-effort traffic to consume
129+
significant server resources leans Medium. Request smuggling and parser bugs
130+
are rated by what an attacker can actually achieve against a common proxy or
131+
in a realistic deployment, not by the shape of the bug.
132+
133+
Responding to a reported vulnerability
134+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
135+
136+
#. **Acknowledge and confirm.** Reply to the reporter in the GitHub private
137+
report or the email thread. Reproduce the issue
138+
(`but don't run untrusted code <Running reproducer code_>`_) against
139+
``master`` and the maintained ``x.y`` branches. If it is not reproducible
140+
or is out of scope, record the decision and close the report.
141+
#. **Open the GHSA draft.** For an email report, if the reporter can't use Github
142+
for some reason, then open a new advisory to track the issue.
143+
#. **Develop the fix privately** in the GHSA's temporary private fork ("Start a
144+
temporary private fork" in the draft) -- never on a public branch or in a
145+
public pull request. Include a regression test unless it would provide too
146+
much information for an attacker to replicate.
147+
#. **Title the advisory** with the vulnerability class and affected component.
148+
#. **Write the description** using the GHSA description template::
149+
150+
### Summary
151+
152+
<one or two sentences describing the vulnerability>
153+
154+
### Impact
155+
156+
<who is affected, prerequisites, and what an attacker achieves>
157+
158+
### Workaround
159+
160+
<if relevant, any workarounds for users unable to upgrade>
161+
162+
---
163+
164+
Patch: <link to commit fixing the issue on the stable x.y branch>
165+
166+
An important detail that Github will use to decide the CVE severity is exploit
167+
maturity. Try to clarify the likelihood of a vulnerability being attacked
168+
(e.g. if exploit code is available, or active attacks in-the-wild already exist).
169+
#. **Set the affected and fixed versions**.
170+
#. **Assign a severity tier** (see `Severity tiers`_).
171+
#. **Credit the reporter** (if report came by email) in the GHSA Credits field.
172+
#. **Credit the developer** using "Remediation developer" if different from Reporter.
173+
#. **Request a CVE** by clicking the button in the advisory.
174+
#. **Get the fix reviewed** in the private fork by at least one other maintainer.
175+
#. **Consider early notify** for high severity issues
176+
(see `Notifying about a disclosed vulnerability`_).
177+
#. **Coordinate a release.**
178+
179+
#. **Agree the timing.** For an embargoed high-severity incident, set the
180+
release date to align with the lift of any private-list embargo.
181+
#. **Merge all the private forks** and create and merge the backports for
182+
each.
183+
#. **Create the release.** Follow `Creating a new release`_.
184+
#. **Update patch link** in GHSA description.
185+
#. **Publish the GHSA** usually around 1 day after release.
186+
#. **Notify** according to the severity tier
187+
(see `Notifying about a disclosed vulnerability`_).
188+
#. **Run the post-incident steps** (see `Post-incident`_).
189+
190+
Notifying about a disclosed vulnerability
191+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
192+
193+
Notification is cumulative -- each tier adds to the ones below it. Mind the
194+
timing: the ``linux-distros`` pre-notification happens under embargo, *before*
195+
the release, while everything else happens at or after the release.
196+
197+
Baseline (all severities)
198+
The published GHSA (which feeds the CVE and the GitHub Advisory Database).
199+
200+
Higher (Medium-High)
201+
For most high severity issues, additionally post to the public ``oss-security``
202+
mailing list once the advisory is public.
203+
204+
Highest (High-Critical)
205+
For the most serious issues, additionally *before* the public release, send
206+
an embargoed pre-notification to the private distribution-security lists -- primarily
207+
``linux-distros`` -- and attempt to notify affected downstreams. Use the
208+
`vuln_search.py dependents-enumeration script
209+
<https://github.com/aio-libs/.github/blob/master/tools/vuln_search.py>`_
210+
to search aiohttp's GitHub dependents; it needs a GitHub token and may need to be
211+
run over a few days, so start it early.
212+
213+
Keep the embargo as short as practical. Typically an embargo of 1 or 2 days is expected.
214+
215+
Template for the embargoed ``linux-distros`` pre-notification::
216+
217+
To: linux-distros@vs.openwall.org
218+
Subject: aiohttp: <one-line summary of the vulnerability>
219+
220+
We are coordinating disclosure of a security vulnerability in aiohttp
221+
(https://github.com/aio-libs/aiohttp), the asyncio HTTP client/server
222+
library.
223+
224+
Summary
225+
<2-3 sentences: the flaw and its impact>
226+
227+
Affected versions
228+
<e.g. aiohttp < 3.12.15 (all 3.11.x and 3.12.x)>
229+
230+
CVE
231+
<CVE-XXXX-XXXXX, or "requested via GitHub Security Advisory, ID pending">
232+
233+
Fix
234+
<link to the patch, or attach the diff>
235+
236+
Proposed public disclosure date
237+
<YYYY-MM-DD> -- as short as practical. On that date we will publish the
238+
GitHub Security Advisory, ship a patched PyPI release, and post to
239+
oss-security.
240+
241+
This issue is not yet public; please observe the embargo until that date.
242+
243+
Contact: <incident lead name and security email>
244+
245+
Template for the public ``oss-security`` disclosure::
246+
247+
To: oss-security@lists.openwall.com
248+
Subject: CVE-XXXX-XXXXX: aiohttp <one-line summary>
249+
250+
A security vulnerability has been fixed in aiohttp, the asyncio HTTP
251+
client/server library (https://github.com/aio-libs/aiohttp).
252+
253+
CVE: CVE-XXXX-XXXXX
254+
Advisory: <GHSA URL>
255+
256+
Affected versions: <...>
257+
Fixed versions: <...>
258+
259+
Description
260+
<what the flaw is>
261+
262+
Impact
263+
<what an attacker can achieve, and any prerequisites>
264+
265+
Mitigation
266+
<workaround, or "upgrade to <version>">
267+
268+
Post-incident
269+
~~~~~~~~~~~~~
270+
271+
#. **Update** ``THREAT_MODEL.md``. Have an AI coding agent fetch the new advisories
272+
and update the document. Review the changes.
273+
#. **File follow-up hardening** as ordinary public issues or pull requests.
274+
275+
Supply-chain or release compromise
276+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
277+
278+
A malicious or tampered release on PyPI, or a compromised publish pipeline.
279+
280+
#. **Pull the bad artifact.** On PyPI, *yank* the affected release -- this
281+
hides it from new resolves while keeping existing pins working. *Delete* it
282+
only if it is actively malicious and removal is the safer choice. This needs
283+
a PyPI project owner or maintainer account.
284+
#. **Verify the legitimate artifacts with Sigstore.** Every genuine release is
285+
signed by the release pipeline, with the ``.sigstore`` bundles attached to
286+
the GitHub Release. Verify the ``sdist`` and wheels; a divergence from what is on
287+
PyPI localizes the tampering.
288+
#. **Lock the publish path.** Publishing uses a PyPI OIDC trusted publisher, so
289+
there is no long-lived token to rotate. Instead, on PyPI temporarily remove
290+
the trusted-publisher binding, and on GitHub restrict or pause the ``pypi``
291+
deployment environment used by the release job.
292+
#. **Audit the release inputs.** Review recent changes to the release workflow,
293+
its tag trigger, and the third-party action versions it uses.
294+
#. **Audit the committed Cython sources.** The generated ``.c`` files ship in
295+
the ``sdist`` and are not checked against their ``.pyx`` sources in CI (see
296+
``THREAT_MODEL.md`` section 5.19). Regenerate them with ``make cythonize``
297+
and ``git diff`` against the released revision. Re-verify the
298+
``vendor/llhttp`` pin and ``package-lock.json``.
299+
#. **Reissue a clean release** via `Creating a new release`_ once the cause is
300+
fixed. Never re-publish over a yanked version number.
301+
#. **Notify** per severity -- a malicious published release is High.
302+
303+
Maintainer account compromise
304+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
305+
306+
A GitHub or PyPI account takeover, or leaked publishing or signing material.
307+
308+
#. **Suspend the account.** Another GitHub organization owner can suspend it,
309+
revoke its repository access, and start the audit-log review. If the
310+
compromised account is the sole organization owner, or no other owner is
311+
reachable, contact GitHub Support at https://support.github.com/contact
312+
for emergency account lock down and organization recovery.
313+
#. **Kill sessions and credentials** on the affected account. On GitHub: change
314+
the password, sign out all sessions, revoke every personal access token,
315+
OAuth app, and SSH or GPG key, then re-enroll two-factor authentication.
316+
On PyPI: change the password, re-enroll two-factor authentication,
317+
and revoke API tokens.
318+
#. **Audit PyPI project ownership.** OIDC publishing means there is usually no
319+
long-lived PyPI token to leak, but a compromised project owner can add a
320+
trusted publisher or upload directly. Review the project collaborators and
321+
trusted-publisher bindings, and remove anything unrecognized.
322+
#. **Audit what the account could have done** from the GitHub audit log:
323+
pushes, tag creation, branch-protection or settings changes, new secrets or
324+
deploy keys, new collaborators, and workflow edits.
325+
#. **Revert and re-verify.** Force-revert any unauthorized commits or tags, and
326+
re-verify recent releases against their ``Sigstore`` bundles. If a malicious
327+
release shipped, escalate to `Supply-chain or release compromise`_.
328+
#. **Handle signing material.** ``Sigstore`` signing is key-less, so there is no
329+
static signing key to rotate. Any separate GPG key used for signed tags must
330+
be treated as compromised, then revoked and rotated.
331+
332+
CI or infrastructure compromise
333+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
334+
335+
A compromise of GitHub Actions, repository settings, or branch protections.
336+
337+
#. **Freeze the release path.** Pause the ``pypi`` deployment environment and
338+
stop tag-triggered deploys until the compromise is scoped.
339+
#. **Review branch protection and required reviews** on ``master`` and the
340+
``x.y`` branches. These are enforced on GitHub and are not visible in the
341+
repository; confirm nothing was relaxed and restore the known-good
342+
configuration.
343+
#. **Audit Actions secrets and environments** -- the repository and
344+
organization secrets, the ``pypi`` environment's protection rules and
345+
reviewers, and any new deploy keys. Confirm the workflow keeps empty
346+
top-level permissions and that ``id-token: write`` is scoped to the deploy
347+
job only.
348+
#. **Diff recently changed workflows** with ``git log -- .github/workflows/``,
349+
especially the release workflow, the auto-merge workflow (which runs in the
350+
privileged ``pull_request_target`` context), and the CodeQL workflow. Look
351+
for added steps, changed action references, or new triggers.
352+
#. **Check the committed Cython sources** as in `Supply-chain or release
353+
compromise`_ -- regenerate them and ``git diff``.
354+
#. **Re-pin and rebuild.** If a third-party action was compromised, pin the
355+
affected actions by full commit hash, re-run CI from a clean known-good
356+
revision, and re-verify with ``Sigstore`` any release made during the suspected
357+
window.
358+
#. **Escalate** to `Supply-chain or release compromise`_ if a release shipped
359+
through the compromised pipeline.

docs/spelling_wordlist.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ backoff
3636
backend
3737
backends
3838
backport
39+
backported
3940
Backport
4041
Backporting
4142
backports
@@ -321,6 +322,7 @@ seekable
321322
sendfile
322323
serializable
323324
serializer
325+
severities
324326
shourtcuts
325327
skipuntil
326328
Skyscanner
@@ -362,6 +364,7 @@ toolbar
362364
toplevel
363365
towncrier
364366
tp
367+
triages
365368
tuples
366369
UI
367370
un

0 commit comments

Comments
 (0)