civet
Scans GitHub Actions workflow files for security issues and returns evidence-backed findings, rule explanations, and demo scans through the MCP tools.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@civetscan my GitHub Actions workflows and give me the risk score"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
🐱 civet
A security auditor for GitHub Actions workflows that runs anywhere Python runs.
36 deterministic rules · zero dependencies · evidence-first · SARIF for CI · MCP server
Why another workflow scanner
zizmor (6.7k★, Rust) and actionlint (4.3k★, Go) are excellent. They are also
compiled binaries you have to download, and actionlint does not do security
audits while zizmor does not do general linting.
civet is the third shape:
zizmor | actionlint | civet | |
Language | Rust binary | Go binary | pure Python, 0 deps |
Install | download a release | download a release |
|
Works offline / air-gapped | needs a binary | needs a binary | yes — stdlib only |
Parses YAML with |
| custom | its own reader, so it can keep line numbers |
Evidence (file:line + source) | yes | yes | yes, plus the raw line under every finding |
Explains the attack | yes | partly |
|
SARIF 2.1.0 | yes | yes | yes |
MCP server | no | no | 4 tools |
The zero-dependency part is not austerity — it is the point. A scanner you cannot run on the slim container that is your CI is a scanner you will not run.
Related MCP server: actions-guard-mcp
Install
pip install civet-audit
# or, without installing anything:
uvx --from civet-audit civet .Three steps
# 1. see it work, on a workflow that is broken on purpose
civet demo
# 2. scan your repository
civet scan .
# 3. understand the worst finding
civet explain CIV201Requires Python ≥ 3.10. Nothing else. No API keys, no network calls, no LLM in the loop — a scan is a pure function of the bytes on disk, so two runs on the same tree always produce the same report.
What the output looks like
Real output from civet demo (trimmed to high and above):
scanning ./civet-demo · 2 workflow file(s) · 36 rules
.github/workflows/civet-demo.yml
line 13 HIGH CIV205 Self-hosted runner reachable from a fork-triggered event
job: build
Job 'build' runs on a self-hosted runner under ['issue_comment', 'pull_request_target'].
› runs-on: self-hosted
line 15 CRITICAL CIV201 pwn request: pull_request_target + checkout of PR head
job: build / step: actions/checkout@v4
pull_request_target checks out PR head code while holding a privileged token.
› - uses: actions/checkout@v4
line 20 CRITICAL CIV101 Untrusted event data interpolated into run:
job: build / step: Greet the contributor
run: interpolates attacker-controlled `${{ github.event.pull_request.title }}`.
› - name: Greet the contributor
line 23 HIGH CIV501 Remote script piped into a shell
job: build / step: Install toolchain
Remote content is piped straight into an interpreter.
› - name: Install toolchain
52 findings · critical 3 high 10 medium 22 low 13
Risk score: 100/100 → CRITICAL RISKEvery line carries: the line number, the job and step it belongs to, the
reason in concrete terms, and the raw source line (›) so you can check
the claim yourself instead of trusting it.
explain — the part that teaches
$ civet explain CIV201
CIV201 CRITICAL [trigger]
pwn request: pull_request_target + checkout of PR head
WHY
pull_request_target runs with the base repo's token, and checking out
github.event.pull_request.head.sha executes the fork's code in that context.
Any attacker PR gets a write-scoped token and access to secrets.
FIX
Use pull_request, or split into two workflows: an unprivileged build that
uploads an artifact and a privileged workflow_run that consumes it.
CWE-269Rule map
Group | Rules | What it covers |
| 5 |
|
| 6 | pwn request, |
| 6 | SHA pinning, branch pins, |
| 5 |
|
| 5 |
|
| 3 | cache poisoning, artifact from privileged run, retention |
| 6 |
|
civet rules # all 36 with severity
civet rules | grep injection # one groupCI gate
- name: Audit workflows
run: |
pip install civet-audit
civet scan . --fail-on high --min-severity mediumExit codes: 0 clean, 1 findings at or above --fail-on, 2 usage error.
Default is --fail-on high.
Upload to code scanning:
- run: civet scan . --format sarif -o civet.sarif --fail-on none
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: civet.sarifTuning, for when a finding is an accepted risk:
civet scan . --ignore-rule CIV301 --ignore-group hygieneMCP server
pip install "civet-audit[mcp]"
civet-mcpClaude Code / Cursor config:
{
"mcpServers": {
"civet": {
"command": "uvx",
"args": ["--from", "civet-audit[mcp]", "civet-mcp"]
}
}
}Tool | What it does |
| scan a path; returns counts, risk score, findings with evidence |
| the attack narrative and fix for one rule |
| list rules, optionally by group |
| write and scan a broken workflow — one-call proof of value |
Design rules
Deterministic. No model, no network, no clock. Same bytes → same report.
Evidence first.
file:line, the job, the step, and the raw source line.Zero dependencies in core.
mcplives in an extra; the CLI never imports it.No silent failures. Unparseable YAML is reported in
errors, not swallowed.Say why. Every rule carries an attack narrative and a minimal fix.
Dogfood
civet audits the workflows of its author's other repositories. On first run it
found 19 findings across vehsec (2 workflows), 19 in pcaplens, 22 in
skillvet and 13 in agent-scan — including a real curl … | sh in a publish
pipeline. They are being fixed, not suppressed.
Roadmap
--fixfor mechanical remediations (SHA pinning,persist-credentials)actions/*version→SHA resolution with a local cacheGitLab CI and Jenkinsfile readers behind the same rule engine
baseline file (
.civet-baseline.json) for incremental adoptionenvironmentprotection checks against the repository API
Contributing
git clone https://github.com/xuange-hu/civet && cd civet
pip install -e ".[mcp]"
PYTHONPATH=src pytest -q # 163 testsAdding a rule means touching three places: rules.py (id, severity, why, fix),
analyze.py (the check), and demo.py (a line that proves it fires). A test in
tests/test_rules.py asserts the id; test_demo.py asserts the demo still
covers 100% of the registry.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Audit GitHub Actions workflows for script injection, unpinned actions and missing permissions.
Scan any public GitHub MCP-server repo for security issues. 37 MCP-specific L1 rules, 8 languages.
GitHub Actions workflow security audit - 21 checks: pinning, permissions, secrets, injection.
Security scanner for n8n workflows + live MCP Trust-Check. 18 rules, OWASP mapped. Paid x402 API.
Related MCP Servers
- AlicenseAqualityCmaintenanceLocal-only GitHub Actions and CI maintenance scanner for AI-built apps. Exposes scan, explanation, and fix-planning tools to MCP clients; modifies nothing and makes no outbound requests by default.316 npm2MIT
- AlicenseAqualityCmaintenanceScans GitHub Actions workflow files for dangerous triggers, template injection, unpinned actions, excessive permissions, and secrets in shell commands before they are committed, exposing the checks as MCP tools for agents.2MIT
- AlicenseAqualityCmaintenanceEnables managing GitHub Actions workflows through MCP, including listing and inspecting workflows, triggering workflow_dispatch and repository_dispatch events, and monitoring or canceling workflow runs.710 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables MCP clients to run deterministic, read-only local repository audits for risky GitHub workflows, undocumented environment variables, possible credentials, and conflicting dependency configuration, returning structured findings without sending source code to a service.45 npmMIT