From 90c67d20a2e4300aefc766747c7785779e40552a Mon Sep 17 00:00:00 2001 From: Di Hei Date: Thu, 10 Sep 2026 17:45:27 -0700 Subject: [PATCH 1/4] stage-capable GAT --- api/base.yaml | 4 +- api/registry.npmjs.com/publish.yaml | 13 +++++++ api/registry.npmjs.com/token.yaml | 58 +++++++++++++++++++++++++---- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/api/base.yaml b/api/base.yaml index 74192fe..ee8e362 100644 --- a/api/base.yaml +++ b/api/base.yaml @@ -67,7 +67,7 @@ tags: **Granular Access Tokens with `bypass_2fa`:** Granular Access Tokens (GATs) created with `bypass_2fa: true` are - allowed to perform automation flows such as direct publish, but they + allowed to perform automation flows within their scopes and publish policy, but they are **not** permitted to perform account-identity or governance write operations. Requests to those endpoints authenticated with a bypass-2FA GAT are rejected with `403 Forbidden` and must instead be @@ -91,7 +91,7 @@ tags: `PUT`/`DELETE /-/team/{orgName}/{teamName}/package` Direct publish (`PUT /{escapedPackageName}`) remains functional for - bypass-2FA GATs. + direct-capable bypass-2FA GATs. It is not permitted for stage-only GATs. - name: Tokens x-displayName: Tokens diff --git a/api/registry.npmjs.com/publish.yaml b/api/registry.npmjs.com/publish.yaml index 73b0a52..56f3058 100644 --- a/api/registry.npmjs.com/publish.yaml +++ b/api/registry.npmjs.com/publish.yaml @@ -20,6 +20,19 @@ paths: tags: - Publish summary: Publish a new version of a package + description: | + Publish a new package version directly to the registry. + + Stage-only Granular Access Tokens cannot publish new versions through this + endpoint, even with `bypass_2fa: true`. Requests rejected by the publish-policy + check return `403 Forbidden` with `E_STAGE_REQUIRED` in the error message. + Earlier authentication or package authorization checks can return other errors, + including `404 Not Found`. Rejected requests are not automatically redirected + to staging. Use `POST /-/stage/package/{package-name}` + (`npm stage publish`), followed by maintainer approval with 2FA. + + The stage-only policy restricts new-version publication, not other package + metadata writes. Existing authorization and 2FA requirements still apply. operationId: publish requestBody: required: true diff --git a/api/registry.npmjs.com/token.yaml b/api/registry.npmjs.com/token.yaml index fef455f..4c41a23 100644 --- a/api/registry.npmjs.com/token.yaml +++ b/api/registry.npmjs.com/token.yaml @@ -8,6 +8,26 @@ paths: Create a new npm access token with customizable permissions, scope restrictions, expiration, and CIDR IP range limitations. + To create a stage-only Granular Access Token (GAT), set + `packages_and_scopes_permission` to `read-write-stage-only` and select + at least one package or scope. This grants package write access but + restricts publishing new versions to `POST /-/stage/package/{package-name}` + (`npm stage publish`). Direct publication is not allowed or automatically staged. + Requests rejected by the publish-policy check return `403 Forbidden` with + `E_STAGE_REQUIRED` in the error message. Earlier authentication or package + authorization checks can return other errors, including `404 Not Found`. + A maintainer must approve the staged version with 2FA before publication. + Stage-only is not a valid value for `orgs_permission`. + + The package must already exist unless new-package staging is enabled for + your account. Creating a stage-only token does not enable that separate capability. + + The publish restriction is separate from `bypass_2fa`. Stage-only tokens + can be created with either setting, and existing scope, permission, and + 2FA checks still apply to all operations. The restriction does not itself + remove other package write capabilities, such as changing dist-tags or + deprecating versions. + **Requirements:** - Must be authenticated - **Two-factor authentication is required for this endpoint** @@ -86,12 +106,14 @@ paths: oneOf: - type: number - type: string - description: 'Expiration in days (number) or ISO date string. Read-write tokens: maximum 90 days, defaults to 7 days. Read-only tokens: unlimited maximum, defaults to 30 days' + description: 'Expiration in days (number) or ISO date string. Read-write tokens, including read-write-stage-only: maximum 90 days, defaults to 7 days. Read-only tokens: unlimited maximum, defaults to 30 days' bypass_2fa: type: boolean description: | Allow token to bypass 2FA requirements for automation - flows such as direct publish. + flows permitted by its package permissions and publish policy. + This does not allow a stage-only token to publish directly or + bypass the 2FA challenge when approving a staged version. Tokens created with `bypass_2fa: true` **cannot** perform account, token, or org/team/package governance write @@ -125,8 +147,17 @@ paths: description: Organizations this token can access. Empty arrays are treated as not provided packages_and_scopes_permission: type: string - enum: ["read-only", "read-write", "no-access"] - description: Permission for packages and scopes. Defaults to "read-only" if packages/scopes arrays have length > 0, otherwise "no-access" + enum: ["read-only", "read-write", "read-write-stage-only", "no-access"] + description: | + Permission for packages and scopes. Defaults to "read-only" if + packages/scopes arrays have length > 0, otherwise "no-access". + + - `read-only`: read package data. + - `read-write`: package write access, including direct and staged publishing. + - `read-write-stage-only`: package write access with new versions restricted + to staged publishing and subsequent maintainer approval with 2FA. + - `no-access`: no package or scope permissions. + orgs_permission: type: string enum: ["read-only", "read-write", "no-access"] @@ -134,6 +165,17 @@ paths: required: - password - name + examples: + stageOnly: + summary: Stage-only GAT for an existing package + value: + password: '' + name: 'staged-release-ci' + packages: ['@myorg/release-tools'] + packages_and_scopes_permission: 'read-write-stage-only' + orgs_permission: 'no-access' + expires: 7 + bypass_2fa: true security: - npmSessionToken: [] - npmAccessToken: [] @@ -216,7 +258,8 @@ paths: action: type: string description: Permission action ("read" or "write"). - description: List of permissions granted to this token. + description: | + List of permissions granted to this token. scopes: type: array items: @@ -258,7 +301,7 @@ paths: invalidPackagesPermission: summary: Invalid packages_and_scopes_permission value value: - error: 'Invalid packages_and_scopes_permission. Must be one of: no-access, read-only, read-write' + error: 'Invalid packages_and_scopes_permission. Must be one of: no-access, read-only, read-write, read-write-stage-only' invalidOrgsPermission: summary: Invalid orgs_permission value value: @@ -302,8 +345,7 @@ paths: tags: - Tokens summary: List npm access tokens - description: | - List all access tokens associated with the authenticated user's account. + description: | List all access tokens associated with the authenticated user's account. **Requirements:** - Must be authenticated with a valid Bearer token From b5d5017a7408e5c80fd92376a1546f6ab4c23457 Mon Sep 17 00:00:00 2001 From: Di Hei Date: Thu, 10 Sep 2026 17:51:21 -0700 Subject: [PATCH 2/4] lint --- api/registry.npmjs.com/token.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/registry.npmjs.com/token.yaml b/api/registry.npmjs.com/token.yaml index 4c41a23..7ac804f 100644 --- a/api/registry.npmjs.com/token.yaml +++ b/api/registry.npmjs.com/token.yaml @@ -345,7 +345,8 @@ paths: tags: - Tokens summary: List npm access tokens - description: | List all access tokens associated with the authenticated user's account. + description: | + List all access tokens associated with the authenticated user's account. **Requirements:** - Must be authenticated with a valid Bearer token From ace91851898b2c2b6444c8000fe19d34371a35d4 Mon Sep 17 00:00:00 2001 From: Di Hei Date: Fri, 11 Sep 2026 08:50:55 -0700 Subject: [PATCH 3/4] Update token.yaml to clarify stage-only token rules Removed note about package existence requirement for stage-only tokens. --- api/registry.npmjs.com/token.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/api/registry.npmjs.com/token.yaml b/api/registry.npmjs.com/token.yaml index 7ac804f..3e442f4 100644 --- a/api/registry.npmjs.com/token.yaml +++ b/api/registry.npmjs.com/token.yaml @@ -19,9 +19,6 @@ paths: A maintainer must approve the staged version with 2FA before publication. Stage-only is not a valid value for `orgs_permission`. - The package must already exist unless new-package staging is enabled for - your account. Creating a stage-only token does not enable that separate capability. - The publish restriction is separate from `bypass_2fa`. Stage-only tokens can be created with either setting, and existing scope, permission, and 2FA checks still apply to all operations. The restriction does not itself From 85deb203200a566cb4960877ab0d2ba0ba10a799 Mon Sep 17 00:00:00 2001 From: Di Hei Date: Fri, 11 Sep 2026 08:51:34 -0700 Subject: [PATCH 4/4] Update publish.yaml to clarify stage-only policy Clarified the stage-only policy description in the YAML file. --- api/registry.npmjs.com/publish.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/registry.npmjs.com/publish.yaml b/api/registry.npmjs.com/publish.yaml index 56f3058..6d2d9c2 100644 --- a/api/registry.npmjs.com/publish.yaml +++ b/api/registry.npmjs.com/publish.yaml @@ -31,8 +31,8 @@ paths: to staging. Use `POST /-/stage/package/{package-name}` (`npm stage publish`), followed by maintainer approval with 2FA. - The stage-only policy restricts new-version publication, not other package - metadata writes. Existing authorization and 2FA requirements still apply. + The stage-only policy restricts new-version publication. Existing authorization + and 2FA requirements still apply. operationId: publish requestBody: required: true