Skip to content

Commit 5d001ee

Browse files
committed
Add extensive elaboration about the permission model
1 parent aac5876 commit 5d001ee

1 file changed

Lines changed: 68 additions & 16 deletions

File tree

peps/pep-0694.rst

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,8 @@ The choice of the root endpoint is left up to the index operator.
255255

256256
.. _authentication:
257257

258-
Authentication for Upload 2.0 API
259-
----------------------------------
258+
Authentication and Authorization
259+
--------------------------------
260260

261261
All endpoints in this specification **MUST** use standard HTTP authentication
262262
mechanisms as defined in :rfc:`7235`.
@@ -271,6 +271,35 @@ Authentication follows the standard HTTP pattern:
271271
The specific authentication schemes (e.g., Bearer, Basic, Digest)
272272
are determined by the index operator.
273273

274+
Authentication establishes the principal making a request. Authorization determines whether that principal
275+
may act on a particular session. All session endpoints defined in this specification -- that is, the URLs
276+
returned under the ``links`` key when a :ref:`publishing session <publishing-session-response>` or :ref:`file
277+
upload session <file-upload-session-response>` is created -- **MUST** be authorized against the project's
278+
upload permissions. Specifically, a server **MUST** verify, contemporaneously on each request, that the
279+
authenticated principal is currently authorized to upload to the project named by the session, and **MUST**
280+
respond with ``403 Forbidden`` if it is not.
281+
282+
Because this check is performed independently on each request, a session is **not** tied to the exact
283+
credentials that created it:
284+
285+
- A principal that is granted upload permission after a session is opened may immediately participate in that
286+
session.
287+
- A principal whose upload permission is revoked while a session is open **MUST** be denied with a
288+
``403 Forbidden`` on any subsequent request, even if that principal created the session.
289+
290+
This denial is evaluated per request and is not "sticky": if a principal's permission is later restored, its
291+
subsequent requests are authorized again. An index **MAY** apply a stricter policy, but this specification
292+
does not require one.
293+
294+
Servers **MUST** perform this authorization check on at least every request that creates, modifies, completes,
295+
extends, cancels, or publishes a publishing session or file upload session. For upload mechanisms that
296+
transfer a file across more than one request (for example, chunked or multipart mechanisms), servers
297+
**SHOULD** authorize each such request.
298+
299+
The unguessable :ref:`stage preview URL <staged-preview>` is a separate capability and is deliberately **not**
300+
governed by this authorization check; it grants read-only preview access to any client that holds the token,
301+
so that (for example) a CI job can install-test a staged release without project upload credentials.
302+
274303

275304
.. _session-errors:
276305

@@ -374,9 +403,15 @@ the "regular" (i.e. :ref:`unstaged <staged-preview>`) access protocols,
374403
If this first-release stage gets canceled,
375404
then the index **SHOULD** delete the project record, as if it were never uploaded.
376405

377-
The session is owned by the user that created it,
378-
and all subsequent requests **MUST** be performed with the same credentials,
379-
otherwise a ``403 Forbidden`` will be returned on those subsequent requests.
406+
A publishing session is **not** bound to the specific credentials that created it. Instead, every request
407+
against the session **MUST** be performed by an authenticated principal that is authorized to upload to the
408+
project at the time of that request, as described in :ref:`authentication`. A request from a principal that
409+
is not, or is no longer, so authorized **MUST** receive a ``403 Forbidden``.
410+
411+
For a first-release session on a project that does not yet exist, there are no existing project upload
412+
permissions to evaluate; the index instead authorizes the request according to its own name-registration
413+
policy, and **SHOULD** treat the creating principal (and, where applicable, an organization it acts on behalf
414+
of) as authorized for the lifetime of the session.
380415

381416
.. _index-specific-metadata:
382417

@@ -483,15 +518,18 @@ Multiple Session Creation Requests
483518
++++++++++++++++++++++++++++++++++
484519

485520
If a second attempt to create a session is received for the same name-version pair while a session for that
486-
pair is still ``pending``, then a new session is *not* created.
487-
Instead, the server **MUST** respond with a ``409 Conflict`` and **MUST** include a ``Location`` header that
488-
points to the :ref:`session status URL <publishing-session-status>`. Subsequent session creation requests
489-
**MUST** be performed with the same credentials as the pending session, otherwise a ``403 Forbidden`` is
490-
returned.
521+
pair is still ``pending``, then a new session is *not* created. Instead, the server **MUST** respond with a
522+
``409 Conflict`` and **MUST** include a ``Location`` header that points to the :ref:`session status URL
523+
<publishing-session-status>`. Like every other session request, such a request **MUST** be performed by a
524+
principal authorized to upload to the project (see :ref:`authentication`); a request from an unauthorized
525+
principal **MUST** receive a ``403 Forbidden`` instead, which takes precedence over the ``409 Conflict`` so
526+
that the existence of the pending session is not disclosed. An authorized principal receives the ``409
527+
Conflict`` and ``Location`` header and may use the referenced session; this is how multiple authorized
528+
publishers (for example, distinct Trusted Publishing workflows) can contribute to the same session.
491529

492530
Otherwise -- that is, when no session for that name-version pair is currently ``pending`` -- a new session is
493-
created with the same ``201 Created`` response and payload, except that the :ref:`publishing session status URL
494-
<publishing-session-status>`, ``session-token``, and ``links.stage`` values **MUST** be different.
531+
created with the same ``201 Created`` response and payload, except that the :ref:`publishing session status
532+
URL <publishing-session-status>`, ``session-token``, and ``links.stage`` values **MUST** be different.
495533

496534
.. _publishing-session-links:
497535

@@ -1275,10 +1313,20 @@ rate limiting either the legacy API or Upload 2.0 API for empty packages or sess
12751313
which supports organizations or :pep:`752`-style implicit namespaces, could implement different rate limiting
12761314
rules for different actors. Such implementations are left as index-specific policy decisions.
12771315

1278-
It's possible for a user with upload permissions at the start of a session to lose such permissions while a
1279-
session is open and before the entire session is published. Indexes should consider re-validating permissions
1280-
at key points during the session lifecycle, such as on artifact uploads, file upload session completion,
1281-
session extension requests, or at publish time.
1316+
Session access is authorized contemporaneously rather than being bound to the credentials that created the
1317+
session (see :ref:`authentication`). Indexes **MUST** re-validate authorization on each session request --
1318+
including artifact uploads, file upload session completion, session extension requests, and publishing -- so
1319+
that a principal that loses upload permission while a session is open is denied on its subsequent requests,
1320+
and a principal that gains permission may join an open session.
1321+
1322+
This model has two consequences worth calling out. First, because every mutating operation is authorized
1323+
uniformly, any principal currently authorized to upload to the project may add to, cancel, or publish another
1324+
principal's open session. The blast radius is limited to the unpublished staging session, since published
1325+
artifacts are immutable and publishing is atomic. Second, the :ref:`stage preview URL <staged-preview>` is a
1326+
capability that is *not* gated by upload permission, so a principal whose permission is revoked mid-session --
1327+
but who has already obtained the stage URL -- retains read-only preview access to the staged files until the
1328+
session is published or canceled. This is a narrow and accepted limitation; an index that considers it a
1329+
concern can mitigate it by canceling the affected session, or by limiting session lifetimes and extensions.
12821330

12831331
Staged releases, while useful for testing and embargoes, do provide some potential for larger scale hosting of
12841332
malware which isn't detectable by third party, external scanning tools, because staged artifacts are only
@@ -1437,6 +1485,10 @@ Change History
14371485
for the same name-version pair is still ``pending``. The previous wording referenced non-existent
14381486
``processing``/``complete`` publishing-session states.
14391487
* Clarify the wording of the **Multiple Sessions** client recommendation example.
1488+
* Relax session access from the exact creating credentials to any principal authorized to upload to the
1489+
project, evaluated contemporaneously on each request. Adds an **Authentication and Authorization** model,
1490+
handles permission changes mid-session, supports rotating Trusted Publishing tokens and multiple
1491+
publishers contributing to one session, and notes the related security implications.
14401492

14411493
* `07-Dec-2025 <https://discuss.python.org/t/pep-694-pypi-upload-api-2-0-round-2/101483/35>`__
14421494

0 commit comments

Comments
 (0)