Skip to main content
pipefort -p . --fix
The --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

CategoryFix
CICD-SEC-5 — Missing permissionsPrepends permissions: read-all at the top of the workflow.
CICD-SEC-1 — Dangerous pull_request_targetRewrites the trigger to pull_request. Handles scalar, sequence, and mapping forms of on:.
CICD-SEC-3 — Unpinned actionResolves the tag/branch to a commit SHA via the GitHub API and pins to owner/repo@<sha> # <original-ref>.
CICD-SEC-4 — Shell injectionLifts 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 secretEnv-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 loggingDeletes 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-errorRemoves the continue-on-error: true entry from the job. Step-level continue-on-error is left alone.
SLSA-BUILD-L2 — Missing id-token: writeAdds id-token: write to the offending job’s permissions: block (creates { contents: read, id-token: write } if no block exists).
SLSA-BUILD-L2write-all permissionsReplaces 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 timeoutAdds 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 | sh patterns (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.com for 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.
--fix is not supported when scanning a remote repo with -g owner/repo. The CLI prints a warning and skips the fix step. Clone the repo yourself if you want to fix and inspect the diff.

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.
# Dry-run first so you see what would change.
pipefort -g owner/repo --github-token <PAT> --fix-settings --dry-run

# Apply.
pipefort -g owner/repo --github-token <PAT> --fix-settings
RuleWhat the fix does
WPERM-WRITEPUT /actions/permissions/workflowdefault_workflow_permissions: read. Preserves the existing can_approve_pull_request_reviews value.
WPERM-PR-APPROVESame endpoint → can_approve_pull_request_reviews: false. Preserves default_workflow_permissions.
DEPENDABOT-ALERTS-OFFPUT /vulnerability-alerts.
DEPENDABOT-FIXES-OFFPUT /automated-security-fixes.
SECRET-SCANNING-OFFPATCH /repos/... with security_and_analysis.secret_scanning.status = enabled.
SECRET-PUSH-PROTECTION-OFFPATCH /repos/... with security_and_analysis.secret_scanning_push_protection.status = enabled.
BP-MISSINGPUT /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-PUSHFetch + mutate + PUT — sets allow_force_pushes: false, preserves every other field.
BP-DELETIONSame shape — sets allow_deletions: false.
BP-ADMIN-BYPASSSame shape — sets enforce_admins: true.
BP-NO-CODEOWNERS-REVIEWSame shape — sets required_pull_request_reviews.require_code_owner_reviews: true.
BP-NO-SIGNED-COMMITSPOST /branches/{default}/protection/required_signatures.

What --fix-settings won’t touch

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, Dependabot
  • actions: write — workflow permissions
If the token lacks a scope, that specific rule fails (other fixes in the same run still proceed). Errors are printed to stderr.

--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 named pipefort/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-token you 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.
Same eight workflow rules are covered in both places — see the “What gets fixed” table above.
1

Commit clean

--fix mutates files. Start from a clean working tree so git diff shows only the fixer’s changes.
2

Run the fixer

pipefort -p . --fix
3

Review the diff

git diff .github/workflows/
Especially check the SHA pinning and PPE rewrites — both involve nontrivial structural changes.
4

Commit and push

The fixer leaves a comment after each pinned SHA so reviewers can see the original tag.