Repository navigation
ci: harden workflows: bot token handling, expressions through env, zizmor, CodeQL for actions, approval-gated bot PRs - #790
Conversation
…nd scripts, validate tag names
The release workflows and the daily performance workflow checked the
repository out with the release bot's token stored in the git
configuration, so every later action in the job could read it, and they
pushed by embedding the token in the push URL. Several steps also pasted
secrets and tag-derived strings straight into script text.
- Every checkout sets persist-credentials: false. The publish checkouts
no longer receive the bot token at all (they only read).
- The steps that push (release tag, release metadata branch in the manual
republish, daily report branch) get the token through step-level env and
send it as a masked one-command Authorization header, never in a URL or
in .git/config. The tag push says that creating v* tags needs the bot
token under the tag ruleset; the other fallbacks are unchanged.
- No ${{ secrets.* }} remains inside run: scripts. Helm registry logins
read the password from env and use --password-stdin. Docker Hub
credentials are given only to the steps that use them; a job-wide
HAS_DOCKERHUB flag replaces the job-level token variables in conditions.
- Version, tag, revision and build time reach scripts through env. The
manual republish accepts only vMAJOR.MINOR.PATCH, checks out
refs/tags/<tag> and verifies it is the checked-out commit. The automatic
release rejects a latest tag or computed version that is not plain
semver.
- prepare-release only reads, so its permissions drop to contents: read
and pull-requests: read.
…QL for actions, pinned-uses test
…pass a scope check
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
⚪ Performance A/B: no change beyond noise8 shape×range — 8 ⚪ same · results vs Loki: 7 same, 0 new difference(s), 1 pre-existing Every shape is within noise of the base build (±30% and 100 ms). 8 unchanged shape×range (within noise)
Cold (first-run) timings — what a user opening a panel sees
Why these shapes
base |
✅ Visual smoke: passed with warningsBase
Gate: a base-vs-PR data difference passes when the PR is closer to Loki, and fails when the PR diverges from Loki or Loki cannot decide it (at a range Loki does not hold, a difference passes when the same page proved it closer to Loki at a shorter range; otherwise label the pull request dd-fields-1h: history-dependent difference, not gated (1): resource /api/datasources/uid/*/resources/patterns?end=2026-10-21T13%3A39%3
dd-service-logs-1h: history-dependent difference, not gated (1): resource /api/datasources/uid/*/resources/patterns?end=2026-10-21T13%3A39%3
All montages of this run: |
PR Quality ReportCompared against base branch Coverage and tests
Compatibility
Performance smokeLower CPU cost (
State
|





Summary
This change hardens the GitHub Actions workflows in three commits, keeping the existing release flow and its bot:
http.extraheaderpassed only to the steps that push, never a token in a URL or in.git/config.--password-stdin.^v[0-9]+\.[0-9]+\.[0-9]+$) and passed through the environment.release.yamlchecks outrefs/tags/<tag>and checks it equalsHEAD.prepare-releaseis read-only.${{ }}expression remains inside anyrun:script; they all go through stepenv:.Security / staticand fails on any new finding; exceptions are reviewed inline# zizmor: ignore[...]comments only.actionslanguage in a separateanalyze (actions)job, so the Goanalyzejob keeps its identity and the required check name.persist-credentials: falseis set on every checkout that does not push.setup-gocache: false.scripts/ci/tests/test_workflow_pins.py, fails on an unpinneduses:or a missing version comment.auto-release(and a manualrelease.yamlrepublish) opens the metadata pull request as the release bot and arms auto-merge.Republish metadata commits are made through the API, so they are signed like the automatic ones.
perf-dailyarms auto-merge on the daily pull request.mainrequires one approval, so these merge right after the maintainer approves and the required checks pass. No workflow approves, and no bypass is used.The existing
release-pr-validation.yamlbecomes the scope check (release-pr-scope). It runs onpull_request_targetfrom main's definition with no checkout, no secrets and no PR code executed, and reads the PR's files as data. It limits bot branches to their files:docs/observability.md,Chart.yaml, with matching versions, the bot or owner as author, and verified commits;Other pull requests pass immediately.
scripts/ci/tests/test_release_pr_validation.pycovers the scope rules and the workflow's shape, so adding a checkout to it fails CI.Proofs
.github/workflowsgithub.event.pull_request.titlein arun:, or a second cachedsetup-gojobpython3 -m unittest discover -s scripts/ci/testsrun:text against a fakeghwith recorded API answers)internal/, unsigned metadata commits, wrong versions, wrong authors and non-numeric PR numbers fail; unrelated, fork and non-main-base PRs pass immediatelyAUTHORIZATION: basicheader is sent, and the token is absent from the URL and.git/configv2.22.1; rejectsv1.2.3$(id),v1.2,main,v1.2.3-rc1, trailing newline${{inside anyrun:block (YAML parse)Required checks keep their names (
analyze,test,Security / static, ...).Shown by the first runs after merge:
actionsresults;release-pr-scopecheck attaching to its head. After that, it can be made a required check.Not applicable: no proxy code changes, so e2e vs Loki, the Grafana range matrix, visual proof, A/B, benchstat and the load test do not apply.