Skip to content

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.json scripts, Terraform local-exec blocks, Docker Compose commands, PowerShell, and batch files.
  • Rollout posture — start with changed files and --fail-on error; expand scope after remediation.

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 scan
on:
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.sarif

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/**",
]

During the pilot, add a small caller workflow to 3–5 representative repos:

name: dcg repository scan
on:
pull_request:
branches: ['**']
merge_group:
permissions:
contents: read
security-events: write
jobs:
dcg:
uses: platform-org/security-workflows/.github/workflows/dcg-scan.yml@main

Add the scan to the same ruleset family as the static-analysis workflow. Start in Evaluate mode, review insights, then flip to Active.

  • 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 error for rollout; consider warnings only after teams understand the signal.

Example project-level exception:

Terminal window
dcg allowlist add core.git:reset-hard \
--reason "Approved test fixture that validates reset behavior" \
--project
  1. Open a test PR that adds git reset --hard HEAD inside a workflow run: block.
  2. Confirm the scan job fails with a stable rule ID such as core.git:reset-hard.
  3. Confirm SARIF appears in Code Scanning if upload is enabled.
  4. Confirm the ruleset blocks merge only after it is switched from Evaluate to Active.