From aa69972f3502c85b567e530694dc23e3da099ba6 Mon Sep 17 00:00:00 2001 From: Voyvodka Date: Mon, 11 May 2026 11:32:29 +0300 Subject: [PATCH] chore(ci): remove dockerhub-description continue-on-error workaround + document scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the 'borç' from .planning/TODO.md: the Sync Docker Hub description step in release.yml has been carrying continue-on-error: true since the very first release because DOCKERHUB_TOKEN lacked repo:write_metadata. The workaround masked a recoverable secret- misconfiguration: every release silently lost the Docker Hub overview sync, leaving the readme-filepath upload as a no-op. Now: - release.yml requires the step to succeed; a 403 surfaces as a CI failure on tag push, not a quiet skip. - docs/RELEASE.md §1 lists the three required token scopes (repo:read, repo:write, repo:write_metadata) and where to set them on Docker Hub. Operators that have already widened the token scope see no behaviour change. Operators that hadn't will get a hard CI failure on the next v* tag push, which is the correct signal to widen the scope once and forget about it. --- .github/workflows/release.yml | 5 ++--- CHANGELOG.md | 4 ++++ docs/RELEASE.md | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 47c7153..28eb3a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,11 +60,10 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - # Best-effort: a PAT without repo:write_metadata returns 403 here. - # The image push above is already committed, so don't fail the job. + # Requires DOCKERHUB_TOKEN with the `repo:write_metadata` scope. + # See docs/RELEASE.md §1 for the operator setup steps. - name: Sync Docker Hub description uses: peter-evans/dockerhub-description@v5 - continue-on-error: true with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b70e53..4b1f655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,11 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht ### Security - **Portal CORS preflight gains a deny-cache.** `PortalCorsMiddleware.HandlePreflightAsync` now caches both allow and deny outcomes for the same TTL as the per-app signing-key lookup (default 60 s). Browsers don't cache rejected preflights, so before this change every `OPTIONS` against `/api/v1/portal/*` from a disallowed origin re-scanned the portal-enabled application set and the in-process JSON deserialize loop — a low-effort DB hammer vector. New regression test `Preflight_Deny_Decision_Is_Cached_Within_Ttl` pins the behaviour. +### Build +- **`release.yml` removes the `continue-on-error` workaround on the `Sync Docker Hub description` step.** The step previously soft-failed on every release because `DOCKERHUB_TOKEN` lacked the `repo:write_metadata` scope; the workaround masked the underlying secret-misconfiguration. The token now needs the documented scope set (see `docs/RELEASE.md` §1) so a release that loses the Docker Hub overview sync surfaces as a hard CI failure instead of vanishing silently. + ### Documentation +- **`docs/RELEASE.md` §1 — `DOCKERHUB_TOKEN` scope requirement made explicit.** The secret table now lists the three required scopes (`repo:read`, `repo:write`, `repo:write_metadata`) and where to set them on Docker Hub. Avoids the recurring "release ran but Docker Hub overview is stale" debugging session. - **ADR-004 — Portal Signing Key Storage: Plaintext + Instant Invalidation, No Rotation Grace.** Locks in the v0.2.0 decisions around per-app `PortalSigningKey` at-rest storage (`varchar(64)` plaintext, mirroring the existing `SigningSecret` pattern), the no-grace rotation lifecycle (instant local invalidation, TTL-bounded multi-replica), and the one-shot reveal contract on `enable` / `rotate`. Documents the threat model, alternatives considered (`pgcrypto`, rotation grace, external KMS), and the explicit revisit triggers (compliance regimes, recurring host-integration friction, key-reuse beyond JWT verification). - **ADR-005 — Portal CORS Preflight Deny-Cache TTL.** Locks in PR #104's choice to cache both allow and deny outcomes for `PortalAuth:LookupCacheTtlSeconds` (default 60s), reusing the existing per-app cache window for symmetry. Documents why no synchronous invalidation hook from `PUT /portal/origins` (the cache key is origin-scoped, the operator action is app-scoped — the bookkeeping cost outweighs the ≤60s staleness), and what would justify revisiting (operator UX complaints, memory-pressure attacks via origin enumeration, richer CORS semantics). - **`docs/API.md` §3.8 — Portal API (Customer-Facing JWT).** Replaces the doc-drift gap surfaced by the v0.2.0 audit (where the portal API surface existed in code and CHANGELOG but was absent from `docs/API.md`). Covers HS256 JWT contract, capability scopes, per-app CORS, rate-limit partition reuse, cross-tenant `404` semantics, every route under `/api/v1/portal/*`, the dashboard portal-admin routes, the portal-specific error code table, and an end-to-end Node.js (`jose`) + cURL probe. diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 6e72f0f..0fd5eec 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -24,7 +24,7 @@ Add these secrets: | Secret | Description | |--------|-------------| | `DOCKERHUB_USERNAME` | Docker Hub username | -| `DOCKERHUB_TOKEN` | Docker Hub access token | +| `DOCKERHUB_TOKEN` | Docker Hub Personal Access Token. **Required scopes: `repo:read`, `repo:write`, `repo:write_metadata`.** The third scope is what lets the `Sync Docker Hub description` step in `release.yml` push the README to the Hub overview; without it that step fails with `403`. Create the token at Docker Hub → Account Settings → Personal Access Tokens. | | `NUGET_API_KEY` | NuGet.org API key | | `NPM_TOKEN` | npm automation token (for portal package — see Section 6) |