Skip to content

Commit e95af5b

Browse files
committed
chore: Implement secure CI/CD pipeline
This commit introduces a more secure and robust CI/CD pipeline by separating untrusted and trusted workflow execution. Key changes include: - Introduced a new `.github/workflows/trusted-ci.yml` workflow for secret-dependent integration tests, triggered by `workflow_run` after untrusted checks pass in `.github/workflows/ci.yml`. - `ci.yml` now focuses on linting, unit tests, and building/uploading combined artifacts, without access to sensitive secrets. - `trusted-ci.yml` downloads pre-built artifacts, ensuring no direct execution of untrusted PR source code in the privileged environment. - Added a "Fail if no access to secrets" step in `trusted-ci.yml` to enforce integration test execution for all pull requests. - Refined job permissions in `trusted-ci.yml` to adhere to the principle of least privilege. - Added `environment: trusted-ci-env` to the `test` job in `trusted-ci.yml` to enable manual approval for sensitive operations (to be configured via GitHub Environments).
1 parent fb3ceb0 commit e95af5b

File tree

3 files changed

+133
-40
lines changed

3 files changed

+133
-40
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ jobs:
120120
run: |-
121121
npm run build
122122
123+
- name: 'Upload Build Artifacts'
124+
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
125+
with:
126+
name: 'build-artifacts'
127+
path: 'packages/*/dist'
128+
123129
- name: 'Run type check'
124130
run: |-
125131
npm run typecheck
@@ -280,10 +286,10 @@ jobs:
280286
run: |-
281287
npm ci
282288
283-
- name: 'Run tests and generate reports'
289+
- name: 'Run unit tests'
284290
env:
285291
NO_COLOR: true
286-
run: 'npm run test:ci'
292+
run: 'npm run test:unit'
287293

288294
- name: 'Publish Test Report (for non-forks)'
289295
if: |-
@@ -311,44 +317,6 @@ jobs:
311317
name: 'coverage-reports-${{ matrix.node-version }}-${{ matrix.os }}'
312318
path: 'packages/*/coverage'
313319

314-
post_coverage_comment:
315-
name: 'Post Coverage Comment'
316-
runs-on: 'ubuntu-latest'
317-
needs: 'test'
318-
if: |-
319-
${{ always() && github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name == github.repository) }}
320-
continue-on-error: true
321-
permissions:
322-
contents: 'read' # For checkout
323-
pull-requests: 'write' # For commenting
324-
strategy:
325-
matrix:
326-
# Reduce noise by only posting the comment once
327-
os:
328-
- 'ubuntu-latest'
329-
node-version:
330-
- '22.x'
331-
steps:
332-
- name: 'Checkout'
333-
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
334-
335-
- name: 'Download coverage reports artifact'
336-
uses: 'actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0' # ratchet:actions/download-artifact@v5
337-
with:
338-
name: 'coverage-reports-${{ matrix.node-version }}-${{ matrix.os }}'
339-
path: 'coverage_artifact' # Download to a specific directory
340-
341-
- name: 'Post Coverage Comment using Composite Action'
342-
uses: './.github/actions/post-coverage-comment' # Path to the composite action directory
343-
with:
344-
cli_json_file: 'coverage_artifact/cli/coverage/coverage-summary.json'
345-
core_json_file: 'coverage_artifact/core/coverage/coverage-summary.json'
346-
cli_full_text_summary_file: 'coverage_artifact/cli/coverage/full-text-summary.txt'
347-
core_full_text_summary_file: 'coverage_artifact/core/coverage/full-text-summary.txt'
348-
node_version: '${{ matrix.node-version }}'
349-
os: '${{ matrix.os }}'
350-
github_token: '${{ secrets.GITHUB_TOKEN }}'
351-
352320
codeql:
353321
name: 'CodeQL'
354322
runs-on: 'ubuntu-latest'

.github/workflows/trusted-ci.yml

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
name: 'Gemini CLI Trusted CI'
2+
3+
on:
4+
workflow_run:
5+
workflows: ['Gemini CLI CI']
6+
types:
7+
- 'completed'
8+
branches:
9+
- 'main'
10+
- 'release'
11+
12+
jobs:
13+
test:
14+
environment: 'trusted-ci-env'
15+
name: 'Test'
16+
runs-on: '${{ matrix.os }}'
17+
if: |-
18+
${{ github.event.workflow_run.conclusion == 'success' &&
19+
github.event.workflow_run.event == 'pull_request' }}
20+
permissions:
21+
contents: 'read'
22+
checks: 'write'
23+
pull-requests: 'write'
24+
strategy:
25+
fail-fast: false # So we can see all test failures
26+
matrix:
27+
os:
28+
- 'macos-latest'
29+
- 'ubuntu-latest'
30+
- 'windows-latest'
31+
node-version:
32+
- '20.x'
33+
- '22.x'
34+
- '24.x'
35+
steps:
36+
- name: 'Checkout base branch'
37+
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
38+
with:
39+
ref: '${{ github.event.workflow_run.base_branch }}' # Checkout base branch
40+
41+
- name: 'Download Build Artifacts'
42+
uses: 'actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0' # ratchet:actions/download-artifact@v5
43+
with:
44+
name: 'build-artifacts'
45+
path: '.' # Download to current directory
46+
47+
- name: 'Set up Node.js ${{ matrix.node-version }}'
48+
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
49+
with:
50+
node-version: '${{ matrix.node-version }}'
51+
cache: 'npm'
52+
53+
- name: 'Fail if no access to secrets'
54+
if: '${{ secrets.GEMINI_API_KEY == '' }}' # Only run if secret is NOT available
55+
run: |
56+
echo "Error: Integration tests cannot be skipped due to missing GEMINI_API_KEY secret."
57+
echo "This workflow requires GEMINI_API_KEY for integration tests."
58+
exit 1 # This will cause the step and thus the job to fail
59+
60+
- name: 'Run integration tests'
61+
if: '${{ secrets.GEMINI_API_KEY == '' }}' # Only run if secret is available
62+
env:
63+
NO_COLOR: true
64+
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}' # Access secret here
65+
run: 'npm run test:integration:all'
66+
67+
- name: 'Publish Test Report'
68+
if: |-
69+
${{ always() }}
70+
uses: 'dorny/test-reporter@dc3a92680fcc15842eef52e8c4606ea7ce6bd3f3' # ratchet:dorny/test-reporter@v2
71+
with:
72+
name: 'Test Results (Node ${{ matrix.node-version }})'
73+
path: 'packages/*/junit.xml'
74+
reporter: 'java-junit'
75+
fail-on-error: 'false'
76+
77+
- name: 'Upload coverage reports'
78+
if: |-
79+
${{ always() }}
80+
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
81+
with:
82+
name: 'coverage-reports-${{ matrix.node-version }}-${{ matrix.os }}'
83+
path: 'packages/*/coverage'
84+
85+
post_coverage_comment:
86+
name: 'Post Coverage Comment'
87+
runs-on: 'ubuntu-latest'
88+
needs: 'test'
89+
if: |-
90+
${{ always() && github.event.workflow_run.conclusion == 'success' &&
91+
github.event.workflow_run.event == 'pull_request' }}
92+
continue-on-error: true
93+
permissions:
94+
contents: 'read' # For checkout
95+
pull-requests: 'write' # For commenting
96+
strategy:
97+
matrix:
98+
# Reduce noise by only posting the comment once
99+
os:
100+
- 'ubuntu-latest'
101+
node-version:
102+
- '22.x'
103+
steps:
104+
- name: 'Checkout base branch'
105+
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
106+
with:
107+
ref: '${{ github.event.workflow_run.base_branch }}' # Checkout base branch
108+
109+
- name: 'Download coverage reports artifact'
110+
uses: 'actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0' # ratchet:actions/download-artifact@v5
111+
with:
112+
name: 'coverage-reports-${{ matrix.node-version }}-${{ matrix.os }}'
113+
path: 'coverage_artifact' # Download to a specific directory
114+
115+
- name: 'Post Coverage Comment using Composite Action'
116+
uses: './.github/actions/post-coverage-comment' # Path to the composite action directory
117+
with:
118+
cli_json_file: 'coverage_artifact/cli/coverage/coverage-summary.json'
119+
core_json_file: 'coverage_artifact/core/coverage/coverage-summary.json'
120+
cli_full_text_summary_file: 'coverage_artifact/cli/coverage/full-text-summary.txt'
121+
core_full_text_summary_file: 'coverage_artifact/core/coverage/full-text-summary.txt'
122+
node_version: '${{ matrix.node-version }}'
123+
os: '${{ matrix.os }}'
124+
github_token: '${{ secrets.GITHUB_TOKEN }}'

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"build:sandbox": "node scripts/build_sandbox.js --skip-npm-install-build",
3232
"bundle": "npm run generate && node esbuild.config.js && node scripts/copy_bundle_assets.js",
3333
"test": "npm run test --workspaces --if-present",
34+
"test:unit": "vitest run --exclude 'integration-tests/**'",
3435
"test:ci": "npm run test:ci --workspaces --if-present && npm run test:scripts",
3536
"test:scripts": "vitest run --config ./scripts/tests/vitest.config.ts",
3637
"test:e2e": "cross-env VERBOSE=true KEEP_OUTPUT=true npm run test:integration:sandbox:none",

0 commit comments

Comments
 (0)