Skip to content

Credential Scanning

QDash runs three tools to keep secrets out of the repository: Lefthook orchestrates a pre-commit hook, Gitleaks blocks staged secrets locally before they enter git history, and Trufflehog scans git history in CI for verified leaks.

Tools

ToolWhere it runsWhat it scansConfiguration
LefthookLocal pre-commitTriggers Gitleaks against staged fileslefthook.yml
GitleaksLocal pre-commitPattern match for secrets in working tree.gitleaks.toml
TrufflehogCI only (push, PR to main/develop)Git history; only verified secrets are reported.trufflehog-exclude-paths.txt

CI definitions live in .github/workflows/secret-scan.yml. Installation instructions are in Setup.

Why two scanners

The two tools support different allowlist granularities, which is why they own different stages:

Path allowlistLiteral-string allowlist
Gitleaksyesyes (regex, in .gitleaks.toml)
Trufflehogyes (.trufflehog-exclude-paths.txt)no

Because Gitleaks can allowlist specific values, it can be configured strictly: only the literals in .gitleaks.toml are exempt — anything else credential-shaped is rejected. That precision makes it the right gate at pre-commit, where it blocks credentials before they enter git history.

Trufflehog has no string-level allowlist, so the only way to suppress a known false positive is to drop the entire file. Compensating for that, it verifies findings by probing the upstream provider to check whether a credential is live, and walks full git history in CI on push and PR. Its role is ongoing detection of live secrets — running on remote CI infrastructure and reaching out to remote providers to confirm validity.

The result is a clear division of labor: Gitleaks is the strict, fast, syntactic gate at commit time; Trufflehog is the slower, semantic, live-credential check that runs in CI. Gitleaks is not run in CI because the gitleaks-action requires a paid license for organization repositories; the local pre-commit hook covers the same purpose, and any commit that slips past it is caught by Trufflehog's history scan.

Lefthook

lefthook.yml defines a single pre-commit command that calls gitleaks protect --staged. Running lefthook install once writes the git hook into .git/hooks/. If the Gitleaks binary is missing, Lefthook skips the step rather than failing the commit, so contributors on platforms without the binary are not blocked.

Gitleaks

Gitleaks scans content for known secret patterns (API keys, tokens, private keys). The repo config .gitleaks.toml sets [extend] useDefault = true to inherit all built-in detection rules (AWS, GitHub, GCP, Azure, Slack, Stripe, etc.) and layers two kinds of allowlist on top:

  • paths — files whose entire contents are excluded from scanning.
  • regexes — literal strings that are ignored wherever they appear.

Allowlist entries and why they are safe

EntryKindWhy allowlisting is safe
ui/src/client/pathOrval-generated TypeScript API client. Regenerated from docs/oas/openapi.json by task generate; any hand edit is overwritten on the next run.
ui/src/schemas/pathOrval-generated TypeScript schemas. Same generation lifecycle as ui/src/client/.
ui/bun.lockpathBun lockfile. Contains package integrity values that look high-entropy but are public package digests. Regenerated by bun install.
docs/oas/openapi.jsonpathOpenAPI document exported from the FastAPI app via curl /openapi.json in task generate. Not hand-edited.
mongodb://root:example@mongo:27017regexLocal-dev compose default. The hostname mongo resolves only inside the compose network and the password is literally the word example — it is not a secret in any environment, just an example value.

The shared property of every path entry is generation-owned: the file is rewritten by a tool, not by a human. A developer pasting a real credential into one of these files would lose it on the next regeneration step, so the scanning blind spot has no practical attack surface. The single regex entry covers a fixed example value that is identical across every developer's machine.

Trufflehog

Trufflehog walks the git history and reports only verified findings — meaning it actively probed the upstream provider and confirmed the credential is live. False positives are rare, so a hit should be treated as a real leak.

Paths in .trufflehog-exclude-paths.txt are excluded from history scanning. Each line is a regex matched against the file path:

\.gitleaks\.toml
docs/design/api-testing-guidelines\.md
docs/development/api/testing\.md
docs/development/credential-scan\.md
poetry\.lock
scripts/migrate_user_tokens\.py
tests/conftest\.py

Exclude entries and why they are safe

Because Trufflehog walks full git history, the exclude list must cover both currently-tracked files and files that only exist in older commits.

EntryStatusWhy excluding is safe
\.gitleaks\.tomltrackedContains the Gitleaks allowlist itself — the literal mongodb://root:example@mongo:27017 and placeholder tokens (ADMIN_TOKEN, YOUR_TOKEN) would otherwise be re-flagged as credentials. None are real secrets; see the Gitleaks allowlist table above.
docs/design/api-testing-guidelines\.mddeleted (history only)Removed in commit 50b0fd31. Old API testing guideline document that referenced example tokens. Excluded so historical commits do not trigger findings.
docs/development/api/testing\.mdtrackedAPI testing guide. May reference token names and example fixtures in code samples. None are live credentials.
docs/development/credential-scan\.mdtrackedThis document itself. Contains example tool output with mock values such as AKIAIOSFODNN7EXAMPLE and fabricated commit hashes used to illustrate finding format.
poetry\.lockdeleted (history only)Removed in commit e840268b when the project moved to uv. Lockfile entries contain public package integrity digests that look high-entropy.
scripts/migrate_user_tokens\.pydeleted (history only)Removed in commit cca42d62. One-shot migration script that operated on user-token records; variable names and field-key strings match credential-detector patterns even though no live secret was ever embedded.
tests/conftest\.pytrackedPytest fixtures set environment variables to obvious dummy values ("test-token", "test-openai-key", etc.) so the test client can boot without real credentials. None are real.

Trufflehog only reports verified findings, so unverified pattern matches in these files would not have triggered an action anyway — but excluding them keeps scans quiet and prevents future verifiers (added by Trufflehog upstream) from probing fixture values.

Running Locally

The repository ships task targets that mirror the CI commands.

CommandWhat it does
task scan-leaksGitleaks against the full working tree
task scan-leaks-stagedGitleaks against staged files only (same as pre-commit)
task scan-secretsTrufflehog against git history, verified findings only
task scan-secrets-allTrufflehog against git history, including unverified hits

task check (the standard pre-push gate) includes scan-leaks.

Reading Output

Gitleaks

A finding looks like:

Finding:     AKIAIOSFODNN7EXAMPLE
Secret:      AKIAIOSFODNN7EXAMPLE
RuleID:      aws-access-token
Entropy:     3.95
File:        src/qdash/api/config.py
Line:        42
Commit:      (staged)
Author:      <staged>
Date:        (staged)
Fingerprint: src/qdash/api/config.py:aws-access-token:42

Key fields when triaging:

  • RuleID — which detector matched. Gives an immediate hint at the secret type.
  • File / Line — where to look. For staged scans, the line number is in the working tree.
  • Fingerprint — stable identifier for the finding; use it if you need to discuss a specific hit.

The exit code is non-zero on any finding, which is what blocks the commit.

Trufflehog

A verified finding looks like:

Found verified result 🐷🔑
Detector Type: AWS
Decoder Type: PLAIN
Raw result: AKIA****************
File: src/qdash/api/legacy.py
Line: 87
Commit: 3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a
Repository: file://.
Email: dev@example.com
Timestamp: 2024-08-12 14:22:10 +0000

Because Trufflehog only flags verified secrets, the credential is almost certainly active. Rotate it before doing anything else; the git history rewrite comes after.

Handling False Positives

Gitleaks — add the path or regex to .gitleaks.toml under [allowlist]:

toml
[allowlist]
  paths = [
    '''path/to/safe/file\.json''',
  ]
  regexes = [
    '''dev-only-fixture-token-[a-z0-9]+''',
  ]

Prefer narrow regexes over broad path allowlists — broad allowlists hide real future leaks in the same file.

Trufflehog — add the path regex to .trufflehog-exclude-paths.txt. Only do this for files that legitimately contain credential-shaped strings (test fixtures, documentation about credentials). If the finding is verified, do not allowlist; rotate.

Released under the Apache 2.0 License.