mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
This commit is contained in:
@@ -81,6 +81,35 @@ concurrency:
|
||||
# `.github/workflows/build-desktop.yml` and is shared with release-staging.yml.
|
||||
# ---------------------------------------------------------------------------
|
||||
jobs:
|
||||
# =========================================================================
|
||||
# Phase 0: Manual approval gate (main_head only).
|
||||
#
|
||||
# The `main_head` path bumps the version, COMMITS to `main`, and pushes that
|
||||
# commit + tag using a GitHub App token that bypasses branch protection
|
||||
# (prepare-build → "Commit, push and tag (main_head)"). If that App private
|
||||
# key (secrets.XGITHUB_APP_PRIVATE_KEY) ever leaked, an attacker could push
|
||||
# arbitrary commits to the default branch — CWE-250. This job parks the run
|
||||
# on the `Release-Approval` environment (configured with required reviewers
|
||||
# in repo settings) so a human must explicitly approve before prepare-build
|
||||
# is allowed to run its push step. A rejected/cancelled approval leaves
|
||||
# prepare-build skipped, so nothing is ever pushed.
|
||||
#
|
||||
# Scoped to `main_head` only: the `staging_tag` promotion path pushes just an
|
||||
# immutable `v<version>` tag at an already-validated staging commit — it does
|
||||
# NOT push a commit to `main` — so it stays gated only by the existing
|
||||
# `Production` environment and is intentionally skipped here (no regression).
|
||||
# =========================================================================
|
||||
review-approval:
|
||||
name: Manual approval gate (main_head)
|
||||
runs-on: ubuntu-latest
|
||||
environment: Release-Approval
|
||||
if: inputs.release_source == 'main_head'
|
||||
steps:
|
||||
- name: Record approval
|
||||
run: |
|
||||
echo "[release-production] main_head push to main approved by a required reviewer."
|
||||
echo "Proceeding to version bump + commit/tag push on prepare-build."
|
||||
|
||||
# =========================================================================
|
||||
# Phase 1: Resolve build ref and (for main_head) bump version + create tag
|
||||
# =========================================================================
|
||||
@@ -88,6 +117,17 @@ jobs:
|
||||
name: Prepare build context
|
||||
runs-on: ubuntu-latest
|
||||
environment: Production
|
||||
needs: [review-approval]
|
||||
# The push-to-main step lives inside this job (main_head path), so gating
|
||||
# prepare-build on review-approval gates the push. `!cancelled()` (not
|
||||
# `always()`) so a cancelled run never pushes; the success/skipped guard
|
||||
# lets the `staging_tag` path through (review-approval is skipped there)
|
||||
# while requiring an explicit approval success for `main_head`. A rejected
|
||||
# approval yields result == 'failure', failing the guard → no push.
|
||||
if: >-
|
||||
!cancelled()
|
||||
&& (needs.review-approval.result == 'success'
|
||||
|| needs.review-approval.result == 'skipped')
|
||||
outputs:
|
||||
version: ${{ steps.resolve.outputs.version }}
|
||||
tag: ${{ steps.resolve.outputs.tag }}
|
||||
|
||||
@@ -68,9 +68,11 @@ There is no separate `staging` branch, staging cuts and production promotions bo
|
||||
### Hotfix from `main` HEAD
|
||||
|
||||
1. Run **Release Production** via `workflow_dispatch` with `release_source = main_head` and the desired `release_type` (`patch` / `minor` / `major`).
|
||||
2. The workflow runs the legacy bump-and-tag path: bump on `main`, commit `chore(release): vX.Y.Z`, push, tag `vX.Y.Z`, build.
|
||||
2. The run first parks on the **`review-approval`** job (`environment: Release-Approval`); a [required reviewer](#release-app-token-approval-gate-and-rotation) must approve before anything is pushed. Only after approval does `prepare-build` run the bump-and-tag path: bump on `main`, commit `chore(release): vX.Y.Z`, push, tag `vX.Y.Z`, build.
|
||||
3. Use this only when a production-only fix needs to ship without going through staging.
|
||||
|
||||
> The `staging_tag` promotion path is **not** gated by `review-approval` — it pushes only an immutable `v<version>` tag at an already-validated staging commit, never a commit to `main`. It remains gated by the existing `Production` environment.
|
||||
|
||||
### Tag policy and rollback
|
||||
|
||||
- **Naming.** Staging tags use the SemVer pre-release suffix `-staging` (`v1.2.4-staging`) so they sort *before* the matching production tag. Promotion to production drops the suffix verbatim; the version embedded in the bundled installer is identical between the two tags.
|
||||
@@ -78,3 +80,32 @@ There is no separate `staging` branch, staging cuts and production promotions bo
|
||||
- **Rollback (production).** A failed build matrix triggers `cleanup-failed-release`, which deletes both the draft GitHub Release and the `v<version>` tag. The staging tag it was promoted from is left untouched and can be re-promoted after fixing.
|
||||
- **Rollback (staging).** A failed staging build deletes the `v<version>-staging` tag. The bump commit on `main` is left in place; the next staging cut continues from the new patch number rather than re-using it (we accept a small “gap” in patch numbers over racing with concurrent merges).
|
||||
- **Who can delete tags.** Same write-access as `main`. Workflow-driven cleanup deletes run with the workflow's token via `actions/github-script` (the GitHub App token is only used by `prepare-build` for the bump commit + tag push); manual deletes (`git push --delete origin <tag>`) require equivalent maintainer permissions.
|
||||
|
||||
## Release App token: approval gate and rotation
|
||||
|
||||
The `main_head` path of `release-production.yml` bumps the version, **commits to `main`**, and pushes that commit + tag with a GitHub App token (`secrets.XGITHUB_APP_ID` / `secrets.XGITHUB_APP_PRIVATE_KEY`) that **bypasses branch protection**. A leaked private key (via a log, a compromised action, or a misconfigured runner) would let an attacker push arbitrary commits to the default branch — [CWE-250: Execution with Unnecessary Privileges](https://cwe.mitre.org/data/definitions/250.html). Two controls bound that blast radius.
|
||||
|
||||
### Manual approval gate
|
||||
|
||||
The `review-approval` job runs **before** `prepare-build` and parks the run on the **`Release-Approval`** GitHub environment, so a human must approve before any push happens. It is scoped to `release_source == 'main_head'`; the `staging_tag` promotion path skips it (it pushes only a tag, never a commit to `main`).
|
||||
|
||||
One-time setup (repo **Settings → Environments**):
|
||||
|
||||
1. Create an environment named **`Release-Approval`** (exact name — the workflow references it verbatim).
|
||||
2. Under **Deployment protection rules**, enable **Required reviewers** and add the maintainers allowed to authorize a production push to `main`. A reviewer **cannot** approve their own run unless _Prevent self-review_ is left off; for a release gate, keeping a second approver is preferred.
|
||||
3. (Optional) Set a short **wait timer** of 0 — the gate is a human decision, not a delay.
|
||||
|
||||
When a `main_head` run starts it shows **"Waiting"** on the `review-approval` job; an approver opens the run and clicks **Review deployments → Approve**. Rejecting (or cancelling) leaves `prepare-build` skipped, so nothing is pushed.
|
||||
|
||||
### Quarterly key rotation
|
||||
|
||||
Rotate `XGITHUB_APP_PRIVATE_KEY` **every quarter** (and immediately on any suspected exposure). Schedule: end of **Mar / Jun / Sep / Dec**.
|
||||
|
||||
1. In the GitHub **App settings** (Org → Settings → Developer settings → GitHub Apps → the release App), under **Private keys** click **Generate a private key**. Download the new `.pem`.
|
||||
2. Update the repo secret: **Settings → Secrets and variables → Actions → `XGITHUB_APP_PRIVATE_KEY`** → paste the full new key (including the `-----BEGIN/END-----` lines). `XGITHUB_APP_ID` is unchanged.
|
||||
3. Trigger a low-risk verification run (e.g. **Release (Staging)**) and confirm the **Generate GitHub App token** step succeeds and the push authenticates.
|
||||
Do not use `main_head` for this check unless you intentionally want to cut a real bump commit and production tag — even with `create_release = false`, `prepare-build` still bumps the version, commits to `main`, and pushes the release tag.
|
||||
4. Back in **App settings → Private keys**, **delete the old key** so only the freshly-issued one remains valid.
|
||||
5. Record the rotation date (PR description, ops log, or `docs/OPERATIONS.md`) so the next quarter's owner can see when it last happened.
|
||||
|
||||
Rotating invalidates any copy of the old key that may have leaked, capping the exposure window at one quarter.
|
||||
|
||||
Reference in New Issue
Block a user