From aac4d10831bd830826790ab1379728de0c206ec5 Mon Sep 17 00:00:00 2001 From: Isaiah Grigsby Date: Mon, 20 Apr 2026 10:18:14 -0400 Subject: [PATCH 1/2] Add advisory TruffleHog org scan workflow (github source) Made-with: Cursor --- .github/workflows/org-trufflehog-fullscan.yml | 123 ++++++++++++++++++ docs/trufflehog-org-fullscan.md | 25 ++++ 2 files changed, 148 insertions(+) create mode 100644 .github/workflows/org-trufflehog-fullscan.yml create mode 100644 docs/trufflehog-org-fullscan.md diff --git a/.github/workflows/org-trufflehog-fullscan.yml b/.github/workflows/org-trufflehog-fullscan.yml new file mode 100644 index 00000000..5f4caa08 --- /dev/null +++ b/.github/workflows/org-trufflehog-fullscan.yml @@ -0,0 +1,123 @@ +# Org-wide secret scan using TruffleHog's built-in GitHub source. +# See: https://github.com/trufflesecurity/trufflehog — `trufflehog github --org=…` +# +# Test now: Actions → this workflow → Run workflow. +# Smoke test: set `single_repo` to one HTTPS URL and leave org as default. + +name: TruffleHog org scan (GitHub) + +on: + workflow_dispatch: + inputs: + org: + description: "GitHub org slug (used when single_repo is empty)" + required: true + default: "grafana" + type: string + single_repo: + description: "Optional smoke test: one repo URL, e.g. https://github.com/grafana/grafana" + required: false + default: "" + type: string + schedule: + # Weekly; delete this block if you only want manual runs. + - cron: "15 4 * * 0" + +permissions: + contents: read + actions: write + +env: + # renovate: datasource=github-releases depName=trufflesecurity/trufflehog + TRUFFLEHOG_VERSION: v3.94.0 + DEFAULT_ORG: grafana + +jobs: + scan: + name: Scan + runs-on: ubuntu-latest + timeout-minutes: 360 + steps: + - name: Install TruffleHog + run: | + if [[ "$(uname -m)" == "aarch64" ]]; then + ARCH="linux_arm64" + else + ARCH="linux_amd64" + fi + BINARY_URL="https://github.com/trufflesecurity/trufflehog/releases/download/${TRUFFLEHOG_VERSION}/trufflehog_${TRUFFLEHOG_VERSION#v}_${ARCH}.tar.gz" + curl -sSfL "${BINARY_URL}" | tar -xz -C /tmp + sudo mv /tmp/trufflehog /usr/local/bin/trufflehog + sudo chmod +x /usr/local/bin/trufflehog + trufflehog --version + + - name: Run scan (advisory — never blocks merges) + env: + TOKEN: ${{ secrets.ORG_TRUFFLEHOG_PAT }} + run: | + if [[ -z "${TOKEN}" ]]; then + echo "::error::Create repository secret ORG_TRUFFLEHOG_PAT (PAT with read access to the org repos you scan)." + exit 1 + fi + + ORG="${DEFAULT_ORG}" + SINGLE_REPO="" + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + ORG="${{ github.event.inputs.org }}" + SINGLE_REPO="${{ github.event.inputs.single_repo }}" + fi + + set +e + if [[ -n "${SINGLE_REPO}" ]]; then + echo "Mode: single repo → ${SINGLE_REPO}" + trufflehog github \ + --repo="${SINGLE_REPO}" \ + --token="${TOKEN}" \ + --json \ + --no-update \ + --results=verified,unverified \ + > results.ndjson + else + echo "Mode: full org → ${ORG}" + trufflehog github \ + --org="${ORG}" \ + --token="${TOKEN}" \ + --json \ + --no-update \ + --results=verified,unverified \ + > results.ndjson + fi + TH_EXIT=$? + set -e + + grep -E '^\{' results.ndjson > filtered.ndjson 2>/dev/null || true + if [[ -s filtered.ndjson ]]; then + jq -R 'fromjson?' filtered.ndjson | jq -s '.' > results.json + else + echo "[]" > results.json + fi + + VERIFIED="$(jq '[.[] | select(.Verified == true)] | length' results.json)" + UNVERIFIED="$(jq '[.[] | select(.Verified == false)] | length' results.json)" + TOTAL=$((VERIFIED + UNVERIFIED)) + + { + echo "### TruffleHog (github source)" + echo "- TruffleHog exit: \`${TH_EXIT}\` (non-zero can mean findings or scan errors; artifacts still uploaded)" + echo "- Verified: \`${VERIFIED}\`" + echo "- Unverified: \`${UNVERIFIED}\`" + echo "- Total: \`${TOTAL}\`" + } >> "${GITHUB_STEP_SUMMARY}" + + exit 0 + + - name: Upload results + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + if: always() + with: + name: trufflehog_github_${{ github.run_id }} + path: | + results.json + results.ndjson + if-no-files-found: warn + retention-days: 14 diff --git a/docs/trufflehog-org-fullscan.md b/docs/trufflehog-org-fullscan.md new file mode 100644 index 00000000..56dbcb9c --- /dev/null +++ b/docs/trufflehog-org-fullscan.md @@ -0,0 +1,25 @@ +# TruffleHog org scan (GitHub source) + +Uses the official CLI: [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog) — `trufflehog github --org=…` / `--repo=…`. + +Workflow: `.github/workflows/org-trufflehog-fullscan.yml` + +## Test on GitHub Actions + +1. Add secret **`ORG_TRUFFLEHOG_PAT`** (read access to repos you scan). +2. **Actions** → **TruffleHog org scan (GitHub)** → **Run workflow**. +3. **Quick test:** set **single_repo** to e.g. `https://github.com/trufflesecurity/test_keys` (public canary) or any repo URL you may read. +4. Download artifact **`trufflehog_github_`** → `results.json` / `results.ndjson`. + +## Test locally (same behavior) + +```bash +trufflehog github --repo=https://github.com/trufflesecurity/test_keys --json --no-update --results=verified,unverified +``` + +With token: add `--token="$GITHUB_TOKEN"`. + +## Notes + +- Workflow is advisory (no `--fail`); do not add as a required ruleset check if you want it off the merge path. +- Weekly schedule is optional; remove `schedule:` in the YAML for manual-only. From a08428b6a5d17165d23485ae69ca8c91317cad86 Mon Sep 17 00:00:00 2001 From: Isaiah Grigsby Date: Mon, 20 Apr 2026 10:22:36 -0400 Subject: [PATCH 2/2] fix(ci): avoid template injection in org TruffleHog workflow (zizmor) Pass workflow_dispatch inputs via step env only; validate org slug and single_repo URL before invoking trufflehog. Made-with: Cursor --- .github/workflows/org-trufflehog-fullscan.yml | 38 +++++++++++++++++-- docs/trufflehog-org-fullscan.md | 7 ++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/.github/workflows/org-trufflehog-fullscan.yml b/.github/workflows/org-trufflehog-fullscan.yml index 5f4caa08..f68ccf70 100644 --- a/.github/workflows/org-trufflehog-fullscan.yml +++ b/.github/workflows/org-trufflehog-fullscan.yml @@ -3,6 +3,9 @@ # # Test now: Actions → this workflow → Run workflow. # Smoke test: set `single_repo` to one HTTPS URL and leave org as default. +# +# Inputs are passed only via step `env` (not ${{ }} inside `run:`) to satisfy +# zizmor template-injection rules; values are validated before use. name: TruffleHog org scan (GitHub) @@ -54,17 +57,46 @@ jobs: - name: Run scan (advisory — never blocks merges) env: TOKEN: ${{ secrets.ORG_TRUFFLEHOG_PAT }} + # Do not interpolate workflow inputs inside `run:` (zizmor: template-injection). + ORG_INPUT: ${{ github.event.inputs.org || '' }} + SINGLE_REPO_INPUT: ${{ github.event.inputs.single_repo || '' }} + EVENT_NAME: ${{ github.event_name }} run: | if [[ -z "${TOKEN}" ]]; then echo "::error::Create repository secret ORG_TRUFFLEHOG_PAT (PAT with read access to the org repos you scan)." exit 1 fi + # GitHub org slug: alphanumeric + hyphen, reasonable length (GitHub max 39). + validate_org() { + local o="$1" + [[ "${o}" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]{0,38}$ ]] + } + + # Single-repo smoke test: strict https://github.com/org/repo only. + validate_repo_url() { + local u="$1" + [[ -z "${u}" ]] && return 0 + [[ "${u}" =~ ^https://github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/?$ ]] + } + ORG="${DEFAULT_ORG}" SINGLE_REPO="" - if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then - ORG="${{ github.event.inputs.org }}" - SINGLE_REPO="${{ github.event.inputs.single_repo }}" + if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then + if [[ -n "${ORG_INPUT}" ]]; then + if ! validate_org "${ORG_INPUT}"; then + echo "::error::Invalid org slug (allowed: letters, digits, hyphen; 1–39 chars)." + exit 1 + fi + ORG="${ORG_INPUT}" + fi + if [[ -n "${SINGLE_REPO_INPUT}" ]]; then + if ! validate_repo_url "${SINGLE_REPO_INPUT}"; then + echo "::error::Invalid single_repo URL (use https://github.com/ORG/REPO with no query or fragment)." + exit 1 + fi + SINGLE_REPO="${SINGLE_REPO_INPUT}" + fi fi set +e diff --git a/docs/trufflehog-org-fullscan.md b/docs/trufflehog-org-fullscan.md index 56dbcb9c..55a2e3f3 100644 --- a/docs/trufflehog-org-fullscan.md +++ b/docs/trufflehog-org-fullscan.md @@ -23,3 +23,10 @@ With token: add `--token="$GITHUB_TOKEN"`. - Workflow is advisory (no `--fail`); do not add as a required ruleset check if you want it off the merge path. - Weekly schedule is optional; remove `schedule:` in the YAML for manual-only. + +## Input validation (workflow_dispatch) + +To avoid unsafe values being interpreted by the shell, inputs are restricted: + +- **org:** GitHub org slug only — letters, digits, hyphen; 1–39 characters (first character must be alphanumeric). +- **single_repo:** Must look like `https://github.com/ORG/REPO` (no query string, fragment, or `.git` suffix in the URL you pass).