agentsclimarketplace

Cicd hardening

Skill roodlicht/accans-sec-skills/skills/cicd-hardening

47 Claude skills for security tradecraft across AppSec, Pentest, Blue Team, and GRC. NL/EU regulatory-anchored. Pattern-level discipline.

Install
npx -y skills add roodlicht/accans-sec-skills --skill cicd-hardening

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

CI/CD pipeline hardening for GitHub Actions and GitLab CI — trust-model (pull_request_target vs pull_request), action pinning to SHA, OIDC-based cloud access, permissions minimization, runner isolation, and supply-chain gates (SLSA provenance, signing).

SKILL.md

13.7 KB, as published. Nobody here has run it

CI/CD Hardening

When to use

This skill treats the pipeline itself as the attack surface, not the code flowing through it. CI/CD compromise is a supply-chain incident: whoever owns the pipeline owns the release artifacts.

Triggers on:

  • A question like "review our workflow files for security", "set up OIDC between GitHub and AWS", "why is pull_request_target dangerous", "pin all actions to SHA", "can we hit SLSA-L3".
  • New or changed .github/workflows/*.yml, .gitlab-ci.yml, Jenkinsfile, Azure Pipelines YAML, CircleCI config, reusable workflow definitions, composite actions.
  • An incident or near-miss: a forked PR could use secrets, a third-party action had a compromise, a self-hosted runner was abused.
  • A compliance audit asking for SSDF evidence or SLSA-level attestation.
  • A supply-chain moment where the pipeline is the last link before release (supply-chain skill calls in here for build-provenance setup).

When NOT (handoff)

  • SAST in the pipeline → sast-orchestrator for tool choice and ruleset. This skill only decides that it runs somewhere in the workflow phase.
  • Secret scanning in the pipeline → secrets-scanner. Same split.
  • Dependency scanning and CVE triage → cve-triage and supply-chain. The gate logic is in security-gate.
  • Container build itself (Dockerfile, base image) → container-hardening. We only cover the workflow that builds it here.
  • Kubernetes deployment by a GitOps agent (Argo CD, Flux) → k8s-security.
  • Infrastructure provisioning (Terraform apply from CI) → iac-security for the resources side, here only the creds flow via OIDC.
  • IR for an actually compromised pipeline → ir-runbook.

Approach

Seven phases. Phase 1 (trust model) is the heart — what goes wrong in CI attacks is often a misconception about who is allowed to run what.

1. Trust model and workflow triggers

GitHub Actions — the pull_request_target trap.

pull_request (default) runs workflow code from the PR branch and has no access to the base repo's secrets. Safe for fork PRs.

pull_request_target runs the workflow code from the base branch (so trusted) but with the PR context. Has secrets. Intended for scenarios like "label the PR after a lint check". Extremely dangerous if you execute the PR code (via checkout + test run), because then your base-trusted workflow runs untrusted fork code with secrets on board. This is the attack class GitHub itself calls "poisoned pipeline execution".

Rule: if you use pull_request_target, never check out the PR branch and never execute code from the PR. Do: set labels, post comments, read metadata.

GitLab CI — merge request pipelines.

merge_request_event runs on the source branch with access to variables depending on the protected flag. Protected variables are only available on protected branches — set prod secrets to protected. Cross-project triggers via pipeline trigger tokens are another vector; treat those tokens as secrets.

Protected branches and required checks.

Set main/release branches to protected: no direct push, review-required, status-checks-required, linear history, no force-push. security-gate as a required check. Without branch protection, a compromised developer account can start release pipelines directly.

CODEOWNERS: required review by specific teams for critical paths (.github/workflows/, infra/, deploy/). Without CODEOWNERS enforcement at branch protection, it is advice, not policy.

2. Action pinning and provenance

Third-party Actions are the underestimated supply-chain risk. An owner getting compromised or an action mutating affects everyone using the tag.

  • Pin to SHA, not to tag. uses: actions/checkout@v4 is mutable (the tag can be moved). uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 is immutable. For critical workflows (release, deploy): always SHA-pin.
  • Configure Renovate/Dependabot to update SHA pins with changelog review. Without auto-updates you run on stale, vulnerable Actions.
  • Verified publishers and Actions from trusted orgs (actions/, github/, docker/, aws-actions/, azure/, google-github-actions/). Third-party marketplace Actions with 10 stars and one maintainer are a choice you have to be able to defend.
  • Lint tools: zizmor (Rust, OSS) for GitHub Actions audit — catches pull_request_target misuse, impersonation, expression injection. actionlint for syntax. poutine for cross-platform CI analysis.
  • GitLab equivalent: pin included templates to a fixed version, no @main for include: remote: or shared templates.

Expression injection. ${{ github.event.pull_request.title }} directly in a run: block is command injection: a PR title with " ; rm -rf / # runs on the runner. Fix: pass the value via an env variable (env: TITLE: ${{ github.event.pull_request.title }}, then "$TITLE" in the script). Zizmor catches this.

3. Secrets and creds: OIDC first

Long-lived cloud credentials in CI secrets is the classic bad pattern. OIDC fixes it: the CI runner gets a short-lived, audience-bound token from the identity provider, the cloud side accepts that token and hands out temporary IAM creds.

  • GitHub → AWS: OIDC provider in AWS (token.actions.githubusercontent.com), IAM role with trust policy on repo:<org>/<repo>:ref:refs/heads/main or environment:<env>. aws-actions/configure-aws-credentials@v4 SHA-pinned.
  • GitHub → GCP: Workload Identity Federation, google-github-actions/auth.
  • GitHub → Azure: federated identity credentials on the App Registration.
  • GitLab → cloud: id_tokens feature (JWT per job) with the equivalent OIDC setup on the cloud side.

OIDC trust policies:

  • Scope as narrowly as possible. Trust only specific branches (refs/heads/main), or better: environments (environment:production). Not on repo:<org>/<repo>:*.
  • Audience claim explicit on sts.amazonaws.com or cloud equivalent. Default is wide; you want it narrow.
  • No fallback to static keys. If OIDC for whatever reason does not work, fail the pipeline — no "oh well, let's use the old AWS_SECRET_ACCESS_KEY for now". That key does not exist anymore, if all is well.

Remaining secrets (third-party API keys that do not support OIDC):

  • Environment-scoped in GitHub Actions (environment: production with protected flag and required reviewers). Or GitLab protected variables on a protected branch.
  • Remove after use where possible; some tooling (HashiCorp Vault integration) generates just-in-time creds.
  • Never echo. A bug where a secret accidentally lands in log output: GitHub auto-masks but is not infallible. Disable set -x / --debug in steps that touch secrets.

4. Permissions: minimal per workflow

The default GITHUB_TOKEN in GitHub Actions was historically write-all. Since 2023 the default is read-only in new repos, but old repos do not flip automatically. Set explicitly:

permissions:
  contents: read          # default for most workflows
jobs:
  deploy:
    permissions:
      contents: read
      id-token: write     # for OIDC
      packages: write     # for ghcr push, only in deploy job
    ...

Workflow-level permissions set a ceiling; job-level can refine downward. For jobs that need push rights, scope strictly to that job.

GitLab CI equivalent: CI_JOB_TOKEN permissions via Project → Settings → CI/CD → Token access, with an allowlist of projects your token may use.

5. Runners: isolation and choice

GitHub-hosted runners are ephemeral (fresh VM per job), good default. Cost: free for public repos, paid for private.

Self-hosted runners are persistent unless you re-provision them every run. Risks:

  • Persistent state: a previous run can leave secrets/artifacts behind for the next run, cross-job contamination.
  • Fork-PR exposure: a forked PR running on a self-hosted runner has access to the entire host. Default advice: self-hosted runners only for workflows that cannot be triggered by forked PRs. Private repos or internal-only workflows.
  • Network positioning: a self-hosted runner in a VPC can reach things you do not want it to.

If you must self-host: ephemeral runners (actions-runner-controller on Kubernetes, or --ephemeral flag), non-root service account, runner scope per repo or per org and not cluster-wide, network-egress policy, no mount of docker.sock (gives a breakout).

Scale-set runners with firecracker/KVM isolation (GitLab) or runner groups with labels (GitHub) for further segmentation.

6. Supply-chain gates in the pipeline

The place where supply-chain, secrets-scanner, cve-triage, sast-orchestrator, and security-gate land as CI steps.

  • Per-PR (blocking): /security-gate as a required check, which internally runs secrets-scanner + sast-orchestrator + cve-triage.
  • Per-build (artifact-related): generate SBOM (syft), sign attestation (cosign), SLSA provenance via slsa-framework/slsa-github-generator. See supply-chain phase 2.
  • Per-release (additional): image scan with Trivy/Grype, signing verify, provenance verify. Fail-fast on unsigned or unresolved-critical.
  • OpenSSF Scorecard as a periodic self-assessment of pipeline hygiene: pinned Actions, branch-protection, SAST present, etc. Reports a score plus per-check detail.

Log CI events to SIEM: who ran which workflow, which secrets were touched, which artifacts produced. Without an audit trail, a compromised-CI incident is not reconstructable.

7. Verification-loop

Layer 1: scope (all workflow files covered? self-hosted runners inventoried? OIDC applied everywhere possible?), assumptions ("we pin to SHA" only if you have actually read the uses: lines), gaps (pull_request_target carefully checked? expression-injection checks done?), consistency (permissions scope matches what the job actually does).

Layer 2: Action SHAs and publisher identities are correct, OWASP CICD Top 10 mapping correct, no invented zizmor/actionlint rule IDs, SLSA-level claims supported.

Output

CI/CD hardening review — <repo/pipeline>
Platform: <GitHub Actions | GitLab CI | Jenkins | Azure | ...>

Trust model:
  Triggers used:           <list pull_request/pull_request_target/push/etc>
  pull_request_target risk:<clean | finding with context>
  Protected branches:      <config summary>
  CODEOWNERS active:       <yes/no>

Action pinning:
  % Actions SHA-pinned:    <N/M>
  Third-party from unknown orgs: <list>
  Expression injection:    <clean | findings>

Credentials:
  OIDC to cloud:           <AWS/GCP/Azure, trust scope>
  Long-lived secrets:      <N, which and why still>
  Environment-protected:   <yes/no per critical environment>

Permissions:
  Workflows with default write-all: <N>
  permissions: empty or too broad:  <list>

Runners:
  Self-hosted in use:      <yes/no + scope>
  Ephemeral:               <yes/no>
  Fork-PR access to self-hosted: <blocked | open>

Supply-chain gates:
  SAST in pipeline:        <sast-orchestrator handoff>
  Secret scan:             <secrets-scanner handoff>
  Dep scan:                <cve-triage handoff>
  SBOM + provenance:       <supply-chain handoff>
  Scorecard:               <score + weakest checks>

Findings (severity-sorted, follow security-review format)

Verification-loop: ...

References

Categories

  • appsec

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.