> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pipefort.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rules overview

> Seventy-four checks across CI/CD platforms: GitHub Actions workflows, online action-pin integrity audits, GitHub and GitLab repository configuration, and GitLab CI.

Pipefort runs deterministic checks per scan across several surfaces:

* **34 GitHub Actions workflow checks** parse `.github/workflows/*.yml` (one, `forbidden-uses`, is driven by your [`.pipefort.yml`](/cli/configuration) policy).
* **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.yml` and `.gitlab-ci/**/*.yml`.
* **5 GitLab project-configuration checks** call the GitLab API for protected branches, merge policy, public-pipeline visibility, and approvals.

Workflow checks cover **all ten** [OWASP CI/CD Top 10](https://owasp.org/www-project-top-10-ci-cd-security-risks/) categories (CICD-SEC-1 through CICD-SEC-10) on both platforms.

## 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`](/rules/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`](/rules/cicd-sec-1-gl-bp-missing)                         | HIGH     | ✗        |
| CICD-SEC-1 | [`cicd-sec-1-gl-bp-force-push`](/rules/cicd-sec-1-gl-bp-force-push)                   | HIGH     | ✗        |
| CICD-SEC-1 | [`cicd-sec-1-gl-merge-without-pipeline`](/rules/cicd-sec-1-gl-merge-without-pipeline) | MEDIUM   | ✓        |
| CICD-SEC-1 | [`cicd-sec-1-gl-no-approvals`](/rules/cicd-sec-1-gl-no-approvals)                     | MEDIUM   | ✗        |
| CICD-SEC-4 | [`cicd-sec-4-gl-public-pipelines`](/rules/cicd-sec-4-gl-public-pipelines)             | MEDIUM   | ✓        |

The two auto-fixable settings (`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.yml` and `.gitlab-ci/*.yml` contents, 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.

Rules carry **framework tags** so they can be filtered by the standard they
serve: [OWASP Top 10 CI/CD Security Risks](https://owasp.org/www-project-top-10-ci-cd-security-risks/)
and the [SLSA v1.2 specification](https://slsa.dev/spec/v1.2/) (Build and
Source tracks). A single rule can belong to multiple frameworks.

## 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](/webapp/rule-settings)
for per-rule control.

## Workflow file checks

| Category                                                        | Title                                                                            | Severity | Auto-fix |
| --------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------- | -------- |
| [CICD-SEC-1](/rules/cicd-sec-1)                                 | Dangerous checkout in `pull_request_target` / `workflow_run`                     | HIGH     | ✓        |
| [CICD-SEC-1](/rules/cicd-sec-1-workflow-run-artifact-poisoning) | `workflow_run` downloads artifacts from the triggering run                       | HIGH     | ✗        |
| [CICD-SEC-1](/rules/cicd-sec-1-checkout-persist-credentials)    | Checkout persists credentials under a privileged trigger                         | MEDIUM   | ✓        |
| [CICD-SEC-1](/rules/cicd-sec-1-spoofable-actor-condition)       | Security decision based on a spoofable actor check                               | MEDIUM   | ✗        |
| [CICD-SEC-1](/rules/cicd-sec-1-unsound-condition)               | Job/step condition is always true                                                | HIGH     | ✓        |
| [CICD-SEC-1](/rules/cicd-sec-1-unsound-contains)                | Spoofable `contains()` membership check                                          | MEDIUM   | ✗        |
| [CICD-SEC-2](/rules/cicd-sec-2)                                 | Long-lived personal access token                                                 | MEDIUM   | ✗        |
| [CICD-SEC-2](/rules/cicd-sec-2-use-trusted-publishing)          | Package published with a long-lived token instead of OIDC                        | MEDIUM   | ✗        |
| [CICD-SEC-3](/rules/cicd-sec-3)                                 | Unpinned third-party action                                                      | MEDIUM   | ✓        |
| [CICD-SEC-3](/rules/cicd-sec-3-unpinned-image)                  | Unpinned container image                                                         | MEDIUM   | ✗        |
| [CICD-SEC-4](/rules/cicd-sec-4)                                 | Poisoned Pipeline Execution (shell injection, incl. laundered `${{ env.X }}`)    | HIGH     | ✓        |
| [CICD-SEC-4](/rules/cicd-sec-4-github-env-injection)            | Untrusted input written to `GITHUB_ENV`/`GITHUB_PATH`                            | HIGH     | ✗        |
| [CICD-SEC-4](/rules/cicd-sec-4-secrets-inherit-pr-target)       | Reusable workflow called with `secrets: inherit` under a privileged trigger      | HIGH     | ✗        |
| [CICD-SEC-4](/rules/cicd-sec-4-obfuscated-expression)           | Obfuscated expression or run script                                              | MEDIUM   | ✗        |
| [CICD-SEC-4](/rules/cicd-sec-4-cache-poisoning-release)         | Caching enabled in a publishing workflow                                         | MEDIUM   | ✗        |
| [CICD-SEC-5](/rules/cicd-sec-5)                                 | Missing permissions specification                                                | MEDIUM   | ✓        |
| [CICD-SEC-5](/rules/cicd-sec-5-forbidden-uses)                  | Action not permitted by forbidden-uses policy (config-driven)                    | HIGH     | ✗        |
| [CICD-SEC-6](/rules/cicd-sec-6)                                 | Hardcoded credentials                                                            | HIGH     | ✓        |
| [CICD-SEC-6](/rules/cicd-sec-6-secret-in-run-output)            | Secret printed to logs or written to step output                                 | HIGH     | ✗        |
| [CICD-SEC-6](/rules/cicd-sec-6-overprovisioned-secrets)         | Secrets over-provisioned to the whole workflow (`toJSON(secrets)`, workflow env) | HIGH     | ✗        |
| [CICD-SEC-7](/rules/cicd-sec-7)                                 | Actions debug logging enabled in workflow                                        | HIGH     | ✓        |
| [CICD-SEC-8](/rules/cicd-sec-8)                                 | `repository_dispatch` trigger without `types:` allowlist                         | MEDIUM   | ✗        |
| [CICD-SEC-9](/rules/cicd-sec-9)                                 | Downloaded artifact has no integrity check                                       | MEDIUM   | ✗        |
| [CICD-SEC-10](/rules/cicd-sec-10)                               | Job-level `continue-on-error` suppresses failure visibility                      | LOW      | ✓        |
| [BEST-PRAC-1](/rules/best-prac-1)                               | Command piped directly to shell (pipe, process-sub, or PowerShell iex)           | HIGH     | ✗        |
| [BEST-PRAC-2](/rules/best-prac-2)                               | Job timeout not configured                                                       | LOW      | ✓        |
| [BEST-PRAC-3](/rules/best-prac-3)                               | Self-hosted runner usage                                                         | LOW      | ✗        |
| [BEST-PRAC-4](/rules/best-prac-4-missing-concurrency)           | Deploy/release workflow has no concurrency guard                                 | LOW      | ✓        |
| [SLSA-BUILD-L2](/rules/slsa-build-l2-provenance)                | Build provenance is not generated                                                | HIGH     | ✗        |
| [SLSA-BUILD-L2](/rules/slsa-build-l2-oidc-token-scope)          | Provenance/signing step missing `id-token: write`                                | MEDIUM   | ✓        |
| [SLSA-BUILD-L2](/rules/slsa-build-l2-perms-overly-broad)        | Permissions block overly broad                                                   | HIGH     | partial  |
| [SLSA-BUILD-L2](/rules/slsa-build-l2-verify-step)               | Workflow consumes artifacts without verifying provenance                         | INFO     | ✗        |
| [SLSA-BUILD-L3](/rules/slsa-build-l3-provenance-isolated)       | Provenance generated in-job (not isolated)                                       | MEDIUM   | ✗        |
| [SLSA-BUILD-L3](/rules/slsa-build-l3-cache-poisoning)           | 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`](/cli/flags) (subject to GitHub's low anonymous rate limit); [`--offline`](/cli/flags) disables it unconditionally. Typosquat matching is itself offline but ships in the same pass.

| Category                                                | Title                                         | Severity | Auto-fix |
| ------------------------------------------------------- | --------------------------------------------- | -------- | -------- |
| [CICD-SEC-3](/rules/cicd-sec-3-known-vulnerable-action) | Known-vulnerable action version (GHSA)        | HIGH     | ✗        |
| [CICD-SEC-3](/rules/cicd-sec-3-impostor-commit)         | Impostor commit pin                           | HIGH     | ✗        |
| [CICD-SEC-3](/rules/cicd-sec-3-ref-version-mismatch)    | Pinned SHA does not match its version comment | MEDIUM   | ✗        |
| [CICD-SEC-3](/rules/cicd-sec-3-typosquat-action)        | Possible typosquatted action                  | MEDIUM   | ✗        |
| [CICD-SEC-3](/rules/cicd-sec-3-archived-action)         | Action's upstream repository is archived      | HIGH     | ✗        |
| [CICD-SEC-3](/rules/cicd-sec-3-stale-action-ref)        | Pinned SHA is not any released tag            | MEDIUM   | ✗        |
| [CICD-SEC-3](/rules/cicd-sec-3-ref-confusion)           | 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](/concepts/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](/rules/cicd-sec-1-bp-missing)                           | Default branch has no branch protection rule                 | **HIGH** | ✓        |
| [BP-FORCE-PUSH](/rules/cicd-sec-1-bp-force-push)                     | Default branch allows force pushes                           | **HIGH** | ✓        |
| [BP-DELETION](/rules/cicd-sec-1-bp-deletion)                         | Default branch can be deleted                                | HIGH     | ✓        |
| [BP-NO-REVIEW](/rules/cicd-sec-1-bp-no-review)                       | Default branch does not require pull request reviews         | **HIGH** | ✗        |
| [BP-FEW-REVIEWERS](/rules/cicd-sec-1-bp-few-reviewers)               | Default branch requires fewer than 2 approving reviews       | MEDIUM   | ✗        |
| [BP-STALE-REVIEWS](/rules/cicd-sec-1-bp-stale-reviews)               | Default branch does not dismiss stale reviews on new commits | MEDIUM   | ✗        |
| [BP-NO-STATUS-CHECKS](/rules/cicd-sec-1-bp-no-status-checks)         | Default branch does not require status checks to pass        | MEDIUM   | ✗        |
| [BP-ADMIN-BYPASS](/rules/cicd-sec-1-bp-admin-bypass)                 | Admins can bypass branch protection                          | **HIGH** | ✓        |
| [BP-NO-CODEOWNERS-REVIEW](/rules/cicd-sec-1-bp-no-codeowners-review) | CODEOWNERS exists but their review is not required           | LOW      | ✓        |
| [BP-NO-SIGNED-COMMITS](/rules/cicd-sec-1-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](/rules/cicd-sec-4-wperm-write)                 | Default GITHUB\_TOKEN permissions are read-write      | **HIGH** | ✓        |
| [WPERM-PR-APPROVE](/rules/cicd-sec-4-wperm-pr-approve)       | GitHub Actions can approve pull requests              | **HIGH** | ✓        |
| [ACTIONS-ALL-ALLOWED](/rules/cicd-sec-5-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](/rules/cicd-sec-3-dependabot-alerts-off) | Dependabot alerts are disabled           | MEDIUM   | ✓        |
| [DEPENDABOT-FIXES-OFF](/rules/cicd-sec-3-dependabot-fixes-off)   | Dependabot security updates are disabled | LOW      | ✓        |

### Credential hygiene (CICD-SEC-6)

| Rule                                                                       | Title                                       | Severity | Auto-fix |
| -------------------------------------------------------------------------- | ------------------------------------------- | -------- | -------- |
| [SECRET-SCANNING-OFF](/rules/cicd-sec-6-secret-scanning-off)               | Secret scanning is disabled                 | MEDIUM   | ✓        |
| [SECRET-PUSH-PROTECTION-OFF](/rules/cicd-sec-6-secret-push-protection-off) | Secret-scanning push protection is disabled | **HIGH** | ✓        |

Auto-fix on repo-settings rules is powered by the CLI's `--fix-settings` flag and the web app's per-finding **Fix** button — see [Auto-fix](/cli/auto-fix#repository-configuration-auto-fix-fix-settings).

## 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](/rules/slsa-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 with `slsa` and 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](/webapp/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 same `ScanBytes(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](/cli/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.
