Skip to content

Add reusable secretScan workflow for TruffleHog secret scanning - #110

Open
fukawi2 wants to merge 5 commits into
mainfrom
phs-trufflehog-secret-scan
Open

fukawi2 wants to merge 5 commits into
mainfrom
phs-trufflehog-secret-scan

Conversation

@fukawi2

@fukawi2 fukawi2 commented Sep 15, 2026

Copy link
Copy Markdown

Details

Adds a reusable workflow that scans a repository for committed credentials using TruffleHog. We currently have no automated secret scanning anywhere in the org. This is the shared half of that work; each repository then adds a short caller workflow.

The scan scope follows the event that triggered the calling workflow, because the TruffleHog action derives its own --since-commit and --branch from github.event_name. A pull_request caller scans only the commits in the PR; a schedule or workflow_dispatch caller scans the full history. So a repository needs two callers for full coverage, which is why this is one reusable workflow rather than two.

Inputs are fail_on_findings (default false), exclude_paths_file, and runner.

Three details in here are non-obvious and are worth a reviewer's attention:

  1. The container tag has no leading v. The action's release tag is v3.97.4 but the ghcr image tag is 3.97.4. I confirmed this against the registry: 3.97.4 returns HTTP 200 and v3.97.4 returns HTTP 404. Pinning v3.97.4 would fail every run.

  2. --results is deliberately omitted. We scan with --no-verification, because verification authenticates each candidate against its live provider and a burst of failed authentication attempts from CI is indistinguishable from credential stuffing in CloudTrail. The consequence is that every finding is then classified unverified. TruffleHog's own README recommends --results=verified,unknown, which combined with --no-verification reports nothing at all. The default (verified,unverified,unknown) is the value we want. There is a comment in the workflow saying so, because this failure mode is a silently green check.

  3. The workflow runs the container directly instead of using trufflesecurity/trufflehog. TruffleHog exits 183 for a finding and 1 for an operational error, and warn-only mode has to suppress the first without hiding the second. The action hardcodes --fail, exposes no exit code, and cannot be overridden — kingpin rejects a repeated flag with flag 'fail' cannot be repeated, which I confirmed in CI. Reading the exit code is the only way to express this, so the workflow derives the scan range from the triggering event itself and branches on the exit code. The image is pinned by digest, which is stronger than the tag pin the action allows.

Default is warn-only so each repository can measure its false-positive rate before enforcing.

Related Issues

https://github.com/Expensify/Expensify/issues/681714

Manual Tests

This is a workflow definition, so the meaningful test is a real run. I ran this repository's own three CI validation scripts locally against the new file:

I also added secretScanSelf.yml, so this repository scans itself through a local workflow ref. A pull request that changes secretScan.yml is therefore checked by the version it proposes. That caught a real bug in this PR before review: an earlier attempt to pass --no-fail failed in 8 seconds with flag 'fail' cannot be repeated.

The exit-code branching is the part worth testing, so I extracted the scan step's shell and ran it against a stubbed docker across every branch:

Event Exit code fail_on_findings Result
pull_request 0 false pass, clean
pull_request 183 false pass, warning annotation
pull_request 183 true fail
pull_request 1 false fail, scan did not complete
schedule 0 false pass, no range, full history
push to new branch 0 false pass, --branch only, no --since-commit
push to existing branch 0 false pass, --since-commit and --branch
release n/a false fail, unsupported event

The fourth row is the one that matters: a broken scan fails the job even in warn-only mode.

validation logs
$ REPO_ROOT=. ./scripts/validateWorkflowSchemas.sh
OK   .github/workflows/secretScan.yml

$ REPO_ROOT=. ./scripts/actionlint.sh
Found actionlint version 1.7.11 already installed
Linting workflows...
Using actionlint file from this repo
Workflows passed actionlint!

$ REPO_ROOT=. ./scripts/validateImmutableActionRefs.sh
Untrusted action usages...
...
trufflesecurity/trufflehog@363923b901c911a9164f50b6c423f47c15372b1c
All untrusted actions are using immutable references

To verify end to end after merge:

  1. Merge this PR so the workflow is available at @main.
  2. Merge the linked Salt PR, which adds the caller workflows.
  3. Open any pull request in Salt and confirm the Secret Scan / TruffleHog check runs and passes.
  4. Go to the Salt Actions tab, select Secret Scan (Full History), and choose Run workflow.
  5. Confirm the job scans the whole history and reports either a clean result or warnings that do not fail the run.

Linked PRs

Enable TruffleHog secret scanning in CI — on HOLD until this PR merges

@fukawi2 fukawi2 self-assigned this Sep 15, 2026
@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@fukawi2

fukawi2 commented Sep 15, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@fukawi2

fukawi2 commented Sep 15, 2026

Copy link
Copy Markdown
Author

recheck

exfy-clabot Bot added a commit to Expensify/CLA that referenced this pull request Sep 15, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d5904168c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread .github/workflows/secretScan.yml Outdated
continue-on-error suppressed every non-zero exit from the TruffleHog
action, so a scan that failed to start reported a pass. TruffleHog exits
183 for a finding and 1 for an operational error, so warn-only mode now
appends --no-fail to suppress only the 183 exit.

Also scan this repository with a local workflow ref, so a change to
secretScan.yml is checked by the version it proposes.
The trufflesecurity/trufflehog action hardcodes --fail and exposes no
exit code, and kingpin rejects a repeated flag with "flag 'fail' cannot
be repeated", so --no-fail cannot override it.

TruffleHog exits 183 for a finding and 1 for an operational error.
Reading the exit code is the only way to let warn-only mode suppress a
finding while still failing the job when the scan itself breaks.

Pin the image by digest and derive the scan range from the triggering
event.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7198ccc294

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread .github/workflows/secretScan.yml
Comment thread .github/workflows/secretScan.yml Outdated
The scan reads file:///repo, so it cannot reach the remote. Two events
did not meet that requirement:

  - pull_request_target checks out the base repository, so an external
    fork's head commit is absent.
  - A force push can leave the previous head unreachable from every
    remaining ref, so --since-commit cannot resolve.

Neither event has a caller, so drop both rather than fetch the missing
objects. Any unlisted event now fails with an explicit error.

Also check that each commit in the scan range resolves before starting.
TruffleHog aborts on a missing commit without saying why.
@fukawi2

fukawi2 commented Sep 15, 2026

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83951f7878

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread .github/workflows/secretScan.yml Outdated
The note ran under if: success(), which is true in warn-only mode even
when TruffleHog exits 183, so a run that found a credential claimed the
scan was clean. Move the note into the exit-code-0 branch.
@fukawi2

fukawi2 commented Sep 15, 2026

Copy link
Copy Markdown
Author

@roryabraham and @neil-marcellini Requesting your reviews as the top contributors to this repo please 🙏

Comment thread README.md

Scans a repository for committed credentials using [TruffleHog](https://github.com/trufflesecurity/trufflehog).

The scan scope follows the event that triggered the calling workflow, so a repository needs two callers for full coverage:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmm I'm wondering if we should use Rulesets to run this on all PRs across the org, following the example of verifyPeerReview and validateActions.yml. That way, each repo does not need to wire in its own secret-scan.yml, and this repo does not need to wire in its own secretScanSelf.yml.

We would lose the weekly scan since running workflows on PRs via Rulesets only supports a few event triggers, but we could replace it with a weekly Bedrock job that scans the whole GitHub org using the Trufflehog CLI: trufflehog github --org=trufflesecurity --exclude-archived

Separately, I'm wondering if just the weekly scan is sufficient? Because once a secret has been pushed in any commit, blocking the pull request from being merged doesn't really do anything: at that point the secret should be considered compromised and be rotated.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on rulesets; that will drop 57 repos of wiring. I'll rework this into a ruleset-triggered workflow, delete secretScanSelf.yml, and close the Salt PR.

On the weekly scan, I'd drop it rather than move it to Bedrock, It was probably misguided in the first place since history is immutable and the version is pinned, so a recurring full-history scan reports identical result every week forever.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking the one gap that might be filled by the full-repo weekly scan would be if someone pushes a token to a branch but never opens a pull request from that branch.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rulesets can't run on push?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately not. Given that our end goal isn't necessarily blocking merge, but alerting someone who then should rotate the compromised secret in question, I think the webhook + manually running the TruffleHog CLI is ultimately a better tool for this than GitHub Actions.

@neil-marcellini

Copy link
Copy Markdown
Contributor

I'm going to drop off of the review here since it sounds like this will go in a different direction anyways, and I'm surprised I'm a top contributor to this repo because I'm still pretty new to GH actions haha.

@neil-marcellini
neil-marcellini removed their request for review September 16, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants