PDP-1182: Add org ruleset support to trufflehog-scan.yml#45
PDP-1182: Add org ruleset support to trufflehog-scan.yml#45GAdityaVarma merged 10 commits intomainfrom
Conversation
Same pattern as copyright-check.yml: - Add workflow_call trigger for backward compat with per-repo callers - Fix Fetch PR head commits: scope git -c extraheader to this repo only (https://github.com/OWNER/REPO/) instead of all of github.com. Credentials are passed in-memory via git -c and never written to .git/config. - Add comment explaining continue-on-error: true is intentional (without it, Parse step / annotations never run when action finds secrets in git history) - Fix grep -qx -> grep -qxF in Parse step (filenames are literals not regex) - Skip Post PR comment for pull_request_target: org required workflow token is read-only for the triggering repo, createComment/updateComment always 403. Annotations (::error, ::warning) from Parse step are visible in the workflow run and are sufficient for developers to find exposed secrets. - Add try/catch to Post PR comment for workflow_call path - Add permission comments explaining why pull-requests: write is retained Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the reusable TruffleHog scanning workflow to support organization-level repository rulesets by allowing direct invocation via pull_request_target, while keeping workflow_call for backward compatibility with existing per-repo callers.
Changes:
- Added
workflow_calltrigger alongsidepull_request_targetto support both reusable workflow callers and org rulesets. - Updated the PR-head
git fetchto use an in-memory scoped auth header (no.git/configwrite) to work on private repos withpersist-credentials: false. - Adjusted reporting behavior: keep annotations always, skip PR comment for
pull_request_target, and wrap the PR-comment logic in a try/catch to avoid masking verified-secret failures.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Remove the TruffleHog GitHub Action step (git history scanner) which ran with continue-on-error: true and never blocked PRs or produced annotations. The Docker filesystem scanner already handles all real work: - Scans current state of PR-changed files - Generates file:line annotations (::warning / ::error) - Drives verified/unverified pass-fail logic The Action step added complexity with no user-visible benefit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
.github/workflows/trufflehog-scan.yml:120
docker run ... || truecombined with2>/dev/nullwill treat scanner execution failures (e.g., image pull failure, docker daemon issue, invalid args) as "no secrets found" becauseSCAN_OUTPUTbecomes empty. With the history scan removed, this becomes a single point of failure that can silently pass PRs. Please capture and check the TruffleHog exit code and fail the job on real execution errors while still allowing non-zero exits that indicate findings (so annotations can be emitted).
# Scan changed files using TruffleHog filesystem mode — pinned tag for reproducibility
SCAN_OUTPUT=$(docker run --rm -v "$(pwd)":/tmp -w /tmp \
ghcr.io/trufflesecurity/trufflehog:3.94.2 \
filesystem /tmp/ \
--json \
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Docker mounts the repo at /tmp/, so TruffleHog sees paths as /tmp/vendor/config.js. Patterns like ^vendor/ in .trufflehog-ignore are anchored to the start and don't match /tmp/vendor/... — causing --exclude-paths to silently skip all exclusions in filesystem mode. Fix: after stripping the /tmp/ prefix from each detected file path, re-check it against .trufflehog-ignore patterns in the bash parse loop. This ensures both default excludes and TRUFFLEHOG_EXCLUDES repo/org variables are correctly enforced. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (4)
.github/workflows/trufflehog-scan.yml:123
docker run ... 2>/dev/null || truewill suppress all stderr (including Docker/TruffleHog runtime failures) and force a successful exit, which can make the workflow report 0 findings even when the scan didn’t run. Instead of discarding stderr, capture/log it and distinguish “secrets found” (non-zero exit but valid JSON output) from actual execution errors (e.g., Docker failure) so the check fails closed when the scanner can’t run.
# Scan changed files using TruffleHog filesystem mode — pinned tag for reproducibility
SCAN_OUTPUT=$(docker run --rm -v "$(pwd)":/tmp -w /tmp \
ghcr.io/trufflesecurity/trufflehog:3.94.2 \
filesystem /tmp/ \
--json \
${{ steps.config.outputs.exclude_args }} \
--no-update 2>/dev/null || true)
.github/workflows/trufflehog-scan.yml:104
- This workflow used to run the TruffleHog action against
base/headSHAs (git history scan) but that step was removed; the current implementation only uses filesystem mode and then filters results to changed files. This is a significant behavioral change (history scanning will no longer catch secrets introduced and then deleted within the PR range). If history scanning is still required (per PR description), re-add the history scan step and keep the filesystem scan for annotations.
- name: Scan changed files for secrets
id: parse
if: github.event_name != 'workflow_dispatch'
run: |
echo "Scanning PR changed files for secrets..."
VERIFIED_COUNT=0
UNVERIFIED_COUNT=0
# Checkout PR head to scan current file state
git checkout ${{ github.event.pull_request.head.sha }} --quiet
# Get list of files changed in this PR (with rename detection)
# -M enables rename detection, showing only the new filename for renamed files
# --diff-filter=d excludes deleted files (we only want files that exist in the PR head)
CHANGED_FILES=$(git diff --name-only -M --diff-filter=d ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} | grep -v '^$' || true)
.github/workflows/trufflehog-scan.yml:222
Post PR comment on findingsruns whenevergithub.event_name == 'workflow_call', butcontext.payload.pull_requestmay be absent if the reusable workflow is invoked from a non-PR caller (e.g., a workflow_dispatch caller). In that case, the script will throw when accessingcontext.payload.pull_request.number. Consider guarding for missing PR context and skipping the comment (similar to.github/workflows/copyright-check.yml:105).
- name: Post PR comment on findings
# workflow_call: token is scoped to the calling repo — write access works.
# pull_request_target (org ruleset): token is read-only for the triggering repo —
# createComment/updateComment will 403. Skip and rely on job annotations instead.
if: github.event_name == 'workflow_call'
uses: actions/github-script@v7
with:
script: |
const commentMarker = '<!-- TRUFFLEHOG-SCAN-COMMENT -->';
const commitSha = '${{ github.event.pull_request.head.sha }}';
const shortSha = commitSha.substring(0, 7);
const hasSecrets = '${{ steps.process.outputs.has_secrets }}' === 'true';
const hasVerified = '${{ steps.process.outputs.has_verified }}' === 'true';
const verifiedCount = '${{ steps.parse.outputs.verified_count }}' || '0';
const unverifiedCount = '${{ steps.parse.outputs.unverified_count }}' || '0';
try {
// Find existing comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
per_page: 100
});
.github/workflows/trufflehog-scan.yml:121
- The TruffleHog filesystem scan is run against
/tmp/(the entire repo) and only filtered down to changed files after the fact. If the goal is “scan changed files”, consider invoking the filesystem scan on just the changed paths (or batching them) to reduce runtime on large repos and avoid scanning excluded/unrelated areas unnecessarily.
echo "Scanning changed files:"
echo "$CHANGED_FILES"
# Scan changed files using TruffleHog filesystem mode — pinned tag for reproducibility
SCAN_OUTPUT=$(docker run --rm -v "$(pwd)":/tmp -w /tmp \
ghcr.io/trufflesecurity/trufflehog:3.94.2 \
filesystem /tmp/ \
--json \
${{ steps.config.outputs.exclude_args }} \
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Pin all mutable action version tags to immutable commit SHAs to prevent supply chain attacks where a compromised upstream tag could run malicious code with elevated pull_request_target permissions. actions/checkout@v4 → @34e114876b (v4) actions/github-script@v7 → @f28e40c7f3 (v7) actions/setup-python@v4 → @7f4fc3e22c (v4) This was one of the findings from SECCMP-1797 / PDP-1182 security review (Aditya's PR #42 — trufflehog-scan.yml pinning). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
copyright-check.yml action pinning belongs in PR #43 only. Keep each PR to a single workflow file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
.github/workflows/trufflehog-scan.yml:99
- The PR description indicates TruffleHog still scans git history (and uses continue-on-error so parse/annotations run), but the workflow now only runs a filesystem scan of the checked-out PR head and no longer invokes the TruffleHog action/history scan. If history scanning is still required, reintroduce it (or an equivalent git scan) alongside the filesystem scan; otherwise update the PR description and expectations/test matrix accordingly since this reduces detection coverage (e.g., secrets present only in history).
- name: Scan changed files for secrets
id: parse
if: github.event_name != 'workflow_dispatch'
run: |
echo "Scanning PR changed files for secrets..."
VERIFIED_COUNT=0
UNVERIFIED_COUNT=0
# Checkout PR head to scan current file state
git checkout ${{ github.event.pull_request.head.sha }} --quiet
.github/workflows/trufflehog-scan.yml:122
docker run … 2>/dev/null || truesuppresses stderr and forces success for all failures (image pull issues, Docker daemon problems, jq/CLI errors), which can produce silent false negatives (secrets not scanned but job passes). Prefer capturing the exit code and only tolerating the expected “secrets found” status, and keep stderr (or log it conditionally) so operational failures are visible.
SCAN_OUTPUT=$(docker run --rm -v "$(pwd)":/tmp -w /tmp \
ghcr.io/trufflesecurity/trufflehog:3.94.2 \
filesystem /tmp/ \
--json \
${{ steps.config.outputs.exclude_args }} \
--no-update 2>/dev/null || true)
.github/workflows/trufflehog-scan.yml:121
- The filesystem scan is run against
/tmp/(the entire repo) and results are filtered afterwards toCHANGED_FILES. On large repos this can be significantly slower than necessary. If TruffleHog supports it in your chosen mode, consider restricting the scan to only the changed paths (or using an include-paths mechanism) to reduce runtime and CI load.
# Scan changed files using TruffleHog filesystem mode — pinned tag for reproducibility
SCAN_OUTPUT=$(docker run --rm -v "$(pwd)":/tmp -w /tmp \
ghcr.io/trufflesecurity/trufflehog:3.94.2 \
filesystem /tmp/ \
--json \
${{ steps.config.outputs.exclude_args }} \
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Pin Docker image by digest to prevent supply chain tag mutation (ghcr.io/trufflesecurity/trufflehog:3.94.2@sha256:dabd9a5f...) - Add -- to grep -qE and grep -qxF to prevent filenames/patterns starting with '-' being misinterpreted as grep options - Skip comment lines (#) in exclusion pattern loop Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…sues.*) The PR comment step uses github.rest.issues.listComments/createComment/ updateComment APIs, which require issues: write. Without it, the comment step 403s silently. copyright-check.yml already has this permission for the same reason. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
For consistency with copyright-check.yml, move all ${{ }} expressions
out of run: shell commands and github-script: blocks into env: vars,
accessed via shell $VAR and process.env.VAR respectively.
Changes:
- Fetch PR head commits: SERVER_URL, REPO, PR_NUMBER, PR_HEAD_SHA -> env:
- Setup exclude config: replace quoted heredoc with printf '\''%s\n'\'' "$DEFAULT_EXCLUDES"
(DEFAULT_EXCLUDES is a workflow-level env var, not attacker-controlled)
- Scan changed files: PR_HEAD_SHA, PR_BASE_SHA, EXCLUDE_ARGS -> env:
- Post PR comment (github-script): PR_HEAD_SHA, HAS_SECRETS, HAS_VERIFIED,
VERIFIED_COUNT, UNVERIFIED_COUNT, SERVER_URL, REPO, RUN_ID -> env:;
all references updated to use process.env.*
None of these values (SHAs, integers, fixed URLs) were exploitable, but
the env: pattern is the documented SECCMP-1797 best practice and matches
the approach used in copyright-check.yml.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ename edge case) echo "$VAR" | grep can mishandle filenames literally named '-n' or '-e' because bash's built-in echo treats them as options, suppressing output and causing grep to match against nothing (false negative for secret scan). Replace with grep ... <<<"$VAR" (herestring) which is not subject to echo option interpretation. Fixes two instances: - Exclusion pattern check: grep -qE -- "$excl_pattern" <<<"$FILE" - Changed files membership: grep -qxF -- "$FILE" <<<"$CHANGED_FILES" Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
.github/workflows/trufflehog-scan.yml:89
TRUFFLEHOG_EXCLUDESis appended viaecho "$TRUFFLEHOG_EXCLUDES" | tr .... Bash built-inechocan treat leading-n/-eas options and suppress or alter output, which would silently drop org/repo exclusion patterns. Use a here-string orprintfas the emitter (e.g.,tr ... <<< "$TRUFFLEHOG_EXCLUDES"orprintf '%s\n' "$TRUFFLEHOG_EXCLUDES" | tr ...) to avoid option interpretation.
if [ -n "$TRUFFLEHOG_EXCLUDES" ]; then
echo "Adding repo/org-level TRUFFLEHOG_EXCLUDES patterns"
# Support both comma-separated and newline-separated patterns
echo "$TRUFFLEHOG_EXCLUDES" | tr ',' '\n' | sed '/^$/d' >> .trufflehog-ignore
fi
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
docker run ... 2>/dev/null || true suppresses all stderr and forces exit 0 in every case. If TruffleHog or Docker fails at runtime (image pull error, OOM, container crash), SCAN_OUTPUT is empty → VERIFIED_COUNT=0 → job passes silently — a fail-open that bypasses the required ruleset check. Fix: capture the exit code without || true. TruffleHog exits 0 (no secrets) or 183 (secrets found) on a successful run. Any other code means the scanner itself failed — fail the job explicitly with ::error rather than silently passing with zero findings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
.github/workflows/trufflehog-scan.yml:89
- In
Setup exclude config, the pipeline usesecho "$TRUFFLEHOG_EXCLUDES" | tr .... Bash’s built-inechocan treat values starting with-n/-eas options and suppress/alter output, which could silently drop org/repo-provided exclusion patterns. Use a here-string (tr ... <<<"$TRUFFLEHOG_EXCLUDES") orprintf '%s'instead ofechoso the exclusion list is always passed verbatim.
if [ -n "$TRUFFLEHOG_EXCLUDES" ]; then
echo "Adding repo/org-level TRUFFLEHOG_EXCLUDES patterns"
# Support both comma-separated and newline-separated patterns
echo "$TRUFFLEHOG_EXCLUDES" | tr ',' '\n' | sed '/^$/d' >> .trufflehog-ignore
fi
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Adds org-level repository ruleset support to
trufflehog-scan.yml, following the same pattern ascopyright-check.yml(PR #43).What changed
workflow_calltriggergit fetchauth — scoped toOWNER/REPOgithub.com/— narrowed togithub.com/OWNER/REPO/. Credentials passed viagit -c(in-memory only, never written to.git/config)Post PR commentforpull_request_targetcreateCommentalways 403. Annotations (::error,::warning) from Docker scan step are sufficient.Post PR commentworkflow_callpath from silently masking verified secrets resultgrep -qx→grep -qxF.in a path would match any charactergrep --flag added-being treated as grep optionsecho "$VAR" | grep→ herestring<<< "$VAR"-nor-ecause bash's built-inechoto suppress output — false negative in exclusion check and changed-files membership test. Herestring is not subject toechooption interpretation.${{ }}expressions moved toenv:varsrun:blocks orgithub-script:— passed as env vars onlydocker run ... || true→ exit code check|| trueforced exit 0 unconditionally. If Docker/TruffleHog crashed,SCAN_OUTPUTempty → 0 findings → job silently passes. Now accepts only exit 0 (no secrets) or 183 (secrets found); any other exit code fails the job with::error.actions/checkoutpinned to commit SHA34e114876b0b11c390a56381ad16ebd13914f8d5trufflehog:3.94.2@sha256:dabd9a5f78ed81211792afc39a35100e2b947baa9f43f1e73a97759d7d15ab86— supply chain hardening (SECCMP-1797)issues: writepermissiongithub.rest.issues.*APIs used by the PR comment stepPwnRequest Safety (SECCMP-1797)
pull_request_targettriggerrefs/pull/N/headchecked out into runner workspaceJira compliance
pull-requests: writenever exercised onpull_request_targetpath (guarded byif: github.event_name == 'workflow_call');issues: writescoped to comment step${{ }}moved toenv:varspull_request_target+ scopedgit fetchauthHow secrets are surfaced to developers
For the org ruleset path (
pull_request_target):::error/::warningannotations visible as inline PR annotationsFor the
workflow_callpath (per-repo callers):Relates to
fix/SECCMP-1797-pwn-request-injection) — fixes the samegit fetchauth issue but scopes credentials too broadly. This PR narrows the scope.Test matrix —
marklogic/copyrighttestTested via org ruleset pointing to
feat/PDP-1182-trufflehog-org-ruleset-support. All 10 scenarios verified against commitb2f0af2on 2026-04-09.AKIAIOSFODNN7EXAMPLE)--diff-filter=dexcludes deletions → early exitvendor/)trufflehog:ignoresuppression