From b418d62e8952bb829a36b7a80f7ead636b903ac2 Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Sun, 6 Sep 2026 00:43:34 +0200 Subject: [PATCH 1/7] ci: pin every third-party action to a commit SHA Floating tags (actions/checkout@v7, softprops/action-gh-release@v3, ...) are mutable: whoever controls the upstream tag controls what runs with our GITHUB_TOKEN. Pinning to the 40-hex commit the current tag resolves to makes the workflow reproducible and immune to tag hijacking; the trailing "# vN.M.P" comment keeps the version human-readable and lets Dependabot's github-actions ecosystem keep both the SHA and the comment current. Resolved on 2026-09-06 via the GitHub refs API (annotated tags dereferenced to their commit): actions/checkout@v7 -> 3d3c42e5 (v7.0.1) actions/setup-go@v7 -> b7ad1dad (v7.0.0) golangci/golangci-lint-action@v9 -> ba0d7d2e (v9.3.0) softprops/action-gh-release@v3 -> efb35369 (v3.0.3) No behavior change: each SHA is exactly the commit the floating tag pointed at. Org hardening review 2026-09-06. --- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/release.yml | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d4d2b6..8655b22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,11 +14,11 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - uses: actions/setup-go@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.27.0" - - uses: golangci/golangci-lint-action@v9 + - uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0 with: version: v2.13 - name: actionlint @@ -33,8 +33,8 @@ jobs: name: Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - uses: actions/setup-go@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.27.0" - name: Run tests @@ -45,8 +45,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v7 - - uses: actions/setup-go@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.27.0" # The testserver helper imports github.com/goceleris/celeris (pinned in diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 71a04f6..149919d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,8 +12,8 @@ jobs: name: Publish to Go Package Registry runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - uses: actions/setup-go@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.27.0" - name: Verify module @@ -45,8 +45,8 @@ jobs: - goos: darwin goarch: arm64 steps: - - uses: actions/checkout@v7 - - uses: actions/setup-go@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.27.0" - name: Build @@ -68,6 +68,6 @@ jobs: cd dist tar czf "loadgen_${GOOS}_${GOARCH}.tar.gz" "loadgen-${GOOS}-${GOARCH}" - name: Attach to release - uses: softprops/action-gh-release@v3 + uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 with: files: dist/loadgen_${{ matrix.goos }}_${{ matrix.goarch }}.tar.gz From fd6f2015fe476f92a4726ca26dc2684a532a05e6 Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Sun, 6 Sep 2026 00:44:16 +0200 Subject: [PATCH 2/7] ci: stop persisting the checkout token in jobs that never push MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit actions/checkout writes the job's GITHUB_TOKEN into .git/config (persist-credentials: true by default). Every subsequent step — and any tool they run, e.g. `go install ...@latest`, golangci-lint, the integration testserver — can read it back. None of the CI jobs (lint, test, integration) nor the release "publish" job push, tag or write to the repository, so they get `persist-credentials: false`. The release "binaries" job is left at the default: it hands a release-writing step (softprops/action-gh-release) the token, and the review rule is to leave the default wherever a step pushes, tags or writes releases. No behavior change for the workflows' own steps. Org hardening review 2026-09-06. --- .github/workflows/ci.yml | 6 ++++++ .github/workflows/release.yml | 2 ++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8655b22..e189cbf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.27.0" @@ -34,6 +36,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.27.0" @@ -46,6 +50,8 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.27.0" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 149919d..51161ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.27.0" From 06aaf3efb17776225df980e6671744aa1ef97b2f Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Sun, 6 Sep 2026 00:45:15 +0200 Subject: [PATCH 3/7] ci(release): attest SLSA build provenance for the release binaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bench orchestrators (probatorium) fetch loadgen tarballs straight from GitHub Releases and run them on the load hosts. Until now the only integrity signal was the SHA-256 GitHub displays next to the asset, which proves the download matched what was uploaded — not that it was built by this repository's workflow from a given commit. The "binaries" job now runs actions/attest-build-provenance (pinned to 4d101475, v4.2.2) on the exact archive it is about to upload, BEFORE the softprops/action-gh-release step, so the attestation covers the bytes that ship. The job gets the minimum permission set the attestation needs: contents: write (upload, unchanged), id-token: write (OIDC identity that Sigstore signs the SLSA predicate with) and attestations: write (store it in the repo's attestation store). The upload step itself is unchanged. README gains a "Verify a release" section: gh attestation verify -R goceleris/loadgen and notes that releases before this change (<= v1.4.13) carry no attestation. Org hardening review 2026-09-06. --- .github/workflows/release.yml | 10 ++++++++++ README.md | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 51161ff..c8f14de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,10 @@ jobs: # orchestrators can fetch them without re-cloning + rebuilding. name: Build cmd/loadgen binaries (${{ matrix.goos }}/${{ matrix.goarch }}) runs-on: ubuntu-latest + permissions: + contents: write # action-gh-release uploads the tarball to the release + id-token: write # OIDC token that signs the SLSA provenance (Sigstore) + attestations: write # store the attestation in the repository's attestation store strategy: matrix: include: @@ -69,6 +73,12 @@ jobs: run: | cd dist tar czf "loadgen_${GOOS}_${GOARCH}.tar.gz" "loadgen-${GOOS}-${GOARCH}" + # SLSA build provenance for the exact archive that ships. Consumers + # verify with: gh attestation verify -R goceleris/loadgen + - name: Attest build provenance + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-path: dist/loadgen_${{ matrix.goos }}_${{ matrix.goarch }}.tar.gz - name: Attach to release uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 with: diff --git a/README.md b/README.md index ecc7a7c..4252caf 100644 --- a/README.md +++ b/README.md @@ -328,6 +328,16 @@ chmod +x /tmp/loadgen-${OS}-${ARCH} GitHub displays the SHA-256 of each release asset on the release page — `gh release download ` and the web UI verify checksums automatically, so no separate `.sha256` sidecar ships. +### Verify a release + +Every release tarball is built by the [release workflow](.github/workflows/release.yml) and carries a signed [SLSA build provenance](https://slsa.dev/provenance/) attestation, generated with `actions/attest-build-provenance` and recorded in GitHub's attestation store. Before running a downloaded binary on a bench host, check that the asset really came from this repository's release pipeline: + +```bash +gh attestation verify loadgen_linux_amd64.tar.gz -R goceleris/loadgen +``` + +The command fails if the archive was tampered with after the build or was not produced by a `goceleris/loadgen` workflow. Add `--format json` to inspect the attested source commit, workflow path and builder. Releases published before the attestation step was added (v1.4.13 and earlier) have no attestation and will fail verification. + ### Reference orchestrator The probatorium cluster bench drives this contract: it cross-compiles loadgen (or fetches a release tarball), pushes it to the load host, executes it, and parses the stdout JSON — decoding `histogram` for cross-cell re-aggregation. See [goceleris/probatorium](https://github.com/goceleris/probatorium). From 54f9147a9ae6228925cb5c49d463293a3743acf9 Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Sun, 6 Sep 2026 00:45:15 +0200 Subject: [PATCH 4/7] chore: add CODEOWNERS with a single default owner Makes review ownership explicit and machine-enforceable: GitHub auto-requests @FumingPower3925 on every pull request, and the "Require review from Code Owners" branch-protection option (configured separately) can turn that into a hard merge gate. The header explains the precedence rule and a commented example shows how to delegate a subsystem to a second maintainer when the time comes. Org hardening review 2026-09-06. --- .github/CODEOWNERS | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..bf52060 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,16 @@ +# Code owners for goceleris/loadgen. +# +# GitHub requests a review from the matching owners on every pull request +# that touches these paths; with "Require review from Code Owners" enabled on +# the main branch protection, their approval is mandatory before merging. +# Later rules take precedence over earlier ones — keep the catch-all first. +# +# Syntax: https://docs.github.com/articles/about-code-owners + +# Default owner for everything in the repository. +* @FumingPower3925 + +# Area delegation example — uncomment and adjust when a second maintainer +# takes ownership of a subsystem (the more specific rule wins): +# /cmd/loadgen/ @FumingPower3925 @WdnLiu +# /.github/ @FumingPower3925 From bd99485dec5ea1b242292b876e8f6ede1806947b Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Sun, 6 Sep 2026 00:45:15 +0200 Subject: [PATCH 5/7] docs: add SECURITY.md with a private reporting channel loadgen had no security policy, so a researcher's only option was a public issue. The new policy prefers GitHub private vulnerability reporting (Security tab -> "Report a vulnerability", enabled on the repository) with security@goceleris.dev as the fallback, commits to an acknowledgement within 72 hours, states that only the latest release is supported, scopes what counts as a loadgen vulnerability (response parsers, TLS handling, federation protocol, result output, release pipeline) and points engine issues at celeris/SECURITY.md. Org hardening review 2026-09-06. --- SECURITY.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..6f68a87 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,60 @@ +# Security Policy + +loadgen is a load-generation *client*: it opens outbound connections to a +target you name on the command line and never listens on a socket in +production use. Its security surface is therefore small — a malicious +server could still try to exploit the HTTP/1.1, HTTP/2, WebSocket and SSE +response parsers, and the JSON result written to stdout is consumed by +orchestrators — so we treat parser and output-handling bugs as security +issues. + +## Reporting a Vulnerability + +**Please do not open a public issue for security problems.** + +Report privately, in order of preference: + +1. **GitHub private vulnerability reporting (preferred)** — open the + repository's **Security** tab and click **"Report a vulnerability"**. + Private vulnerability reporting is enabled on this repository; the report + is visible only to the maintainers and becomes the draft advisory that + ships with the fix. +2. **Email** — [security@goceleris.dev](mailto:security@goceleris.dev). + +Include a description of the issue, the loadgen version (the release tag, or +the `loadgen_version` field of a result), reproduction steps or a +proof-of-concept, and the impact you believe it has. + +You will receive an **acknowledgement within 72 hours**. We will keep you +informed as we triage, fix and disclose, and we credit reporters in the +release notes unless they prefer otherwise. + +## Supported Versions + +Only the latest release of loadgen receives security fixes. Pre-built +binaries and the Go module are cut from the same tag, so upgrade to the most +recent `v1.x` tag to remain covered. + +## Scope + +In scope: + +- The HTTP/1.1, HTTP/2 (prior-knowledge and h2c upgrade), WebSocket and SSE + client implementations and their response parsers +- TLS configuration handling (`-insecure`, custom `tls.Config`) +- The federation peer protocol +- The JSON result output consumed by orchestrators +- The release pipeline (workflows, published binaries and their provenance) + +Out of scope: + +- Vulnerabilities in the server under test +- Denial of service of a target caused by *intended* use of a load generator + +## The celeris engine + +Vulnerabilities in the HTTP server framework that loadgen is built to test +belong to the [goceleris/celeris](https://github.com/goceleris/celeris) +repository — see +[celeris/SECURITY.md](https://github.com/goceleris/celeris/blob/main/SECURITY.md) +for its supported-versions table and reporting channels. From b045c3765e75cc886232fa5a77d303630b1b094e Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Sun, 6 Sep 2026 00:45:15 +0200 Subject: [PATCH 6/7] docs: add CONTRIBUTING.md and a pull request template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents what CI actually runs (go build/vet, golangci-lint v2.13, the -race unit suite, the live-celeris integration matrix) so contributors can reproduce a green run locally, the branch/commit conventions shared with celeris, and the merge rule — protected main, PR + CI + code-owner approval, maintainer merge — by reference to celeris/GOVERNANCE.md so the org has one governance source of truth. The PR template (Summary, Changes, Test Plan, Closes #) mirrors celeris' and adds the loadgen specific reminder that JSON-schema or flag changes must update the README contract that probatorium parses. Org hardening review 2026-09-06. --- .github/PULL_REQUEST_TEMPLATE.md | 16 ++++++++ CONTRIBUTING.md | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CONTRIBUTING.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..1ed0005 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +## Summary + +Brief description of the change and why it is needed. + +## Changes + +- + +## Test Plan + +- [ ] Unit tests added/updated (`go test -race -count=1 ./...`) +- [ ] `golangci-lint run` and `go vet ./...` pass +- [ ] Integration matrix passes if the H1/H2/WS/SSE client paths changed (`go test -tags integration -run TestIntegrationH2CMatrix .`) +- [ ] README cluster-bench contract updated if the JSON result schema or CLI flags changed + +Closes # diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f3e2718 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,66 @@ +# Contributing to loadgen + +Thanks for helping improve loadgen, the load generator behind the celeris +benchmark suite. loadgen is deliberately dependency-light (two direct Go +dependencies) and is exercised on real hardware by +[goceleris/probatorium](https://github.com/goceleris/probatorium), so +changes are held to the same standard as the engine they measure. + +## Prerequisites + +- Go **1.27.0** (the version pinned in `go.mod` and in CI) +- [golangci-lint](https://golangci-lint.run/) v2.13+ (the CI pin) +- Linux or macOS. The client itself is portable; the process-CPU sampler + and recv-queue probe have Linux implementations and `_other.go` + fallbacks. + +## Build and test + +```bash +go build ./... # compile everything, including cmd/loadgen +go vet ./... +golangci-lint run # same config CI uses (.golangci.yml) +go test -race -count=1 -timeout 120s ./... # unit tests, exactly as CI runs them + +# Integration matrix against a live celeris server. The testserver helper is +# a nested module that imports celeris; the tests spawn it as a subprocess. +go test -tags integration -race -count=1 -timeout 180s -v -run TestIntegrationH2CMatrix . +``` + +`gofmt` and `goimports` (with `github.com/goceleris/loadgen` as the local +prefix) are enforced by golangci-lint, so run it before pushing. + +## Pull request flow + +1. Fork the repository and create a topic branch from `main` + (`feat/…`, `fix/…`, `perf/…`, `chore/…`). +2. Keep each PR focused on a single change and include tests — a + correctness fix without a regression test is not complete. +3. Write commit messages in the `type: description` format + (`feat:`, `fix:`, `perf:`, `security:`, `test:`, `ci:`, `docs:`, `chore:`) + and explain *why* in the body when it is not obvious from the diff. +4. Fill in the pull request template (Summary, Changes, Test Plan, + `Closes #…`). Changes to the JSON result schema or the CLI flags must + also update the README's cluster-bench contract section, because + probatorium parses that output. +5. Make sure the CI workflow (lint, actionlint, unit tests, integration + matrix) is green. + +## Merge rule + +loadgen follows the same governance as celeris — see +[celeris/GOVERNANCE.md](https://github.com/goceleris/celeris/blob/main/GOVERNANCE.md). +In short: `main` is protected, every change lands through a pull request +that passes CI and is approved by a code owner (see `.github/CODEOWNERS`), +and pull requests are merged by a maintainer — never force-pushed. Releases +are tagged from `main` and the release workflow builds, attests and +publishes the binaries. + +## Reporting security issues + +Do not open a public issue. Follow [SECURITY.md](SECURITY.md). + +## License + +By contributing you agree that your contributions are licensed under the +[Apache License 2.0](LICENSE) that covers the project. From 1ac84cfbde8918020d7117169b8748cd827e7af3 Mon Sep 17 00:00:00 2001 From: Albert Bausili Date: Sun, 6 Sep 2026 00:45:15 +0200 Subject: [PATCH 7/7] chore: add .github/release.yml for generated release notes Auto-generated release notes were uncategorised. Adopt the same categories as celeris (Breaking Changes, Security, Fixes, Performance, Features, Other) so release pages across the org read alike, and exclude the dependencies / github_actions labels because Dependabot bumps are noise for people downloading the binaries. Org hardening review 2026-09-06. --- .github/release.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/release.yml diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..556d91b --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,31 @@ +# Automatically generated release notes (GitHub "Generate release notes"). +# Same categories as goceleris/celeris so the org's release pages read alike. +# Labels are applied on the pull request; dependabot / actions bumps are +# excluded because they are noise for consumers of the binaries. +changelog: + exclude: + labels: + - dependencies + - github_actions + categories: + - title: Breaking Changes + labels: + - breaking + - title: Security + labels: + - security + - title: Fixes + labels: + - bug + - fix + - title: Performance + labels: + - performance + - perf + - title: Features + labels: + - enhancement + - feature + - title: Other Changes + labels: + - "*"