Skip to content

chore: org hardening — SHA-pinned actions, governance docs, Scorecard, issue forms - #501

Merged
FumingPower3925 merged 7 commits into
mainfrom
chore/org-hardening
Sep 5, 2026
Merged

FumingPower3925 merged 7 commits into
mainfrom
chore/org-hardening

Conversation

@FumingPower3925

Copy link
Copy Markdown
Contributor

File-based part of the org hardening review 2026-09-06 for goceleris/celeris. GitHub settings/rulesets and the bench cluster are handled separately; this PR only touches files. Workflow semantics are unchanged except where listed.

Changes and rationale

Supply chain / workflows

Change Why
SHA-pin every third-party action in ci.yml, drivers.yml, release.yml (+ the new scorecard.yml): actions/checkout@3d3c42e5… # v7.0.1, actions/setup-go@b7ad1dad… # v7.0.0, golangci/golangci-lint-action@ba0d7d2e… # v9.3.0, ossf/scorecard-action@2d114668… # v2.4.4, github/codeql-action/upload-sarif@cdf488f5… # v4.37.9. SHAs resolved from the current tags via the GitHub API (annotated tags dereferenced to the commit). A floating tag can be moved by anyone with push access to the action repo; a SHA cannot. The trailing version comment lets dependabot's github-actions ecosystem (already configured) keep the SHAs current.
persist-credentials: false on every actions/checkout step in jobs that never push (all of ci.yml, drivers.yml, and notify-proxy in release.yml). tag-submodules keeps the default because it pushes tags (explicit zizmor: ignore[artipacked] with the reason). The job token otherwise stays in .git/config for every later step and any compromised dependency to read.
Pin tool installs: mage@v1.17.2, actionlint@v1.7.12 (were @latest). govulncheck stays @latest. A new upstream release can no longer turn main red by itself. govulncheck's DB is fetched at runtime, so its binary version is not the freshness that matters.
zizmor step next to actionlint: pipx run --spec zizmor==1.30.0 zizmor .github/workflows with GH_TOKEN: ${{ github.token }} (ephemeral read-only job token, no repository secrets). actionlint checks syntax; zizmor checks security posture (unpinned actions, persisted credentials, template injection, excessive permissions). Passes with 0 findings on this branch (2 explicit, commented ignores).
release.yml least privilege: top-level contents: read; contents: write only on tag-submodules. Only the tag-push job needs write; the proxy-notify and validate jobs no longer can push. Caller permissions still cover the reusable ci.yml (contents: read).
release.yml template injection: ${{ github.event.release.tag_name }} moved from six run: bodies into step env: and read as $TAG / $VERSION. Identical behaviour for every valid tag; a crafted tag name can no longer expand into shell code. Required for the zizmor step to pass.
release.yml notify-proxy setup-go cache: false. The job only runs go list -m against the proxy; a module cache restored into a release-triggered job is a cache-poisoning vector.
New scorecard.yml (OpenSSF Scorecard): triggers branch_protection_rule, weekly schedule, push to main; permissions: read-all at top, job security-events: write + id-token: write; checkout without credentials; publish_results: true; SARIF upload via SHA-pinned codeql-action/upload-sarif. Continuous, public measurement of the posture this review hardens.

Note: the reusable-workflow call stays uses: ./.github/workflows/ci.yml — actionlint 1.7.12 does not yet accept GitHub's newer $/ self-repository form, so it carries a commented zizmor: ignore[self-repository].

Governance and process

Change Why
GOVERNANCE.md (one page): roles Contributor → contributors team member (write on celeris only; may approve; may merge own PRs only after a code-owner approval and green required checks; ~3 merged non-trivial PRs + sustained engagement) → Maintainer (admin; cuts releases; owns CODEOWNERS; currently @FumingPower3925). Merge rule: required checks green + code-owner approval; author with write or approving maintainer (auto-merge) merges; nobody merges their own PR without a code-owner approval; admin bypass reserved for release/infra emergencies and visible in the audit log. Decisions: single maintainer today; lazy consensus (72 h) once there is more than one. Releases: tagged after the probatorium nightly + weekend soak pass; notes from PR labels + hand-written highlights; GitHub Releases is the changelog (no CHANGELOG.md). Writes down how the project is actually run so the rules that the rulesets enforce are also legible to humans.
MAINTAINERS.md: maintainer table (@FumingPower3925, areas: all) and the contributors team (@WdnLiu), with how to become one (links GOVERNANCE.md). Names are otherwise only visible in org settings.
.github/CODEOWNERS: header, * @FumingPower3925, commented area-delegation example (# /middleware/ @FumingPower3925 @WdnLiu). Gives the "code-owner approval" ruleset something to resolve against.
CONTRIBUTING.md: new "How changes get merged", "Labels and release notes" (bug / enhancement / performance / security / breaking / dependencies; no CHANGELOG.md), "Commit sign-off" (git commit -s, DCO recommended, not mandatory yet); links GOVERNANCE.md and MAINTAINERS.md; security paragraph points at private reporting. Everything else kept. Mirrors the governance rule where contributors actually read.
SECURITY.md: private vulnerability reporting (Security tab → "Report a vulnerability", https://github.com/goceleris/celeris/security/advisories/new) is now the preferred channel, above security@goceleris.dev; acknowledgement within 72 hours. Supported-versions table and release notes kept. Private reporting is enabled on the repo; 72 h is what a single-maintainer project can actually promise.
.github/release.yml: changelog.exclude labels dependencies, github_actions; categories Breaking changes [breaking], Security [security], Fixes [bug], Performance [performance], Features [enhancement], Other [*]. Makes the generated release notes the changelog.
Issue forms replace the markdown templates: bug_report.yml (Engine dropdown std/epoll/io_uring/adaptive required, Architecture dropdown amd64/arm64/other required, celeris version required, OS/kernel, Minimal reproduction render: go required, Expected vs actual; labels bug, needs-triage), feature_request.yml (problem / proposal / alternatives; labels enhancement, needs-triage), config.yml (blank_issues_enabled: false, contact link "Security vulnerability" → private advisory form). bug_report.md and feature_request.md deleted. Engine and architecture decide whether an engine bug can be reproduced at all; blank issues and public security reports are closed off.
PR template: adds Closes #, Tested on: [ ] std [ ] epoll [ ] io_uring — [ ] amd64 [ ] arm64, [ ] Breaking change? (label breaking), [ ] Labeled for release notes. Matches the label-driven release notes and the engine matrix.

Verification

  • actionlint (v1.7.12, local): clean on all four workflows.
  • zizmor 1.30.0 with online audits (GH_TOKEN set, as CI will run it): No findings (2 ignored with reasons, 16 suppressed).
  • python3 -c 'import yaml' parse of every .github/**/*.yml: OK (workflows, issue forms, release.yml, dependabot.yml).
  • grep -rnE 'uses: [^./][^@]*@v' .github/ → nothing (no unpinned third-party references remain; the only non-SHA uses: is the in-repo ./.github/workflows/ci.yml).
  • No Go code changed, so no go build / go vet was needed.

Not in this PR

Rulesets / branch protection, required checks, the contributors team scope, private-vulnerability-reporting toggle and the bench cluster are configured separately as part of the same review. Labels referenced by .github/release.yml and the issue forms (performance, security, breaking, needs-triage) must exist in the repository for the sections/forms to apply them.

…s, add zizmor

Every third-party action in ci.yml and drivers.yml now references the
commit SHA the current tag resolves to (with the version as a trailing
comment so dependabot's github-actions ecosystem keeps it current):
actions/checkout v7.0.1, actions/setup-go v7.0.0,
golangci/golangci-lint-action v9.3.0. A floating major tag can be moved
by anyone with push access to the action repository; a SHA cannot.

actions/checkout gets `persist-credentials: false` in every job — none
of them pushes or uses the token after checkout, so the job token no
longer sits in .git/config where any later step (or a compromised
dependency) could read it.

Tool installs are pinned too: mage v1.17.2 and actionlint v1.7.12
instead of @latest, so a new upstream release cannot turn main red on
its own. govulncheck stays @latest on purpose — its vulnerability
database is fetched at runtime, the binary version is not the freshness
that matters.

A zizmor step runs next to actionlint (pipx, exact version 1.30.0).
actionlint checks workflow syntax; zizmor checks security posture —
unpinned actions, persisted credentials, template injection, excessive
permissions. It only needs the ephemeral read-only job token for its
online audits, no repository secrets.

Workflow semantics are otherwise unchanged.

org hardening review 2026-09-06
Same SHA pins as ci.yml (checkout v7.0.1, setup-go v7.0.0).

The workflow granted `contents: write` to every job; only tag-submodules
pushes tags. The top level is now `contents: read` and the write grant
moves onto that single job, so a compromise of the proxy-notify or
validate steps cannot push to the repository.

`${{ github.event.release.tag_name }}` was spliced directly into six
`run:` scripts. It is now passed through step `env:` and read as a plain
shell variable — identical behaviour for every valid tag, but a crafted
tag name can no longer become shell code (zizmor template-injection).

notify-proxy's setup-go gets `cache: false`: the job only runs
`go list -m` against the proxy and needs no module cache, and a cache
restored into a release-triggered job is a poisoning vector.

notify-proxy's checkout gets `persist-credentials: false`; tag-submodules
keeps the default because it pushes (explicit zizmor ignore with the
reason on the step). The reusable-workflow call keeps the `./` form
because actionlint 1.7.12 does not yet accept GitHub's `$/` syntax.

org hardening review 2026-09-06
Runs ossf/scorecard-action (v2.4.4, SHA-pinned) on push to main, weekly,
and whenever branch protection changes; publishes results to the
Scorecard API and uploads SARIF to code scanning via
github/codeql-action/upload-sarif (v4.37.9, SHA-pinned).

Top-level permissions are read-all; the single job adds only
security-events: write (SARIF upload) and id-token: write (OIDC for
publish_results). Checkout does not persist credentials.

Scorecard gives a continuous, public measurement of the supply-chain
posture the rest of this review hardens (pinned dependencies, token
permissions, branch protection, dangerous workflow patterns).

org hardening review 2026-09-06
Write down how the project is actually run so contributors do not have
to infer it: Contributor (fork PRs) -> member of the org team
`contributors` (write on celeris only; may approve; may merge own PRs
only after a code-owner approval and green required checks; ~3 merged
non-trivial PRs + sustained engagement) -> Maintainer (admin, cuts
releases, owns CODEOWNERS; currently FumingPower3925).

The merge rule is stated once: required checks green + approval from a
code owner; the author with write access or the approving maintainer
(auto-merge) merges; nobody merges their own PR without a code-owner
approval; the admin bypass is for release/infra emergencies and is
visible in the audit log. Decisions are the single maintainer's today,
lazy consensus (72 h) once there is more than one. Releases are tagged
after the probatorium nightly and weekend soak pass; release notes come
from PR labels; GitHub Releases is the changelog (no CHANGELOG.md).

CODEOWNERS routes every PR to @FumingPower3925 and shows how to delegate
an area (commented example). MAINTAINERS.md lists the maintainer and the
`contributors` team (WdnLiu) and links to the criteria.

org hardening review 2026-09-06
… sign-off

Mirror the governance merge rule in the place contributors actually
read; tell them which label to put on a PR (bug / enhancement /
performance / security / breaking / dependencies) now that release notes
are generated from labels and there is no CHANGELOG.md; recommend
`git commit -s` (DCO) without making it mandatory yet; point the
security paragraph at private vulnerability reporting; link
GOVERNANCE.md and MAINTAINERS.md. Everything else is unchanged.

org hardening review 2026-09-06
…dgement

Private vulnerability reporting is enabled on the repository, so the
Security tab -> "Report a vulnerability" flow becomes the preferred
channel (report, advisory, CVE and credit all in one place, visible only
to maintainers); security@goceleris.dev stays as the alternative. The
acknowledgement window is stated as 72 hours, which is what a
single-maintainer project can actually promise. Supported-versions table
and per-release notes are kept as they were.

org hardening review 2026-09-06
…onfig

Replace the markdown issue templates with issue forms. The bug form
requires engine (std / epoll / io_uring / adaptive), architecture
(amd64 / arm64 / other), celeris version and a Go reproduction, and asks
for OS/kernel and expected-vs-actual — the fields that decide whether an
engine bug can be reproduced at all. Both forms apply `needs-triage`.
config.yml disables blank issues and routes security reports to the
private advisory form instead of a public issue.

The PR template gains "Closes #", an engine x architecture "Tested on"
line, a breaking-change checkbox and a "labeled for release notes"
checkbox, matching the label-driven release notes.

.github/release.yml defines the generated release-notes sections
(Breaking changes / Security / Fixes / Performance / Features / Other)
and excludes dependabot's `dependencies` and `github_actions` labels.
GitHub Releases is the changelog; there is no CHANGELOG.md.

org hardening review 2026-09-06
@FumingPower3925
FumingPower3925 merged commit 8f6c38e into main Sep 5, 2026
34 checks passed
@FumingPower3925
FumingPower3925 deleted the chore/org-hardening branch September 5, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant