From c7297ba36b5ea24e9ecb5f6905538e301a6dc6bc Mon Sep 17 00:00:00 2001 From: rldyourmnd Date: Tue, 4 Aug 2026 00:49:24 +0500 Subject: [PATCH 1/2] docs: add personal-account consumer tier (doc 18 + example) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A repository owned by a personal GitHub account (not an organization) inherits the private-free posture but cannot reach an org-level self-hosted runner group — a platform gate, not a setting. It needs repo-level runner registration and a caller that routes every job to that label. - docs/18-personal-account-tier.md: the runner-registration procedure (org-runner vs repo-runner reachability), the inversion against private-free (same posture, different runner), and the 2000 min/month personal quota. - examples/personal/security-selfhosted.yml: private-free stack (gitleaks, actionlint, zizmor-no-SARIF) with runner: input on every call. - scripts/validate_catalog.py: whitelist the new aggregate example. - docs/00-overview.md: tier note + doc index entry. - CHANGELOG.md: [Unreleased] entry. No catalog/capabilities.yml or workflow file changed — the capability set is identical to private-free; only the runner differs. --- CHANGELOG.md | 14 ++++ docs/00-overview.md | 13 ++- docs/18-personal-account-tier.md | 97 +++++++++++++++++++++++ examples/personal/security-selfhosted.yml | 47 +++++++++++ scripts/validate_catalog.py | 1 + 5 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 docs/18-personal-account-tier.md create mode 100644 examples/personal/security-selfhosted.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 3981fc7..f94f8c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ ## [Unreleased] +### Added + +- **Personal-account consumer tier.** A repository owned by a *personal* GitHub + account (not an organization) inherits the private-free posture but cannot + reach an org-level self-hosted runner group — it needs a **repo-level** + runner registration and a caller that routes every job to that label. New + [docs/18-personal-account-tier.md](docs/18-personal-account-tier.md) records + the runner-registration procedure (org-runner vs repo-runner reachability is a + platform gate, not a setting) and the inversion against [02 private-free], + and new [examples/personal/security-selfhosted.yml](examples/personal/security-selfhosted.yml) + is the private-free stack (gitleaks, actionlint, zizmor-no-SARIF) with a + `runner:` input on every call. No catalog or workflow file changed — the + capability set is identical to private-free; only the runner differs. + ## [0.13.3] - 2026-08-03 ### Added diff --git a/docs/00-overview.md b/docs/00-overview.md index 1a311cc..53cdb8c 100644 --- a/docs/00-overview.md +++ b/docs/00-overview.md @@ -47,6 +47,16 @@ most consequential correction: Artifact Attestations are gated on Enterprise Cloud for private repos, and this estate **has** it, so private repositories release with full provenance instead of the `-free` variant. +### Personal-account repositories + +A repository owned by a **personal** account (not an organization) takes the +[private-free](02-private-free.md) posture regardless of visibility — it has no +GHAS, no Enterprise Cloud, and a 2 000 min/month Actions quota with no org pool. +Its runner strategy differs from a generic private-free repo: every job is +routed to a **repo-level** self-hosted runner, because an org-level runner is +not reachable from a personal-account namespace. See +[18 Personal-account tier](18-personal-account-tier.md). + ## How to consume a reusable workflow Reference by `owner/repo/.github/workflows/.yml@` from a caller @@ -106,7 +116,8 @@ For end-to-end caller examples per tier, see the tier docs and the repository [02 Private free](02-private-free.md) · [03 Private paid / GHAS](03-private-paid-ghas.md) · [16 Code Quality](16-code-quality.md) · - [17 NDDev estate](17-nddev-tier.md) + [17 NDDev estate](17-nddev-tier.md) · + [18 Personal account](18-personal-account-tier.md) - Platform: [04 Actions core](04-actions-core.md) · [05 Runners](05-runners.md) - Security: [06 Security scanning](06-security-scanning.md) · [07 Supply chain / SLSA / SBOM / attestations](07-supply-chain-slsa-sbom-attestations.md) diff --git a/docs/18-personal-account-tier.md b/docs/18-personal-account-tier.md new file mode 100644 index 0000000..14c2a93 --- /dev/null +++ b/docs/18-personal-account-tier.md @@ -0,0 +1,97 @@ +# Personal-account tier — a private repo under a personal GitHub account + +The other tier docs classify a repository by **visibility** (public / private) +and **plan** (free / GHAS). This one adds a third axis the generic model does +not surface: whether the repository is owned by an **organization** or by a +**personal account**. A personal-account repository is the *degenerate* case of +private-free — it inherits that posture in full, *and* it cannot reach an +organization's self-hosted runner fleet, so its runner strategy is different. + +## Why this doc exists + +The [NDDev estate tier](17-nddev-tier.md) records that the `NDDev-it-com` +organization has bought Enterprise Cloud, Code Security, Secret Protection, and +Code Quality, so its private repositories run the paid stack. **A repository +owned by a personal account has none of that**, even if the same human owns both. +Licenses attach to the organization, not to the user's personal namespace, so: + +- a private personal-account repo has **no GHAS** → no CodeQL, no native secret + scanning, no dependency review; +- a private personal-account repo has **no Enterprise Cloud** → no Artifact + Attestations; +- a private personal-account repo is billed against the **2 000 min/month** + personal Actions quota (catalog fact + `github-actions-private-free-personal`), with no organization pool to spill + into. + +Functionally this is identical to [02 private-free](02-private-free.md). The +runner routing is the only part that differs, and it is the reason this doc +exists. + +## The correction this tier exists to make + +[02 private-free] tells a private repo to use GitHub-hosted runners and simply +accept the 2 000 min/month ceiling. For a *personal-account* repository that +ceiling is per-user, not per-organization, and there is no committer-pool to +amortize it across. A single active project with a few minutes of CI per push +can exhaust the monthly quota in days. **Route every job to a self-hosted +runner registered on the repository itself.** + +| Concern | Generic private-free | Personal-account repo | +| --- | --- | --- | +| Posture (capabilities) | private-free | **private-free** (same) | +| CodeQL / secret scanning / dep review | excluded (paid) | **excluded** (no GHAS, same exclusions) | +| Artifact attestations | `release-supply-chain-free.yml` | **`release-supply-chain-free.yml`** (same) | +| Runner | GitHub-hosted `ubuntu-latest` | **self-hosted, repo-level registration** | + +## Runner registration: org vs personal + +A self-hosted runner registered on an **organization** is reachable only from +repositories *inside that organization*. A repository owned by a personal +account is in a different namespace and **cannot** consume an org-level runner +group, regardless of the group's `visibility` setting — `visibility=all` means +"all repos *in this org*", not "all repos everywhere". This is a platform gate, +not a configuration oversight; it cannot be relaxed by settings. + +To serve a personal-account repository, register the runner **on the repository +itself**: + +1. `gh api -X POST repos/{owner}/{repo}/actions/runners/registration-token` → a + short-lived token (≈1 h). This endpoint is available on private repos with + admin access; GitHub Pro is **not** required for repo-level runner + registration. +2. On the runner host, run `./config.sh --url https://github.com/{owner}/{repo} + --token --labels ",linux,x64"` once per repository. A single + runner process serves exactly one repository; N repositories need N + separate runner install directories. +3. Install the systemd unit (`./svc.sh install && ./svc.sh start`) so the + listener survives reboots. +4. In the caller workflow, set `runs-on: [self-hosted, Linux, X64, ]` and + pass the same label as `runner:` to every reusable workflow call. + +See [05 Runners → Routing by visibility](05-runners.md#routing-by-visibility) +for the load-bearing invariant: a self-hosted runner reachable from a public +repository is an RCE path, so this routing is for **private** personal-account +repos only. If the repository is or may become public, use the GitHub-hosted +caller in [examples/private-free/security.yml](../examples/private-free/security.yml). + +## Recommended caller + +[`examples/personal/security-selfhosted.yml`](../examples/personal/security-selfhosted.yml) +— the private-free stack (gitleaks, actionlint, zizmor-no-SARIF) with every job +routed to the repository's self-hosted runner. It is +`examples/private-free/security.yml` plus a `runner:` input on each call; the +capability set is identical because the tier posture is identical. + +## What is deliberately not here + +- **No CodeQL, no SARIF upload, no dependency review, no native secret + scanning.** These are paid on private repos and a personal account has not + bought them. The free substitutes (gitleaks, zizmor-no-SARIF, semgrep) are + what this tier runs. +- **No Artifact Attestations on release.** They require GitHub Enterprise Cloud + on private repos; use `release-supply-chain-free.yml` (SBOM + checksums, no + provenance). +- **No cross-account runner sharing.** An org runner does not serve a + personal-account repo; do not attempt to relax this with settings, it is a + platform gate. diff --git a/examples/personal/security-selfhosted.yml b/examples/personal/security-selfhosted.yml new file mode 100644 index 0000000..99a39cb --- /dev/null +++ b/examples/personal/security-selfhosted.yml @@ -0,0 +1,47 @@ +# Personal-account tier — PRIVATE repository variant. +# +# A repository owned by a *personal* GitHub account (e.g. `rldyourmnd/*`) takes +# the **private-free** posture regardless of visibility: even when private, it +# has no GitHub Advanced Security, no native secret scanning, no CodeQL, no +# dependency review — exactly the same exclusions as [02 private-free]. The +# difference from `examples/private-free/security.yml` is only the **runner**: +# a personal account has a 2 000 min/month Actions quota with no org pool to +# draw on, so every job is routed to a self-hosted runner to keep the meter at +# zero. See [docs/18-personal-account-tier.md]. +# +# Do NOT copy this file into a public repository. A forked pull request against +# a public repo executes attacker-controlled code, so pointing a public repo at +# a self-hosted runner is a remote-code-execution path into your own hardware. +# If the repository is or may become public, use `examples/private-free/security.yml` +# (GitHub-hosted) or the public-OSS suite instead. +# +# Replace @ with a pinned full commit SHA of ci-workflows, and