From c05c38389b103bd86429c0b35e930a57deb8d449 Mon Sep 17 00:00:00 2001 From: Nikita Belonogov Date: Mon, 2 Nov 2026 20:25:19 +0400 Subject: [PATCH 1/2] ci: replace gitleaks with trufflehog Same scope as before: the commits between the base and the head of each PR (and push, where gitleaks ran on push). --no-verification, so every candidate fails the job and nothing is sent to provider APIs; findings print as detector + file:line only. False positives: `trufflehog:ignore` after the value on its line, or a path regex in .trufflehogignore. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/cicd-pipeline.yml | 4 +- .github/workflows/gitleaks.yml | 84 ------------------------ .github/workflows/trufflehog.yml | 99 +++++++++++++++++++++++++++++ .trufflehogignore | 2 + 4 files changed, 103 insertions(+), 86 deletions(-) delete mode 100644 .github/workflows/gitleaks.yml create mode 100644 .github/workflows/trufflehog.yml create mode 100644 .trufflehogignore diff --git a/.github/workflows/cicd-pipeline.yml b/.github/workflows/cicd-pipeline.yml index 5ac15ffd..95917f2c 100644 --- a/.github/workflows/cicd-pipeline.yml +++ b/.github/workflows/cicd-pipeline.yml @@ -42,10 +42,10 @@ jobs: .then(response => core.setOutput("membership", true)) .catch(reason => core.setOutput("membership", false)); - gitleaks: + trufflehog: name: "Linter" if: github.event_name == 'pull_request' - uses: ./.github/workflows/gitleaks.yml + uses: ./.github/workflows/trufflehog.yml with: head_sha: ${{ github.sha }} base_sha: ${{ github.event.pull_request.base.sha || github.event.before }} diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml deleted file mode 100644 index 37a3e6c1..00000000 --- a/.github/workflows/gitleaks.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: "Gitleaks" - -on: - workflow_call: - inputs: - head_sha: - required: true - type: string - base_sha: - required: true - type: string - version: - required: false - type: string - default: "latest" - -env: - ACTIONS_STEP_DEBUG: '${{ secrets.ACTIONS_STEP_DEBUG }}' - -jobs: - gitleaks: - name: "Gitleaks" - runs-on: ubuntu-latest - steps: - - uses: hmarr/debug-action@v3.0.0 - - - name: install - shell: bash - run: | - set -euo pipefail - arch="$(uname)_$(uname -m)" - platform=$(echo $arch | tr '[:upper:]' '[:lower:]' ) - echo "PLATFORM=$platform" >> $GITHUB_ENV - if [[ "${{ inputs.version }}" == "latest" ]]; then - echo "GITLEAKS_VERSION=$(curl -s https://api.github.com/repos/gitleaks/gitleaks/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')" >> $GITHUB_ENV - else - echo "GITLEAKS_VERSION=${{ inputs.version }}" >> $GITHUB_ENV - fi - - - name: Cache gitleaks archive - id: cache_gitleaks - uses: actions/cache@v6 - with: - path: /usr/local/bin/gitleaks - key: gitleaks-${{ env.PLATFORM }}-${{ env.GITLEAKS_VERSION }} - - - name: Download and configure gitleaks - shell: bash - run: | - set -euo pipefail - if [[ "${{ steps.cache_gitleaks.outputs.cache-hit }}" != "true" ]]; then - DOWNLOAD_URL="https://github.com/gitleaks/gitleaks/releases/download/v${{ env.GITLEAKS_VERSION }}/gitleaks_${{ env.GITLEAKS_VERSION }}_linux_x64.tar.gz" - echo "Download Gitleaks ${{ env.GITLEAKS_VERSION }} for ${{ env.PLATFORM }} from ${DOWNLOAD_URL}" - curl -fsSL "$DOWNLOAD_URL" | tar xzf - -C /usr/local/bin - fi - chmod +x /usr/local/bin/gitleaks - - - name: Checkout - uses: actions/checkout@v7 - with: - fetch-depth: 0 - ref: ${{ inputs.head_sha }} - - - name: Run gitleaks - run: | - set -euo pipefail ${ACTIONS_STEP_DEBUG:+-x} - - gitleaks \ - detect \ - --source="." \ - --redact \ - -v \ - --exit-code=2 \ - --report-format=sarif \ - --report-path=results.sarif \ - --log-level=debug \ - --log-opts='${{ inputs.base_sha }}..${{ inputs.head_sha }}' - - - name: Upload test results - if: failure() - uses: actions/upload-artifact@v7 - with: - name: GitLeaks results - path: results.sarif diff --git a/.github/workflows/trufflehog.yml b/.github/workflows/trufflehog.yml new file mode 100644 index 00000000..8ad75d85 --- /dev/null +++ b/.github/workflows/trufflehog.yml @@ -0,0 +1,99 @@ +name: "TruffleHog" + +# Secret scan of the commits between the base and the head. +# Unverified results fail the job too: candidates are never sent to their providers. +# Not a secret? Add `trufflehog:ignore` on its line after the value (not before), or a path regex to .trufflehogignore. + +on: + workflow_call: + inputs: + head_sha: + required: true + type: string + base_sha: + required: true + type: string + +env: + ACTIONS_STEP_DEBUG: '${{ secrets.ACTIONS_STEP_DEBUG }}' + HEAD_SHA: ${{ inputs.head_sha }} + BASE_SHA: ${{ inputs.base_sha }} + +jobs: + trufflehog: + name: "TruffleHog" + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: hmarr/debug-action@v3.0.0 + + - name: Install + shell: bash + run: | + set -euo pipefail + # Authenticated call: unauthenticated api.github.com requests are rate-limited per IP on shared runners + TRUFFLEHOG_VERSION="$(curl -fsS -H "Authorization: token ${{ github.token }}" https://api.github.com/repos/trufflesecurity/trufflehog/releases/latest | jq -r .tag_name | sed 's/^v//')" + if [[ -z "$TRUFFLEHOG_VERSION" || "$TRUFFLEHOG_VERSION" == "null" ]]; then + echo "::error::Failed to resolve trufflehog version (got '${TRUFFLEHOG_VERSION}')" + exit 1 + fi + echo "TRUFFLEHOG_VERSION=$TRUFFLEHOG_VERSION" >> "$GITHUB_ENV" + + - name: Cache trufflehog archive + id: cache_trufflehog + uses: actions/cache@v6 + with: + path: /usr/local/bin/trufflehog + key: trufflehog-linux-amd64-${{ env.TRUFFLEHOG_VERSION }} + + - name: Download and configure trufflehog + shell: bash + run: | + set -euo pipefail + if [[ "${{ steps.cache_trufflehog.outputs.cache-hit }}" != "true" ]]; then + DOWNLOAD_URL="https://github.com/trufflesecurity/trufflehog/releases/download/v${{ env.TRUFFLEHOG_VERSION }}/trufflehog_${{ env.TRUFFLEHOG_VERSION }}_linux_amd64.tar.gz" + echo "Download trufflehog ${{ env.TRUFFLEHOG_VERSION }} from ${DOWNLOAD_URL}" + curl -fsSL "$DOWNLOAD_URL" | tar xzf - -C /usr/local/bin trufflehog + fi + chmod +x /usr/local/bin/trufflehog + + - name: Checkout + uses: actions/checkout@v7 + with: + # 2147483647 is git's "infinite" depth: full history of this ref only, unlike 0 which + # also fetches every branch and tag. + fetch-depth: 2147483647 + ref: ${{ inputs.head_sha }} + - name: Fetch base commit + if: env.BASE_SHA != '' && env.BASE_SHA != '0000000000000000000000000000000000000000' + run: | + set -euo pipefail ${ACTIONS_STEP_DEBUG:+-x} + + # A PR base (base branch tip) is not always an ancestor of the head. Fetch it with full history + # so the merge base can be computed; shared history is not re-downloaded. + git fetch --no-tags --depth=2147483647 origin "$BASE_SHA" + + - name: Run trufflehog + run: | + set -uo pipefail ${ACTIONS_STEP_DEBUG:+-x} + + if [[ -z "$BASE_SHA" || "$BASE_SHA" =~ ^0+$ ]]; then + echo "No base commit (new branch): its commits were scanned on the branch they came from." + exit 0 + fi + flags=(--no-verification --fail --no-update --json) + if [[ -f .trufflehogignore ]]; then + flags+=(--exclude-paths=.trufflehogignore) + fi + # --since-commit must be an ancestor of --branch, otherwise trufflehog scans nothing and + # reports success. The merge base always is; the PR base is not once the base branch moves on. + trufflehog git file://. \ + --since-commit="$(git merge-base "$BASE_SHA" "$HEAD_SHA")" \ + --branch="$HEAD_SHA" \ + "${flags[@]}" > trufflehog.json + rc=$? + + # Print locations only; the JSON holds raw secret values, so it is not uploaded. + jq -r '.SourceMetadata.Data.Git as $m + | "\(.DetectorName)\t\($m.file):\($m.line)\t\(($m.commit // "")[0:12])"' trufflehog.json + exit $rc diff --git a/.trufflehogignore b/.trufflehogignore new file mode 100644 index 00000000..1dae9944 --- /dev/null +++ b/.trufflehogignore @@ -0,0 +1,2 @@ +(^|/)tests/cassettes/test_label_studio_skill/test_label_studio_skill_with_ner\.yaml$ +(^|/)tests/cassettes/test_skills/test_linear_skillset\.yaml$ From 75113eb672ecf994773b93ec668f3d59b036eb7a Mon Sep 17 00:00:00 2001 From: Nikita Belonogov Date: Tue, 3 Nov 2026 14:11:22 +0400 Subject: [PATCH 2/2] ci: drop .trufflehogignore PRs only scan their own commits, so the old cassette lines are never scanned; excluding the files would only hide a key in a re-recorded cassette. Co-Authored-By: Claude Opus 5.5 --- .trufflehogignore | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .trufflehogignore diff --git a/.trufflehogignore b/.trufflehogignore deleted file mode 100644 index 1dae9944..00000000 --- a/.trufflehogignore +++ /dev/null @@ -1,2 +0,0 @@ -(^|/)tests/cassettes/test_label_studio_skill/test_label_studio_skill_with_ner\.yaml$ -(^|/)tests/cassettes/test_skills/test_linear_skillset\.yaml$