- 34 GitHub Actions workflow checks parse
.github/workflows/*.yml(one,forbidden-uses, is driven by your.pipefort.ymlpolicy). - 7 online supply-chain audits verify the integrity of pinned actions (known-vulnerable, impostor-commit, ref/version-mismatch, typosquat, archived-action, stale-action-ref, ref-confusion) — run automatically when a GitHub token is available, forced with
--audit-pins, disabled with--offline. - 17 GitHub repository-configuration checks call the GitHub API for branch protection, default workflow permissions, secret scanning, and Dependabot.
- 11 GitLab CI workflow checks parse
.gitlab-ci.ymland.gitlab-ci/**/*.yml. - 5 GitLab project-configuration checks call the GitLab API for protected branches, merge policy, public-pipeline visibility, and approvals.
GitLab CI workflow checks
The GitLab rule IDs are parallel to the GitHub ones — same OWASP category, different IDs (e.g.cicd-sec-1-gl-mr-target for the merge-request-target
analog of cicd-sec-1-ppe-checkout). Findings tagged -gl- only fire on
.gitlab-ci.yml / .gitlab-ci/*.yml. The two structural rules
best-prac-1-pipe-to-shell and cicd-sec-9-download-without-checksum
share their ID across both platforms.
| Category | GitLab rule ID | Severity | Auto-fix |
|---|---|---|---|
| CICD-SEC-1 | cicd-sec-1-gl-mr-target | HIGH | ✗ |
| CICD-SEC-2 | cicd-sec-2-gl-pat-secret | MEDIUM | ✗ |
| CICD-SEC-3 | cicd-sec-3-gl-unpinned-include | MEDIUM | ✗ |
| CICD-SEC-4 | cicd-sec-4-gl-shell-injection | HIGH | ✗ |
| CICD-SEC-6 | cicd-sec-6-gl-hardcoded-secrets | HIGH | ✗ |
| CICD-SEC-7 | cicd-sec-7-gl-debug-trace | HIGH | ✓ |
| CICD-SEC-8 | cicd-sec-8-gl-trigger-unfiltered | MEDIUM | ✗ |
| CICD-SEC-10 | cicd-sec-10-gl-allow-failure | LOW | ✓ |
| BEST-PRAC-2 | best-prac-2-gl-missing-timeout | LOW | ✓ |
| BEST-PRAC-3 | best-prac-3-gl-self-hosted-tags | LOW | ✗ |
| BEST-PRAC-4 | best-prac-4-gl-missing-resource-group | LOW | ✗ |
GitLab project-settings checks
These read the GitLab project configuration over the API (CLI:--git at a
GitLab host with --gitlab-token; the web app uses its connected GitLab token)
and surface under the Repository-configuration group.
| Category | GitLab rule ID | Severity | Auto-fix |
|---|---|---|---|
| CICD-SEC-1 | cicd-sec-1-gl-bp-missing | HIGH | ✗ |
| CICD-SEC-1 | cicd-sec-1-gl-bp-force-push | HIGH | ✗ |
| CICD-SEC-1 | cicd-sec-1-gl-merge-without-pipeline | MEDIUM | ✓ |
| CICD-SEC-1 | cicd-sec-1-gl-no-approvals | MEDIUM | ✗ |
| CICD-SEC-4 | cicd-sec-4-gl-public-pipelines | MEDIUM | ✓ |
merge-without-pipeline, public-pipelines) are
remediated by the CLI’s --fix-settings-gl flag.
v1 limitations
include:graph traversal is not performed — the scanner inspects only the literal.gitlab-ci.ymland.gitlab-ci/*.ymlcontents, not files referenced from another project or URL.- Approval-rule auditing (
cicd-sec-1-gl-no-approvals) needs the GitLab Premium approvals API; on Free tier it is skipped rather than firing.
Confidence and personas
Every finding carries a confidence (HIGH/MEDIUM/LOW) alongside its
severity: severity says how bad the issue would be, confidence says how sure
the check is that the finding is real. Deterministic checks (a missing
permissions: block either exists or doesn’t) are HIGH; heuristic ones
(typosquat edit distance, secret-name patterns) are MEDIUM. Filter with the
CLI’s --min-confidence flag; the web app badges medium/low-confidence
findings and offers a “High confidence only” toggle.
Rules are also tiered into personas for noise control: regular (the
default — high-signal security checks), pedantic (adds hygiene nits like
missing timeouts and job-level continue-on-error), and auditor (adds
everything, e.g. self-hosted-runner usage). Select with the CLI’s --persona
flag. The web app runs all tiers and relies on rule settings
for per-rule control.
Workflow file checks
| Category | Title | Severity | Auto-fix |
|---|---|---|---|
| CICD-SEC-1 | Dangerous checkout in pull_request_target / workflow_run | HIGH | ✓ |
| CICD-SEC-1 | workflow_run downloads artifacts from the triggering run | HIGH | ✗ |
| CICD-SEC-1 | Checkout persists credentials under a privileged trigger | MEDIUM | ✓ |
| CICD-SEC-1 | Security decision based on a spoofable actor check | MEDIUM | ✗ |
| CICD-SEC-1 | Job/step condition is always true | HIGH | ✓ |
| CICD-SEC-1 | Spoofable contains() membership check | MEDIUM | ✗ |
| CICD-SEC-2 | Long-lived personal access token | MEDIUM | ✗ |
| CICD-SEC-2 | Package published with a long-lived token instead of OIDC | MEDIUM | ✗ |
| CICD-SEC-3 | Unpinned third-party action | MEDIUM | ✓ |
| CICD-SEC-3 | Unpinned container image | MEDIUM | ✗ |
| CICD-SEC-4 | Poisoned Pipeline Execution (shell injection, incl. laundered ${{ env.X }}) | HIGH | ✓ |
| CICD-SEC-4 | Untrusted input written to GITHUB_ENV/GITHUB_PATH | HIGH | ✗ |
| CICD-SEC-4 | Reusable workflow called with secrets: inherit under a privileged trigger | HIGH | ✗ |
| CICD-SEC-4 | Obfuscated expression or run script | MEDIUM | ✗ |
| CICD-SEC-4 | Caching enabled in a publishing workflow | MEDIUM | ✗ |
| CICD-SEC-5 | Missing permissions specification | MEDIUM | ✓ |
| CICD-SEC-5 | Action not permitted by forbidden-uses policy (config-driven) | HIGH | ✗ |
| CICD-SEC-6 | Hardcoded credentials | HIGH | ✓ |
| CICD-SEC-6 | Secret printed to logs or written to step output | HIGH | ✗ |
| CICD-SEC-6 | Secrets over-provisioned to the whole workflow (toJSON(secrets), workflow env) | HIGH | ✗ |
| CICD-SEC-7 | Actions debug logging enabled in workflow | HIGH | ✓ |
| CICD-SEC-8 | repository_dispatch trigger without types: allowlist | MEDIUM | ✗ |
| CICD-SEC-9 | Downloaded artifact has no integrity check | MEDIUM | ✗ |
| CICD-SEC-10 | Job-level continue-on-error suppresses failure visibility | LOW | ✓ |
| BEST-PRAC-1 | Command piped directly to shell (pipe, process-sub, or PowerShell iex) | HIGH | ✗ |
| BEST-PRAC-2 | Job timeout not configured | LOW | ✓ |
| BEST-PRAC-3 | Self-hosted runner usage | LOW | ✗ |
| BEST-PRAC-4 | Deploy/release workflow has no concurrency guard | LOW | ✓ |
| SLSA-BUILD-L2 | Build provenance is not generated | HIGH | ✗ |
| SLSA-BUILD-L2 | Provenance/signing step missing id-token: write | MEDIUM | ✓ |
| SLSA-BUILD-L2 | Permissions block overly broad | HIGH | partial |
| SLSA-BUILD-L2 | Workflow consumes artifacts without verifying provenance | INFO | ✗ |
| SLSA-BUILD-L3 | Provenance generated in-job (not isolated) | MEDIUM | ✗ |
| SLSA-BUILD-L3 | Cache key in pull_request_target from PR-controlled input | HIGH | ✗ |
Online pinned-action audits
These audit the integrity of pinned actions and need network access. They run automatically whenever a GitHub token is supplied —--github-token, $GITHUB_TOKEN, or $GH_TOKEN on the CLI, and the web app’s scan always runs them with its installation token. Without a token the scan stays offline unless you force the pass with --audit-pins (subject to GitHub’s low anonymous rate limit); --offline disables it unconditionally. Typosquat matching is itself offline but ships in the same pass.
| Category | Title | Severity | Auto-fix |
|---|---|---|---|
| CICD-SEC-3 | Known-vulnerable action version (GHSA) | HIGH | ✗ |
| CICD-SEC-3 | Impostor commit pin | HIGH | ✗ |
| CICD-SEC-3 | Pinned SHA does not match its version comment | MEDIUM | ✗ |
| CICD-SEC-3 | Possible typosquatted action | MEDIUM | ✗ |
| CICD-SEC-3 | Action’s upstream repository is archived | HIGH | ✗ |
| CICD-SEC-3 | Pinned SHA is not any released tag | MEDIUM | ✗ |
| CICD-SEC-3 | Action ref exists as both a branch and a tag | HIGH | ✗ |
Repository configuration checks
These read GitHub-side settings and surface under a “Repository configuration” group in the UI (CLI:<repository settings> file label). They need the expanded GitHub App permissions described in GitHub App permissions; the CLI needs --github-token (or $GITHUB_TOKEN, or gh auth token).
Branch protection (CICD-SEC-1)
| Rule | Title | Severity | Auto-fix |
|---|---|---|---|
| BP-MISSING | Default branch has no branch protection rule | HIGH | ✓ |
| BP-FORCE-PUSH | Default branch allows force pushes | HIGH | ✓ |
| BP-DELETION | Default branch can be deleted | HIGH | ✓ |
| BP-NO-REVIEW | Default branch does not require pull request reviews | HIGH | ✗ |
| BP-FEW-REVIEWERS | Default branch requires fewer than 2 approving reviews | MEDIUM | ✗ |
| BP-STALE-REVIEWS | Default branch does not dismiss stale reviews on new commits | MEDIUM | ✗ |
| BP-NO-STATUS-CHECKS | Default branch does not require status checks to pass | MEDIUM | ✗ |
| BP-ADMIN-BYPASS | Admins can bypass branch protection | HIGH | ✓ |
| BP-NO-CODEOWNERS-REVIEW | CODEOWNERS exists but their review is not required | LOW | ✓ |
| BP-NO-SIGNED-COMMITS | Default branch does not require signed commits | LOW | ✓ |
Actions runtime (CICD-SEC-4, CICD-SEC-5)
| Rule | Title | Severity | Auto-fix |
|---|---|---|---|
| WPERM-WRITE | Default GITHUB_TOKEN permissions are read-write | HIGH | ✓ |
| WPERM-PR-APPROVE | GitHub Actions can approve pull requests | HIGH | ✓ |
| ACTIONS-ALL-ALLOWED | All GitHub Actions and reusable workflows are allowed | MEDIUM | ✗ |
Dependency hygiene (CICD-SEC-3)
| Rule | Title | Severity | Auto-fix |
|---|---|---|---|
| DEPENDABOT-ALERTS-OFF | Dependabot alerts are disabled | MEDIUM | ✓ |
| DEPENDABOT-FIXES-OFF | Dependabot security updates are disabled | LOW | ✓ |
Credential hygiene (CICD-SEC-6)
| Rule | Title | Severity | Auto-fix |
|---|---|---|---|
| SECRET-SCANNING-OFF | Secret scanning is disabled | MEDIUM | ✓ |
| SECRET-PUSH-PROTECTION-OFF | Secret-scanning push protection is disabled | HIGH | ✓ |
--fix-settings flag and the web app’s per-finding Fix button — see Auto-fix.
Rulesets
The CLI’s--ruleset flag (and the web app’s ruleset selector) controls which checks contribute to the final list. Filtering is by framework membership, not by category prefix — see SLSA framework overview for a rule-by-rule mapping.
all(default) — every check listed above.owasp— every rule tagged with the OWASP framework.slsa— every rule tagged with any SLSA v1.2 framework (Build or Source).slsa-build-l1/slsa-build-l2/slsa-build-l3— rules for that specific Build level (and only that level — to find every rule a repo needs to satisfy L3, run withslsaand look at the heatmap).slsa-source-l2/slsa-source-l3/slsa-source-l4— rules for that specific Source level. (L1 is “Version Controlled” — trivially satisfied for any GitHub repo.)
Enabling and disabling individual rules
The web app lets you toggle any individual rule on or off without changing the ruleset — per user, with optional per-repository overrides. See Rule settings for the model and the UI. The CLI’s filtering is limited to the coarser--ruleset choice above; multi-tenant
preferences depend on the database and are web-only.
How the checks run
Each workflow check is a function that takes the parsed workflow YAML AST and returns a list of findings. The CLI and the web app both invoke the sameScanBytes(name, content) entrypoint.
Repository-configuration checks are a separate pass: the API client first fetches the relevant GitHub settings (FetchRepositorySettings) and then runs ScanRepositorySettings(context) to produce findings. These findings carry a synthetic file path (<repository settings>) and a zero line/column so consumers can render them apart from per-file findings.
See Auto-fix for which workflow categories the CLI’s --fix flag rewrites. Repository-configuration findings have no auto-fix — they’re flagged for manual remediation via GitHub’s UI.