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
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/skillsThe 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-secureTo 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 dumps —
toJSON(secrets),toJSON(github), ortoJSON(env)written into logs or an artifact, exposing every secret at once. $GITHUB_ENVand$GITHUB_PATHhijack — attacker-influenced text is appended to those files, letting an attacker set environment variables or prepend a directory toPATHfor every later step.- Write tokens on untrusted triggers —
pull-requests: writeand 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 execution —
curl | 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 declarespermissions, instead of inheriting a repository default that may be read-write everything.- Write grants scoped to jobs — no workflow-level
writefor any scope exceptid-token, which is ci-score's check rather than this one, and nopermissions: 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/configwhere 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-secure | ci-score | ci-speedup | |
|---|---|---|---|
| Question | Can someone outside my org compromise my CI? | Does my CI config follow best practices? | What actually makes my CI slow? |
| Input | Workflow YAML, plus four checks over the gh API | Your local checkout's workflow YAML and configs | Real run history sampled over the gh API |
| Output | Findings with attacker scenarios, and a fix offer per finding | A score plus one ranked fix per failed check | A measured root-cause report of the merge-gating long pole |
| Requires | A checkout, Python, and PyYAML; gh recommended | A full checkout, Python, and PyYAML | The above plus an authenticated gh CLI |
| Zero findings means | Those ten paths are closed, not that CI is secure | Nothing — the score is the output | No 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
See ci-secure
Watch the skill scan a GitHub Actions setup for attack vectors.
Skill source
Read the open-source skill and its implementation.
Launch article
Learn why StarSling built ci-secure.
Attack vector catalog
Read each vector in full, with its detection logic and reference incidents.
Why these ten
See the selection criteria and what was deliberately left out.