Skip to content

Commit 679169d

Browse files
committed
Address comments for stronger wording including section on avoiding denial of service by having scanning wait until all file uploads complete.
1 parent 3562b92 commit 679169d

1 file changed

Lines changed: 46 additions & 16 deletions

File tree

‎peps/pep-0694.rst‎

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,16 @@ system alone can neither publish a release nor discard a pending one.
299299
Cancellation is protected because it is destructive: it discards the staged files and frees the name-version
300300
pair. An index that left it at the upload-permission level would let a compromised upload credential delete a
301301
release a maintainer has staged and is waiting to publish (a denial-of-release), or discard a session that
302-
has been flagged and is awaiting review. An index that instead wants an automated uploader to be able to
303-
abort its *own* in-progress uploads **MAY** leave cancellation at the upload-permission level. How any
304-
additional authorization is expressed, and whether it is offered at all, is determined by the index operator.
302+
has been flagged and is awaiting review. For a first release of a new project, cancellation also releases the
303+
temporary project-name reservation the index holds for the session, so an index that did not protect it would
304+
let a compromised upload credential discard the session and allow the name to be claimed by someone else. An
305+
index that instead wants an automated uploader to be able to abort its *own* in-progress uploads **MAY** leave
306+
cancellation at the upload-permission level.
307+
308+
How any additional authorization is expressed, and whether it is offered at all, is determined by the index
309+
operator. An index **MAY** keep this choice operator-wide, or expose it to individual project owners, for
310+
example by letting a project owner require the separate authorization for their own project, or allow a single
311+
principal to hold both.
305312

306313

307314
.. _session-errors:
@@ -618,29 +625,36 @@ expected to remain available anyway, as this PEP does not propose a deprecation
618625
an index **MAY** allow a legacy upload to create a publishing session, so that from creation onward the session is the
619626
same as one created directly.
620627

621-
An index that supports this **MUST** document it, and **SHOULD** accept a ``staged`` field with the value
628+
An index that supports this **MUST** document it, and **MUST** accept a ``staged`` field with the value
622629
``true`` in the legacy ``multipart/form-data`` upload request. When that field is present, the index creates a
623630
publishing session in the ``open`` state for the uploaded file's project and version, adds the file to it as a
624-
:ref:`completed <file-upload-session-states>` file upload, and does not publish it. The index **SHOULD**
631+
:ref:`completed <file-upload-session-states>` file upload, and does not publish it. The index **MUST**
625632
return the :ref:`publishing session creation response body <publishing-session-response>` from that upload,
626633
including the ``links`` and ``session-token`` keys, so that the publisher can then use the endpoints in this
627634
PEP to :ref:`preview <staged-preview>`, :ref:`publish <publishing-session-completion>`, or :ref:`cancel
628-
<publishing-session-cancellation>` the session. An index **MAY** also create a session for an upload based on
635+
<publishing-session-cancellation>` the session. Returning the body is required because a client that sets
636+
``staged=true`` but receives no session handle would be left unable to publish or cancel the staged release.
637+
Note that a client that sets ``staged=true`` without understanding the response may record it (for example in
638+
public CI logs); the ``session-token`` it contains grants only read-only :ref:`preview <staged-preview>`
639+
access, but indexes and publishers **SHOULD** be aware that it can be exposed this way. An index **MAY** also create a session for an upload based on
629640
its own policy or the project's configuration, without the field being present; this allows a project to
630641
require that its releases are staged in a way that an upload client cannot bypass. In this policy driven
631642
case no client change is required at all; the ``staged`` field lets a publisher opt in per upload with only a
632643
minimal change to existing tooling.
633644

634645
Because the legacy API uploads a single file per request, subsequent legacy uploads for the same project and
635646
version **SHOULD** be added to the same open session when that session was itself created through a legacy
636-
upload, so that the release is still published as a unit.
647+
upload, so that the release is still published as a unit. Once such a session exists, a subsequent legacy
648+
upload for that project and version is added to it whether or not it carries the ``staged`` field: the field
649+
is not required again, and the index **MUST NOT** reject the upload for omitting it. The field is still
650+
permitted on these uploads as an explicit statement of intent.
637651

638652
A session's creation path is fixed when it is created, and the two paths **MUST NOT** be mixed.
639653
If a non-terminal session already exists for a name-version pair (see :ref:`publishing-session-multiple`), a request to
640-
contribute to it through the *other* path **MUST** be rejected with a ``409 Conflict``: a legacy
641-
``staged=true`` upload for a pair that already has an open session created through the Upload 2.0 API is
642-
rejected, and an Upload 2.0 request that would add to a session created through a legacy upload is likewise
643-
rejected.
654+
contribute to it through the *other* path **MUST** be rejected with a ``409 Conflict``: any legacy upload for
655+
a pair that already has an open session created through the Upload 2.0 API is rejected (whether or not it
656+
carries the ``staged`` field), and an Upload 2.0 request that would add to a session created through a legacy
657+
upload is likewise rejected.
644658

645659
Fixing the path at creation avoids ambiguity in how the session reaches a terminal state. An Upload 2.0
646660
session is driven to completion by a client that holds its ``session-token`` and calls the control plane
@@ -826,6 +840,15 @@ change: deferred processing resolves to either ``published`` on success or ``err
826840
resolves to ``error``, the session remains editable and the reason is reported in the session's ``notices``,
827841
as described in :ref:`publishing-session-states`.
828842

843+
Reviewing an upload can produce two kinds of result, surfaced at different points. Correctness problems that
844+
can be decided about a single file in isolation, such as its size, checksum, metadata, or compatibility tags,
845+
are surfaced when that :ref:`file upload session <file-upload-session-states>` completes, moving the file to
846+
the ``error`` state, from which it cannot be repaired in place. Results that concern whether the release may
847+
become public, such as malware detection, instead gate the publish step and are described below. An index
848+
**MAY** run malware review per file, as each file is uploaded, as well as (or instead of) at publication; a
849+
file remains uploadable to a stage for some time before the release is published, so reviewing each file as it
850+
arrives can shorten the window in which an unreviewed file sits in a stage.
851+
829852
The ``processing`` state **MAY** be used to run asynchronous review of a session's files before it is
830853
published, such as malware scanning. If the review completes without an adverse result, the session resolves
831854
to ``published`` as normal. If the review does not complete within a period chosen by the index, the server
@@ -849,7 +872,9 @@ change is left to the index: it might run to completion, rescan only the changed
849872
Because editing a session can trigger re-review, an index **SHOULD** guard against abuse of the review system.
850873
For example, a client might repeatedly upload a file it knows will be flagged, delete it, and re-upload it to
851874
force repeated scans. Mitigations such as rate-limiting reviews, capping the number of review attempts for a
852-
session, or moving a persistently adverse session to a terminal state are left to the index.
875+
session, or moving a persistently adverse session to a terminal state are left to the index. An index can
876+
also avoid this class of abuse entirely by performing the review only at publication time, when the session's
877+
files are fixed for the duration of the publish and cannot be swapped out to force another scan.
853878

854879
A publish attempt that fails *synchronously* (i.e. within the publish request itself) is returned to the
855880
client as an :ref:`error response <session-errors>` and leaves the session in its current editable state; it
@@ -1955,16 +1980,21 @@ as experience is gained operating Upload 2.0.
19551980
Change History
19561981
==============
19571982

1958-
* `01-Aug-2026 <https://discuss.python.org/t/pre-pep-staged-releases-separated-from-pep-694/107804/58>`__
1983+
* `01-Oct-2026 <https://discuss.python.org/t/pre-pep-staged-releases-separated-from-pep-694/107804/58>`__
19591984

19601985
* Add :ref:`Legacy Upload API Interoperability <legacy-interop>`, allowing a legacy upload to create a
19611986
publishing session (via a ``staged`` field, or by index or project configuration that an upload client
19621987
cannot bypass) so that staging is usable before upload tooling adopts this API, and allowing an index to
1963-
publish such a session itself once automated processing resolves or its window elapses.
1988+
publish such a session itself once automated processing resolves or its window elapses. Require that an
1989+
index supporting this accept the ``staged`` field and return the session creation response body, specify
1990+
that subsequent legacy uploads to an open stage need not repeat the field, and forbid mixing the two
1991+
session-creation paths.
19641992
* Note that the ``processing`` state **MAY** be used for asynchronous review such as malware scanning, with
1965-
an index-chosen window after which the review is treated as having produced no adverse result.
1993+
an index-chosen window after which the review is treated as having produced no adverse result. Distinguish
1994+
per-file correctness results from release-level review, allow review per file or at publication, and note
1995+
that reviewing at publication time avoids re-scan abuse.
19661996
* Allow an index to require additional authorization, beyond upload permission, to publish or cancel a
1967-
session, so that duties can be separated.
1997+
session, so that duties can be separated, optionally exposing that choice to project owners.
19681998

19691999
* `29-Jul-2026 <https://discuss.python.org/t/pep-694-pypi-upload-api-2-0-round-4/108320>`__
19702000

0 commit comments

Comments
 (0)