From martinholovsky-claude-skills-generator
Designs secure CI/CD pipelines for desktop app builds with GitHub Actions, focusing on secret management, code signing, artifact security, and supply chain protection.
npx claudepluginhub joshuarweaver/cascade-code-general-misc-2 --plugin martinholovsky-claude-skills-generatorThis skill uses the workspace's default tool permissions.
**CRITICAL**: Before implementing ANY CI/CD pipeline, you MUST read the relevant reference files:
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
CRITICAL: Before implementing ANY CI/CD pipeline, you MUST read the relevant reference files:
| Trigger Condition | Reference File |
|---|---|
| Configuring secrets, code signing, OIDC, supply chain protection | references/security-examples.md |
| Multi-platform builds, caching, release automation | references/advanced-patterns.md |
| Security assessment, defense-in-depth, security gates | references/threat-model.md |
Risk Level: HIGH
Justification: CI/CD pipelines have access to signing keys, deployment credentials, and can modify production artifacts. Compromised pipelines can inject malicious code into releases (supply chain attacks), expose secrets, or deploy unauthorized changes.
You are an expert in CI/CD pipeline security, specializing in:
| Feature | Purpose | Usage |
|---|---|---|
permissions | Restrict GITHUB_TOKEN | Always explicitly set |
environment | Require approvals | For production deploys |
| OIDC | Keyless auth | Cloud provider access |
| Secrets | Encrypted storage | Never log or expose |
- name: Dependency Scanning
uses: github/dependency-review-action@v3
- name: SAST Scanning
uses: github/codeql-action/analyze@v2
- name: Secret Detection
uses: trufflesecurity/trufflehog@main
- name: Container Scanning
uses: aquasecurity/trivy-action@master
name: Secure Build Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
# CRITICAL: Restrict default permissions
permissions:
contents: read
jobs:
security-scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/analyze@v2
- uses: actions/dependency-review-action@v3
if: github.event_name == 'pull_request'
build:
needs: security-scan
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
with:
node-version: '20'
- run: npm run build
๐ See references/advanced-patterns.md for release jobs and environment protection.
jobs:
deploy-staging:
environment: staging
env:
API_KEY: ${{ secrets.STAGING_API_KEY }}
deploy-production:
environment: production
env:
API_KEY: ${{ secrets.PRODUCTION_API_KEY }}
# CORRECT: Use environment variables
- name: Use Secret
env:
API_KEY: ${{ secrets.API_KEY }}
run: curl -H "Authorization: Bearer $API_KEY" https://api.example.com
Never: echo ${{ secrets.API_KEY }} - exposes in logs!
Windows signing core pattern:
- name: Import Certificate
env:
CERTIFICATE_BASE64: ${{ secrets.WINDOWS_CERTIFICATE }}
CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
run: |
$certBytes = [Convert]::FromBase64String($env:CERTIFICATE_BASE64)
$certPath = Join-Path $env:RUNNER_TEMP "certificate.pfx"
[IO.File]::WriteAllBytes($certPath, $certBytes)
$securePassword = ConvertTo-SecureString $env:CERTIFICATE_PASSWORD -AsPlainText -Force
Import-PfxCertificate -FilePath $certPath -CertStoreLocation Cert:\CurrentUser\My -Password $securePassword
Remove-Item $certPath
macOS signing core pattern:
- name: Import Apple Certificates
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
rm certificate.p12
๐ See references/security-examples.md for complete signing workflows and notarization.
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Authenticate to AWS
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
aws-region: us-east-1
# No secrets needed! Uses OIDC token
๐ See references/security-examples.md for GCP and Azure OIDC patterns.
| CVE | Severity | Mitigation |
|---|---|---|
| CVE-2024-23897 | Critical (9.8) | Update Jenkins, restrict CLI |
| CVE-2023-49291 | Critical (9.8) | Pin actions by SHA |
| CVE-2025-30066 | High (8.6) | Audit tj-actions usage |
Key Insight: Supply chain attacks through third-party actions are a major threat. Always pin by SHA and audit action sources.
| Risk | Key Controls |
|---|---|
| Insufficient Flow Control | Required reviews, environment protection |
| Inadequate Identity/Access | OIDC, least privilege, MFA |
| Dependency Chain Abuse | Pin by SHA, scan dependencies |
| Poisoned Pipeline Execution | Protect workflow files, limit triggers |
| Insufficient Credential Hygiene | Rotate secrets, scope narrowly |
# Pin actions by SHA (not tag)
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
# Generate SBOM for transparency
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
artifact-name: sbom.spdx.json
๐ See references/security-examples.md for complete supply chain protection.
# Test workflow changes in PR
on:
pull_request:
paths:
- '.github/workflows/**'
jobs:
validate-workflows:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate YAML
run: |
pip install yamllint
yamllint .github/workflows/
- name: Check for secrets in logs
run: grep -r 'echo.*secrets\.' .github/workflows/ && exit 1 || true
- name: Verify SHA pinning
run: grep -E 'uses:.*@[^a-f0-9]' .github/workflows/ && exit 1 || true
Before creating or modifying a workflow, write tests that validate expected behavior:
# .github/workflows/test-workflows.yml
name: Validate Workflows
on: [push, pull_request]
jobs:
test-workflow-syntax:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install actionlint
run: |
bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
- name: Lint workflows
run: ./actionlint -color
test-security-compliance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check permissions are explicit
run: |
for f in .github/workflows/*.yml; do
if ! grep -q "^permissions:" "$f"; then
echo "FAIL: $f missing explicit permissions"
exit 1
fi
done
- name: Check actions are SHA-pinned
run: |
if grep -rE 'uses:.*@v[0-9]' .github/workflows/; then
echo "FAIL: Found unpinned actions"
exit 1
fi
Create the workflow configuration that satisfies the test requirements:
# .github/workflows/build.yml
name: Build
on: [push]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- run: npm ci && npm run build
Add caching, parallelization, and security enhancements:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
- uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65
with:
node-version: '20'
cache: 'npm'
- run: npm ci && npm run build
# Local validation
actionlint .github/workflows/
yamllint .github/workflows/
# Security checks
grep -rE 'uses:.*@v[0-9]' .github/workflows/ && echo "FAIL: Unpinned actions" || echo "PASS"
grep -r 'echo.*secrets\.' .github/workflows/ && echo "FAIL: Secret exposure" || echo "PASS"
# Push and verify CI passes
git push && gh run watch
Good - Aggressive caching with proper keys:
- uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
~/.cargo/registry
target
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json', '**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-deps-
Bad - No caching or poor cache keys:
# Missing caching - slow builds every time
- run: npm ci
- run: cargo build
Good - Independent jobs run in parallel:
jobs:
lint:
runs-on: ubuntu-latest
steps: [...]
test-unit:
runs-on: ubuntu-latest
steps: [...]
test-e2e:
runs-on: ubuntu-latest
steps: [...]
build:
needs: [lint, test-unit, test-e2e] # Waits for all parallel jobs
runs-on: ubuntu-latest
Bad - Sequential jobs that could be parallel:
jobs:
lint:
runs-on: ubuntu-latest
test-unit:
needs: lint # Unnecessary dependency
test-e2e:
needs: test-unit # Unnecessary dependency
Good - Compress and limit artifact retention:
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
retention-days: 7
compression-level: 9
Bad - Large uncompressed artifacts with long retention:
- uses: actions/upload-artifact@v4
with:
name: everything
path: . # Uploads entire repo
retention-days: 90
Good - Skip unchanged components:
- name: Check for changes
id: changes
uses: dorny/paths-filter@v2
with:
filters: |
frontend:
- 'src/frontend/**'
backend:
- 'src/backend/**'
- name: Build frontend
if: steps.changes.outputs.frontend == 'true'
run: npm run build
- name: Build backend
if: steps.changes.outputs.backend == 'true'
run: cargo build --release
Bad - Always rebuild everything:
- run: npm run build
- run: cargo build --release
# Runs even when no changes to those components
Good - Run expensive jobs only when needed:
on:
push:
branches: [main]
paths:
- 'src/**'
- 'Cargo.toml'
- 'package.json'
jobs:
expensive-test:
if: contains(github.event.head_commit.message, '[full-test]') || github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
Bad - Run everything on every push:
on: [push] # Triggers on every branch, every commit
jobs:
full-e2e-suite: # Expensive job runs unnecessarily
runs-on: ubuntu-latest
# WRONG
permissions: write-all
# CORRECT
permissions:
contents: read
# WRONG: Tag/branch can be moved
- uses: actions/checkout@v4
- uses: actions/checkout@main
# CORRECT: SHA is immutable
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
# WRONG: Secret in command line
- run: curl -u user:${{ secrets.TOKEN }} https://api.example.com
# CORRECT: Secret in environment variable
- env:
TOKEN: ${{ secrets.TOKEN }}
run: curl -u "user:$TOKEN" https://api.example.com
# DANGEROUS: Runs with write access on untrusted code
on:
pull_request_target:
jobs:
build:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # Untrusted!
- run: npm install # Can execute malicious scripts
๐ See references/threat-model.md for safe patterns and trust boundaries.
references/threat-model.md for security considerationscontents: readactionlint on all workflowsyamllint for syntax validationecho.*secrets patterns@v* patterns)act if possibleYour goal is to create CI/CD pipelines that are:
CI/CD pipelines are high-value targets because they have access to signing keys and credentials, can modify production artifacts, and run automatically on code changes.
Security Reminder: ALWAYS pin actions by SHA. ALWAYS use least privilege permissions. ALWAYS protect secrets from exposure. When in doubt, consult references/threat-model.md for attack scenarios.