opencode-skills-collection 3.0.31 → 3.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +5 -1
- package/bundled-skills/container-security-hardening/SKILL.md +988 -0
- package/bundled-skills/container-security-hardening/references/base-image-comparison.md +245 -0
- package/bundled-skills/container-security-hardening/references/kubernetes-pod-security.md +561 -0
- package/bundled-skills/container-security-hardening/references/seccomp-profile-template.json +337 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/github-actions-advanced/SKILL.md +1075 -0
- package/bundled-skills/longbridge/SKILL.md +91 -0
- package/bundled-skills/runaway-guard/SKILL.md +331 -0
- package/package.json +1 -1
- package/skills_index.json +131 -27
|
@@ -0,0 +1,1075 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: github-actions-advanced
|
|
3
|
+
description: >
|
|
4
|
+
Design, debug, and harden GitHub Actions CI/CD workflows, including reusable
|
|
5
|
+
workflows, matrix builds, self-hosted runners, OIDC authentication, caching,
|
|
6
|
+
environments, secrets, and release automation.
|
|
7
|
+
category: devops
|
|
8
|
+
risk: safe
|
|
9
|
+
source: community
|
|
10
|
+
date_added: "2026-05-30"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# GitHub Actions Advanced Skill
|
|
14
|
+
|
|
15
|
+
Expert guidance for designing, writing, debugging, and securing **production-grade** GitHub Actions workflows.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## When to Use This Skill
|
|
20
|
+
|
|
21
|
+
- User mentions GitHub Actions, `.github/workflows`, CI/CD pipelines, runners, jobs, steps, or actions
|
|
22
|
+
- User wants to automate builds, tests, deployments, or releases via GitHub
|
|
23
|
+
- User asks about matrix builds, reusable workflows, composite actions, or self-hosted runners
|
|
24
|
+
- User needs help with OIDC authentication, caching strategies, or secrets management
|
|
25
|
+
- User says "my GitHub pipeline is failing" or "set up CI for my repo"
|
|
26
|
+
- User asks about workflow security, hardening, or environment protection rules
|
|
27
|
+
|
|
28
|
+
## When NOT to Use This Skill
|
|
29
|
+
|
|
30
|
+
- The user is working with GitLab CI/CD → recommend `gitlab-ci-patterns`
|
|
31
|
+
- The user is working with CircleCI, Jenkins, or other CI platforms
|
|
32
|
+
- The task is purely about Docker image building without GitHub context → recommend `docker-expert`
|
|
33
|
+
- The task is about Kubernetes deployment configuration → recommend `kubernetes-architect`
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Step 1: Understand Context Before Responding
|
|
38
|
+
|
|
39
|
+
When invoked, first gather context:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Discover existing workflows in the repo
|
|
43
|
+
find .github/workflows -name "*.yml" -o -name "*.yaml" 2>/dev/null | head -20
|
|
44
|
+
|
|
45
|
+
# Check for composite actions
|
|
46
|
+
find .github/actions -name "action.yml" 2>/dev/null
|
|
47
|
+
|
|
48
|
+
# Detect tech stack (influences runner OS, language setup actions)
|
|
49
|
+
ls package.json requirements.txt Gemfile go.mod Cargo.toml pom.xml 2>/dev/null
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Then adapt recommendations to:
|
|
53
|
+
- Existing workflow patterns in the repo
|
|
54
|
+
- The tech stack and language runtime
|
|
55
|
+
- Whether this is a monorepo or single-project repo
|
|
56
|
+
- Whether self-hosted or GitHub-hosted runners are in use
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Workflow Structure Reference
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
name: Workflow Name
|
|
64
|
+
|
|
65
|
+
on: # Triggers (see Triggers section)
|
|
66
|
+
push:
|
|
67
|
+
branches: [main]
|
|
68
|
+
|
|
69
|
+
permissions: # Always declare — principle of least privilege
|
|
70
|
+
contents: read
|
|
71
|
+
|
|
72
|
+
env: # Workflow-level env vars
|
|
73
|
+
NODE_VERSION: '20'
|
|
74
|
+
|
|
75
|
+
concurrency: # Prevent duplicate runs
|
|
76
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
77
|
+
cancel-in-progress: true # Cancel older runs for same branch
|
|
78
|
+
|
|
79
|
+
jobs:
|
|
80
|
+
job-id:
|
|
81
|
+
name: Human-readable name
|
|
82
|
+
runs-on: ubuntu-24.04 # Pin OS version — never use -latest in prod
|
|
83
|
+
timeout-minutes: 15 # Always set — prevents runaway jobs
|
|
84
|
+
environment: production # Links to GitHub Environment (approvals/secrets)
|
|
85
|
+
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
88
|
+
- name: Step name
|
|
89
|
+
run: echo "hello"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Triggers (`on:`)
|
|
95
|
+
|
|
96
|
+
### Common Patterns
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
on:
|
|
100
|
+
push:
|
|
101
|
+
branches: [main, 'release/**']
|
|
102
|
+
paths-ignore: ['**.md', 'docs/**'] # Skip docs-only changes
|
|
103
|
+
|
|
104
|
+
pull_request:
|
|
105
|
+
types: [opened, synchronize, reopened]
|
|
106
|
+
branches: [main]
|
|
107
|
+
|
|
108
|
+
workflow_dispatch: # Manual trigger with inputs
|
|
109
|
+
inputs:
|
|
110
|
+
environment:
|
|
111
|
+
description: 'Deploy target'
|
|
112
|
+
required: true
|
|
113
|
+
type: choice
|
|
114
|
+
options: [staging, production]
|
|
115
|
+
dry-run:
|
|
116
|
+
description: 'Dry run only?'
|
|
117
|
+
type: boolean
|
|
118
|
+
default: false
|
|
119
|
+
|
|
120
|
+
schedule:
|
|
121
|
+
- cron: '0 2 * * 1' # Monday 2am UTC
|
|
122
|
+
|
|
123
|
+
workflow_call: # Called by other workflows (reusable)
|
|
124
|
+
inputs:
|
|
125
|
+
image-tag:
|
|
126
|
+
type: string
|
|
127
|
+
required: true
|
|
128
|
+
secrets:
|
|
129
|
+
deploy-token:
|
|
130
|
+
required: true
|
|
131
|
+
|
|
132
|
+
release:
|
|
133
|
+
types: [published] # Trigger only on published releases
|
|
134
|
+
|
|
135
|
+
pull_request_target: # Runs with repo secrets — use with care!
|
|
136
|
+
types: [labeled] # Gate with label + author_association check
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
> **Security Warning:** `pull_request_target` runs with repo secrets. Only use after a maintainer labels the PR. Never check out fork code without explicit sandboxing.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Reusable Workflows
|
|
144
|
+
|
|
145
|
+
Split large pipelines into composable units stored in `.github/workflows/`.
|
|
146
|
+
|
|
147
|
+
**Convention:** Prefix internal/reusable workflows with `_` (e.g., `_build.yml`).
|
|
148
|
+
|
|
149
|
+
### Caller (`.github/workflows/deploy.yml`)
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
jobs:
|
|
153
|
+
call-build:
|
|
154
|
+
uses: ./.github/workflows/_build.yml # Same-repo reusable
|
|
155
|
+
# uses: org/repo/.github/workflows/build.yml@main # Cross-repo
|
|
156
|
+
with:
|
|
157
|
+
image-tag: ${{ github.sha }}
|
|
158
|
+
secrets: inherit # Pass all caller secrets down
|
|
159
|
+
|
|
160
|
+
call-test:
|
|
161
|
+
uses: ./.github/workflows/_test.yml
|
|
162
|
+
with:
|
|
163
|
+
node-version: '20'
|
|
164
|
+
secrets:
|
|
165
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # Explicit secret passing
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Reusable Workflow (`.github/workflows/_build.yml`)
|
|
169
|
+
|
|
170
|
+
```yaml
|
|
171
|
+
on:
|
|
172
|
+
workflow_call:
|
|
173
|
+
inputs:
|
|
174
|
+
image-tag:
|
|
175
|
+
type: string
|
|
176
|
+
required: true
|
|
177
|
+
push:
|
|
178
|
+
type: boolean
|
|
179
|
+
default: false
|
|
180
|
+
secrets:
|
|
181
|
+
registry-token:
|
|
182
|
+
required: false
|
|
183
|
+
outputs:
|
|
184
|
+
digest:
|
|
185
|
+
description: "Image digest"
|
|
186
|
+
value: ${{ jobs.build.outputs.digest }}
|
|
187
|
+
|
|
188
|
+
jobs:
|
|
189
|
+
build:
|
|
190
|
+
runs-on: ubuntu-24.04
|
|
191
|
+
timeout-minutes: 20
|
|
192
|
+
outputs:
|
|
193
|
+
digest: ${{ steps.build.outputs.digest }}
|
|
194
|
+
steps:
|
|
195
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
196
|
+
- id: build
|
|
197
|
+
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
|
|
198
|
+
with:
|
|
199
|
+
push: ${{ inputs.push }}
|
|
200
|
+
tags: myapp:${{ inputs.image-tag }}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Matrix Builds
|
|
206
|
+
|
|
207
|
+
```yaml
|
|
208
|
+
jobs:
|
|
209
|
+
test:
|
|
210
|
+
strategy:
|
|
211
|
+
fail-fast: false # Don't cancel others if one fails
|
|
212
|
+
max-parallel: 4 # Limit concurrent runners
|
|
213
|
+
matrix:
|
|
214
|
+
os: [ubuntu-24.04, windows-2022, macos-14]
|
|
215
|
+
node: ['18', '20', '22']
|
|
216
|
+
exclude:
|
|
217
|
+
- os: windows-2022
|
|
218
|
+
node: '18'
|
|
219
|
+
include:
|
|
220
|
+
- os: ubuntu-24.04
|
|
221
|
+
node: '22'
|
|
222
|
+
experimental: true # Custom matrix variable
|
|
223
|
+
|
|
224
|
+
runs-on: ${{ matrix.os }}
|
|
225
|
+
timeout-minutes: 20
|
|
226
|
+
|
|
227
|
+
steps:
|
|
228
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
229
|
+
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
|
230
|
+
with:
|
|
231
|
+
node-version: ${{ matrix.node }}
|
|
232
|
+
cache: 'npm'
|
|
233
|
+
- run: npm ci
|
|
234
|
+
- run: npm test
|
|
235
|
+
continue-on-error: ${{ matrix.experimental == true }}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Dynamic Matrix via Script
|
|
239
|
+
|
|
240
|
+
```yaml
|
|
241
|
+
jobs:
|
|
242
|
+
generate-matrix:
|
|
243
|
+
runs-on: ubuntu-24.04
|
|
244
|
+
outputs:
|
|
245
|
+
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
|
246
|
+
steps:
|
|
247
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
248
|
+
- id: set-matrix
|
|
249
|
+
run: |
|
|
250
|
+
SERVICES=$(ls services/ | jq -R -s -c 'split("\n")[:-1]')
|
|
251
|
+
echo "matrix={\"service\":$SERVICES}" >> $GITHUB_OUTPUT
|
|
252
|
+
|
|
253
|
+
build:
|
|
254
|
+
needs: generate-matrix
|
|
255
|
+
strategy:
|
|
256
|
+
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
|
257
|
+
runs-on: ubuntu-24.04
|
|
258
|
+
steps:
|
|
259
|
+
- run: echo "Building ${{ matrix.service }}"
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Caching Strategies
|
|
265
|
+
|
|
266
|
+
### Language Setup Actions (Preferred — No Extra Step Needed)
|
|
267
|
+
|
|
268
|
+
```yaml
|
|
269
|
+
# Node.js
|
|
270
|
+
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
|
271
|
+
with:
|
|
272
|
+
node-version: '20'
|
|
273
|
+
cache: 'npm' # or 'yarn' or 'pnpm'
|
|
274
|
+
|
|
275
|
+
# Python
|
|
276
|
+
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
|
277
|
+
with:
|
|
278
|
+
python-version: '3.12'
|
|
279
|
+
cache: 'pip'
|
|
280
|
+
|
|
281
|
+
# Go
|
|
282
|
+
- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
|
|
283
|
+
with:
|
|
284
|
+
go-version: '1.23'
|
|
285
|
+
cache: true
|
|
286
|
+
|
|
287
|
+
# Java / Gradle / Maven
|
|
288
|
+
- uses: actions/setup-java@7a6d8a8234af8eb26422e24052f73b12b0e46a27 # v4.6.0
|
|
289
|
+
with:
|
|
290
|
+
distribution: 'temurin'
|
|
291
|
+
java-version: '21'
|
|
292
|
+
cache: 'maven' # or 'gradle'
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Manual Cache (Any Tool)
|
|
296
|
+
|
|
297
|
+
```yaml
|
|
298
|
+
- uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
|
|
299
|
+
id: cache-deps
|
|
300
|
+
with:
|
|
301
|
+
path: |
|
|
302
|
+
~/.cache/pip
|
|
303
|
+
.venv
|
|
304
|
+
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
|
|
305
|
+
restore-keys: |
|
|
306
|
+
${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
|
|
307
|
+
${{ runner.os }}-pip-
|
|
308
|
+
|
|
309
|
+
- name: Install deps (only on cache miss)
|
|
310
|
+
if: steps.cache-deps.outputs.cache-hit != 'true'
|
|
311
|
+
run: pip install -r requirements.txt
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Docker Layer Caching
|
|
315
|
+
|
|
316
|
+
```yaml
|
|
317
|
+
- uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
|
|
318
|
+
with:
|
|
319
|
+
cache-from: type=gha
|
|
320
|
+
cache-to: type=gha,mode=max
|
|
321
|
+
# For registry-backed cache (cross-branch):
|
|
322
|
+
# cache-from: type=registry,ref=ghcr.io/myorg/myapp:buildcache
|
|
323
|
+
# cache-to: type=registry,ref=ghcr.io/myorg/myapp:buildcache,mode=max
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## OIDC Authentication (Keyless Cloud Auth)
|
|
329
|
+
|
|
330
|
+
**Never store long-lived cloud credentials as secrets.** Use OIDC to get short-lived tokens that expire automatically.
|
|
331
|
+
|
|
332
|
+
### AWS
|
|
333
|
+
|
|
334
|
+
```yaml
|
|
335
|
+
permissions:
|
|
336
|
+
id-token: write
|
|
337
|
+
contents: read
|
|
338
|
+
|
|
339
|
+
steps:
|
|
340
|
+
- uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
|
|
341
|
+
with:
|
|
342
|
+
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
|
|
343
|
+
aws-region: us-east-1
|
|
344
|
+
role-session-name: GitHubActions-${{ github.run_id }}
|
|
345
|
+
|
|
346
|
+
# Trust policy on the IAM role must include:
|
|
347
|
+
# "token.actions.githubusercontent.com" as OIDC provider
|
|
348
|
+
# Condition: "repo:org/repo:ref:refs/heads/main" (restrict to branch)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### GCP (Workload Identity Federation)
|
|
352
|
+
|
|
353
|
+
```yaml
|
|
354
|
+
permissions:
|
|
355
|
+
id-token: write
|
|
356
|
+
contents: read
|
|
357
|
+
|
|
358
|
+
steps:
|
|
359
|
+
- uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7
|
|
360
|
+
with:
|
|
361
|
+
workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider
|
|
362
|
+
service_account: github-actions@my-project.iam.gserviceaccount.com
|
|
363
|
+
token_format: access_token # or 'id_token'
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Azure (Federated Identity)
|
|
367
|
+
|
|
368
|
+
```yaml
|
|
369
|
+
permissions:
|
|
370
|
+
id-token: write
|
|
371
|
+
contents: read
|
|
372
|
+
|
|
373
|
+
steps:
|
|
374
|
+
- uses: azure/login@a65d910e8af852a8061c627c456678983e180302 # v2.2.0
|
|
375
|
+
with:
|
|
376
|
+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
|
377
|
+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
|
378
|
+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
|
379
|
+
# No client secret needed! Uses OIDC federated credentials
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Environments & Deployment Protection
|
|
385
|
+
|
|
386
|
+
```yaml
|
|
387
|
+
jobs:
|
|
388
|
+
deploy-staging:
|
|
389
|
+
environment:
|
|
390
|
+
name: staging
|
|
391
|
+
url: https://staging.myapp.com
|
|
392
|
+
runs-on: ubuntu-24.04
|
|
393
|
+
timeout-minutes: 30
|
|
394
|
+
steps:
|
|
395
|
+
- run: ./scripts/deploy.sh staging
|
|
396
|
+
|
|
397
|
+
deploy-production:
|
|
398
|
+
needs: deploy-staging
|
|
399
|
+
environment:
|
|
400
|
+
name: production
|
|
401
|
+
url: https://myapp.com # Shown in the GitHub UI deployment panel
|
|
402
|
+
runs-on: ubuntu-24.04
|
|
403
|
+
timeout-minutes: 30
|
|
404
|
+
steps:
|
|
405
|
+
- run: ./scripts/deploy.sh production
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Configure in Settings → Environments:**
|
|
409
|
+
- **Required reviewers** — manual approval gate before run
|
|
410
|
+
- **Wait timer** — delay after approval (e.g., 10-minute buffer)
|
|
411
|
+
- **Branch/tag restrictions** — only `main` or `v*` tags can deploy to prod
|
|
412
|
+
- **Environment-specific secrets** — override repo-level secrets per environment
|
|
413
|
+
- **Deployment branches** — whitelist which branches can target this environment
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Secrets Management
|
|
418
|
+
|
|
419
|
+
```yaml
|
|
420
|
+
# Access repo/org/environment secrets
|
|
421
|
+
env:
|
|
422
|
+
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
|
423
|
+
|
|
424
|
+
# Auto-provided token — no setup needed
|
|
425
|
+
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
|
426
|
+
with:
|
|
427
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
428
|
+
|
|
429
|
+
# Hierarchy (most specific wins):
|
|
430
|
+
# environment secret > repo secret > org secret
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Masking Dynamic Values
|
|
434
|
+
|
|
435
|
+
```yaml
|
|
436
|
+
- name: Generate and mask dynamic token
|
|
437
|
+
run: |
|
|
438
|
+
TOKEN=$(./scripts/generate-token.sh)
|
|
439
|
+
echo "::add-mask::$TOKEN" # Mask in all subsequent logs
|
|
440
|
+
echo "DEPLOY_TOKEN=$TOKEN" >> $GITHUB_ENV
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Secrets in Composite Actions
|
|
444
|
+
|
|
445
|
+
```yaml
|
|
446
|
+
# Secrets cannot be passed as inputs to composite actions
|
|
447
|
+
# Pass them as env vars instead:
|
|
448
|
+
- uses: ./.github/actions/my-action
|
|
449
|
+
env:
|
|
450
|
+
SECRET_VALUE: ${{ secrets.MY_SECRET }}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Composite Actions
|
|
456
|
+
|
|
457
|
+
Package reusable step sequences into local actions. No container spin-up, no separate workflow file needed.
|
|
458
|
+
|
|
459
|
+
### Action Definition (`.github/actions/setup-app/action.yml`)
|
|
460
|
+
|
|
461
|
+
```yaml
|
|
462
|
+
name: Setup App
|
|
463
|
+
description: Install and configure application dependencies
|
|
464
|
+
|
|
465
|
+
inputs:
|
|
466
|
+
node-version:
|
|
467
|
+
description: 'Node.js version'
|
|
468
|
+
required: false
|
|
469
|
+
default: '20'
|
|
470
|
+
install-flags:
|
|
471
|
+
description: 'Additional npm install flags'
|
|
472
|
+
required: false
|
|
473
|
+
default: ''
|
|
474
|
+
|
|
475
|
+
outputs:
|
|
476
|
+
cache-hit:
|
|
477
|
+
description: 'Whether the dependency cache was hit'
|
|
478
|
+
value: ${{ steps.cache.outputs.cache-hit }}
|
|
479
|
+
|
|
480
|
+
runs:
|
|
481
|
+
using: composite
|
|
482
|
+
steps:
|
|
483
|
+
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
|
484
|
+
with:
|
|
485
|
+
node-version: ${{ inputs.node-version }}
|
|
486
|
+
cache: npm
|
|
487
|
+
|
|
488
|
+
- id: cache
|
|
489
|
+
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
|
|
490
|
+
with:
|
|
491
|
+
path: node_modules
|
|
492
|
+
key: ${{ runner.os }}-node-${{ inputs.node-version }}-${{ hashFiles('package-lock.json') }}
|
|
493
|
+
|
|
494
|
+
- name: Install dependencies
|
|
495
|
+
if: steps.cache.outputs.cache-hit != 'true'
|
|
496
|
+
shell: bash
|
|
497
|
+
run: npm ci ${{ inputs.install-flags }}
|
|
498
|
+
|
|
499
|
+
- name: Build
|
|
500
|
+
shell: bash
|
|
501
|
+
run: npm run build
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Usage in a Workflow
|
|
505
|
+
|
|
506
|
+
```yaml
|
|
507
|
+
steps:
|
|
508
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
509
|
+
- uses: ./.github/actions/setup-app
|
|
510
|
+
with:
|
|
511
|
+
node-version: '22'
|
|
512
|
+
install-flags: '--ignore-scripts'
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Self-Hosted Runners
|
|
518
|
+
|
|
519
|
+
```yaml
|
|
520
|
+
jobs:
|
|
521
|
+
build-gpu:
|
|
522
|
+
runs-on: [self-hosted, linux, x64, gpu] # Label matching
|
|
523
|
+
timeout-minutes: 60
|
|
524
|
+
|
|
525
|
+
build-arm:
|
|
526
|
+
runs-on: [self-hosted, linux, arm64]
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Runner Best Practices
|
|
530
|
+
|
|
531
|
+
| Practice | Details |
|
|
532
|
+
|---|---|
|
|
533
|
+
| **Ephemeral runners** | Use Actions Runner Controller (ARC) on Kubernetes for fresh runners per job |
|
|
534
|
+
| **Isolation** | Never share prod runners with untrusted/fork PR workflows |
|
|
535
|
+
| **Cleanup hooks** | Set `ACTIONS_RUNNER_HOOK_JOB_COMPLETED` to reset environment |
|
|
536
|
+
| **Runner groups** | Use groups to restrict which repos/workflows can access which runners |
|
|
537
|
+
| **Labels** | Use custom labels (e.g., `gpu`, `high-memory`) for precise targeting |
|
|
538
|
+
| **Security** | Disable fork PR access to self-hosted runners in Settings |
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
# Actions Runner Controller (Kubernetes) — recommended for ephemeral runners
|
|
542
|
+
helm install arc \
|
|
543
|
+
--namespace arc-systems \
|
|
544
|
+
--create-namespace \
|
|
545
|
+
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## Conditional Execution & Flow Control
|
|
551
|
+
|
|
552
|
+
```yaml
|
|
553
|
+
# Condition on branch + event
|
|
554
|
+
- run: ./scripts/deploy.sh
|
|
555
|
+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
556
|
+
|
|
557
|
+
# Continue on error (non-blocking steps)
|
|
558
|
+
- run: ./scripts/lint.sh
|
|
559
|
+
continue-on-error: true
|
|
560
|
+
|
|
561
|
+
# Job dependency and conditional execution
|
|
562
|
+
jobs:
|
|
563
|
+
test:
|
|
564
|
+
runs-on: ubuntu-24.04
|
|
565
|
+
outputs:
|
|
566
|
+
result: ${{ steps.run-tests.outcome }}
|
|
567
|
+
|
|
568
|
+
deploy:
|
|
569
|
+
needs: [test, build]
|
|
570
|
+
if: |
|
|
571
|
+
needs.test.result == 'success' &&
|
|
572
|
+
needs.build.result == 'success' &&
|
|
573
|
+
github.ref == 'refs/heads/main'
|
|
574
|
+
runs-on: ubuntu-24.04
|
|
575
|
+
|
|
576
|
+
notify-failure:
|
|
577
|
+
needs: [test, deploy]
|
|
578
|
+
if: failure() # Runs even if earlier jobs fail
|
|
579
|
+
runs-on: ubuntu-24.04
|
|
580
|
+
steps:
|
|
581
|
+
- run: ./scripts/notify-slack.sh "Pipeline failed!"
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Passing Data Between Jobs
|
|
585
|
+
|
|
586
|
+
```yaml
|
|
587
|
+
jobs:
|
|
588
|
+
prepare:
|
|
589
|
+
runs-on: ubuntu-24.04
|
|
590
|
+
outputs:
|
|
591
|
+
version: ${{ steps.get-version.outputs.version }}
|
|
592
|
+
should-deploy: ${{ steps.check.outputs.deploy }}
|
|
593
|
+
|
|
594
|
+
steps:
|
|
595
|
+
- id: get-version
|
|
596
|
+
run: |
|
|
597
|
+
VERSION=$(cat VERSION)
|
|
598
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
599
|
+
|
|
600
|
+
- id: check
|
|
601
|
+
run: |
|
|
602
|
+
if git log -1 --pretty=%B | grep -q '\[deploy\]'; then
|
|
603
|
+
echo "deploy=true" >> $GITHUB_OUTPUT
|
|
604
|
+
else
|
|
605
|
+
echo "deploy=false" >> $GITHUB_OUTPUT
|
|
606
|
+
fi
|
|
607
|
+
|
|
608
|
+
build:
|
|
609
|
+
needs: prepare
|
|
610
|
+
if: needs.prepare.outputs.should-deploy == 'true'
|
|
611
|
+
runs-on: ubuntu-24.04
|
|
612
|
+
steps:
|
|
613
|
+
- run: echo "Building version ${{ needs.prepare.outputs.version }}"
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
## Security Hardening
|
|
619
|
+
|
|
620
|
+
### 1. Always Declare Permissions (Least Privilege)
|
|
621
|
+
|
|
622
|
+
```yaml
|
|
623
|
+
# Workflow-level default — restrict everything
|
|
624
|
+
permissions:
|
|
625
|
+
contents: read
|
|
626
|
+
|
|
627
|
+
jobs:
|
|
628
|
+
publish:
|
|
629
|
+
# Job-level override — only expand what's needed
|
|
630
|
+
permissions:
|
|
631
|
+
contents: write # Only for release/publish jobs
|
|
632
|
+
packages: write # Only for container push jobs
|
|
633
|
+
pull-requests: write # Only for PR comment jobs
|
|
634
|
+
id-token: write # Only for OIDC auth jobs
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### 2. Pin Third-Party Actions to Full Commit SHA
|
|
638
|
+
|
|
639
|
+
```yaml
|
|
640
|
+
# ❌ UNSAFE — tag can be mutated or hijacked
|
|
641
|
+
- uses: actions/checkout@v4
|
|
642
|
+
|
|
643
|
+
# ✅ SAFE — commit SHA is immutable
|
|
644
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
645
|
+
|
|
646
|
+
# Tool to automate SHA pinning:
|
|
647
|
+
# npx pin-github-action .github/workflows/*.yml
|
|
648
|
+
# or: pip install ratchet && ratchet pin .github/workflows/
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### 3. Prevent Script Injection
|
|
652
|
+
|
|
653
|
+
```yaml
|
|
654
|
+
# ❌ UNSAFE — attacker controls PR title, which gets expanded in shell
|
|
655
|
+
- run: echo "${{ github.event.pull_request.title }}"
|
|
656
|
+
|
|
657
|
+
# ✅ SAFE — pass through environment variable (shell doesn't evaluate it)
|
|
658
|
+
- env:
|
|
659
|
+
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
660
|
+
run: echo "$PR_TITLE"
|
|
661
|
+
|
|
662
|
+
# ✅ SAFE — expressions in if: conditions are evaluated by Actions, not shell
|
|
663
|
+
- if: github.event.pull_request.draft == false
|
|
664
|
+
run: echo "Not a draft"
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### 4. Restrict `pull_request_target` Usage
|
|
668
|
+
|
|
669
|
+
```yaml
|
|
670
|
+
# Only run when a maintainer adds a specific label — prevents untrusted execution
|
|
671
|
+
on:
|
|
672
|
+
pull_request_target:
|
|
673
|
+
types: [labeled]
|
|
674
|
+
|
|
675
|
+
jobs:
|
|
676
|
+
validate:
|
|
677
|
+
# Double-guard: check label name AND author_association
|
|
678
|
+
if: |
|
|
679
|
+
github.event.label.name == 'safe-to-test' &&
|
|
680
|
+
(github.event.pull_request.author_association == 'COLLABORATOR' ||
|
|
681
|
+
github.event.pull_request.author_association == 'MEMBER' ||
|
|
682
|
+
github.event.pull_request.author_association == 'OWNER')
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### 5. Harden with StepSecurity
|
|
686
|
+
|
|
687
|
+
```yaml
|
|
688
|
+
# Add to every workflow — hardens runner, monitors outbound traffic
|
|
689
|
+
- uses: step-security/harden-runner@4d991eb9995541a0b71d1b66f1f98a5f1bef422c # v2.11.0
|
|
690
|
+
with:
|
|
691
|
+
egress-policy: audit # Start with 'audit', move to 'block' after confirming allowlist
|
|
692
|
+
allowed-endpoints: >
|
|
693
|
+
api.github.com:443
|
|
694
|
+
registry.npmjs.org:443
|
|
695
|
+
objects.githubusercontent.com:443
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
## Debugging Techniques
|
|
701
|
+
|
|
702
|
+
```yaml
|
|
703
|
+
# Enable runner diagnostic logging via repo secrets:
|
|
704
|
+
# ACTIONS_RUNNER_DEBUG = true
|
|
705
|
+
# ACTIONS_STEP_DEBUG = true
|
|
706
|
+
|
|
707
|
+
# Dump full GitHub context for inspection
|
|
708
|
+
- name: Debug — dump github context
|
|
709
|
+
if: runner.debug == '1'
|
|
710
|
+
env:
|
|
711
|
+
GITHUB_CONTEXT: ${{ toJson(github) }}
|
|
712
|
+
run: echo "$GITHUB_CONTEXT" | jq '.'
|
|
713
|
+
|
|
714
|
+
# Dump all available contexts
|
|
715
|
+
- name: Debug — dump all contexts
|
|
716
|
+
if: runner.debug == '1'
|
|
717
|
+
run: |
|
|
718
|
+
echo "github: ${{ toJson(github) }}"
|
|
719
|
+
echo "env: ${{ toJson(env) }}"
|
|
720
|
+
echo "vars: ${{ toJson(vars) }}"
|
|
721
|
+
echo "runner: ${{ toJson(runner) }}"
|
|
722
|
+
|
|
723
|
+
# SSH into a failing runner for interactive debugging
|
|
724
|
+
- uses: mxschmitt/action-tmate@7b04f3521e6b0a9fc56fa8f9f50da4bcfb5fc7b5 # v3.19.0
|
|
725
|
+
if: failure() && runner.debug == '1'
|
|
726
|
+
with:
|
|
727
|
+
limit-access-to-actor: true # Only the workflow triggerer can SSH in
|
|
728
|
+
timeout-minutes: 30
|
|
729
|
+
|
|
730
|
+
# Check what's pre-installed on GitHub-hosted runners
|
|
731
|
+
- run: |
|
|
732
|
+
echo "=== Tool Versions ==="
|
|
733
|
+
node --version
|
|
734
|
+
python3 --version
|
|
735
|
+
go version
|
|
736
|
+
docker --version
|
|
737
|
+
echo "=== Disk Space ==="
|
|
738
|
+
df -h
|
|
739
|
+
echo "=== Memory ==="
|
|
740
|
+
free -h
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
---
|
|
744
|
+
|
|
745
|
+
## Complete Pipeline Patterns
|
|
746
|
+
|
|
747
|
+
### Pattern 1: Build → Test → Push → Deploy
|
|
748
|
+
|
|
749
|
+
```yaml
|
|
750
|
+
name: CI/CD Pipeline
|
|
751
|
+
|
|
752
|
+
on:
|
|
753
|
+
push:
|
|
754
|
+
branches: [main]
|
|
755
|
+
pull_request:
|
|
756
|
+
branches: [main]
|
|
757
|
+
|
|
758
|
+
concurrency:
|
|
759
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
760
|
+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
761
|
+
|
|
762
|
+
permissions:
|
|
763
|
+
contents: read
|
|
764
|
+
|
|
765
|
+
jobs:
|
|
766
|
+
# ── Build & Test ──────────────────────────────────────
|
|
767
|
+
build-test:
|
|
768
|
+
runs-on: ubuntu-24.04
|
|
769
|
+
timeout-minutes: 20
|
|
770
|
+
permissions:
|
|
771
|
+
contents: read
|
|
772
|
+
checks: write # For test result reporting
|
|
773
|
+
|
|
774
|
+
steps:
|
|
775
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
776
|
+
|
|
777
|
+
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
|
778
|
+
with:
|
|
779
|
+
node-version: '20'
|
|
780
|
+
cache: 'npm'
|
|
781
|
+
|
|
782
|
+
- run: npm ci
|
|
783
|
+
- run: npm run lint
|
|
784
|
+
- run: npm run test -- --coverage
|
|
785
|
+
- run: npm run build
|
|
786
|
+
|
|
787
|
+
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
|
788
|
+
with:
|
|
789
|
+
name: build-artifacts
|
|
790
|
+
path: dist/
|
|
791
|
+
retention-days: 7
|
|
792
|
+
|
|
793
|
+
# ── Push Image (main branch only) ─────────────────────
|
|
794
|
+
push-image:
|
|
795
|
+
needs: build-test
|
|
796
|
+
if: github.ref == 'refs/heads/main'
|
|
797
|
+
runs-on: ubuntu-24.04
|
|
798
|
+
timeout-minutes: 20
|
|
799
|
+
permissions:
|
|
800
|
+
contents: read
|
|
801
|
+
packages: write
|
|
802
|
+
id-token: write # For OIDC
|
|
803
|
+
outputs:
|
|
804
|
+
image-digest: ${{ steps.push.outputs.digest }}
|
|
805
|
+
|
|
806
|
+
steps:
|
|
807
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
808
|
+
|
|
809
|
+
- uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
|
|
810
|
+
|
|
811
|
+
- uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
|
812
|
+
with:
|
|
813
|
+
registry: ghcr.io
|
|
814
|
+
username: ${{ github.actor }}
|
|
815
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
816
|
+
|
|
817
|
+
- uses: docker/metadata-action@70b2cdc6480c1a8b86edf1777157f8f437de2166 # v5.5.1
|
|
818
|
+
id: meta
|
|
819
|
+
with:
|
|
820
|
+
images: ghcr.io/${{ github.repository }}
|
|
821
|
+
tags: |
|
|
822
|
+
type=sha,format=long
|
|
823
|
+
type=raw,value=latest
|
|
824
|
+
|
|
825
|
+
- id: push
|
|
826
|
+
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
|
|
827
|
+
with:
|
|
828
|
+
context: .
|
|
829
|
+
push: true
|
|
830
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
831
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
832
|
+
cache-from: type=gha
|
|
833
|
+
cache-to: type=gha,mode=max
|
|
834
|
+
provenance: true # SLSA provenance attestation
|
|
835
|
+
sbom: true # Software Bill of Materials
|
|
836
|
+
|
|
837
|
+
# ── Deploy Staging ────────────────────────────────────
|
|
838
|
+
deploy-staging:
|
|
839
|
+
needs: push-image
|
|
840
|
+
runs-on: ubuntu-24.04
|
|
841
|
+
timeout-minutes: 30
|
|
842
|
+
environment:
|
|
843
|
+
name: staging
|
|
844
|
+
url: https://staging.myapp.com
|
|
845
|
+
steps:
|
|
846
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
847
|
+
- run: ./scripts/deploy.sh staging ${{ needs.push-image.outputs.image-digest }}
|
|
848
|
+
|
|
849
|
+
# ── Deploy Production (manual approval required) ──────
|
|
850
|
+
deploy-production:
|
|
851
|
+
needs: deploy-staging
|
|
852
|
+
runs-on: ubuntu-24.04
|
|
853
|
+
timeout-minutes: 30
|
|
854
|
+
environment:
|
|
855
|
+
name: production
|
|
856
|
+
url: https://myapp.com
|
|
857
|
+
steps:
|
|
858
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
859
|
+
- run: ./scripts/deploy.sh production ${{ needs.push-image.outputs.image-digest }}
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
### Pattern 2: Automated Release with Changelog
|
|
863
|
+
|
|
864
|
+
```yaml
|
|
865
|
+
name: Release
|
|
866
|
+
|
|
867
|
+
on:
|
|
868
|
+
push:
|
|
869
|
+
tags: ['v[0-9]+.[0-9]+.[0-9]+']
|
|
870
|
+
|
|
871
|
+
permissions:
|
|
872
|
+
contents: write
|
|
873
|
+
|
|
874
|
+
jobs:
|
|
875
|
+
release:
|
|
876
|
+
runs-on: ubuntu-24.04
|
|
877
|
+
timeout-minutes: 15
|
|
878
|
+
|
|
879
|
+
steps:
|
|
880
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
881
|
+
with:
|
|
882
|
+
fetch-depth: 0 # Full history needed for changelog generation
|
|
883
|
+
|
|
884
|
+
- uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8 # v2.0.9
|
|
885
|
+
with:
|
|
886
|
+
generate_release_notes: true # Auto-generates from PR titles and commits
|
|
887
|
+
make_latest: true
|
|
888
|
+
fail_on_unmatched_files: true
|
|
889
|
+
files: |
|
|
890
|
+
dist/**/*.tar.gz
|
|
891
|
+
dist/**/*.zip
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
### Pattern 3: Dependency Auto-Update with PR
|
|
895
|
+
|
|
896
|
+
```yaml
|
|
897
|
+
name: Dependency Updates
|
|
898
|
+
|
|
899
|
+
on:
|
|
900
|
+
schedule:
|
|
901
|
+
- cron: '0 9 * * 1' # Every Monday at 9am UTC
|
|
902
|
+
workflow_dispatch:
|
|
903
|
+
|
|
904
|
+
permissions:
|
|
905
|
+
contents: write
|
|
906
|
+
pull-requests: write
|
|
907
|
+
|
|
908
|
+
jobs:
|
|
909
|
+
update-deps:
|
|
910
|
+
runs-on: ubuntu-24.04
|
|
911
|
+
timeout-minutes: 20
|
|
912
|
+
steps:
|
|
913
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
914
|
+
|
|
915
|
+
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
|
916
|
+
with:
|
|
917
|
+
node-version: '20'
|
|
918
|
+
|
|
919
|
+
- run: npx npm-check-updates -u
|
|
920
|
+
- run: npm install
|
|
921
|
+
|
|
922
|
+
- uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
|
923
|
+
with:
|
|
924
|
+
commit-message: 'chore: update npm dependencies'
|
|
925
|
+
title: 'chore: update npm dependencies'
|
|
926
|
+
branch: 'chore/npm-updates'
|
|
927
|
+
delete-branch: true
|
|
928
|
+
body: |
|
|
929
|
+
Automated dependency updates generated by the dependency update workflow.
|
|
930
|
+
Please review and test before merging.
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
### Pattern 4: Security Scanning Pipeline
|
|
934
|
+
|
|
935
|
+
```yaml
|
|
936
|
+
name: Security Scan
|
|
937
|
+
|
|
938
|
+
on:
|
|
939
|
+
push:
|
|
940
|
+
branches: [main]
|
|
941
|
+
pull_request:
|
|
942
|
+
branches: [main]
|
|
943
|
+
schedule:
|
|
944
|
+
- cron: '0 6 * * *' # Daily at 6am UTC
|
|
945
|
+
|
|
946
|
+
permissions:
|
|
947
|
+
contents: read
|
|
948
|
+
security-events: write # For uploading SARIF results
|
|
949
|
+
|
|
950
|
+
jobs:
|
|
951
|
+
codeql:
|
|
952
|
+
runs-on: ubuntu-24.04
|
|
953
|
+
timeout-minutes: 30
|
|
954
|
+
permissions:
|
|
955
|
+
security-events: write
|
|
956
|
+
actions: read
|
|
957
|
+
contents: read
|
|
958
|
+
steps:
|
|
959
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
960
|
+
- uses: github/codeql-action/init@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1
|
|
961
|
+
with:
|
|
962
|
+
languages: javascript-typescript
|
|
963
|
+
- uses: github/codeql-action/autobuild@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1
|
|
964
|
+
- uses: github/codeql-action/analyze@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1
|
|
965
|
+
|
|
966
|
+
container-scan:
|
|
967
|
+
runs-on: ubuntu-24.04
|
|
968
|
+
timeout-minutes: 15
|
|
969
|
+
steps:
|
|
970
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
971
|
+
- uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8 # v0.28.0
|
|
972
|
+
with:
|
|
973
|
+
scan-type: 'fs'
|
|
974
|
+
format: 'sarif'
|
|
975
|
+
output: 'trivy-results.sarif'
|
|
976
|
+
severity: 'CRITICAL,HIGH'
|
|
977
|
+
- uses: github/codeql-action/upload-sarif@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1
|
|
978
|
+
with:
|
|
979
|
+
sarif_file: 'trivy-results.sarif'
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
---
|
|
983
|
+
|
|
984
|
+
## Common Pitfalls & Fixes
|
|
985
|
+
|
|
986
|
+
| Problem | Cause | Fix |
|
|
987
|
+
|---|---|---|
|
|
988
|
+
| Workflow doesn't trigger on PR from fork | Fork PRs use restricted `GITHUB_TOKEN` | Use `pull_request` not `pull_request_target`; avoid repo secrets in fork context |
|
|
989
|
+
| Secret is `***` in logs but exposed | Dynamic value not masked | Use `echo "::add-mask::$VALUE"` before using it |
|
|
990
|
+
| Cache never hits across branches | Cache key too specific | Add `restore-keys` fallback without branch or hash segment |
|
|
991
|
+
| Matrix job fails silently | `fail-fast: true` (default) cancels siblings | Set `fail-fast: false` during debugging |
|
|
992
|
+
| Job hangs indefinitely | No `timeout-minutes` set | Always set `timeout-minutes` on every job |
|
|
993
|
+
| `$GITHUB_OUTPUT` not set | Old `set-output` command used | Use `echo "key=value" >> $GITHUB_OUTPUT` |
|
|
994
|
+
| OIDC token request fails | Missing `id-token: write` permission | Add to job-level `permissions` block |
|
|
995
|
+
| Reusable workflow can't access caller secrets | No `secrets: inherit` | Add `secrets: inherit` or explicitly pass secrets |
|
|
996
|
+
|
|
997
|
+
---
|
|
998
|
+
|
|
999
|
+
## GitHub Actions Expressions Reference
|
|
1000
|
+
|
|
1001
|
+
```yaml
|
|
1002
|
+
# Context objects available in expressions
|
|
1003
|
+
${{ github.sha }} # Commit SHA
|
|
1004
|
+
${{ github.ref }} # Branch/tag ref
|
|
1005
|
+
${{ github.ref_name }} # Short branch/tag name
|
|
1006
|
+
${{ github.event_name }} # Event name (push, pull_request, etc.)
|
|
1007
|
+
${{ github.actor }} # Username who triggered the run
|
|
1008
|
+
${{ github.repository }} # org/repo
|
|
1009
|
+
${{ github.run_id }} # Unique run ID
|
|
1010
|
+
${{ runner.os }} # Linux, Windows, macOS
|
|
1011
|
+
|
|
1012
|
+
# Built-in functions
|
|
1013
|
+
${{ toJson(github) }} # Serialize context to JSON
|
|
1014
|
+
${{ fromJson(needs.job.outputs.matrix) }} # Parse JSON string
|
|
1015
|
+
${{ hashFiles('**/package-lock.json') }} # Hash file(s) for cache keys
|
|
1016
|
+
${{ format('{0}/{1}', var1, var2) }} # String formatting
|
|
1017
|
+
${{ join(matrix.items, ',') }} # Join array
|
|
1018
|
+
|
|
1019
|
+
# Status functions (use in if: conditions)
|
|
1020
|
+
${{ success() }} # All previous steps succeeded
|
|
1021
|
+
${{ failure() }} # Any previous step failed
|
|
1022
|
+
${{ cancelled() }} # Workflow was cancelled
|
|
1023
|
+
${{ always() }} # Always runs (success OR failure OR cancelled)
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
---
|
|
1027
|
+
|
|
1028
|
+
## Production Readiness Checklist
|
|
1029
|
+
|
|
1030
|
+
Before merging any workflow to `main`, verify:
|
|
1031
|
+
|
|
1032
|
+
### Security
|
|
1033
|
+
- [ ] All third-party actions pinned to full commit SHA
|
|
1034
|
+
- [ ] `permissions:` declared at workflow and job level (least privilege)
|
|
1035
|
+
- [ ] No `${{ }}` expressions directly in `run:` blocks (use env vars)
|
|
1036
|
+
- [ ] OIDC used for cloud credentials (no long-lived secrets stored)
|
|
1037
|
+
- [ ] `pull_request_target` gated with label check + author_association guard
|
|
1038
|
+
- [ ] Secrets never echoed or logged
|
|
1039
|
+
|
|
1040
|
+
### Reliability
|
|
1041
|
+
- [ ] `timeout-minutes` set on every job
|
|
1042
|
+
- [ ] `fail-fast: false` set for matrix builds used for debugging
|
|
1043
|
+
- [ ] `concurrency` configured to cancel stale runs
|
|
1044
|
+
- [ ] Retry logic for flaky external calls
|
|
1045
|
+
- [ ] Artifact retention policy set appropriately
|
|
1046
|
+
|
|
1047
|
+
### Performance
|
|
1048
|
+
- [ ] Dependency caching configured (setup-* cache or actions/cache)
|
|
1049
|
+
- [ ] Docker layer caching enabled (`type=gha`)
|
|
1050
|
+
- [ ] Path filters on `push`/`pull_request` to skip unrelated changes
|
|
1051
|
+
- [ ] Matrix parallelism appropriate (not exhausting runner pool)
|
|
1052
|
+
|
|
1053
|
+
### Maintainability
|
|
1054
|
+
- [ ] Reusable workflows used for repeated patterns
|
|
1055
|
+
- [ ] Composite actions used for repeated step sequences
|
|
1056
|
+
- [ ] Workflow names and step names are human-readable
|
|
1057
|
+
- [ ] `_` prefix on internal/reusable workflow files
|
|
1058
|
+
- [ ] Environment protection rules configured for `production`
|
|
1059
|
+
|
|
1060
|
+
---
|
|
1061
|
+
|
|
1062
|
+
## Related Skills
|
|
1063
|
+
|
|
1064
|
+
- `gha-security-review` — Deep security audit of existing workflow files
|
|
1065
|
+
- `github-actions-templates` — Copy-paste ready workflow templates
|
|
1066
|
+
- `docker-expert` — Container build optimization and Dockerfile best practices
|
|
1067
|
+
- `kubernetes-architect` — Deploying to Kubernetes from GitHub Actions
|
|
1068
|
+
- `gitlab-ci-patterns` — GitLab CI/CD equivalent patterns
|
|
1069
|
+
|
|
1070
|
+
## Limitations
|
|
1071
|
+
|
|
1072
|
+
- Use this skill only when the task clearly matches the scope described above.
|
|
1073
|
+
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
|
|
1074
|
+
- Always test reusable workflows in a feature branch before merging to main.
|
|
1075
|
+
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
|