From e0f844453d1ab7e08b0d81a873cc14f18faa4c26 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sat, 10 Oct 2026 08:50:32 -0500 Subject: [PATCH 1/2] feat(releases): publish immutable standards bundles Package committed standards as ZIP and tar.gz archives with checksums, source manifests, provenance, and protected publication. Propose semantic versions through reviewed Release Please PRs, starting at 0.1.0. Co-Authored-By: Codex (GPT-6) Signed-off-by: James Maes --- .github/workflows/ci.yml | 37 +++++ .github/workflows/release-standards.yml | 175 +++++++++++++++++++++ .github/workflows/release.yml | 24 +++ .release-please-manifest.json | 1 + AGENTS.md | 65 ++++++++ CHANGELOG.md | 4 + CONTRIBUTING.md | 16 ++ README.md | 7 + docs/adr/0001-release-standards-bundles.md | 47 ++++++ docs/releases.md | 128 +++++++++++++++ docs/security/threat-model.md | 81 ++++++++++ exceptions/register.md | 26 +++ release-please-config.json | 20 +++ tools/Build-StandardsPackage.ps1 | 98 ++++++++++++ tools/Test-StandardsPackage.ps1 | 122 ++++++++++++++ version.txt | 1 + zizmor.yml | 2 + 17 files changed, 854 insertions(+) create mode 100644 .github/workflows/release-standards.yml create mode 100644 .github/workflows/release.yml create mode 100644 .release-please-manifest.json create mode 100644 AGENTS.md create mode 100644 CHANGELOG.md create mode 100644 docs/adr/0001-release-standards-bundles.md create mode 100644 docs/releases.md create mode 100644 docs/security/threat-model.md create mode 100644 release-please-config.json create mode 100644 tools/Build-StandardsPackage.ps1 create mode 100644 tools/Test-StandardsPackage.ps1 create mode 100644 version.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5fd817..e10d50f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ name: ci on: + workflow_call: pull_request: push: branches: [main] @@ -92,7 +93,43 @@ jobs: shell: pwsh run: ./tools/Test-RepoConformance.ps1 -SelfTest + - name: Standards package tests + shell: pwsh + run: ./tools/Test-StandardsPackage.ps1 + # Static mode reads only the checkout: no API calls, no admin scope needed. - name: Static conformance of this repository shell: pwsh run: ./tools/Test-RepoConformance.ps1 -LocalPath . -StaticOnly + + release-please: + name: release-please + needs: [security, codeql, docs, conformance] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.RELEASE_APP_CLIENT_ID != '' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + # EX-0006: a main-only bot key, never a repository secret. + environment: release-automation + concurrency: + group: standards-release-pr + cancel-in-progress: false + permissions: {} + steps: + # An App token lets the generated PR trigger CI; GITHUB_TOKEN does not. + - name: Release PR token + id: app + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.RELEASE_APP_CLIENT_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write + + - name: Propose a semantic release + uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 + with: + token: ${{ steps.app.outputs.token }} + target-branch: main + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + skip-github-release: true diff --git a/.github/workflows/release-standards.yml b/.github/workflows/release-standards.yml new file mode 100644 index 0000000..8f4870f --- /dev/null +++ b/.github/workflows/release-standards.yml @@ -0,0 +1,175 @@ +name: release-standards + +on: + workflow_call: + +permissions: {} + +jobs: + validate: + name: validate + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + issues: read + pull-requests: read + outputs: + version: ${{ steps.tag.outputs.version }} + checklist: ${{ steps.tag.outputs.checklist }} + pr: ${{ steps.tag.outputs.pr }} + steps: + - name: Checkout tagged source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Validate version, main ancestry, and checklist + id: tag + env: + TAG: ${{ github.ref_name }} + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + set -euo pipefail + [[ "$GITHUB_REF" == refs/tags/v* ]] + [[ "$TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || { + echo '::error::Standards bundles require a stable vMAJOR.MINOR.PATCH tag.' + exit 1 + } + git merge-base --is-ancestor HEAD origin/main + version="${TAG#v}" + [[ "$(cat version.txt)" == "$version" ]] + [[ "$(jq -r '."."' .release-please-manifest.json)" == "$version" ]] + echo "version=$version" >> "$GITHUB_OUTPUT" + commit="$(git rev-parse HEAD)" + pr="$(gh api "repos/$GH_REPO/commits/$commit/pulls" | jq -r --arg sha "$commit" '.[] | select(.merge_commit_sha == $sha and .merged_at != null and .base.ref == "main" and any(.labels[]; .name == "autorelease: pending")) | .number')" + [[ "$pr" =~ ^[1-9][0-9]*$ ]] || { + echo '::error::Tag the merge commit of the pending release PR.' + exit 1 + } + echo "pr=$pr" >> "$GITHUB_OUTPUT" + # Preflight is approved before tagging; shipping checks follow publication. + checklist='' + if [[ "$version" == *.0 ]]; then + annotation="$(git for-each-ref --format='%(contents)' "refs/tags/$TAG")" + prefix="https://github.com/$GH_REPO/issues/" + checklist="$(printf '%s\n' "$annotation" | grep -F 'Release checklist: ' | head -n 1 | sed 's/^Release checklist: //')" + [[ "$checklist" == "$prefix"* ]] + issue="${checklist#"$prefix"}" + [[ "$issue" =~ ^[1-9][0-9]*$ ]] + gh api "repos/$GH_REPO/issues/$issue" | jq -e \ + '.pull_request == null and any(.labels[]; .name == "release: ready")' >/dev/null + fi + echo "checklist=$checklist" >> "$GITHUB_OUTPUT" + + gates: + name: gates + needs: validate + uses: ./.github/workflows/ci.yml + permissions: + contents: read + security-events: write + + build: + name: build + needs: [validate, gates] + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + id-token: write + attestations: write + steps: + - name: Checkout tagged source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Build committed standards bundle + shell: pwsh + env: + K22_VERSION: ${{ needs.validate.outputs.version }} + RELEASE_CHECKLIST: ${{ needs.validate.outputs.checklist }} + run: ./tools/Build-StandardsPackage.ps1 -Version $env:K22_VERSION -ChecklistUrl $env:RELEASE_CHECKLIST + + - name: Attest every release asset + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-path: artifacts/standards/* + + - name: Upload release assets + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: standards-release-assets + path: artifacts/standards/* + if-no-files-found: error + + publish: + name: publish + needs: [validate, build] + runs-on: ubuntu-24.04 + timeout-minutes: 15 + environment: release + permissions: + contents: write + pull-requests: write + steps: + - name: Download release assets + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: standards-release-assets + path: release + + - name: Settings read token + id: app + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.RELEASE_APP_CLIENT_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + permission-administration: read + + - name: Require immutable releases + env: + GH_TOKEN: ${{ steps.app.outputs.token }} + GH_REPO: ${{ github.repository }} + run: | + set -euo pipefail + [[ "$(gh api "repos/$GH_REPO/immutable-releases" --jq '.enabled')" == true ]] + + - name: Create draft with all assets + working-directory: release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + K22_VERSION: ${{ needs.validate.outputs.version }} + run: | + set -euo pipefail + sha256sum --check SHA256SUMS + # Creation fails if this tag already has a release. Never replace assets. + gh release create "$TAG" --draft --verify-tag \ + --title "KofTwentyTwo standards $K22_VERSION" \ + --notes-file "KofTwentyTwo-standards-$K22_VERSION.notes.md" ./* + + - name: Publish immutable release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + gh release edit "$TAG" --draft=false + [[ "$(gh api "repos/$GH_REPO/releases/tags/$TAG" --jq '.immutable')" == true ]] + + # Release Please will not propose another version while a merged PR is pending. + - name: Mark the release PR as published + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + PR_NUMBER: ${{ needs.validate.outputs.pr }} + run: | + set -euo pipefail + gh label create 'autorelease: tagged' --color 0e8a16 --force + gh pr edit "$PR_NUMBER" --remove-label 'autorelease: pending' --add-label 'autorelease: tagged' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a333a5e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,24 @@ +name: standards-release + +on: + push: + tags: ['v*'] + workflow_dispatch: + +permissions: {} + +concurrency: + group: standards-release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + if: startsWith(github.ref, 'refs/tags/v') + uses: ./.github/workflows/release-standards.yml + permissions: + contents: write + issues: read + pull-requests: write + security-events: write + id-token: write + attestations: write diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1 @@ +{} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7191894 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,65 @@ +# Repository Guidelines + +## Project Structure & Module Organization + +This repository publishes KofTwentyTwo engineering standards and enforcement tooling. +`policies/` defines requirements; `standards/` provides implementation and language +profiles; `architecture/` contains reference designs. `configs/` holds IDE, formatter, +linter, and scanner settings. `templates/` contains starter repository files and +workflow callers; `.github/workflows/` contains reusable workflows and repository CI. +PowerShell tooling lives in `tools/`, workstation setup in `setup/`, framework +crosswalks in `compliance/`, and approved deviations in `exceptions/`. + +## Build, Test, and Development Commands + +There is no application build or local server. Run these checks from the repository +root with the corresponding tools installed: + +```powershell +npx markdownlint-cli2 "**/*.md" +lychee --offline --include-fragments --no-progress './**/*.md' +pwsh tools/Test-RepoConformance.ps1 -SelfTest +pwsh tools/Test-RepoConformance.ps1 -LocalPath . -StaticOnly +zizmor .github/workflows +actionlint +gitleaks detect --source . --no-banner +``` + +These check Markdown, internal links and anchors, checker behavior, local conformance, +workflow security, workflow syntax, and secrets respectively. For GitHub settings, +run the checker with `-Repository KofTwentyTwo/standards -LocalPath .`; this requires +authenticated GitHub access and sufficient permissions. + +## Coding Style & Naming Conventions + +Follow `.editorconfig`: UTF-8, LF, final newline, three-space indentation by default, +and two spaces for YAML. Follow language profiles in `standards/coding/` and their +matching `configs/`; PowerShell uses approved verbs and Allman braces. Markdown +follows `.markdownlint-cli2.yaml`, which disables line-length limits. Use descriptive +kebab-case document names, such as `security-program.md`. + +Write requirements as `K22--` with MUST, SHOULD, or MAY, followed by +*Why* and *Verified by*. Never reuse or renumber IDs. Extend enforcement for new MUSTs +and update applicable compliance crosswalks in the same change. + +## Testing Guidelines + +Checker regression tests are built into `-SelfTest`; there is no separate test +directory or repository-wide coverage gate. Extend those tests when changing checker +behavior. Validate documentation with Markdown and internal-link checks; validate +workflow changes with actionlint and zizmor. All required CI checks must pass. + +## Commit & Pull Request Guidelines + +History uses Conventional Commits, such as `feat(ci): ...` and `fix(docs): ...`. +Use topic branches like `docs/clarify-dco`; target `main`. Sign commits and add DCO +sign-off with `git commit -s`. PR titles must also be Conventional Commits. Describe +the change and validation, link relevant issues, and resolve review threads before +squash merging. Discuss requirement changes in an issue first; label incompatible +new MUSTs `breaking`. + +## Security & Agent Instructions + +Keep content original; never copy employer or client documents. Pin external Actions +to full commit SHAs with version comments and images to digests. Follow `SECURITY.md` +for private reports and `policies/ai-assisted-development.md` for agent permissions. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..66c197e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +Release Please maintains this file from Conventional Commits. No standards bundle +has been published yet; the first release proposal will be `0.1.0`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a224624..53f6315 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,6 +35,22 @@ themselves: everything below comes from the [SDLC policy](policies/sdlc.md). ## Running the checks locally +Documentation and package checks: + +```powershell +npx markdownlint-cli2 "**/*.md" +lychee --offline --include-fragments --no-progress './**/*.md' +pwsh tools/Test-RepoConformance.ps1 -SelfTest +pwsh tools/Test-StandardsPackage.ps1 +pwsh tools/Test-RepoConformance.ps1 -LocalPath . -StaticOnly +``` + +Package tests use an isolated temporary Git fixture; they do not commit changes in +this repository. For version proposals and release bundles, see +[bundle releases](docs/releases.md). + +Workflow and security checks: + ```powershell zizmor .github/workflows actionlint diff --git a/README.md b/README.md index e233b39..bb3e4f4 100644 --- a/README.md +++ b/README.md @@ -303,6 +303,13 @@ the shared release workflow ([release standard](standards/releases.md)). ## Staying current +Download a versioned ZIP or tar.gz standards bundle from +[GitHub Releases](https://github.com/KofTwentyTwo/standards/releases). Bundles preserve +the repository layout, include checksums and provenance attestations, and become +immutable on publication. See [bundle releases](docs/releases.md) for semantic version +proposals, maintainer setup, publishing, verification, and local reproduction. +The first bundle has not been published yet. + - This repository is versioned with [Semantic Versioning](https://semver.org). A new **MUST** that existing repositories do not meet is a MAJOR release; read its release notes before updating. diff --git a/docs/adr/0001-release-standards-bundles.md b/docs/adr/0001-release-standards-bundles.md new file mode 100644 index 0000000..b492bd7 --- /dev/null +++ b/docs/adr/0001-release-standards-bundles.md @@ -0,0 +1,47 @@ +# ADR-0001: Release standards as immutable GitHub bundles + +- **Status:** Accepted with the reviewed implementation PR +- **Date:** 2026-10-10 +- **Decider:** Repository maintainer + +## Context + +Consumers need a versioned package of the policies, standards, configs, templates, +and tooling. Content spans several languages and two primary licenses; it has no +application runtime or build. Existing policies require Conventional Commits, +maintainer-created protected tags, gated reusable release workflows, checksums, +attestations, and immutable GitHub Releases. + +## Options considered + +- **GitHub ZIP/tar.gz bundle:** preserves the full repository layout and works for + every consumer with no package-manager dependency. Extraction and copying configs + remain explicit consumer actions. +- **npm or NuGet package:** convenient for one ecosystem, but imposes that ecosystem + on documentation and polyglot tooling. Adds a registry and publishing policy. +- **Automatic tags on every merge:** fewer maintainer steps, but conflicts with the + current tag-creation ruleset and release-checklist process. + +## Decision + +Use Release Please for version/changelog PRs and publish ZIP/tar.gz bundles through +the reusable `release-standards.yml` workflow when the maintainer tags the merged +release PR. Build from the Git commit, attest all assets, upload everything to a +draft, and publish only with repository immutability enabled. + +## Consequences + +- Consumers can pin, verify, and extract a standards snapshot across platforms. +- Hidden configuration and license notices remain part of the package. +- Version proposals pass normal review; maintainers retain tag creation and release + approval. No ruleset bypass or registry publishing secret is added. +- A scoped GitHub App is needed for release PR CI and the settings-read check. Its + key is held in environment secrets; installation tokens are scoped per job and + revoked. Main-only access for the version bot is covered by + [EX-0006](../../exceptions/register.md#ex-0006); publishing remains tag-only. +- Packaging excludes local edits and untracked files. Rebuilds require the same + source commit, version, checklist URL, and compatible Git tooling. +- Complete App/environment setup and the first-release checklist before activating + the pipeline. This ADR is reviewed with the implementation PR. +- Review the [distribution threat model](../security/threat-model.md), including the + version-bot credential boundary and its scoped exception, in the same PR. diff --git a/docs/releases.md b/docs/releases.md new file mode 100644 index 0000000..ddb2da4 --- /dev/null +++ b/docs/releases.md @@ -0,0 +1,128 @@ +# Standards bundle releases + +The official distribution channel is +[GitHub Releases](https://github.com/KofTwentyTwo/standards/releases). Each stable +`vMAJOR.MINOR.PATCH` release contains ZIP and tar.gz archives of the **committed +repository**, with the directory layout intact, including hidden configurations, +licenses, workflows, templates, and tooling. Git metadata, untracked files, and +uncommitted edits are excluded. Extract either archive to use the standards offline +or copy its configs and templates into another repository. + +## Semantic versions + +[Release Please](https://github.com/googleapis/release-please) opens and updates a +release PR after successful `main` CI, changing `version.txt`, `CHANGELOG.md`, and +`.release-please-manifest.json`. The first proposal is `0.1.0`; `0.0.0` currently means +unreleased. The tag is the published version's authority; the files are checked +against it before packaging. + +| Commit or squash PR title | Bump | Example | +| --- | --- | --- | +| `fix:`, `docs:`, `security:` | PATCH | `docs(testing): clarify coverage calculation` | +| `feat:` | MINOR | `feat(coding): add a language profile` | +| Any type with `!` or `BREAKING CHANGE:` | MAJOR | `feat(repository)!: require a new control` | + +Use `feat` for new guidance and `!` for new obligations or removed allowances as +defined in [the versioning policy](../policies/README.md#versioning-of-the-standards). +Infrastructure changes appear in release notes and can produce PATCH releases; +`test`, `style`, and `chore` alone do not produce release notes. Review the proposed +version and notes before merging. A `breaking` label alone does not change the version. + +## One-time maintainer setup + +1. Keep **Settings → General → Releases → Enable release immutability** enabled. + The publisher checks the setting before creating a release and refuses to proceed + if it is disabled or unreadable. GitHub locks assets and the tag on publication; + immutability applies to future releases, not retroactively. +2. Keep [EX-0006](../exceptions/register.md#ex-0006) open and unexpired before + enabling version proposals. `K22-CI-16` otherwise restricts remaining secrets to + tag-only environments; the release-PR bot needs a narrow main-only exception. + Approval is recorded in the reviewed release-bundle implementation PR. +3. Create a GitHub App installed **only on this repository**, with Contents and Pull + requests **write**, Administration **read**, and no ruleset bypass. Store its client + ID in the Actions variable `RELEASE_APP_CLIENT_ID`. Short-lived App tokens let + release PRs trigger CI; a PR made using `GITHUB_TOKEN` would not. +4. Configure `release-automation` with deployment access for `main` only, and + `release` with a maintainer reviewer and deployment access for `v*` tags only. + Store `RELEASE_APP_PRIVATE_KEY` separately in these **environment secrets**; + never as a repository secret or committed file. Environments in the reusable + publisher resolve their own secrets; caller workflows do not pass the key. + Keep `protect-main` and `protect-release-tags` intact; the App does not create + tags or bypass review. Allow the SHA-pinned Release Please action if repository + action restrictions require an explicit allowlist entry. + +No npm/NuGet publishing credential or registry package is required. Upstream App-token +and Release Please actions are pinned and tracked by Dependabot. See the +[distribution ADR](adr/0001-release-standards-bundles.md). + +## Publish a version + +1. Review the release PR, complete its CI and review gates, and squash-merge it. +2. For MINOR and MAJOR releases, complete the pre-tag checks in an issue made from + [the release checklist](../templates/release-checklist.md). Attach the full + conformance result and security assessment. PATCH releases still need conformance + and scan results. Keep the README's draft/adoption status accurate before `v1.0.0`. + Apply the maintainer-only `release: ready` label after sections 1–4 and the + green-main/release-notes check in section 5 are complete. Leave publication checks + unchecked until verified, then close the issue after shipping. +3. Tag the **release PR's merge commit** with its proposed version. Only the maintainer + can create protected tags. For a MINOR or MAJOR, the annotation must contain + `Release checklist: https://github.com/KofTwentyTwo/standards/issues/`. + + ```bash + git fetch origin main + git tag -s v0.1.0 \ + -m "Release checklist: https://github.com/KofTwentyTwo/standards/issues/123" + git push origin v0.1.0 + ``` + +4. The tag workflow validates ancestry, version files, the pending release PR, and + the checklist; reruns all repository CI gates; builds and attests the assets; + and waits for the `release` environment approval. Publication creates a draft with + all assets and notes, then publishes it. The release PR is marked + `autorelease: tagged` so the next version can be proposed. + +These bundles ship no compiled software, so the compiled-artifact SBOM requirement +does not apply. The included workflows/configs retain their upstream pins; source +provenance and checksums identify the published bundle. + +## Verify and reproduce + +Every release includes `KofTwentyTwo-standards-.zip`, `.tar.gz`, +`.manifest.json`, `.notes.md`, and `SHA256SUMS`. The manifest records the exact commit +and license split. Download assets from a specific version, then verify: + +```bash +gh release download v0.1.0 --repo KofTwentyTwo/standards --dir standards-0.1.0 +cd standards-0.1.0 +sha256sum --check SHA256SUMS +gh attestation verify KofTwentyTwo-standards-0.1.0.zip --repo KofTwentyTwo/standards +``` + +On Windows, compare `Get-FileHash -Algorithm SHA256` to the corresponding entry in +`SHA256SUMS`. Pin reusable workflow callers to the release's full commit SHA, with a +version comment; do not use a moving branch or tag as a workflow pin. + +To reproduce from a checkout containing the release commit: + +```powershell +pwsh tools/Build-StandardsPackage.ps1 -Version 0.1.0 -Ref v0.1.0 -OutputPath artifacts/rebuild-0.1.0 +pwsh tools/Test-StandardsPackage.ps1 +``` + +Use the original checklist URL with `-ChecklistUrl` to reproduce `.notes.md` and +`SHA256SUMS` exactly. ZIP and tar.gz output is deterministic for a commit using the +same Git/archive tool versions. The builder refuses an existing output directory. + +## Failed releases + +Never replace published assets or move/reuse a version tag. Fix a published error +with a new release. A failed upload or publication leaves a draft; inspect its run +and assets, then remove **only the unpublished draft** before rerunning the tag's +`standards-release` workflow using **Run workflow** with that tag selected. Keep the +tag unchanged. If publication succeeded but PR labeling failed, change the PR label +from `autorelease: pending` to `autorelease: tagged`; do not rebuild the release. + +References: [GitHub immutable releases](https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases), +[enabling immutability](https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/establish-provenance-and-integrity/prevent-release-changes), +and [Release Please token behavior](https://github.com/googleapis/release-please-action#other-actions-on-release-please-prs). diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md new file mode 100644 index 0000000..69e8725 --- /dev/null +++ b/docs/security/threat-model.md @@ -0,0 +1,81 @@ +# Threat model: standards distribution + +- **Version reviewed:** v0.1.0 release-bundle implementation +- **Last updated:** 2026-10-10 +- **Status:** Implementation reviewed; provisioning is verified in the release checklist. + +## Product and actors + +This public repository distributes engineering guidance, configurations, templates, +and scripts. Consumers download a versioned bundle or call reusable workflows pinned +to a commit. The bundle does not run an application or collect consumer data. +Contributors submit changes; the maintainer reviews and merges PRs, creates protected +version tags, and approves publication. GitHub-hosted Actions runners build artifacts; +GitHub stores releases, environment secrets, and attestations. Upstream Actions are +third-party code trusted only at reviewed, pinned versions. + +## Data flow and boundaries + +```mermaid +flowchart LR + contributor[Contributor] --> pr[Pull request] + pr --> gates[CI and maintainer review] + gates --> main[Protected main] + main --> proposal[Version and changelog PR] + proposal --> gates + main --> tag[Maintainer-created protected tag] + tag --> build[Reusable gated build] + build --> draft[Draft assets and attestations] + draft --> approval[Release environment approval] + approval --> release[Immutable GitHub Release] + release --> consumer[Consumer verifies and extracts] +``` + +Untrusted PR content cannot access version-bot or publication secrets. The +`release-automation` environment admits only protected `main`; its App key is used +after CI succeeds. The `release` environment admits only protected `v*` tags and +requires maintainer approval. Tokens are scoped per job and revoked on completion. +Publishing checks GitHub's immutability setting before creating the draft. + +## External interfaces and assets + +| Interface | Data and authentication | Validation | +| --- | --- | --- | +| Git checkout and archive | Public source, tagged commit | Main ancestry, committed version/manifest/changelog | +| GitHub REST API | App installation token or job-scoped `GITHUB_TOKEN` over HTTPS | Release PR, maintainer-approved preflight checklist, immutability setting | +| Actions secrets | App private key in named environments | Deployment policy, reviewed job definitions, no repository secret | +| OIDC attestations | GitHub-signed build provenance | Consumer `gh attestation verify` | +| Release downloads | Versioned archives, manifest, notes, checksums | HTTPS, checksum and provenance verification | + +Assets are the App credential, integrity of standards and executable tooling, +protected source history, release tags, artifact bytes, and distribution availability. + +## Threats and controls + +| Threat / STRIDE category | Control | Remaining condition | +| --- | --- | --- | +| Forged or malicious contribution / spoofing | Signed commits, DCO, PR gates and maintainer review | Review remains accountable to a human | +| Modified source or artifact / tampering | Commit-based archives, checksums, attestations, immutable publication | Consumer must verify downloads | +| Disputed release origin / repudiation | Protected tagged commit, manifest SHA, release checklist, attestations | Maintainer reviews checklist evidence | +| Credential theft / information disclosure | Environment restrictions, scoped App installation, ephemeral tokens, secret scans | EX-0006 must remain open and unexpired | +| Failed or duplicate publication / denial of service | Timeouts, per-tag concurrency, draft-first upload, no replacement | Failed unpublished drafts need maintainer recovery | +| Workflow privilege escalation | SHA-pinned Actions, no cached release dependencies, no App ruleset bypass | Compromised trusted action remains a supply-chain risk | + +## Exceptions and review + +[EX-0006](../../exceptions/register.md#ex-0006) covers main-only access to the version +bot's key. Existing single-maintainer review uses +[EX-0001](../../exceptions/register.md#ex-0001). Review this model with the implementation +PR and each MINOR/MAJOR checklist; update it when credentials, channels, or workflow +trust boundaries change. + +## Review log + +- **2026-10-10, v0.1.0:** Reviewed source-only packaging, release-PR validation, + preflight approval, App token scopes, environment restrictions, provenance, and + immutable publication. Main and tag protections remain in place. Most likely + failure is incomplete draft publication; recovery retains the tag and replaces + no published assets. Highest-impact threat is a compromised credential or upstream + Action; restricted environments, scoped installation, reviewed SHA pins, and + required PR gates limit its reach. Provisioning and live checks are recorded in + the first-release checklist. diff --git a/exceptions/register.md b/exceptions/register.md index 8015cb2..7136da6 100644 --- a/exceptions/register.md +++ b/exceptions/register.md @@ -10,6 +10,7 @@ Open and closed exceptions to the KofTwentyTwo standards. The process is in | [EX-0003](#ex-0003) | Open | K22-DEP-02 (license allowlist) | gclo | Reviewed yearly, next on 2027-10-09 | | [EX-0004](#ex-0004) | Open | K22-CI-30, K22-REL-04 (shared release workflow) | gclo | When the shared reusable release workflow ships, at the latest 2027-04-09 | | [EX-0005](#ex-0005) | Open | K22-AI-10 (agents via PR only), K22-AI-11 (in part: unconfirmed pushes) | second-brain, nix, Praetor, Jellyfin | Reviewed yearly, next on 2027-10-10 | +| [EX-0006](#ex-0006) | Open | K22-CI-16 (tag-only environment secrets) | standards | 2027-04-10; review 2027-01-10 | ## EX-0001 @@ -143,3 +144,28 @@ Open and closed exceptions to the KofTwentyTwo standards. The process is in requests per `K22-AI-10`. - **Expires:** reviewed yearly, next on 2027-10-10. - **Owner:** the maintainer. + +## EX-0006 + +**Main-only release-version bot credential** + +- **Status:** Open; approved with the reviewed release-bundle implementation PR. +- **Requirements:** `K22-CI-16` restricts remaining secrets to environments whose + deployment rules allow only protected `v*` tags. +- **Scope:** `KofTwentyTwo/standards`, only `RELEASE_APP_PRIVATE_KEY` in the + `release-automation` environment for the `ci.yml` release-PR job on protected `main`. +- **Reason:** Release Please needs a GitHub App token to create version PRs that + trigger CI without enabling PR creation by `GITHUB_TOKEN`. No native GitHub App + OIDC exchange is configured; the private key is therefore needed before a tag exists. +- **Risk:** compromised version-PR automation could obtain the App key and exercise + the App's repository permissions. +- **Compensating controls:** App installation limited to this repository; Contents and + Pull requests write, Administration read only; no ruleset bypass; main-only + environment deployment policy; job runs only after all main CI gates succeed; + SHA-pinned actions; short-lived scoped installation tokens revoked after each job; + all resulting PRs still require normal checks and maintainer review. The bot neither + creates release tags nor signs or publishes packages. Publication still uses the + protected tag-only `release` environment and GitHub's immutable-releases setting. +- **Expires:** 2027-04-10, or earlier when version proposals use credential federation. +- **Owner:** the maintainer. +- **Review date:** 2027-01-10. diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..64c098f --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "simple", + "include-component-in-tag": false, + "initial-version": "0.1.0", + "changelog-sections": [ + { "type": "security", "section": "Security", "hidden": false }, + { "type": "feat", "section": "Features", "hidden": false }, + { "type": "fix", "section": "Bug Fixes", "hidden": false }, + { "type": "docs", "section": "Documentation", "hidden": false }, + { "type": "perf", "section": "Performance", "hidden": false }, + { "type": "refactor", "section": "Refactoring", "hidden": false }, + { "type": "build", "section": "Build", "hidden": false }, + { "type": "ci", "section": "Automation", "hidden": false }, + { "type": "revert", "section": "Reverts", "hidden": false } + ], + "packages": { + ".": { "version-file": "version.txt" } + } +} diff --git a/tools/Build-StandardsPackage.ps1 b/tools/Build-StandardsPackage.ps1 new file mode 100644 index 0000000..8660577 --- /dev/null +++ b/tools/Build-StandardsPackage.ps1 @@ -0,0 +1,98 @@ +#Requires -Version 7.4 +<# +.SYNOPSIS +Build versioned standards archives from a Git commit. +.DESCRIPTION +Uses git archive so untracked files and local edits cannot enter a release. Writes +ZIP, tar.gz, source manifest, release notes, and SHA256SUMS without overwriting output. +.PARAMETER Version +Stable semantic version matching the committed version.txt and release manifest. +.PARAMETER RepositoryPath +Local source repository; defaults to the parent directory of this script. +.PARAMETER Ref +Commit to package; defaults to HEAD. +.PARAMETER OutputPath +New output directory; defaults to artifacts/standards in the source repository. +.PARAMETER ChecklistUrl +Completed release checklist to link in the notes, when applicable. +#> +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [ValidatePattern('^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$')] + [string] $Version, + [string] $RepositoryPath = (Split-Path -Parent $PSScriptRoot), + [ValidateNotNullOrEmpty()] + [string] $Ref = 'HEAD', + [string] $OutputPath, + [AllowEmptyString()] + [string] $ChecklistUrl = '' +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $true + +$repository = (Resolve-Path -LiteralPath $RepositoryPath).Path +$commit = (git -C $repository rev-parse --verify "$Ref^{commit}").Trim() +$committedVersion = (git -C $repository show "${commit}:version.txt").Trim() +$releaseManifest = (git -C $repository show "${commit}:.release-please-manifest.json") | ConvertFrom-Json -AsHashtable +if($committedVersion -ne $Version -or $releaseManifest['.'] -ne $Version) +{ + throw "Version $Version does not match version.txt and the release manifest at $commit." +} + +$changelog = (git -C $repository show "${commit}:CHANGELOG.md") -join "`n" +$escapedVersion = [regex]::Escape($Version) +$section = [regex]::Match($changelog, "(?ms)^## (?:\[$escapedVersion\](?:\([^\r\n]*\))?|$escapedVersion)(?=\s|$)[^\r\n]*\r?\n.*?(?=^## |\z)") +if(-not $section.Success) +{ + throw "CHANGELOG.md has no release notes for $Version at $commit." +} +$notes = $section.Value.Trim() +if($ChecklistUrl) +{ + if($ChecklistUrl -notmatch '^https://github\.com/[^/]+/[^/]+/issues/[1-9][0-9]*$') + { + throw 'ChecklistUrl must be a GitHub issue URL.' + } + $notes += "`n`nRelease checklist: $ChecklistUrl" +} + +if(-not $OutputPath) +{ + $OutputPath = Join-Path $repository 'artifacts/standards' +} +$outputDirectory = [System.IO.Path]::GetFullPath($OutputPath) +if(Test-Path -LiteralPath $outputDirectory) +{ + throw "Output directory already exists: $outputDirectory. Choose a new directory." +} +New-Item -ItemType Directory -Path $outputDirectory | Out-Null + +$name = "KofTwentyTwo-standards-$Version" +foreach($format in @('zip', 'tar.gz')) +{ + $archive = Join-Path $outputDirectory "$name.$format" + git -C $repository archive "--format=$format" "--prefix=$name/" "--output=$archive" $commit +} + +$manifest = [ordered]@{ + name = 'KofTwentyTwo/standards' + version = $Version + tag = "v$Version" + commit = $commit + repository = 'https://github.com/KofTwentyTwo/standards' + licenses = @('CC-BY-4.0 (documentation)', 'MIT (tooling)', 'Apache-2.0 (Kingsrook files; see NOTICE)') +} +$utf8 = [System.Text.UTF8Encoding]::new($false) +$manifestText = ($manifest | ConvertTo-Json -Depth 4).Replace("`r`n", "`n") + "`n" +[System.IO.File]::WriteAllText((Join-Path $outputDirectory "$name.manifest.json"), $manifestText, $utf8) +[System.IO.File]::WriteAllText((Join-Path $outputDirectory "$name.notes.md"), $notes.Replace("`r`n", "`n") + "`n", $utf8) +$checksums = foreach($file in (Get-ChildItem -LiteralPath $outputDirectory -File | Sort-Object -Property Name)) +{ + $hash = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + "$hash $($file.Name)" +} +[System.IO.File]::WriteAllText((Join-Path $outputDirectory 'SHA256SUMS'), ($checksums -join "`n") + "`n", $utf8) +Write-Output "Built $name from $commit in $outputDirectory" diff --git a/tools/Test-StandardsPackage.ps1 b/tools/Test-StandardsPackage.ps1 new file mode 100644 index 0000000..0b9334b --- /dev/null +++ b/tools/Test-StandardsPackage.ps1 @@ -0,0 +1,122 @@ +#Requires -Version 7.4 +<# +.SYNOPSIS +Exercise standards packaging with an isolated local Git fixture. +.DESCRIPTION +Checks committed content, hidden files, both archive formats, determinism, hashes, +version validation, release notes, and refusal to overwrite existing output. +#> +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $true + +<# +.SYNOPSIS +Fail a behavioral assertion with a descriptive message. +#> +function Test-Condition +{ + param([bool] $Condition, [string] $Message) + if(-not $Condition) { throw "FAIL: $Message" } + Write-Output "PASS: $Message" +} + + + +<# +.SYNOPSIS +Verify an operation refuses invalid input for the expected reason. +#> +function Test-Exception +{ + param([scriptblock] $Operation, [string] $Pattern) + $failure = $null + try { & $Operation | Out-Null } + catch { $failure = $_.Exception.Message } + Test-Condition ($null -ne $failure -and $failure -match $Pattern) "rejects $Pattern" +} + +$builder = Join-Path $PSScriptRoot 'Build-StandardsPackage.ps1' +$fixtureRoot = Join-Path ([System.IO.Path]::GetTempPath()) "standards-package-$([guid]::NewGuid().ToString('N'))" +$source = Join-Path $fixtureRoot 'source' +New-Item -ItemType Directory -Path $source | Out-Null +git init --quiet $source +git -C $source config core.autocrlf false +git -C $source config core.hooksPath /dev/null +Set-Content -LiteralPath (Join-Path $source 'version.txt') -Value '1.2.3' +Set-Content -LiteralPath (Join-Path $source '.release-please-manifest.json') -Value '{".":"1.2.3"}' +Set-Content -LiteralPath (Join-Path $source '.editorconfig') -Value 'root = true' +Set-Content -LiteralPath (Join-Path $source 'README.md') -Value 'committed content' +Set-Content -LiteralPath (Join-Path $source 'CHANGELOG.md') -Value @' +# Changelog + +## [1.2.4](https://example.com/next) (2026-10-11) + +Future notes. + +## [1.2.3](https://example.com/current) (2026-10-10) + +### Security + +Current notes. + +## 1.2.2 + +Older notes. +'@ +git -C $source add -- . +git -C $source -c user.name=Fixture -c user.email=fixture@example.invalid -c commit.gpgsign=false commit --quiet -m 'test: packaging fixture' + +Set-Content -LiteralPath (Join-Path $source 'README.md') -Value 'uncommitted content' +Set-Content -LiteralPath (Join-Path $source 'local-only.txt') -Value 'untracked content' +$first = Join-Path $fixtureRoot 'first' +$second = Join-Path $fixtureRoot 'second' +$checklist = 'https://github.com/KofTwentyTwo/standards/issues/123' +& $builder -Version '1.2.3' -RepositoryPath $source -OutputPath $first -ChecklistUrl $checklist +& $builder -Version '1.2.3' -RepositoryPath $source -OutputPath $second -ChecklistUrl $checklist + +$prefix = 'KofTwentyTwo-standards-1.2.3' +$zip = [System.IO.Compression.ZipFile]::OpenRead((Join-Path $first "$prefix.zip")) +try +{ + $names = @($zip.Entries.FullName) + Test-Condition ($names -contains "$prefix/.editorconfig") 'ZIP preserves hidden configuration files' + Test-Condition (-not ($names -match '/(?:\.git/|local-only\.txt$)')) 'ZIP excludes Git metadata and untracked files' + $reader = [System.IO.StreamReader]::new($zip.GetEntry("$prefix/README.md").Open()) + try { Test-Condition ($reader.ReadToEnd().Trim() -eq 'committed content') 'ZIP contains committed content despite local edits' } + finally { $reader.Dispose() } +} +finally { $zip.Dispose() } + +$extracted = Join-Path $fixtureRoot 'extracted' +New-Item -ItemType Directory -Path $extracted | Out-Null +tar -xzf (Join-Path $first "$prefix.tar.gz") -C $extracted +Test-Condition ((Get-Content -LiteralPath (Join-Path $extracted "$prefix/README.md") -Raw).Trim() -eq 'committed content') 'tar.gz contains committed content' +Test-Condition (Test-Path -LiteralPath (Join-Path $extracted "$prefix/.editorconfig")) 'tar.gz preserves hidden configuration files' +$manifest = Get-Content -LiteralPath (Join-Path $first "$prefix.manifest.json") -Raw | ConvertFrom-Json +Test-Condition ($manifest.version -eq '1.2.3' -and $manifest.commit -eq (git -C $source rev-parse HEAD)) 'manifest identifies version and exact source commit' +$notes = Get-Content -LiteralPath (Join-Path $first "$prefix.notes.md") -Raw +Test-Condition ($notes -match 'Current notes' -and $notes -notmatch 'Older notes|Future notes' -and $notes.Contains($checklist)) 'notes contain only this release and its checklist' +foreach($file in (Get-ChildItem -LiteralPath $first -File)) +{ + Test-Condition ((Get-FileHash -LiteralPath $file.FullName).Hash -eq (Get-FileHash -LiteralPath (Join-Path $second $file.Name)).Hash) "$($file.Name) is reproducible" +} +$checksums = @(Get-Content -LiteralPath (Join-Path $first 'SHA256SUMS')) +Test-Condition ($checksums.Count -eq 4) 'checksums cover every asset except the checksum file itself' +foreach($line in $checksums) +{ + $hash, $fileName = $line -split ' ', 2 + Test-Condition ($hash -eq (Get-FileHash -LiteralPath (Join-Path $first $fileName)).Hash.ToLowerInvariant()) "$fileName matches its checksum" +} +Test-Exception { & $builder -Version '1.2.4' -RepositoryPath $source -OutputPath (Join-Path $fixtureRoot 'mismatch') } 'does not match' +Test-Exception { & $builder -Version '01.2.3' -RepositoryPath $source } 'ValidatePattern|validation|pattern' +Test-Exception { & $builder -Version '1.2.3' -RepositoryPath $source -OutputPath $first } 'already exists' +Test-Exception { & $builder -Version '1.2.3' -RepositoryPath $source -OutputPath (Join-Path $fixtureRoot 'invalid-url') -ChecklistUrl 'http://example.com/123' } 'GitHub issue URL' +Set-Content -LiteralPath (Join-Path $source 'CHANGELOG.md') -Value '# Changelog without release notes' +git -C $source add -- CHANGELOG.md +git -C $source -c user.name=Fixture -c user.email=fixture@example.invalid -c commit.gpgsign=false commit --quiet -m 'test: missing release notes fixture' +Test-Exception { & $builder -Version '1.2.3' -RepositoryPath $source -OutputPath (Join-Path $fixtureRoot 'missing-notes') } 'no release notes' +Write-Output "All standards package tests passed. Fixture: $fixtureRoot" diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..77d6f4c --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +0.0.0 diff --git a/zizmor.yml b/zizmor.yml index c1ec7e5..f6de413 100644 --- a/zizmor.yml +++ b/zizmor.yml @@ -7,3 +7,5 @@ rules: # not yet understood by actionlint 1.7.12, which is also a required gate. ignore: - ci.yml + - release.yml + - release-standards.yml -- 2.54.0 From 92c2b3af80748562b8a10ea3cdb0add2c1da688a Mon Sep 17 00:00:00 2001 From: James Maes Date: Sat, 10 Oct 2026 08:55:14 -0500 Subject: [PATCH 2/2] docs(releases): record security dispositions and pre-1.0 status Document the reviewed Scorecard process findings and their reassessment dates. Keep draft standards and response commitments separate from the 0.1.0 bundle release. Co-Authored-By: Codex (GPT-6) Signed-off-by: James Maes --- CHANGELOG.md | 3 +-- README.md | 3 +-- SECURITY.md | 2 +- docs/releases.md | 4 ++-- docs/security/scorecard-dispositions.md | 22 ++++++++++++++++++++++ docs/security/threat-model.md | 4 +++- 6 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 docs/security/scorecard-dispositions.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 66c197e..06f0180 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,3 @@ # Changelog -Release Please maintains this file from Conventional Commits. No standards bundle -has been published yet; the first release proposal will be `0.1.0`. +Release Please maintains this file from Conventional Commits. diff --git a/README.md b/README.md index bb3e4f4..f26244e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ reusable GitHub Actions workflows, repository templates, and a conformance check ## Status -> **Draft, before the first release (`v1.0.0`).** The requirements are written and the +> **Draft standards, before `v1.0.0`.** The requirements are written and the > enforcement tooling exists, but no KofTwentyTwo repository has been verified against > them yet; adoption starts with this repository, then > [AppKit](https://github.com/KofTwentyTwo/AppKit) and @@ -308,7 +308,6 @@ Download a versioned ZIP or tar.gz standards bundle from the repository layout, include checksums and provenance attestations, and become immutable on publication. See [bundle releases](docs/releases.md) for semantic version proposals, maintainer setup, publishing, verification, and local reproduction. -The first bundle has not been published yet. - This repository is versioned with [Semantic Versioning](https://semver.org). A new **MUST** that existing repositories do not meet is a MAJOR release; read its release diff --git a/SECURITY.md b/SECURITY.md index 333a90d..68a8e6b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -17,7 +17,7 @@ conformance check that passes when it should fail. | Fix released | Critical 7 days, High 30 days, Medium 90 days, Low next release, from confirmation | | Public disclosure | coordinated with the reporter; by default 90 days after the report, sooner once fixed | -Until this repository's first release (`v1.0.0`) these timeframes are targets the +Before `v1.0.0` these timeframes are targets the maintainer works to; from `v1.0.0` they are commitments. Fixed vulnerabilities are published as GitHub Security Advisories with a CVE. These diff --git a/docs/releases.md b/docs/releases.md index ddb2da4..1c5a158 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -12,8 +12,8 @@ or copy its configs and templates into another repository. [Release Please](https://github.com/googleapis/release-please) opens and updates a release PR after successful `main` CI, changing `version.txt`, `CHANGELOG.md`, and -`.release-please-manifest.json`. The first proposal is `0.1.0`; `0.0.0` currently means -unreleased. The tag is the published version's authority; the files are checked +`.release-please-manifest.json`. The first proposal is `0.1.0`; `0.0.0` is the +unreleased bootstrap placeholder. The tag is the published version's authority; the files are checked against it before packaging. | Commit or squash PR title | Bump | Example | diff --git a/docs/security/scorecard-dispositions.md b/docs/security/scorecard-dispositions.md new file mode 100644 index 0000000..97b3209 --- /dev/null +++ b/docs/security/scorecard-dispositions.md @@ -0,0 +1,22 @@ +# Scorecard finding dispositions + +Reviewed on 2026-10-10 for the v0.1.0 release. These are repository-process +heuristics, not identified vulnerabilities in shipped code or dependencies. +Static review inspected the reported main revision, current rulesets, the security +policy, and EX-0001. It does not establish an independent human review of historical +changes or predict future maintenance. + +| Alert | Disposition and evidence | Reassess | +| --- | --- | --- | +| [#4 Code-Review](https://github.com/KofTwentyTwo/standards/security/code-scanning/4) | Accepted single-maintainer risk under [EX-0001](../../exceptions/register.md#ex-0001). Outside contributors cannot merge; PR checks, AI review, CodeQL, signed commits, and maintainer merge remain required. Scorecard counts human approvals, so it does not represent these compensating controls. | 2027-01-10, or when a second maintainer joins | +| [#3 Maintained](https://github.com/KofTwentyTwo/standards/security/code-scanning/3) | Age heuristic: GitHub reports creation on 2026-10-09, with recent feature PRs and active CI. The message identifies a repository younger than 90 days, rather than abandoned shipped software. | 2027-01-10 | +| [#1 Branch-Protection](https://github.com/KofTwentyTwo/standards/security/code-scanning/1) | Accepted human-approval gap under EX-0001. Active `protect-main` has no bypass and enforces PR-only squash merges, nine required checks, signed commits, linear history, and resolved threads. Human approval by another maintainer is unavailable. | 2027-01-10, or when a second maintainer joins | + +Keep the accepted risk visible; dismissal is not a claim that the missing second +maintainer exists. Do not disable Scorecard or weaken protections. Reopen and +reassess these dispositions when their assumptions change. The LOW best-practices +badge and MEDIUM fuzzing recommendations remain open for follow-up. + +No OpenVEX statement is fabricated for these process findings: no vulnerable shipped +component or exploit was identified. Any actual vulnerability present but not +exploitable needs the OpenVEX statement required by K22-SEC-42. diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md index 69e8725..8a6d95d 100644 --- a/docs/security/threat-model.md +++ b/docs/security/threat-model.md @@ -78,4 +78,6 @@ trust boundaries change. no published assets. Highest-impact threat is a compromised credential or upstream Action; restricted environments, scoped installation, reviewed SHA pins, and required PR gates limit its reach. Provisioning and live checks are recorded in - the first-release checklist. + the first-release checklist. Existing Scorecard process findings are recorded in + [their dispositions](scorecard-dispositions.md); EX-0001 retains the accepted + single-maintainer risk. -- 2.54.0