StarSling
Skills

ci-secure

Scan your GitHub Actions workflows for the ten critical CI/CD attack vectors, each with a plain-English attacker scenario and a fix

View Markdown

ci-secure is a free, open-source, MIT-licensed agent skill that runs on your machine, scans a repository's GitHub Actions workflows for the ten critical CI/CD attack vectors, and offers your coding agent a fix for every finding. It works independently of StarSling Runners and does not require the StarSling GitHub App.

See the ci-secure overview for a quick introduction before following the operational guide below.

ci-secure is deliberately not a comprehensive security audit. It checks critical exploit-chain vectors only — complete outsider-to-compromise paths with real incidents behind them. A clean ci-secure report means those ten paths are closed, not that your CI is secure. The selection criteria and the rejection record document what was left out and why.

ci-secure scans for attack vectors. To grade your workflow configuration against CI best practices, use ci-score; to find what actually makes your CI slow, use ci-speedup. All three are local, on-demand skills; StarSling optimization PRs are a separate paid, hosted service.

Before you start

You need:

  • A full local checkout of the repository you want to scan, with a .github/workflows/ directory. The skill stops early if the repo has no GitHub Actions workflows.
  • Python 3.9 or newer and PyYAML — the scanner's only third-party dependency.
  • Node.js and npm to install or invoke the skill with npx.

The GitHub CLI is optional but recommended. Four checks need it: the impostor-SHA vector, which cannot be answered from YAML alone, the dormancy note on findings, and two repository settings read over the API. Without gh, the scan still runs — the impostor-SHA check reports as skipped, and the two API-gated settings report as unmeasured coverage gaps rather than as passes.

Install and run

Install the skill with Vercel's Skills CLI:

npx skills add starslingdev/skills

The CLI lets you select the coding agent and whether to install the skill for the current project or globally. It supports a long list of coding agents, including Claude Code, Codex, Cursor, and OpenCode.

In a repository, invoke the installed skill from your coding agent:

/ci-secure

To use it without installing, ask your coding agent to run this command and follow the generated instructions:

npx skills use "https://github.com/starslingdev/skills" --skill "ci-secure"

What it scans for

Each of the ten vectors is a complete path from outside your organization to compromise, and each has a documented real-world incident behind it. Every finding renders with a "what an attacker could do" scenario rather than a rule name, so you can judge the risk without reading the detector.

  • Template injection in run: blocks — attacker-controlled text is interpolated into a shell script before the shell ever sees it, so a crafted branch name or issue title executes as code on the runner.
  • Fork code executed with privileges — a workflow on an untrusted trigger checks out and runs the attacker's head ref while holding secrets and a write token, the classic "pwn request".
  • Cache poisoning from pull_request_target — a job with fork code writes the shared cache, and the poisoned entry is then restored by trusted builds on your default branch.
  • Impostor or unreachable action SHAs — an action is pinned to a commit that is not reachable from the action's own repository, which looks like the safest possible pin while resolving to code its maintainers never published.
  • Whole-context secret dumpstoJSON(secrets), toJSON(github), or toJSON(env) written into logs or an artifact, exposing every secret at once.
  • $GITHUB_ENV and $GITHUB_PATH hijack — attacker-influenced text is appended to those files, letting an attacker set environment variables or prepend a directory to PATH for every later step.
  • Write tokens on untrusted triggerspull-requests: write and similar grants handed to a workflow anyone can trigger from a fork.
  • Credentials in caches and artifacts — a cache or artifact path: that sweeps in known credential files such as .npmrc, .git/config, or a cloud credentials directory.
  • Unverified remote code executioncurl | bash, and fetch-and-run against a mutable ref, where whoever controls the remote host or branch controls what runs in your job.
  • Dependency install scripts in privileged jobs — lifecycle scripts from the dependency tree executing in a job that holds secrets, so one compromised transitive package reaches them.

Configuration hygiene checks

Alongside the vector scan, ci-secure reports eight pass/fail configuration facts. These are hygiene, not exploit chains — a failure is a weakened defense rather than a demonstrated path in:

  • permissions: declared — every workflow declares permissions, instead of inheriting a repository default that may be read-write everything.
  • Write grants scoped to jobs — no workflow-level write for any scope except id-token, which is ci-score's check rather than this one, and no permissions: write-all. Write grants belong on the jobs that need them.
  • CODEOWNERS covers workflows — a CODEOWNERS entry covers .github/workflows/, so workflow changes need a specific approval rather than any approval.
  • No fork code checked out on untrusted triggers — bare untrusted triggers pass; checking out the attacker's ref is what fails.
  • No blanket secrets: inherit — reusable workflows are passed secrets by name, so a called workflow's blast radius is visible in the caller.
  • Checkout credentials not persisted — untrusted-trigger workflows set persist-credentials: false, keeping the token out of .git/config where a later step could read it.
  • Required checks cannot be skipped — every required status check is produced by a job that always runs. GitHub counts a skipped required check as a pass, so a check only a conditional job reports can be satisfied by never running it.
  • Fork-PR approval is effective — the repository's approval gate covers more than accounts brand new to GitHub, a setting that otherwise gates nobody real.

Two of these read repository settings over the API, so they report as unmeasured rather than as passes when gh is unavailable. An unmeasured check is a coverage gap, not a clean bill.

How the three skills differ

ci-secureci-scoreci-speedup
QuestionCan someone outside my org compromise my CI?Does my CI config follow best practices?What actually makes my CI slow?
InputWorkflow YAML, plus four checks over the gh APIYour local checkout's workflow YAML and configsReal run history sampled over the gh API
OutputFindings with attacker scenarios, and a fix offer per findingA score plus one ranked fix per failed checkA measured root-cause report of the merge-gating long pole
RequiresA checkout, Python, and PyYAML; gh recommendedA full checkout, Python, and PyYAMLThe above plus an authenticated gh CLI
Zero findings meansThose ten paths are closed, not that CI is secureNothing — the score is the outputNo single dominant long pole was found

All three are free, open source, run on your machine, and send nothing to StarSling.

What it will and will not do

The skill asks which findings you want fixed and dispatches one subagent per finding group. It never commits, pushes, or opens a pull request unasked — by default you review the working-tree diff yourself.

Zero findings is a first-class result, reported as such rather than padded with lower-severity noise.

Resources

On this page