--fix flag rewrites workflow YAML in place for the categories where a safe, deterministic fix exists. After applying fixes, the scanner re-runs so the final report shows only what’s left.
What gets fixed
| Category | Fix |
|---|---|
| CICD-SEC-5 — Missing permissions | Prepends permissions: read-all at the top of the workflow. |
CICD-SEC-1 — Dangerous pull_request_target | Rewrites the trigger to pull_request. Handles scalar, sequence, and mapping forms of on:. |
| CICD-SEC-3 — Unpinned action | Resolves the tag/branch to a commit SHA via the GitHub API and pins to owner/repo@<sha> # <original-ref>. |
| CICD-SEC-4 — Shell injection | Lifts each ${{ github.event.* }} reference out of the inline run: script into the step’s env: block, then replaces the interpolation with $VAR_NAME. |
| CICD-SEC-6 — Hardcoded secret | Env-var case: replaces the literal with ${{ secrets.<KEY_UPPER> }}. Run-script case: hoists typed literals (ghp_*, xoxb-*, AKIA*) into the step’s env: block as ${{ secrets.GH_TOKEN }} / ${{ secrets.SLACK_TOKEN }} / ${{ secrets.AWS_ACCESS_KEY_ID }} and rewrites occurrences as $VAR. The generic var := "..." pattern is not auto-fixed. |
| CICD-SEC-7 — Actions debug logging | Deletes the ACTIONS_STEP_DEBUG / ACTIONS_RUNNER_DEBUG entry from the offending env: block. Sibling env entries are preserved. |
| CICD-SEC-10 — Job-level continue-on-error | Removes the continue-on-error: true entry from the job. Step-level continue-on-error is left alone. |
SLSA-BUILD-L2 — Missing id-token: write | Adds id-token: write to the offending job’s permissions: block (creates { contents: read, id-token: write } if no block exists). |
SLSA-BUILD-L2 — write-all permissions | Replaces a permissions: write-all scalar (workflow- or job-level) with read-all. The mapping form with 4+ explicit writes is left for manual review — those scopes may have been deliberately chosen. |
| BEST-PRAC-2 — Missing timeout | Adds timeout-minutes: 30 to the job. |
What it won’t fix
- The “generic
var := "..."” CICD-SEC-6 pattern — matches an enclosing assignment, not just the secret value; a blind rewrite would corrupt syntax. curl | shpatterns (BEST-PRAC-1). There’s no safe automatic rewrite; flagged for manual review.- Self-hosted runners (BEST-PRAC-3). Often intentional infra choice.
What you need before running
- Network access to
api.github.comfor CICD-SEC-3 — the fixer hits the GitHub API to resolve each tag to a commit SHA. If resolution fails, the action is left as-is and a warning is printed to stderr. - Write access to the workflow files — fixes are applied in place. Commit clean first so the diff is reviewable.
Repository-configuration auto-fix (--fix-settings)
--fix rewrites workflow YAML in your working tree. --fix-settings is the GitHub-API counterpart: it remediates the repository-configuration findings (branch protection, default GITHUB_TOKEN, Dependabot, secret scanning) by hitting the GitHub API directly. It’s a separate flag because the required token scopes are wider — opt in only when you want it.
| Rule | What the fix does |
|---|---|
| WPERM-WRITE | PUT /actions/permissions/workflow → default_workflow_permissions: read. Preserves the existing can_approve_pull_request_reviews value. |
| WPERM-PR-APPROVE | Same endpoint → can_approve_pull_request_reviews: false. Preserves default_workflow_permissions. |
| DEPENDABOT-ALERTS-OFF | PUT /vulnerability-alerts. |
| DEPENDABOT-FIXES-OFF | PUT /automated-security-fixes. |
| SECRET-SCANNING-OFF | PATCH /repos/... with security_and_analysis.secret_scanning.status = enabled. |
| SECRET-PUSH-PROTECTION-OFF | PATCH /repos/... with security_and_analysis.secret_scanning_push_protection.status = enabled. |
| BP-MISSING | PUT /branches/{default}/protection with sensible defaults: 2 required reviews, dismiss stale reviews, enforce admins, no force-push or deletion. Does not add required status checks (we don’t know which). |
| BP-FORCE-PUSH | Fetch + mutate + PUT — sets allow_force_pushes: false, preserves every other field. |
| BP-DELETION | Same shape — sets allow_deletions: false. |
| BP-ADMIN-BYPASS | Same shape — sets enforce_admins: true. |
| BP-NO-CODEOWNERS-REVIEW | Same shape — sets required_pull_request_reviews.require_code_owner_reviews: true. |
| BP-NO-SIGNED-COMMITS | POST /branches/{default}/protection/required_signatures. |
What --fix-settings won’t touch
- BP-NO-REVIEW, BP-FEW-REVIEWERS, BP-STALE-REVIEWS: when BP-MISSING fires, the defaults the fixer installs already cover these. When BP exists but lacks one of them, the fix isn’t yet automated — coming in a follow-up.
- BP-NO-STATUS-CHECKS: we can’t infer which checks should be required.
- ACTIONS-ALL-ALLOWED: switching from
alltoselectedrequires building an allowlist; not automated.
Token requirements
The fixer uses the same--github-token (or $GITHUB_TOKEN or gh auth token) that the audit uses, but needs wider scopes for the writes:
administration: write— branch protection, secret scanning, Dependabotactions: write— workflow permissions
--dry-run
--dry-run short-circuits PUT/PATCH/POST before they hit GitHub but lets the GET requests through, so the printed action descriptions still reflect the real current state. Use it whenever you’re unsure.
Web app: open a fix PR
The web dashboard exposes the same fixers on a different surface: per-finding, click Open fix PR to have Pipefort fetch the workflow file from GitHub, run the fixer in memory, push the result to a branch namedpipefort/fix/<rule>/<file>, and open a pull request for human review.
Compared to the CLI’s --fix:
- The CLI mutates files in your local working tree; the web app opens a PR. Nothing lands on your default branch until you merge it.
- The web app uses the GitHub App installation token (see GitHub App permissions). The CLI uses whatever scopes are on the
--github-tokenyou pass. - Repeat clicks on the same finding converge on the same PR — the branch name is deterministic from
<rule_id>/<file>. Re-running the fix after the file changed pushes an updated commit to the same branch. - The web app re-scans the freshly-fetched file before applying the fix. If the finding has been remediated manually since the last scan, you get a clean “already remediated — nothing to change” response instead of an empty PR.
Recommended workflow
Commit clean
--fix mutates files. Start from a clean working tree so git diff shows only the fixer’s changes.Review the diff