Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ jobs:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- uses: pnpm/action-setup@v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9

- uses: actions/setup-node@v6
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
Expand All @@ -30,11 +30,11 @@ jobs:
package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- uses: pnpm/action-setup@v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9

- uses: actions/setup-node@v6
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: 22
cache: pnpm
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/contract-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ jobs:
if: github.event.pull_request.user.login != 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0

- uses: pnpm/action-setup@v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9

- uses: actions/setup-node@v6
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: 22
cache: pnpm
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/gitguardian.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0

- name: GitGuardian scan
uses: GitGuardian/ggshield/actions/secret@v1.53.0
uses: GitGuardian/ggshield/actions/secret@faa02e4bc7218c28e0ccfdb8bf33309ed72dbb99 # v1.53.0
env:
GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }}
GITHUB_PUSH_BASE_SHA: ${{ github.event.base }}
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

- uses: pnpm/action-setup@v6
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9

- uses: actions/setup-node@v6
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: 22
cache: pnpm
Expand All @@ -55,7 +55,7 @@ jobs:
run: pnpm --filter @frontagent/desktop run release

- name: Upload zip artifact
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: desktop-${{ matrix.os }}
path: apps/desktop/release/*.zip
Expand All @@ -71,12 +71,12 @@ jobs:
contents: write
steps:
- name: Download all platform zips
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: dist-zips
merge-multiple: true

- name: Attach zips to GitHub Release
uses: softprops/action-gh-release@v3
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
files: dist-zips/*.zip
6 changes: 5 additions & 1 deletion .github/workflows/repo-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ jobs:
# pull_request_target,两种写法行为相同;但若守卫日后扩展到更多事件,
# 一个无条件置位的开关会把 issue_comment 这条边界更宽的路径也静默放行。
# 表达式渲染为布尔字面量 true / false。
- uses: actions/checkout@v4
# 刻意留在 v4:v5.0.0 起 actions/checkout 改用 node24 运行时,而本 job 跑在
# 自托管 runner(vars.REPO_GUARD_RUNNER),它是否满足 node24 从本仓库看不到。
# 猜错的失败形态与 #437 逐字相同——checkout 处红、评审 step skipped、我们这边
# 零改动。dependabot 提 v6/v7 升级时:先确认 runner 版本,再合。见 #440。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

注释把「先确认 runner 版本再升 v6/v7」这条前提挂在 #440 上,而本 PR Closes #440;建议改指向一个新开的 follow-up issue(update-npm-downloads.yml:15workflow-rules.test.mjs:462 同源,一并改),否则这条推迟决定合并当天就失去 tracker。

- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

停在 v4.4.0 的理由(v5 起 node24、self-hosted runner 版本不可见)只写在 PR/CHANGELOG,dependabot 的 github-actions 段无 major ignore、新契约测试也不校验 major;在本行加一句 hold 说明,或加 dependabot ignore(update-npm-downloads.yml:15 同因,两处应一起)。

if: github.event_name == 'pull_request_target' || (github.event_name == 'issue_comment' && github.event.issue.pull_request)
with:
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || format('refs/pull/{0}/head', github.event.issue.number) }}
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/update-npm-downloads.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 刻意留在 v4:与 repo-guard.yml 的 checkout 保持同版本,按 #440 item 2
# 一起动。本 job 跑在托管 runner,不存在那边的 node24 阻塞;`git push` 依赖的
# `persist-credentials` 默认值在 v4.4.0 与 v6.1.0 上也已核对为一致(都是 true)。
# 也就是说这一处本身可以升,只是没有理由单独升。
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Fetch cumulative downloads and patch README
run: |
END=$(date -u -d yesterday +%F)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.

### Changed

- **ci**: Every third-party action is pinned to a commit SHA with a trailing version comment. #437 was caused by a silent behavior change on the moving `@v4` tag — `actions/checkout` began refusing fork checkouts under `pull_request_target` and Repo Guard broke on every fork PR with no change on our side. The regression test added for it asserts workflow text, and Actions only warns on unknown `with:` keys, so the same class of drift could break checkout again while `test:workflows` stayed green; a new contract test now requires the SHA form. Each action is pinned where it already was — the two `actions/checkout@v4` usages stay on v4.4.0 rather than moving to v6, because that jump crosses the node20→node24 runtime change and repo-guard runs on a self-hosted runner whose version cannot be checked from here; aligning them is a separate, verifiable change. `ceilf6/repo-guard@main` is deliberately left on a moving ref — it is first-party, and whether to pin it is a maintainer policy call, noted rather than decided here. (#440)
- **deps**: Dev-dependency group bump. Biome 2.4.16 → 2.5.6 needed a config migration (`recommended` → `preset`) and surfaced four findings, all real: an unsafe optional chain in a desktop test whose failure would have pointed at the wrong line, an SVG with no accessible name, a string concatenation, and three env vars `contract-check.mjs` reads without declaring — silenced by a `biome.json` `overrides` entry scoped to `scripts/workflows/**`, since that script is not a turbo task and declaring its variables in `globalPassThroughEnv` would have exposed them to every package's build/test/typecheck just to quiet one lint; any future script under that path inherits the exemption. The same rename silently disabled a guard in the Harness contract test that asserted the lint rule used as a traversal probe is still on — it now accepts both spellings, verified against every way 2.5.6 can switch that rule off. gitnexus 1.6.6 → 1.6.9 also moves the exact-version pin in that test, which is deliberate: the pin exists so a gitnexus upgrade is a reviewed change rather than a lockfile side effect. Also electron 42.4 → 42.8, turbo 2.9.16 → 2.10.8, vitest 4.1.8 → 4.1.10, vite 7.3.2 → 7.3.6, esbuild → `^0.28.1` everywhere (`apps/desktop` came from `^0.25.0`, so the Electron bundler crosses three minors; the root and `apps/vscode` from `^0.28.0`), concurrently 9.2.1 → 9.2.4 and @types/node 25.9.1 → 25.9.5. (#449)
- **ci**: Contract Guard now skips dependabot-authored PRs. The gate requires two things dependabot structurally cannot supply — a `scripts/tests/` contract test in the same PR whenever a critical file changes, and a structured GitNexus impact summary in the PR body — so every dependency bump touching `.github/workflows/` failed it permanently. Those bumps are still gated by CODEOWNERS review and by CI, which is the only required status check. The skip is keyed on the PR author rather than `github.actor`, so it survives a maintainer running update-branch or pushing to the dependabot branch. (#443)
- **ci**: Repo Guard can review fork PRs again. `actions/checkout` refuses fork checkouts under `pull_request_target` unless `allow-unsafe-pr-checkout` is set, and it does not read the workflow's own actor allowlist — so every fork PR failed at checkout before the review step ran. The opt-in is set explicitly and scoped by event to `pull_request_target` only, matching the path its justification covers: that path is gated on the PR author being a repo branch or a named allowlist entry. The `issue_comment` branch of the same step gates on the *commenter* rather than the PR author, and stays opted out, so a future widening of the action's guard cannot silently admit it. (#437)
Expand Down
45 changes: 45 additions & 0 deletions scripts/tests/workflow-rules.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
existsSync,
mkdirSync,
mkdtempSync,
readdirSync,
readFileSync,
rmSync,
statSync,
Expand Down Expand Up @@ -441,6 +442,50 @@ test('contract guard skips dependabot-authored PRs by author, not actor', () =>
]);
});

// #437 的机制:`actions/checkout@v4` 这条**移动的 tag** 悄悄改了行为(开始拒绝
// pull_request_target 下的 fork checkout),Repo Guard 在我们这边零改动的情况下对
// 每个 fork PR 全红。#438 补的回归测试断言的是 workflow **文本**,而 GitHub Actions
// 对未知的 `with:` 键只告警不失败——tag 再动一次、把 `allow-unsafe-pr-checkout`
// 改名或删掉,fork PR 会在 checkout 处坏掉而 test:workflows 照旧全绿,正是藏住
// #437 的那套机制。所以这里要求的是 SHA,不是「某个 tag 名」。
test('third-party actions are pinned to a commit SHA', () => {
// .yaml 也是合法的 workflow 后缀;只收 .yml 会让一个新增的 x.yaml 静默豁免整条规则。
//
// 边界:只扫 .github/workflows/。复合 action(.github/actions/**/action.yml)里的
// `uses:` 同样能引入移动 tag,同样会被这条规则静默豁免。今天这个目录不存在,
// 所以是零影响;真要新增复合 action 时,把它一并纳入扫描源,别重新推导一遍。
const workflows = readdirSync('.github/workflows').filter((file) => /\.ya?ml$/u.test(file));
assert.ok(workflows.length > 0, 'no workflows found');

// 第一方 action 例外:仓库自己控制它,钉 SHA 意味着 repo-guard 每次改动都要回来
// 提一个 bump PR。它确实也是移动 ref,且跑在带 secrets 的自托管 runner 上——
// 要不要一并钉住是维护者的策略决定,不在本条断言的范围内(#440)。
const FIRST_PARTY_UNPINNED = new Set(['ceilf6/repo-guard@main']);
const pinned = /^[0-9a-f]{40}$/u;

const offenders = [];
for (const file of workflows) {
const text = readFileSync(join('.github/workflows', file), 'utf8');
for (const line of text.split('\n')) {
const match = /^\s*(?:-\s*)?uses:\s*(\S+)/u.exec(line);
if (!match) continue;
const [owner, ref] = [match[1], match[1].split('@')[1] ?? ''];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ 的本地 action / reusable workflow ref 会被误判为未钉;在取 ref 前跳过 ./ 开头的 uses

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(\S+) 捕获后直接 split('@'),带引号的 uses: 'owner/action@<sha>' 会因尾引号被误判为未 pin;捕获后先剥一层引号。

// 本地 action / reusable workflow(`uses: ./.github/actions/foo`)没有 @ref,
// 结构上就钉不了 SHA;它们是本仓库的代码,不存在第三方漂移。
if (owner.startsWith('./')) continue;
if (FIRST_PARTY_UNPINNED.has(owner)) continue;
if (!pinned.test(ref)) offenders.push(`${file}: ${owner}`);
// 光有 SHA 读不出这是哪个版本,升级时无从判断跨了多少。要求尾注版本号。
// 锚定到 SHA 之后的注释:不锚定的话,行内任何位置出现的数字(比如一句
// 「# 见 #440」)都会被当成版本号,这条断言就形同虚设。
else if (!new RegExp(`@${ref}\\s*#\\s*v?\\d+(\\.\\d+)*(\\s|$)`, 'u').test(line))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

版本注释正则要求版本号后紧跟空白或行尾,# v1.2.0-rc1# v2.0.0-beta 这类带后缀的 tag 会被判成 (no version comment)。dependabot 默认不提预发布版本,所以概率低,但这是全仓门禁,误红的代价是拦住一次合法 bump;把结尾放宽成 [\w.+-]*(\s|$) 即可。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

只校验「有版本注释」,错标签静默通过;补一条离线可查的一致性断言(同 SHA ⇒ 同注释、同 action+同注释 ⇒ 同 SHA)即可覆盖重复 4 次的 setup-node/action-setup,且不妨碍 checkout 刻意的 v4/v6 分叉。

offenders.push(`${file}: ${owner} (no version comment)`);
}
}

assert.deepEqual(offenders, [], `pin these to a commit SHA with a trailing # version comment`);
});

test('desktop app has an unsigned electron-builder packaging path', () => {
const pkg = JSON.parse(readFileSync('apps/desktop/package.json', 'utf8'));
const config = readFileSync('apps/desktop/electron-builder.yml', 'utf8');
Expand Down
Loading