Skip to content

ci: pin credential-holding third-party actions by sha, and pass the Turbo cache credentials only to merge-queue and main runs - #25559

Merged
os-zhuang merged 3 commits into
mainfrom
claude/project-thread-gl3pmg
Oct 24, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/project-thread-gl3pmg

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

CI supply-chain hardening, two commits that can land or be dropped independently:

  1. Pin the credential-holding third-party actions by commit sha (96212c2).
  2. Pass the Turbo remote-cache credentials only to merge-queue and main runs (7ea2af7).

1. Third-party actions pinned by sha

Each names the commit its floating major resolved to when this was written (read with git ls-remote against the upstream repository), with a trailing version comment, the form release.yml already uses for changesets/action.

workflow action was now credential in reach
docker-publish.yml docker/setup-qemu-action @v4 99012661954931238ded8c8b007157a8430204e1 # v4.4.0 packages: write job
docker-publish.yml docker/setup-buildx-action @v4 f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 packages: write job
docker-publish.yml docker/login-action @v4 81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0 GITHUB_TOKEN as registry password
docker-publish.yml docker/metadata-action @v6 dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 packages: write job
docker-publish.yml docker/build-push-action @v7 c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 pushes with the logged-in registry credential
check-links.yml (2 steps) lycheeverse/lychee-action @v2 e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 token input defaults to the job token
ci.yml (filter job) dorny/paths-filter @v4 ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 token input defaults to the job token

Already pinned and untouched: changesets/action (release.yml), google/osv-scanner-action (validate-deps.yml). Left on tags: first-party actions/* and github/codeql-action (GitHub-owned); pnpm/action-setup in showcase-smoke.yml receives no credential. The readers of the paths-filter ref (check-ci-filter-parity.mjs, dispatch-gates.mjs) match the action by prefix, so a pinned ref reads the same.

2. Turbo remote-cache credentials scoped by event

Before: every same-repo pull_request run received TURBO_TOKEN and TURBO_REMOTE_CACHE_SIGNATURE_KEY. The read-only remote:r setting is turbo client configuration; the token itself can write and the key can sign, so any code a PR runs during its build could upload a signed artifact that a later queue or main build replays.

After, in all twelve carriers (ci.yml ×7, lint.yml ×5), both secrets are gated on the same condition:

TURBO_TOKEN: ${{ (github.event_name == 'merge_group' || github.ref == 'refs/heads/main') && secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY != '' && secrets.TURBO_TOKEN || '' }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ (github.event_name == 'merge_group' || github.ref == 'refs/heads/main') && secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY || '' }}
  • Credentialed: merge_group, and every run on refs/heads/main (push, the hourly schedule, workflow_dispatch on main). Not credentialed: pull_request, and a dispatch on any other branch. TURBO_CACHE and TURBO_TEAM are unchanged, so the write rule is exactly as before.
  • Measured question, PR reads with or without the secrets: Vercel's remote cache authenticates reads as well as writes, and with remoteCache.signature: true turbo needs the key to verify a download, which is the same key that signs. So a PR run cannot read the remote without holding both; the answer is not at all. A PR run builds from the restored Actions-cache seed (local:rw), which main saves. ⚠️ I could not reach api.vercel.com from this container (egress refused), so the "reads need the token" half is read from turbo's behaviour as assert-turbo-remote-cache.mjs already documents it (any of the three unset turns the remote off), not from a live probe; this PR's own CI run is the first live reading.
  • Cost, measured on this PR's own runs. With the remote withheld, a PR build reads only the Actions seed main saves:
  • assert-turbo-remote-cache.mjs gains isCredentialedRun() (same condition) and reports a disabled remote on a non-credentialed run as a notice ("withheld by design"), not a warning. Writing runs still red when the remote is off. New self-test battery credentialed run (5 cases): 37 cases across 7 batteries, exit 0.
  • Build Core's "Credentials" comment, the authority the other carriers point back to, is rewritten to the new rule.

Verification

Gates derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 7ea2af7; results under "Gates" below.

Gates (local, head 7ea2af7)

  • node scripts/pm/dispatch-gates.mjs --commands derived 70 families from the diff; 66 exit 0; 4 NOT MEASURED (exit 3, prerequisite: they read built dist/, and no package source is in this diff): check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content. CI runs them on a real build.
  • Reconciled: dispatch-gates.mjs --ran reports "70 derived families accounted for — 66 run, 4 NOT-MEASURED", exit 0.
  • Among the 66 green: check:ci-filter-parity (+ self-test), check:required-contexts, check:pm-governed-merges, check:select-gate-families, check:self-test-wired, check:workflow-step-name-quoting, check:nul-bytes, and assert-turbo-remote-cache.mjs --self-test (37 cases, 7 batteries).
  • No changeset: the diff touches .github/workflows/** and scripts/ci/**, which no package publishes (skip-changeset).

维护者速读(草稿)

  • 改了什么:①CI 里拿得到凭据的第三方 action(docker 发布五个、lychee、paths-filter)改为按提交 sha 钉死;②Turbo 远程缓存的 token 和签名密钥只给合并队列和 main 上的运行,PR 运行不再拿到。
  • 为什么改:浮动标签可以被上游重指,下一次运行就把凭据交给未经审阅的代码;PR 里跑的任何代码(包括新加依赖的安装脚本)此前都握着 main 信任的缓存签名密钥。
  • 风险与代价(含回滚):PR 构建不再读远程缓存,只读 main 存下的 Actions 缓存种子。实测:ci: prune each Test Core Turbo seed to its own run's tasks before saving it #25523 落地前种子被挤掉,PR 的 Build Core 0 命中、6 分 4 秒;ci: prune each Test Core Turbo seed to its own run's tasks before saving it #25523 落地后本 PR 的 Build Core 76/76 全部命中种子、构建 0.9 秒。剩余代价是种子之后新合并改动过的包需要冷构建。回滚:单独 revert 第二个提交即可,第一个提交独立成立。
  • 席位意见:
  • 你要做的:第②条已于 2026-10-24 裁定采用(收回凭据并依赖 Actions 种子);批准后由席位落地。本 PR 保持 draft。

Session: https://claude.ai/code/session_01W9H2WdXx3kPkf6moDnRdRE

🤖 Generated with Claude Code

https://claude.ai/code/session_01W9H2WdXx3kPkf6moDnRdRE

docker/setup-qemu-action, docker/setup-buildx-action, docker/login-action,
docker/metadata-action and docker/build-push-action in docker-publish.yml
(a packages: write job), lycheeverse/lychee-action in check-links.yml and
dorny/paths-filter in ci.yml each name the commit their floating major
resolved to, with a trailing version comment, as release.yml already does
for changesets/action.

Claude-Session: https://claude.ai/code/session_01W9H2WdXx3kPkf6moDnRdRE
Co-authored-by: Claude <noreply@anthropic.com>
…ain runs

TURBO_TOKEN and TURBO_REMOTE_CACHE_SIGNATURE_KEY now reach a carrier only on
merge_group or a run on refs/heads/main, in all twelve carriers (ci.yml x7,
lint.yml x5). A pull_request run builds from the restored Actions-cache seed;
`remote:r` was a client setting, while the token can write and the key can
sign. assert-turbo-remote-cache reports such a run as a notice (credentials
withheld by design) instead of a warning, with a self-test battery for the
condition.

Claude-Session: https://claude.ai/code/session_01W9H2WdXx3kPkf6moDnRdRE
Co-authored-by: Claude <noreply@anthropic.com>
@os-zhuang
os-zhuang marked this pull request as ready for review October 24, 2026 01:45
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit ba9e0c3 Oct 24, 2026
48 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-gl3pmg branch October 24, 2026 02:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants