Layer 2b — Destructive Command Scanning
Static analysis (Layer 2) audits GitHub Actions security
posture. This layer adds a narrower check: destructive command strings inside executable
repository contexts. A destructive-command guard (dcg) extracts run: blocks from
workflows and evaluates the commands with a destructive-command rule engine.
- Primary target — destructive shell commands committed to
.github/workflows/*.yml. - Secondary targets — shell scripts, Dockerfiles, Makefiles,
package.jsonscripts, Terraformlocal-execblocks, Docker Compose commands, PowerShell, and batch files. - Rollout posture — start with changed files and
--fail-on error; expand scope after remediation.
Step 1 — Add a shared scan workflow
Section titled “Step 1 — Add a shared scan workflow”Place this next to zizmor-audit.yml in platform-org/security-workflows. Keep it
internal, reusable, and required by the same ruleset pattern.
.github/workflows/dcg-scan.yml:
name: dcg repository scanon: workflow_call: pull_request: branches: ['**'] merge_group:
permissions: contents: read security-events: write
jobs: dcg: name: Run dcg scan runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@<SHA> with: fetch-depth: 0 persist-credentials: false
- name: Install dcg run: | curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/destructive_command_guard/main/install.sh" | bash echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Scan changed executable contexts run: | BASE="origin/${{ github.base_ref || github.event.merge_group.base_ref }}" dcg scan --git-diff "$BASE..HEAD" \ --format sarif \ --fail-on error \ > dcg.sarif
- name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@<SHA> with: sarif_file: dcg.sarifStep 2 — Add a conservative config
Section titled “Step 2 — Add a conservative config”Commit the shared config in the platform workflow repo first. Repos can override it only through reviewed PRs.
.dcg/hooks.toml:
[scan]fail_on = "error"format = "sarif"redact = "quoted"truncate = 120
[scan.paths]include = [ ".github/workflows/**", "Dockerfile", "Dockerfile.*", "Makefile", "scripts/**", "package.json",]exclude = [ "node_modules/**", "vendor/**", "target/**", "dist/**",]Step 3 — Call the reusable workflow
Section titled “Step 3 — Call the reusable workflow”During the pilot, add a small caller workflow to 3–5 representative repos:
name: dcg repository scanon: pull_request: branches: ['**'] merge_group:
permissions: contents: read security-events: write
jobs: dcg: uses: platform-org/security-workflows/.github/workflows/dcg-scan.yml@mainStep 4 — Gate merges
Section titled “Step 4 — Gate merges”Add the scan to the same ruleset family as the static-analysis workflow. Start in Evaluate mode, review insights, then flip to Active.
Tune findings without weakening the gate
Section titled “Tune findings without weakening the gate”- Prefer code changes — replace destructive cleanup commands with narrower paths or safer recovery flows.
- Use allowlists sparingly — if a command is intentional, add a reviewed project-level allowlist with a reason.
- Keep the threshold conservative — use
--fail-on errorfor rollout; consider warnings only after teams understand the signal.
Example project-level exception:
dcg allowlist add core.git:reset-hard \ --reason "Approved test fixture that validates reset behavior" \ --projectVerification
Section titled “Verification”- Open a test PR that adds
git reset --hard HEADinside a workflowrun:block. - Confirm the scan job fails with a stable rule ID such as
core.git:reset-hard. - Confirm SARIF appears in Code Scanning if upload is enabled.
- Confirm the ruleset blocks merge only after it is switched from Evaluate to Active.