@ponharu/pkgflare 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,7 +30,11 @@ Changes to this path must preserve:
30
30
 
31
31
  ## Authentication boundary
32
32
 
33
- Secrets provide a small deployment-owned token set. There is no identity database or per-user session lifecycle. Read and publish permissions apply registry-wide; separate deployments provide separate trust boundaries. Adding per-package permissions would require an explicit authorization design, not just another token label.
33
+ Secrets provide a small deployment-owned token set with registry-wide read or publish permissions. GitHub Actions OIDC adds short-lived workflow identity with explicit repository ID, owner ID, ref, workflow, permission, and package grants. Both methods enter the same route authorization boundary, so package grants cover metadata, tarballs, publish, and dist-tag operations consistently.
34
+
35
+ pkgflare uses the GitHub JWT directly as the npm Bearer token. A token exchange would shorten or reshape the credential but would also require a pkgflare signing key, a new session endpoint, and another token lifecycle. Direct verification keeps standard npm/Bun requests, retains GitHub's short expiry, and makes GitHub's fixed issuer/JWKS the only signing trust root. JWKS fetches are bounded, cached, and fail closed.
36
+
37
+ Normal and reusable workflows are separate trust cases. A normal rule requires the absence of `job_workflow_ref`; a reusable rule matches both the caller's `workflow_ref` and the called `job_workflow_ref`. Pull-request contexts are not accepted. Immutable numeric repository and owner IDs are authoritative; workflow path claims add execution constraints rather than replacing identity.
34
38
 
35
39
  Unexpected diagnostics identify the request and operation without recording credentials or package payloads. Normal authenticated metadata responses do contain package manifests; the privacy restriction applies to diagnostics and error responses.
36
40
 
@@ -51,6 +55,7 @@ Migrations run before Worker deployment. New migrations must remain compatible w
51
55
  | `src/runtime/publish.ts` | Publish validation and D1 commit/reconciliation |
52
56
  | `src/runtime/read.ts` | Metadata and tarball reads |
53
57
  | `src/runtime/dist-tags.ts` | Tag reads and mutations |
58
+ | `src/runtime/github-oidc.ts` | GitHub JWT verification, bounded JWKS retrieval, and package grants |
54
59
  | `migrations/` | Append-only D1 schema history |
55
60
 
56
61
  ## Deliberate boundaries
@@ -81,12 +81,68 @@ npx pkgflare deploy --adopt-existing
81
81
 
82
82
  Adoption permits applying migrations and deploying over existing resources; it does not import or validate arbitrary registry data. Use it only for resources intended for this pkgflare deployment. A successful Worker creation whose result was lost can also require adoption on retry. Saved D1 and R2 identifiers are reused.
83
83
 
84
- ## Tokens and access
84
+ ## Registry authentication
85
85
 
86
- All tokens cover the whole registry. Publish permission includes read access and dist-tag changes. Use read-only tokens for installation jobs and keep publish tokens limited to package release jobs.
86
+ Cloudflare Secret tokens cover the whole registry. Publish permission includes read access and dist-tag changes. Use read-only tokens for installation jobs and keep publish tokens limited to package release jobs.
87
87
 
88
88
  For rotation, add a new binding while retaining the old one, deploy, register the new Secret, and switch clients to it. Once clients have switched, remove the old binding and deploy again, then delete the old Secret. Removing a configured binding revokes that token after deployment. For urgent revocation, delete its Secret through Cloudflare; other configured tokens remain usable. Revocation cannot retract package bytes already downloaded by clients.
89
89
 
90
+ ## GitHub Actions OIDC
91
+
92
+ GitHub Actions jobs may authenticate without a stored registry token. pkgflare accepts a GitHub OIDC JWT directly as the npm Bearer token. This preserves normal npm-compatible requests and avoids adding a pkgflare session-signing Secret or token-exchange endpoint. The JWT is short-lived and must be requested separately by each job.
93
+
94
+ This feature is registry authentication only. It is not npmjs.org Trusted Publishing, does not publish to npmjs.org, and does not authenticate Wrangler or the Cloudflare management API.
95
+
96
+ Configure `auth.githubOidc.audience` and one or more subjects. Each subject is an allow rule:
97
+
98
+ ```ts
99
+ githubOidc: {
100
+ audience: "pkgflare://packages.example.com",
101
+ subjects: [{
102
+ repositoryId: "123456789",
103
+ repositoryOwnerId: "987654321",
104
+ ref: "refs/tags/v*",
105
+ workflowRef: "acme/example/.github/workflows/publish.yml@refs/tags/v*",
106
+ permissions: ["publish"],
107
+ packages: ["@acme/example"],
108
+ }],
109
+ }
110
+ ```
111
+
112
+ Repository and owner IDs are decimal GitHub IDs and remain the primary repository identity checks across renames. `ref`, `workflowRef`, and optional `jobWorkflowRef` are exact matches unless they end in `*`, in which case only that final prefix wildcard is supported. Refs must be branch or tag refs. Package grants are exact scoped package names or a complete scope wildcard such as `@acme/*`; they must belong to a configured registry scope.
113
+
114
+ The complete normalized registry configuration must fit Cloudflare's 5 KiB per-variable limit. pkgflare checks this before deployment; prefer a scope wildcard or another registry when a very large subject/package matrix would exceed it.
115
+
116
+ For a normal workflow, omit `jobWorkflowRef`. A token containing `job_workflow_ref` will not match that rule. For a reusable workflow, set all of these independently:
117
+
118
+ - `repositoryId`, `repositoryOwnerId`, `ref`, and `workflowRef` identify and constrain the caller.
119
+ - `jobWorkflowRef` identifies the called reusable workflow and its trusted ref.
120
+
121
+ OIDC requests from `pull_request`, `pull_request_target`, related pull-request events, and merge queues are rejected even if another claim pattern would match. Use a trusted branch, tag, or manually dispatched workflow. A `publish` grant includes reads and dist-tag changes only for its allowed packages; a `read` grant cannot publish or change tags. Metadata and tarball reads apply the same package grant.
122
+
123
+ The job needs `id-token: write`. Keep a normal scope-specific `.npmrc` with `${NPM_TOKEN}`, then obtain the JWT through command substitution so it is not printed:
124
+
125
+ ```yaml
126
+ permissions:
127
+ contents: read
128
+ id-token: write
129
+
130
+ steps:
131
+ - uses: actions/checkout@v4
132
+ - uses: actions/setup-node@v4
133
+ with:
134
+ node-version: 22
135
+ - run: npm ci
136
+ - name: Publish package
137
+ run: NPM_TOKEN="$(npx pkgflare auth github --audience 'pkgflare://packages.example.com')" npm publish
138
+ ```
139
+
140
+ For read-only CI, grant `permissions: ["read"]` and run the same token command with `npm ci`, pnpm, Yarn Classic, or Bun. Never echo the command result or enable shell tracing around it.
141
+
142
+ The JWT is a Bearer credential and can be replayed until it expires. Request it immediately before the package command, do not persist it in files or job outputs, and keep untrusted scripts out of the authenticated step.
143
+
144
+ The Worker accepts only RS256 tokens issued by `https://token.actions.githubusercontent.com` for the configured audience. It validates signature, `typ`, expiry, not-before, issued-at age, subject presence, JWT ID, repository and owner IDs, ref, workflow, event, permission, and package. JWKS is fetched only from GitHub's fixed endpoint with a five-second timeout, 64 KiB/16-key response limits, a five-minute in-isolate cache, and a 30-second unknown-key refresh cooldown. Invalid tokens are rejected. An unavailable or invalid JWKS endpoint fails closed with 503; token contents are not logged.
145
+
90
146
  ## Backups and restoration
91
147
 
92
148
  pkgflare does not provide an automated backup or restore command. A recoverable registry needs the D1 metadata, every R2 object referenced by that metadata, the deployment configuration/state, and access to the token secret store. Back up D1 and R2 separately using Cloudflare or compatible storage tooling. D1 [Time Travel](https://developers.cloudflare.com/d1/reference/time-travel/) covers database recovery, not R2 objects or Worker Secrets.
@@ -102,10 +158,10 @@ Failed publishing may leave unreachable objects. Automatic orphan collection is
102
158
  | Symptom | What to check |
103
159
  | ----------------------- | ---------------------------------------------------------------------------------------------------------- |
104
160
  | 401 | `NPM_TOKEN` is set and the `.npmrc` authentication hostname matches the registry URL |
105
- | 403 | The token matches a configured Secret and grants the required permission |
161
+ | 403 | The Secret or OIDC token matches its configured permission, package, and workflow trust rules |
106
162
  | 409 on publish | The version already exists; inspect it before deciding whether to publish a new version |
107
163
  | 413 | Publish metadata exceeds 1 MiB, or the encoded request exceeds Cloudflare's limit |
108
- | 503 | Retry after checking storage availability; a publish result may be uncertain, so inspect the version first |
164
+ | 503 | Check storage or GitHub JWKS availability; a publish result may be uncertain, so inspect the version first |
109
165
  | Existing resource error | Restore saved state or verify ownership before explicit adoption |
110
166
  | Account mismatch | Configuration, environment, and saved state select the same Cloudflare account |
111
167
 
@@ -24,9 +24,12 @@ The deployment repository contains `pkgflare.config.ts` and a package-manager lo
24
24
  - one or more allowed npm scopes
25
25
  - an optional Cloudflare account ID, required when Wrangler credentials expose multiple accounts
26
26
  - an optional custom hostname
27
- - Cloudflare Secret binding names and their `read` or `publish` permissions
27
+ - optional Cloudflare Secret binding names with registry-wide `read` or `publish` permissions
28
+ - optional GitHub OIDC audience and subject rules containing repository/owner IDs, ref, workflow, permissions, and allowed package patterns
28
29
 
29
- Secret values are never stored in configuration or deployment state. A `publish` token also grants read access. Every token applies to all packages in the registry; per-scope and per-package authorization are not supported.
30
+ At least one Secret token or GitHub OIDC configuration is required. Secret values are never stored in configuration or deployment state. A `publish` grant also grants read access and dist-tag mutation. Secret tokens apply to all packages; OIDC subjects are restricted to their exact packages or configured scope wildcards.
31
+
32
+ The normalized runtime configuration is rejected when its UTF-8 JSON representation exceeds the 5 KiB Cloudflare Worker variable limit.
30
33
 
31
34
  ## Deployment state and resources
32
35
 
@@ -82,7 +85,17 @@ Supported publish sizes depend on the deployment plan and request processing cos
82
85
 
83
86
  ## Authentication and rotation
84
87
 
85
- Every Registry operation requires a Bearer token. Tokens are compared against configured Cloudflare Secret bindings without logging request credentials. Missing bindings do not disable other valid bindings.
88
+ Every Registry operation requires a Bearer token. Static tokens are compared against configured Cloudflare Secret bindings without logging request credentials. Missing bindings do not disable other valid bindings.
89
+
90
+ When GitHub OIDC is configured, a non-Secret Bearer token may be a GitHub JWT. The Worker accepts only RS256 with JWT type, the fixed `https://token.actions.githubusercontent.com` issuer and fixed GitHub JWKS URL, the configured audience, required timing and identity claims, and a maximum issued age of ten minutes. It matches numeric repository and owner IDs, branch/tag ref, caller workflow, optional reusable workflow, permission, and package. Pull-request-related and merge-group events are rejected.
91
+
92
+ Normal workflow rules require `job_workflow_ref` to be absent. Reusable workflow rules require it to match `jobWorkflowRef` in addition to matching the caller's `workflow_ref`. Exact matching is the default; a terminal `*` is the only pattern syntax. OIDC package grants are enforced before metadata, tarball, publish, and dist-tag handlers.
93
+
94
+ GitHub keys are fetched under timeout and response-size/key-count limits, cached per isolate, and refreshed after a cooldown when an unknown key is encountered. Invalid signatures/claims fail with 403. JWKS retrieval and validation failures fail closed with 503. Tokens and claims are not included in diagnostics.
95
+
96
+ `pkgflare auth github --audience <audience>` requests a JWT through the GitHub Actions OIDC environment and prints only the token. npm-compatible clients use it as `NPM_TOKEN`. The direct JWT design does not issue pkgflare sessions and is independent of npmjs.org Trusted Publishing and Cloudflare API authentication.
97
+
98
+ Automated tests use locally generated signing keys and a mocked fixed GitHub JWKS response. A real GitHub-issued token and a deployed registry require a separate trusted-workflow acceptance run.
86
99
 
87
100
  Rotation uses overlapping bindings: add the new binding, deploy, register and distribute the new Secret, remove the old binding and deploy, then delete the old Secret. Both tokens work during the overlap; the removed token stops working after the second deployment.
88
101
 
@@ -110,5 +123,6 @@ v1 is accepted when automated tests demonstrate:
110
123
  8. consistent package metadata snapshots during publication
111
124
  9. overlapping-token rotation and missing-binding tolerance
112
125
  10. absence of credentials and package contents from diagnostics
126
+ 11. GitHub OIDC signature, issuer, audience, time, repository, owner, ref, normal/reusable workflow, event, permission, package, key rotation, and failure-closed checks
113
127
 
114
128
  Deployment acceptance should also exercise initial setup, repeat deployment, failure recovery, and large publishes on the intended Cloudflare account. Record the account plan, encoded request size, tarball size, CPU time, and result when assessing capacity.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ponharu/pkgflare",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Deploy a private npm registry to your own Cloudflare account.",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -60,6 +60,7 @@
60
60
  },
61
61
  "dependencies": {
62
62
  "jiti": "^2.7.0",
63
+ "jose": "6.2.10",
63
64
  "semver": "^7.8.5",
64
65
  "wrangler": "^4.125.0"
65
66
  },