From 98ada5a7319c92829ce6ae097836ead525b74d41 Mon Sep 17 00:00:00 2001 From: "r.trofimenkov" Date: Mon, 22 Sep 2025 13:10:17 +0500 Subject: [PATCH 01/23] gitleaks action + caller Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 157 +++++++++++++++++++++++++++++++++++++ gitleaks/gitleaks-test.yml | 18 +++++ 2 files changed, 175 insertions(+) create mode 100644 gitleaks/action.yml create mode 100644 gitleaks/gitleaks-test.yml diff --git a/gitleaks/action.yml b/gitleaks/action.yml new file mode 100644 index 0000000..96c6285 --- /dev/null +++ b/gitleaks/action.yml @@ -0,0 +1,157 @@ +name: gitleaks-reusable + +on: + workflow_call: + inputs: + gitleaks_version: + description: "Pinned Gitleaks version (tag)" + required: false + default: v8.28.0 + type: string + scan_mode: + description: "full | diff" + required: false + default: full + type: string + config_path: + description: "Optional path to .gitleaks.toml (empty = built-in defaults)" + required: false + default: "" + type: string + runner_labels: + description: "Runner labels" + required: false + default: "self-hosted,linux,x64" + type: string + +permissions: + contents: read + +jobs: + gitleaks: + name: Gitleaks (${{ inputs.scan_mode }}) + runs-on: ${{ fromJSON('["' + replace(inputs.runner_labels, ',', '","') + '"]') }} + + steps: + - name: Checkout + uses: actions/checkout@v3.5.2 + with: + fetch-depth: 0 + + - name: Install Gitleaks ${{ inputs.gitleaks_version }} + shell: bash + run: | + set -euo pipefail + ver="${{ inputs.gitleaks_version }}" + url="https://github.com/gitleaks/gitleaks/releases/download/${ver}/gitleaks_${ver#v}_linux_x64.tar.gz" + echo "Downloading: $url" + curl -sSL "$url" -o gitleaks.tgz + tar -xzf gitleaks.tgz gitleaks + chmod +x gitleaks + sudo mv gitleaks /usr/local/bin/gitleaks + gitleaks version + + - name: Ensure stub .gitleaks.toml (optional) + if: ${{ inputs.config_path != '' }} + shell: bash + run: | + set -euo pipefail + cfg="${{ inputs.config_path }}" + mkdir -p "$(dirname "$cfg")" + if [[ ! -f "$cfg" ]]; then + printf '%s\n' \ + '# Placeholder config to be tuned later.' \ + '# ВАЖНО: если вы используете этот файл, он полностью заменит встроенные правила.' \ + '[allowlist]' \ + ' description = "example placeholder"' \ + ' regexTarget = "match" # пример, удалите/измените позже' \ + > "$cfg" + fi + echo "Stub config ensured at: $cfg" + + - name: Compute base ref for PR + id: base + if: ${{ inputs.scan_mode == 'diff' && github.event_name == 'pull_request' }} + shell: bash + run: | + set -euo pipefail + git remote -v + git fetch origin "${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" --depth=0 || true + BASE_SHA=$(git merge-base "HEAD" "origin/${GITHUB_BASE_REF}") + echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT" + + - name: Gitleaks detect (full) + if: ${{ inputs.scan_mode == 'full' }} + shell: bash + run: | + set -euo pipefail + extra=() + if [[ -n "${{ inputs.config_path }}" ]]; then + extra+=( -c "${{ inputs.config_path }}" ) + fi + echo "▶ Running full scan..." + if gitleaks detect --no-banner --no-color --report-format json --report-path - --source . "${extra[@]}" | tee gitleaks.json; then + echo "✅ No leaks found." + else + echo "❌ Leaks found." + jq -r --arg url "${{ github.server_url }}/${{ github.repository }}" ' + .findings[]? as $f | + ($f.File // $f.file) as $file | + ($f.StartLine // $f.Line // $f.line // 0) as $line | + ($f.RuleID // $f.Rule // $f.rule) as $rule | + ($f.Commit // $f.commit // "") as $sha | + ($f.Author // $f.author // "") as $author | + ($f.Email // $f.email // "") as $email | + ($f.Date // $f.date // "") as $date | + ($sha|length>0 and $file|length>0 and $line>0) + as $linkable | + ($linkable + | if . then "\($url)/blob/\($sha)/\($file)#L\($line)" else "" end) as $link | + "• [\($rule)] \($file):\($line) | commit=\($sha[0:7]) | author=\($author) <\($email)> | date=\($date)\n \($link)" + ' gitleaks.json + exit 1 + fi + + - name: Gitleaks detect (diff) + if: ${{ inputs.scan_mode == 'diff' && github.event_name == 'pull_request' }} + shell: bash + run: | + set -euo pipefail + BASE_SHA="${{ steps.base.outputs.base_sha }}" + if [[ -z "$BASE_SHA" ]]; then + echo "No base SHA computed; fallback to full scan." + exit 2 + fi + extra=() + if [[ -n "${{ inputs.config_path }}" ]]; then + extra+=( -c "${{ inputs.config_path }}" ) + fi + echo "▶ Running diff scan from $BASE_SHA...HEAD" + if gitleaks detect --no-banner --no-color --report-format json --report-path - --source . --log-opts "$BASE_SHA...HEAD" "${extra[@]}" | tee gitleaks.json; then + echo "✅ No leaks found in diff." + else + echo "❌ Leaks found in diff." + jq -r --arg url "${{ github.server_url }}/${{ github.repository }}" ' + .findings[]? as $f | + ($f.File // $f.file) as $file | + ($f.StartLine // $f.Line // $f.line // 0) as $line | + ($f.RuleID // $f.Rule // $f.rule) as $rule | + ($f.Commit // $f.commit // "") as $sha | + ($f.Author // $f.author // "") as $author | + ($f.Email // $f.email // "") as $email | + ($f.Date // $f.date // "") as $date | + ($sha|length>0 and $file|length>0 and $line>0) + as $linkable | + ($linkable + | if . then "\($url)/blob/\($sha)/\($file)#L\($line)" else "" end) as $link | + "• [\($rule)] \($file):\($line) | commit=\($sha[0:7]) | author=\($author) <\($email)> | date=\($date)\n \($link)" + ' gitleaks.json + exit 1 + fi + + - name: Fallback to full scan (non-PR diff mode) + if: ${{ inputs.scan_mode == 'diff' && github.event_name != 'pull_request' }} + shell: bash + run: | + echo "scan_mode=diff, но событие ${{ github.event_name }} не PR — выполняю полный скан." + exit 2 \ No newline at end of file diff --git a/gitleaks/gitleaks-test.yml b/gitleaks/gitleaks-test.yml new file mode 100644 index 0000000..d643801 --- /dev/null +++ b/gitleaks/gitleaks-test.yml @@ -0,0 +1,18 @@ +# .github/workflows/_selftest_gitleaks.yml +name: _selftest_gitleaks +on: + workflow_dispatch: + +jobs: + selftest-full: + uses: ./gitleaks/gitleaks-reusable.yml + with: + scan_mode: full + runner_labels: ubuntu-latest + + selftest-diff: + if: github.event_name == 'workflow_dispatch' + uses: ./gitleaks/gitleaks-reusable.yml + with: + scan_mode: diff + runner_labels: ubuntu-latest \ No newline at end of file From 1af5b3fedb7cd46276ac1da84e1b40bb0c905cde Mon Sep 17 00:00:00 2001 From: rtrofimenkov-ssdlc Date: Mon, 22 Sep 2025 13:19:38 +0500 Subject: [PATCH 02/23] Add workflow_dispatch inputs to gitleaks action Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index 96c6285..0d43f5f 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -23,6 +23,28 @@ on: required: false default: "self-hosted,linux,x64" type: string + workflow_dispatch: + inputs: + gitleaks_version: + description: "Pinned Gitleaks version (tag)" + required: false + default: v8.28.0 + type: string + scan_mode: + description: "full | diff" + required: false + default: full + type: string + config_path: + description: "Optional path to .gitleaks.toml (empty = built-in defaults)" + required: false + default: "" + type: string + runner_labels: + description: "Runner labels" + required: false + default: "self-hosted,linux,x64" + type: string permissions: contents: read From 77ac1ec5b3148101bb8c040004ac830b4e251694 Mon Sep 17 00:00:00 2001 From: rtrofimenkov-ssdlc Date: Mon, 22 Sep 2025 13:25:29 +0500 Subject: [PATCH 03/23] Add gitleaks self-test workflow configuration Signed-off-by: Roman Trofimenkov --- {gitleaks => .github}/gitleaks-test.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {gitleaks => .github}/gitleaks-test.yml (100%) diff --git a/gitleaks/gitleaks-test.yml b/.github/gitleaks-test.yml similarity index 100% rename from gitleaks/gitleaks-test.yml rename to .github/gitleaks-test.yml From 3db1c31fc9e5de4f4eabfd5ccc9d11fda0e4450c Mon Sep 17 00:00:00 2001 From: rtrofimenkov-ssdlc Date: Mon, 29 Sep 2025 21:47:09 +0500 Subject: [PATCH 04/23] action fix Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 257 +++++++++++++++----------------------------- 1 file changed, 88 insertions(+), 169 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index 0d43f5f..df57b07 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -1,179 +1,98 @@ -name: gitleaks-reusable +name: "Gitleaks scan" +description: "Run Gitleaks in full or diff mode (composite action)" -on: - workflow_call: - inputs: - gitleaks_version: - description: "Pinned Gitleaks version (tag)" - required: false - default: v8.28.0 - type: string - scan_mode: - description: "full | diff" - required: false - default: full - type: string - config_path: - description: "Optional path to .gitleaks.toml (empty = built-in defaults)" - required: false - default: "" - type: string - runner_labels: - description: "Runner labels" - required: false - default: "self-hosted,linux,x64" - type: string - workflow_dispatch: - inputs: - gitleaks_version: - description: "Pinned Gitleaks version (tag)" - required: false - default: v8.28.0 - type: string - scan_mode: - description: "full | diff" - required: false - default: full - type: string - config_path: - description: "Optional path to .gitleaks.toml (empty = built-in defaults)" - required: false - default: "" - type: string - runner_labels: - description: "Runner labels" - required: false - default: "self-hosted,linux,x64" - type: string +inputs: + gitleaks_version: + description: "Gitleaks tag to install (e.g. v8.28.0)" + required: false + default: "v8.28.0" + scan_mode: + description: "Scan mode: full | diff" + required: false + default: "full" + config_path: + description: "Path to .gitleaks.toml (leave empty for default rules)" + required: false + default: "" -permissions: - contents: read +runs: + using: "composite" + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 -jobs: - gitleaks: - name: Gitleaks (${{ inputs.scan_mode }}) - runs-on: ${{ fromJSON('["' + replace(inputs.runner_labels, ',', '","') + '"]') }} + - name: Install Gitleaks ${{ inputs.gitleaks_version }} + shell: bash + run: | + set -euo pipefail + ver="${{ inputs.gitleaks_version }}" + file_ver="${ver#v}" - steps: - - name: Checkout - uses: actions/checkout@v3.5.2 - with: - fetch-depth: 0 + arch="$(uname -m)" + case "$arch" in + x86_64|amd64) pkg_arch="linux_x64" ;; + aarch64|arm64) pkg_arch="linux_arm64" ;; + *) echo "Unsupported arch: $arch"; exit 1 ;; + esac - - name: Install Gitleaks ${{ inputs.gitleaks_version }} - shell: bash - run: | - set -euo pipefail - ver="${{ inputs.gitleaks_version }}" - url="https://github.com/gitleaks/gitleaks/releases/download/${ver}/gitleaks_${ver#v}_linux_x64.tar.gz" - echo "Downloading: $url" - curl -sSL "$url" -o gitleaks.tgz - tar -xzf gitleaks.tgz gitleaks - chmod +x gitleaks - sudo mv gitleaks /usr/local/bin/gitleaks - gitleaks version + url="https://github.com/gitleaks/gitleaks/releases/download/${ver}/gitleaks_${file_ver}_${pkg_arch}.tar.gz" + echo "Downloading: $url" + curl -sSL "$url" -o gitleaks.tgz + tar -xzf gitleaks.tgz gitleaks + chmod +x gitleaks - - name: Ensure stub .gitleaks.toml (optional) - if: ${{ inputs.config_path != '' }} - shell: bash - run: | - set -euo pipefail - cfg="${{ inputs.config_path }}" - mkdir -p "$(dirname "$cfg")" - if [[ ! -f "$cfg" ]]; then - printf '%s\n' \ - '# Placeholder config to be tuned later.' \ - '# ВАЖНО: если вы используете этот файл, он полностью заменит встроенные правила.' \ - '[allowlist]' \ - ' description = "example placeholder"' \ - ' regexTarget = "match" # пример, удалите/измените позже' \ - > "$cfg" - fi - echo "Stub config ensured at: $cfg" + install_dir="$HOME/.local/bin" + mkdir -p "$install_dir" + mv gitleaks "$install_dir/gitleaks" + echo "$install_dir" >> "$GITHUB_PATH" - - name: Compute base ref for PR - id: base - if: ${{ inputs.scan_mode == 'diff' && github.event_name == 'pull_request' }} - shell: bash - run: | - set -euo pipefail - git remote -v - git fetch origin "${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" --depth=0 || true - BASE_SHA=$(git merge-base "HEAD" "origin/${GITHUB_BASE_REF}") - echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT" + gitleaks version - - name: Gitleaks detect (full) - if: ${{ inputs.scan_mode == 'full' }} - shell: bash - run: | - set -euo pipefail - extra=() - if [[ -n "${{ inputs.config_path }}" ]]; then - extra+=( -c "${{ inputs.config_path }}" ) - fi - echo "▶ Running full scan..." - if gitleaks detect --no-banner --no-color --report-format json --report-path - --source . "${extra[@]}" | tee gitleaks.json; then - echo "✅ No leaks found." - else - echo "❌ Leaks found." - jq -r --arg url "${{ github.server_url }}/${{ github.repository }}" ' - .findings[]? as $f | - ($f.File // $f.file) as $file | - ($f.StartLine // $f.Line // $f.line // 0) as $line | - ($f.RuleID // $f.Rule // $f.rule) as $rule | - ($f.Commit // $f.commit // "") as $sha | - ($f.Author // $f.author // "") as $author | - ($f.Email // $f.email // "") as $email | - ($f.Date // $f.date // "") as $date | - ($sha|length>0 and $file|length>0 and $line>0) - as $linkable | - ($linkable - | if . then "\($url)/blob/\($sha)/\($file)#L\($line)" else "" end) as $link | - "• [\($rule)] \($file):\($line) | commit=\($sha[0:7]) | author=\($author) <\($email)> | date=\($date)\n \($link)" - ' gitleaks.json - exit 1 - fi + - name: Compute base SHA (diff mode on PR) + id: base + if: ${{ inputs.scan_mode == 'diff' && github.event_name == 'pull_request' }} + shell: bash + run: | + set -euo pipefail + git fetch origin "${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" --depth=0 || true + BASE_SHA=$(git merge-base HEAD "origin/${GITHUB_BASE_REF}") + echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT" - - name: Gitleaks detect (diff) - if: ${{ inputs.scan_mode == 'diff' && github.event_name == 'pull_request' }} - shell: bash - run: | - set -euo pipefail - BASE_SHA="${{ steps.base.outputs.base_sha }}" - if [[ -z "$BASE_SHA" ]]; then - echo "No base SHA computed; fallback to full scan." - exit 2 - fi - extra=() - if [[ -n "${{ inputs.config_path }}" ]]; then - extra+=( -c "${{ inputs.config_path }}" ) - fi - echo "▶ Running diff scan from $BASE_SHA...HEAD" - if gitleaks detect --no-banner --no-color --report-format json --report-path - --source . --log-opts "$BASE_SHA...HEAD" "${extra[@]}" | tee gitleaks.json; then - echo "✅ No leaks found in diff." - else - echo "❌ Leaks found in diff." - jq -r --arg url "${{ github.server_url }}/${{ github.repository }}" ' - .findings[]? as $f | - ($f.File // $f.file) as $file | - ($f.StartLine // $f.Line // $f.line // 0) as $line | - ($f.RuleID // $f.Rule // $f.rule) as $rule | - ($f.Commit // $f.commit // "") as $sha | - ($f.Author // $f.author // "") as $author | - ($f.Email // $f.email // "") as $email | - ($f.Date // $f.date // "") as $date | - ($sha|length>0 and $file|length>0 and $line>0) - as $linkable | - ($linkable - | if . then "\($url)/blob/\($sha)/\($file)#L\($line)" else "" end) as $link | - "• [\($rule)] \($file):\($line) | commit=\($sha[0:7]) | author=\($author) <\($email)> | date=\($date)\n \($link)" - ' gitleaks.json - exit 1 - fi + - name: Gitleaks detect (full) + if: ${{ inputs.scan_mode == 'full' }} + shell: bash + run: | + set -euo pipefail + extra=() + [[ -n "${{ inputs.config_path }}" ]] && extra+=( -c "${{ inputs.config_path }}" ) - - name: Fallback to full scan (non-PR diff mode) - if: ${{ inputs.scan_mode == 'diff' && github.event_name != 'pull_request' }} - shell: bash - run: | - echo "scan_mode=diff, но событие ${{ github.event_name }} не PR — выполняю полный скан." - exit 2 \ No newline at end of file + echo "▶ Running full scan..." + # exit code 1 => leaks found; action/job will fail (that’s intended) + gitleaks detect --no-banner --report-format json --report-path gitleaks.json --source . "${extra[@]}" + + - name: Gitleaks detect (diff PR) + if: ${{ inputs.scan_mode == 'diff' && github.event_name == 'pull_request' }} + shell: bash + run: | + set -euo pipefail + BASE_SHA="${{ steps.base.outputs.base_sha }}" + if [[ -z "$BASE_SHA" ]]; then + echo "Base SHA not found; falling back to full scan" + exit 2 + fi + extra=() + [[ -n "${{ inputs.config_path }}" ]] && extra+=( -c "${{ inputs.config_path }}" ) + + echo "▶ Running diff scan $BASE_SHA...HEAD" + gitleaks detect --no-banner --report-format json --report-path gitleaks.json \ + --source . --log-opts "$BASE_SHA...HEAD" "${extra[@]}" + + - name: Upload report (always) + if: always() + uses: actions/upload-artifact@v4 + with: + name: gitleaks-report + path: gitleaks.json + if-no-files-found: ignore \ No newline at end of file From 684536d6e52f93674eff7ac3041de093cb88429d Mon Sep 17 00:00:00 2001 From: rtrofimenkov-ssdlc Date: Mon, 29 Sep 2025 21:59:04 +0500 Subject: [PATCH 05/23] path fix Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index df57b07..9ccc9f5 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -46,8 +46,16 @@ runs: install_dir="$HOME/.local/bin" mkdir -p "$install_dir" mv gitleaks "$install_dir/gitleaks" + echo "$install_dir" >> "$GITHUB_PATH" + "$install_dir/gitleaks" version + + - name: Sanity check PATH (should find gitleaks) + shell: bash + run: | + set -euo pipefail + command -v gitleaks gitleaks version - name: Compute base SHA (diff mode on PR) @@ -67,9 +75,7 @@ runs: set -euo pipefail extra=() [[ -n "${{ inputs.config_path }}" ]] && extra+=( -c "${{ inputs.config_path }}" ) - echo "▶ Running full scan..." - # exit code 1 => leaks found; action/job will fail (that’s intended) gitleaks detect --no-banner --report-format json --report-path gitleaks.json --source . "${extra[@]}" - name: Gitleaks detect (diff PR) @@ -84,7 +90,6 @@ runs: fi extra=() [[ -n "${{ inputs.config_path }}" ]] && extra+=( -c "${{ inputs.config_path }}" ) - echo "▶ Running diff scan $BASE_SHA...HEAD" gitleaks detect --no-banner --report-format json --report-path gitleaks.json \ --source . --log-opts "$BASE_SHA...HEAD" "${extra[@]}" From 3dc83f3d9f743a43bb6f10ed0c9df33e32e1b79e Mon Sep 17 00:00:00 2001 From: rtrofimenkov-ssdlc Date: Mon, 29 Sep 2025 22:22:52 +0500 Subject: [PATCH 06/23] added leaks finding in job output Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 55 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index 9ccc9f5..b5d6e53 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -14,6 +14,10 @@ inputs: description: "Path to .gitleaks.toml (leave empty for default rules)" required: false default: "" + print_limit: + description: "Max lines to print to logs/summary" + required: false + default: "200" runs: using: "composite" @@ -46,9 +50,7 @@ runs: install_dir="$HOME/.local/bin" mkdir -p "$install_dir" mv gitleaks "$install_dir/gitleaks" - echo "$install_dir" >> "$GITHUB_PATH" - "$install_dir/gitleaks" version - name: Sanity check PATH (should find gitleaks) @@ -94,6 +96,55 @@ runs: gitleaks detect --no-banner --report-format json --report-path gitleaks.json \ --source . --log-opts "$BASE_SHA...HEAD" "${extra[@]}" + - name: Print findings (always) + if: always() + shell: bash + run: | + set -euo pipefail + [[ -f gitleaks.json ]] || { echo "No gitleaks.json produced"; exit 0; } + + LIMIT="${{ inputs.print_limit }}" + REPO_URL="${{ github.server_url }}/${{ github.repository }}" + + # jq-скрипт, устойчивый к разным схемам (findings[] или массив верхнего уровня) + JQ_FILTER=' + def norm: + { + file: (.File // .file // .Target // .Location.File // ""), + line: (.StartLine // .Line // .Location.StartLine // 0), + rule: (.RuleID // .Rule // .Description // ""), + commit: (.Commit // .commit // "") + }; + (if type=="object" and has("findings") then .findings + elif type=="array" then . + else [] end)[] | norm + ' + + COUNT=$(jq -r 'if type=="object" and has("findings") then (.findings|length) + elif type=="array" then length else 0 end' gitleaks.json) + + echo "Findings: $COUNT" + echo "### Gitleaks findings (showing up to $LIMIT) :closed_lock_with_key:" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Rule | File:Line | Commit | Link |" >> "$GITHUB_STEP_SUMMARY" + echo "|---|---|---|---|" >> "$GITHUB_STEP_SUMMARY" + + jq -r "$JQ_FILTER | [.rule, (.file+\":\"+(.line|tostring)), (.commit[0:7]), + (if (.commit|length)>0 and (.file|length)>0 and (.line|tonumber)>0 + then \"$REPO_URL/blob/\"+.commit+\"/\"+.file+\"#L\"+(.line|tostring) + else \"\" end)] + | @tsv" gitleaks.json \ + | head -n "$LIMIT" \ + | while IFS=$'\t' read -r rule loc sha link; do + printf '• %s %s %s %s\n' "[$rule]" "$loc" "$sha" "$link" + printf '| `%s` | `%s` | `%s` | %s |\n' "$rule" "$loc" "$sha" "${link:-"-"}" >> "$GITHUB_STEP_SUMMARY" + done + + if (( COUNT > LIMIT )); then + MORE=$((COUNT - LIMIT)) + echo "... and $MORE more (see artifact gitleaks-report)." | tee -a "$GITHUB_STEP_SUMMARY" + fi + - name: Upload report (always) if: always() uses: actions/upload-artifact@v4 From 9dbd38665f64d70048950d873643be2ba60b1389 Mon Sep 17 00:00:00 2001 From: rtrofimenkov-ssdlc Date: Wed, 1 Oct 2025 13:05:33 +0500 Subject: [PATCH 07/23] gitleaks action: deleted unneccessary envs Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 58 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index b5d6e53..085cd59 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -1,23 +1,11 @@ name: "Gitleaks scan" -description: "Run Gitleaks in full or diff mode (composite action)" +description: "Run Gitleaks in full or diff mode (composite action, fixed version/config/limit)" inputs: - gitleaks_version: - description: "Gitleaks tag to install (e.g. v8.28.0)" - required: false - default: "v8.28.0" scan_mode: description: "Scan mode: full | diff" required: false default: "full" - config_path: - description: "Path to .gitleaks.toml (leave empty for default rules)" - required: false - default: "" - print_limit: - description: "Max lines to print to logs/summary" - required: false - default: "200" runs: using: "composite" @@ -27,11 +15,11 @@ runs: with: fetch-depth: 0 - - name: Install Gitleaks ${{ inputs.gitleaks_version }} + - name: Install Gitleaks v8.28.0 shell: bash run: | set -euo pipefail - ver="${{ inputs.gitleaks_version }}" + ver="v8.28.0" file_ver="${ver#v}" arch="$(uname -m)" @@ -41,9 +29,11 @@ runs: *) echo "Unsupported arch: $arch"; exit 1 ;; esac - url="https://github.com/gitleaks/gitleaks/releases/download/${ver}/gitleaks_${file_ver}_${pkg_arch}.tar.gz" - echo "Downloading: $url" - curl -sSL "$url" -o gitleaks.tgz + base="https://github.com/gitleaks/gitleaks/releases/download/${ver}" + tgz="gitleaks_${file_ver}_${pkg_arch}.tar.gz" + + echo "Downloading: $base/$tgz" + curl -sSL "$base/$tgz" -o gitleaks.tgz tar -xzf gitleaks.tgz gitleaks chmod +x gitleaks @@ -60,6 +50,15 @@ runs: command -v gitleaks gitleaks version + - name: Ensure config exists (.github/gitleaks.toml) + shell: bash + run: | + set -euo pipefail + if [[ ! -f ".github/gitleaks.toml" ]]; then + echo "Required config '.github/gitleaks.toml' is missing. Please add it to the repo." >&2 + exit 1 + fi + - name: Compute base SHA (diff mode on PR) id: base if: ${{ inputs.scan_mode == 'diff' && github.event_name == 'pull_request' }} @@ -75,10 +74,11 @@ runs: shell: bash run: | set -euo pipefail - extra=() - [[ -n "${{ inputs.config_path }}" ]] && extra+=( -c "${{ inputs.config_path }}" ) - echo "▶ Running full scan..." - gitleaks detect --no-banner --report-format json --report-path gitleaks.json --source . "${extra[@]}" + echo "▶ Running full scan with .github/gitleaks.toml" + gitleaks detect --no-banner \ + --report-format json --report-path gitleaks.json \ + -c ".github/gitleaks.toml" \ + --source . - name: Gitleaks detect (diff PR) if: ${{ inputs.scan_mode == 'diff' && github.event_name == 'pull_request' }} @@ -90,11 +90,11 @@ runs: echo "Base SHA not found; falling back to full scan" exit 2 fi - extra=() - [[ -n "${{ inputs.config_path }}" ]] && extra+=( -c "${{ inputs.config_path }}" ) - echo "▶ Running diff scan $BASE_SHA...HEAD" - gitleaks detect --no-banner --report-format json --report-path gitleaks.json \ - --source . --log-opts "$BASE_SHA...HEAD" "${extra[@]}" + echo "▶ Running diff scan $BASE_SHA...HEAD with .github/gitleaks.toml" + gitleaks detect --no-banner \ + --report-format json --report-path gitleaks.json \ + -c ".github/gitleaks.toml" \ + --source . --log-opts "$BASE_SHA...HEAD" - name: Print findings (always) if: always() @@ -102,11 +102,9 @@ runs: run: | set -euo pipefail [[ -f gitleaks.json ]] || { echo "No gitleaks.json produced"; exit 0; } - - LIMIT="${{ inputs.print_limit }}" + LIMIT=200 REPO_URL="${{ github.server_url }}/${{ github.repository }}" - # jq-скрипт, устойчивый к разным схемам (findings[] или массив верхнего уровня) JQ_FILTER=' def norm: { From 62fa042dd6872384191e9978c8b85c9dfc1a96b2 Mon Sep 17 00:00:00 2001 From: rtrofimenkov-ssdlc Date: Wed, 1 Oct 2025 14:01:15 +0500 Subject: [PATCH 08/23] added README.md Signed-off-by: Roman Trofimenkov --- gitleaks/README.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 gitleaks/README.md diff --git a/gitleaks/README.md b/gitleaks/README.md new file mode 100644 index 0000000..c045ca2 --- /dev/null +++ b/gitleaks/README.md @@ -0,0 +1,74 @@ +# 🕵️ Gitleaks GitHub Action + +## 📌 Что это такое + +Этот GitHub Actions workflow автоматически запускает [Gitleaks](https://github.com/gitleaks/gitleaks) — инструмент для поиска секретов в коде (токены, ключи, пароли и т.п.). Он сканирует изменения либо полностью, либо в виде diff-а для Pull Request'ов. + +Цель — предотвратить утечку секретов в публичные или приватные репозитории до попадания кода в основную ветку. + +--- + +## ⚙️ Как работает + +Workflow состоит из двух режимов: + +- **Diff scan** (режим по умолчанию для PR): + - Запускается при создании или обновлении Pull Request. + - Сканирует только изменения между текущей веткой и целевой (base) веткой. + +- **Full scan**: + - Запускается по расписанию или вручную. + - Сканирует весь репозиторий. + +Компоненты: +- 📄 `gitleaks.yml`: основной workflow с тремя триггерами — `pull_request`, `schedule`, `workflow_dispatch`. +- ⚙️ `deckhouse/modules-actions/gitleaks@feature/gitleaks`: composite action, устанавливающая и запускающая Gitleaks. +- 🛠 `.github/gitleaks.toml`: обязательный конфигурационный файл правил для Gitleaks (настраивается под ваш проект). + +--- + +## 🚀 Как подключить + +### 1. Добавьте конфиг Gitleaks + +Создайте файл `.github/gitleaks.toml` в корне вашего репозитория. Пример можно взять из официального репозитория Gitleaks или настроить под себя: +📎 https://github.com/gitleaks/gitleaks/blob/main/config/gitleaks.toml + +### 2. Добавьте workflow-файл + +Создайте файл `.github/workflows/gitleaks.yml` со следующим содержимым: + +```yaml +name: Gitleaks + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + schedule: + - cron: "0 2 * * *" # ежедневно в 02:00 UTC + workflow_dispatch: {} # ручной запуск + +permissions: + contents: read + +concurrency: + group: "gitleaks-${{ github.ref }}" + cancel-in-progress: false + +jobs: + gitleaks-diff: + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + steps: + - uses: deckhouse/modules-actions/gitleaks@feature/gitleaks + with: + scan_mode: diff + + gitleaks-full: + if: ${{ github.event_name != 'pull_request' }} + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: deckhouse/modules-actions/gitleaks@feature/gitleaks + with: + scan_mode: full From b14413435d6adcc00dfa6058473076433980eaad Mon Sep 17 00:00:00 2001 From: rtrofimenkov-ssdlc Date: Tue, 7 Oct 2025 11:38:38 +0500 Subject: [PATCH 09/23] Remove Gitleaks self-test workflow and update action to support configurable Gitleaks version. Enhance README with optional config details and usage examples. Signed-off-by: Roman Trofimenkov --- .github/gitleaks-test.yml | 18 ------------------ gitleaks/README.md | 39 +++++++++++++++++++++++++++++++++++---- gitleaks/action.yml | 36 +++++++++++++++++++++++------------- 3 files changed, 58 insertions(+), 35 deletions(-) delete mode 100644 .github/gitleaks-test.yml diff --git a/.github/gitleaks-test.yml b/.github/gitleaks-test.yml deleted file mode 100644 index d643801..0000000 --- a/.github/gitleaks-test.yml +++ /dev/null @@ -1,18 +0,0 @@ -# .github/workflows/_selftest_gitleaks.yml -name: _selftest_gitleaks -on: - workflow_dispatch: - -jobs: - selftest-full: - uses: ./gitleaks/gitleaks-reusable.yml - with: - scan_mode: full - runner_labels: ubuntu-latest - - selftest-diff: - if: github.event_name == 'workflow_dispatch' - uses: ./gitleaks/gitleaks-reusable.yml - with: - scan_mode: diff - runner_labels: ubuntu-latest \ No newline at end of file diff --git a/gitleaks/README.md b/gitleaks/README.md index c045ca2..8ca21c9 100644 --- a/gitleaks/README.md +++ b/gitleaks/README.md @@ -21,18 +21,21 @@ Workflow состоит из двух режимов: - Сканирует весь репозиторий. Компоненты: + - 📄 `gitleaks.yml`: основной workflow с тремя триггерами — `pull_request`, `schedule`, `workflow_dispatch`. - ⚙️ `deckhouse/modules-actions/gitleaks@feature/gitleaks`: composite action, устанавливающая и запускающая Gitleaks. -- 🛠 `.github/gitleaks.toml`: обязательный конфигурационный файл правил для Gitleaks (настраивается под ваш проект). +- 🛠 `gitleaks.toml` (опционально): конфигурационный файл правил для Gitleaks в корне репозитория. Если отсутствует — используются встроенные правила Gitleaks. --- ## 🚀 Как подключить -### 1. Добавьте конфиг Gitleaks +### 1. (Опционально) Добавьте конфиг Gitleaks + +Если вы хотите использовать собственные правила сканирования, создайте файл `gitleaks.toml` в **корне** вашего репозитория. Пример можно взять из официального репозитория Gitleaks: +📎 -Создайте файл `.github/gitleaks.toml` в корне вашего репозитория. Пример можно взять из официального репозитория Gitleaks или настроить под себя: -📎 https://github.com/gitleaks/gitleaks/blob/main/config/gitleaks.toml +**Если файл отсутствует:** Gitleaks будет использовать встроенные дефолтные правила. ### 2. Добавьте workflow-файл @@ -63,6 +66,7 @@ jobs: - uses: deckhouse/modules-actions/gitleaks@feature/gitleaks with: scan_mode: diff + # gitleaks_version: v8.28.0 # опционально, по умолчанию v8.28.0 gitleaks-full: if: ${{ github.event_name != 'pull_request' }} @@ -72,3 +76,30 @@ jobs: - uses: deckhouse/modules-actions/gitleaks@feature/gitleaks with: scan_mode: full + # gitleaks_version: v8.28.0 # опционально, по умолчанию v8.28.0 +``` + +--- + +## 📝 Входные параметры (Inputs) + +| Параметр | Описание | Обязательный | Значение по умолчанию | +|----------|----------|--------------|----------------------| +| `scan_mode` | Режим сканирования: `full` или `diff` | Нет | `full` | +| `gitleaks_version` | Версия Gitleaks для установки | Нет | `v8.28.0` | + +### Примеры использования + +**Использование конкретной версии Gitleaks:** + +```yaml +- uses: deckhouse/modules-actions/gitleaks@feature/gitleaks + with: + scan_mode: full + gitleaks_version: v8.20.0 +``` + +**Минимальная конфигурация (используются дефолты):** + +```yaml +- uses: deckhouse/modules-actions/gitleaks@feature/gitleaks diff --git a/gitleaks/action.yml b/gitleaks/action.yml index 085cd59..d4e0c07 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -1,11 +1,15 @@ name: "Gitleaks scan" -description: "Run Gitleaks in full or diff mode (composite action, fixed version/config/limit)" +description: "Run Gitleaks in full or diff mode (composite action, configurable version)" inputs: scan_mode: description: "Scan mode: full | diff" required: false default: "full" + gitleaks_version: + description: "Gitleaks version to install" + required: false + default: "v8.28.0" runs: using: "composite" @@ -15,11 +19,11 @@ runs: with: fetch-depth: 0 - - name: Install Gitleaks v8.28.0 + - name: Install Gitleaks shell: bash run: | set -euo pipefail - ver="v8.28.0" + ver="${{ inputs.gitleaks_version }}" file_ver="${ver#v}" arch="$(uname -m)" @@ -32,7 +36,7 @@ runs: base="https://github.com/gitleaks/gitleaks/releases/download/${ver}" tgz="gitleaks_${file_ver}_${pkg_arch}.tar.gz" - echo "Downloading: $base/$tgz" + echo "📥 Installing Gitleaks $ver..." curl -sSL "$base/$tgz" -o gitleaks.tgz tar -xzf gitleaks.tgz gitleaks chmod +x gitleaks @@ -50,13 +54,17 @@ runs: command -v gitleaks gitleaks version - - name: Ensure config exists (.github/gitleaks.toml) + - name: Check for optional config + id: config shell: bash run: | set -euo pipefail - if [[ ! -f ".github/gitleaks.toml" ]]; then - echo "Required config '.github/gitleaks.toml' is missing. Please add it to the repo." >&2 - exit 1 + if [[ -f "gitleaks.toml" ]]; then + echo "config_arg=-c gitleaks.toml" >> "$GITHUB_OUTPUT" + echo "✅ Found config: gitleaks.toml" + else + echo "config_arg=" >> "$GITHUB_OUTPUT" + echo "⚠️ Config file not found. Proceeding with default rules." fi - name: Compute base SHA (diff mode on PR) @@ -74,10 +82,11 @@ runs: shell: bash run: | set -euo pipefail - echo "▶ Running full scan with .github/gitleaks.toml" + CONFIG_ARG="${{ steps.config.outputs.config_arg }}" + echo "🕵️ Running full scan..." gitleaks detect --no-banner \ --report-format json --report-path gitleaks.json \ - -c ".github/gitleaks.toml" \ + $CONFIG_ARG \ --source . - name: Gitleaks detect (diff PR) @@ -85,15 +94,16 @@ runs: shell: bash run: | set -euo pipefail + CONFIG_ARG="${{ steps.config.outputs.config_arg }}" BASE_SHA="${{ steps.base.outputs.base_sha }}" if [[ -z "$BASE_SHA" ]]; then - echo "Base SHA not found; falling back to full scan" + echo "❌ Base SHA not found; falling back to full scan" exit 2 fi - echo "▶ Running diff scan $BASE_SHA...HEAD with .github/gitleaks.toml" + echo "🕵️ Running diff scan $BASE_SHA...HEAD..." gitleaks detect --no-banner \ --report-format json --report-path gitleaks.json \ - -c ".github/gitleaks.toml" \ + $CONFIG_ARG \ --source . --log-opts "$BASE_SHA...HEAD" - name: Print findings (always) From 36b1b06e63cb99058ebee5e89af8caa2391d5d48 Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Mon, 13 Oct 2025 20:05:35 +0500 Subject: [PATCH 10/23] (gitleaks): allow diff scan under pull_request_target Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index d4e0c07..2002051 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -67,9 +67,9 @@ runs: echo "⚠️ Config file not found. Proceeding with default rules." fi - - name: Compute base SHA (diff mode on PR) + - name: Compute base SHA (diff mode) id: base - if: ${{ inputs.scan_mode == 'diff' && github.event_name == 'pull_request' }} + if: ${{ inputs.scan_mode == 'diff' }} shell: bash run: | set -euo pipefail @@ -89,8 +89,8 @@ runs: $CONFIG_ARG \ --source . - - name: Gitleaks detect (diff PR) - if: ${{ inputs.scan_mode == 'diff' && github.event_name == 'pull_request' }} + - name: Gitleaks detect (diff) + if: ${{ inputs.scan_mode == 'diff' }} shell: bash run: | set -euo pipefail From 07378ff0af208ecf4776ea45749418850052337c Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Tue, 14 Oct 2025 10:57:08 +0500 Subject: [PATCH 11/23] refactor(summary): rename section to "Secret findings" and remove result limit note Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index 2002051..860bec4 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -132,7 +132,7 @@ runs: elif type=="array" then length else 0 end' gitleaks.json) echo "Findings: $COUNT" - echo "### Gitleaks findings (showing up to $LIMIT) :closed_lock_with_key:" >> "$GITHUB_STEP_SUMMARY" + echo "### Secret findings :closed_lock_with_key:" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "| Rule | File:Line | Commit | Link |" >> "$GITHUB_STEP_SUMMARY" echo "|---|---|---|---|" >> "$GITHUB_STEP_SUMMARY" From c84aa1483ba26dd351c91188f958d3b838221cc0 Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Tue, 14 Oct 2025 11:17:24 +0500 Subject: [PATCH 12/23] fix(gitleaks): scan PR head vs base on pull_request_target Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index 860bec4..ee13a6d 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -10,14 +10,29 @@ inputs: description: "Gitleaks version to install" required: false default: "v8.28.0" + checkout_repo: + description: "owner/repo to checkout (PR head)" + required: false + default: "${{ github.repository }}" + checkout_ref: + description: "ref/sha to checkout (PR head SHA)" + required: false + default: "" + base_sha: + description: "Base SHA for diff (PR base SHA)" + required: false + default: "" runs: using: "composite" steps: - - name: Checkout repository + - name: Checkout (PR head or default) uses: actions/checkout@v4 with: + repository: ${{ inputs.checkout_repo }} + ref: ${{ inputs.checkout_ref }} fetch-depth: 0 + persist-credentials: false - name: Install Gitleaks shell: bash @@ -73,9 +88,21 @@ runs: shell: bash run: | set -euo pipefail - git fetch origin "${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" --depth=0 || true - BASE_SHA=$(git merge-base HEAD "origin/${GITHUB_BASE_REF}") - echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT" + if [[ -n "${{ inputs.base_sha }}" ]]; then + echo "Using provided BASE_SHA: ${{ inputs.base_sha }}" + echo "base_sha=${{ inputs.base_sha }}" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [[ -n "${GITHUB_BASE_REF:-}" ]]; then + # fetch the base branch tip to resolve its SHA + git fetch --no-tags origin "${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" + BASE_SHA="$(git rev-parse "origin/${GITHUB_BASE_REF}")" + echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT" + else + echo "❌ Base SHA not found; falling back to full scan" + exit 2 + fi - name: Gitleaks detect (full) if: ${{ inputs.scan_mode == 'full' }} From 774855f191e100155c14cbdb646f0766ef74cda0 Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Tue, 14 Oct 2025 12:24:58 +0500 Subject: [PATCH 13/23] feat(gitleaks): use tree-only mode for diff scans Switch diff mode to --no-git to avoid false positives from git history. Full mode remains unchanged for complete repository audits. Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index ee13a6d..1bad6c3 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -82,9 +82,9 @@ runs: echo "⚠️ Config file not found. Proceeding with default rules." fi - - name: Compute base SHA (diff mode) + - name: Compute base SHA (full mode with history) id: base - if: ${{ inputs.scan_mode == 'diff' }} + if: ${{ inputs.scan_mode == 'full' }} shell: bash run: | set -euo pipefail @@ -100,8 +100,7 @@ runs: BASE_SHA="$(git rev-parse "origin/${GITHUB_BASE_REF}")" echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT" else - echo "❌ Base SHA not found; falling back to full scan" - exit 2 + echo "⚠️ Base SHA not found; scanning full repository" fi - name: Gitleaks detect (full) @@ -116,22 +115,17 @@ runs: $CONFIG_ARG \ --source . - - name: Gitleaks detect (diff) + - name: Gitleaks detect (diff / tree-only) if: ${{ inputs.scan_mode == 'diff' }} shell: bash run: | set -euo pipefail CONFIG_ARG="${{ steps.config.outputs.config_arg }}" - BASE_SHA="${{ steps.base.outputs.base_sha }}" - if [[ -z "$BASE_SHA" ]]; then - echo "❌ Base SHA not found; falling back to full scan" - exit 2 - fi - echo "🕵️ Running diff scan $BASE_SHA...HEAD..." - gitleaks detect --no-banner \ + echo "🕵️ Running tree-only diff scan (no git history)..." + gitleaks detect --no-banner --no-git \ --report-format json --report-path gitleaks.json \ $CONFIG_ARG \ - --source . --log-opts "$BASE_SHA...HEAD" + --source . - name: Print findings (always) if: always() From 3879cf23a0a66f2e28e83ae79b65d02c22eba394 Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Tue, 14 Oct 2025 12:43:41 +0500 Subject: [PATCH 14/23] feat(gitleaks): enhance diff scan with PR patch collection Implement a new step to collect added and modified files in PRs, allowing for a more targeted scan of changes. The patch map is generated to filter findings based on added lines, improving the accuracy of results in diff mode. Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 127 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 111 insertions(+), 16 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index 1bad6c3..fbe4db7 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -82,26 +82,78 @@ runs: echo "⚠️ Config file not found. Proceeding with default rules." fi - - name: Compute base SHA (full mode with history) - id: base - if: ${{ inputs.scan_mode == 'full' }} + - name: Collect PR patch (files & added lines) + id: prpatch + if: ${{ inputs.scan_mode == 'diff' }} shell: bash run: | set -euo pipefail - if [[ -n "${{ inputs.base_sha }}" ]]; then - echo "Using provided BASE_SHA: ${{ inputs.base_sha }}" - echo "base_sha=${{ inputs.base_sha }}" >> "$GITHUB_OUTPUT" - exit 0 - fi - if [[ -n "${GITHUB_BASE_REF:-}" ]]; then - # fetch the base branch tip to resolve its SHA + # 1) Определяем HEAD/BASE + HEAD_SHA="$(git rev-parse HEAD)" + BASE_SHA="${{ inputs.base_sha }}" + if [[ -z "${BASE_SHA}" && -n "${GITHUB_BASE_REF:-}" ]]; then git fetch --no-tags origin "${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" BASE_SHA="$(git rev-parse "origin/${GITHUB_BASE_REF}")" - echo "base_sha=$BASE_SHA" >> "$GITHUB_OUTPUT" - else - echo "⚠️ Base SHA not found; scanning full repository" fi + if [[ -z "${BASE_SHA}" ]]; then + echo "No BASE SHA for PR; empty patch." + echo "src_dir=" >> "$GITHUB_OUTPUT" + echo "patch_map=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # 2) Только добавленные/изменённые/переименованные файлы (без удалённых) + mapfile -t FILES < <(git diff --name-only --diff-filter=AMR "${BASE_SHA}" "${HEAD_SHA}") + + # Пустой патч? — выходим мягко. + if (( ${#FILES[@]} == 0 )); then + echo "No changed files." + echo "src_dir=" >> "$GITHUB_OUTPUT" + echo "patch_map=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # 3) Готовим tmp-дерево только из изменённых файлов + SRC_DIR="$(mktemp -d)" + for f in "${FILES[@]}"; do + [[ -f "$f" ]] || continue + mkdir -p "${SRC_DIR}/$(dirname "$f")" + # сохраняем относительные пути + cp "$f" "${SRC_DIR}/$f" + done + + # 4) Собираем «карту добавленных строк» по каждому файлу (для пост-фильтра находок) + # Формат: JSON { "file": [[start,end], ...], ... } + PATCH_JSON="$(mktemp)" + echo '{}' > "$PATCH_JSON" + + # В zero-context диффе каждая добавка даёт @@ -a,b +c,d @@ + # Возьмём только плюсовые hunks (+c,d) + while IFS= read -r file; do + # извлекаем hunks только для этого файла, только добавленные строки + HUNKS="$(git diff --unified=0 "${BASE_SHA}" "${HEAD_SHA}" -- "$file" \ + | awk '/^@@/ {print}' \ + | sed -n 's/.*+\([0-9]\+\),\([0-9]\+\).*/\1 \2/p')" + + # собираем массив диапазонов + RANGES=() + while read -r start count; do + [[ -z "${start:-}" || -z "${count:-}" ]] && continue + end=$((start + count - 1)) + RANGES+=("[${start},${end}]") + done <<< "$HUNKS" + + # пишем в JSON (если есть добавления) + if (( ${#RANGES[@]} > 0 )); then + jq --arg f "$file" --argjson arr "[$(IFS=,; echo "${RANGES[*]}")]" '. + {($f): $arr}' "$PATCH_JSON" > "$PATCH_JSON.tmp" + mv "$PATCH_JSON.tmp" "$PATCH_JSON" + fi + done <<< "$(printf '%s\n' "${FILES[@]}")" + + echo "Collected ${#FILES[@]} files into: ${SRC_DIR}" + echo "src_dir=${SRC_DIR}" >> "$GITHUB_OUTPUT" + echo "patch_map=${PATCH_JSON}" >> "$GITHUB_OUTPUT" - name: Gitleaks detect (full) if: ${{ inputs.scan_mode == 'full' }} @@ -115,17 +167,60 @@ runs: $CONFIG_ARG \ --source . - - name: Gitleaks detect (diff / tree-only) + - name: Gitleaks detect (patch tree-only) if: ${{ inputs.scan_mode == 'diff' }} shell: bash run: | set -euo pipefail + SRC="${{ steps.prpatch.outputs.src_dir }}" CONFIG_ARG="${{ steps.config.outputs.config_arg }}" - echo "🕵️ Running tree-only diff scan (no git history)..." + if [[ -z "${SRC}" ]]; then + echo "No patch to scan." + touch gitleaks.json + echo "[]" > gitleaks.json + exit 0 + fi + + echo "🕵️ Scanning PR patch (tree-only) in ${SRC}..." gitleaks detect --no-banner --no-git \ --report-format json --report-path gitleaks.json \ $CONFIG_ARG \ - --source . + --source "${SRC}" + + - name: Filter findings by added lines (patch) + if: ${{ inputs.scan_mode == 'diff' }} + shell: bash + run: | + set -euo pipefail + PATCH_MAP="${{ steps.prpatch.outputs.patch_map }}" + [[ -f "gitleaks.json" ]] || { echo "No gitleaks.json"; exit 0; } + + # Если нет карты — значит нет добавлений, делаем отчёт пустым + if [[ -z "$PATCH_MAP" || ! -s "$PATCH_MAP" ]]; then + echo "[]" > gitleaks.json + exit 0 + fi + + # JQ-фильтр: пропускаем только совпадения, линия которых входит в любой диапазон файла + jq --argfile map "$PATCH_MAP" ' + def norm: + if type=="object" and has("findings") then .findings + elif type=="array" then . + else [] end; + + norm + | map( + . as $f + | ($f.File // $f.file // $f.Target // $f.Location.File // "") as $file + | ($f.StartLine // $f.Line // $f.Location.StartLine // 0) as $line + | if ($map[$file] // empty) as $ranges + | any($ranges[]; $line >= .[0] and $line <= .[1]) + then $f else empty end + ) + ' gitleaks.json > gitleaks.filtered.json + + mv gitleaks.filtered.json gitleaks.json + echo "Filtered findings to only added lines in PR patch" - name: Print findings (always) if: always() From 00265d39ae9bf4ce20e28e35028b65e546812a8f Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Tue, 14 Oct 2025 12:51:54 +0500 Subject: [PATCH 15/23] output fix Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index fbe4db7..c6045bd 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -192,17 +192,19 @@ runs: shell: bash run: | set -euo pipefail - PATCH_MAP="${{ steps.prpatch.outputs.patch_map }}" + PATCH_MAP_PATH="${{ steps.prpatch.outputs.patch_map }}" [[ -f "gitleaks.json" ]] || { echo "No gitleaks.json"; exit 0; } - # Если нет карты — значит нет добавлений, делаем отчёт пустым - if [[ -z "$PATCH_MAP" || ! -s "$PATCH_MAP" ]]; then + # Нет карты — нет добавлений => пустой отчёт + if [[ -z "${PATCH_MAP_PATH}" || ! -s "${PATCH_MAP_PATH}" ]]; then echo "[]" > gitleaks.json exit 0 fi - # JQ-фильтр: пропускаем только совпадения, линия которых входит в любой диапазон файла - jq --argfile map "$PATCH_MAP" ' + # Читаем карту и прокидываем её как argjson (без --argfile) + MAP_JSON="$(cat "${PATCH_MAP_PATH}")" + + jq --argjson map "${MAP_JSON}" ' def norm: if type=="object" and has("findings") then .findings elif type=="array" then . From f7b3f4a2b3d28ec973863c9aba1c5d8bc627e741 Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Tue, 14 Oct 2025 13:01:54 +0500 Subject: [PATCH 16/23] syntax fix Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index c6045bd..6088ac3 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -89,7 +89,6 @@ runs: run: | set -euo pipefail - # 1) Определяем HEAD/BASE HEAD_SHA="$(git rev-parse HEAD)" BASE_SHA="${{ inputs.base_sha }}" if [[ -z "${BASE_SHA}" && -n "${GITHUB_BASE_REF:-}" ]]; then @@ -103,10 +102,8 @@ runs: exit 0 fi - # 2) Только добавленные/изменённые/переименованные файлы (без удалённых) mapfile -t FILES < <(git diff --name-only --diff-filter=AMR "${BASE_SHA}" "${HEAD_SHA}") - # Пустой патч? — выходим мягко. if (( ${#FILES[@]} == 0 )); then echo "No changed files." echo "src_dir=" >> "$GITHUB_OUTPUT" @@ -114,29 +111,21 @@ runs: exit 0 fi - # 3) Готовим tmp-дерево только из изменённых файлов SRC_DIR="$(mktemp -d)" for f in "${FILES[@]}"; do [[ -f "$f" ]] || continue mkdir -p "${SRC_DIR}/$(dirname "$f")" - # сохраняем относительные пути cp "$f" "${SRC_DIR}/$f" done - # 4) Собираем «карту добавленных строк» по каждому файлу (для пост-фильтра находок) - # Формат: JSON { "file": [[start,end], ...], ... } PATCH_JSON="$(mktemp)" echo '{}' > "$PATCH_JSON" - # В zero-context диффе каждая добавка даёт @@ -a,b +c,d @@ - # Возьмём только плюсовые hunks (+c,d) while IFS= read -r file; do - # извлекаем hunks только для этого файла, только добавленные строки HUNKS="$(git diff --unified=0 "${BASE_SHA}" "${HEAD_SHA}" -- "$file" \ | awk '/^@@/ {print}' \ | sed -n 's/.*+\([0-9]\+\),\([0-9]\+\).*/\1 \2/p')" - # собираем массив диапазонов RANGES=() while read -r start count; do [[ -z "${start:-}" || -z "${count:-}" ]] && continue @@ -144,7 +133,6 @@ runs: RANGES+=("[${start},${end}]") done <<< "$HUNKS" - # пишем в JSON (если есть добавления) if (( ${#RANGES[@]} > 0 )); then jq --arg f "$file" --argjson arr "[$(IFS=,; echo "${RANGES[*]}")]" '. + {($f): $arr}' "$PATCH_JSON" > "$PATCH_JSON.tmp" mv "$PATCH_JSON.tmp" "$PATCH_JSON" @@ -195,13 +183,11 @@ runs: PATCH_MAP_PATH="${{ steps.prpatch.outputs.patch_map }}" [[ -f "gitleaks.json" ]] || { echo "No gitleaks.json"; exit 0; } - # Нет карты — нет добавлений => пустой отчёт if [[ -z "${PATCH_MAP_PATH}" || ! -s "${PATCH_MAP_PATH}" ]]; then echo "[]" > gitleaks.json exit 0 fi - # Читаем карту и прокидываем её как argjson (без --argfile) MAP_JSON="$(cat "${PATCH_MAP_PATH}")" jq --argjson map "${MAP_JSON}" ' From b6183bb644c7c27fc5994957aec7315f00ab003c Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Tue, 14 Oct 2025 13:09:33 +0500 Subject: [PATCH 17/23] stdoutfix Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index 6088ac3..a09dfcc 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -210,6 +210,82 @@ runs: mv gitleaks.filtered.json gitleaks.json echo "Filtered findings to only added lines in PR patch" + - name: Print findings to stdout + if: ${{ inputs.scan_mode == 'diff' }} + shell: bash + env: + SRC_DIR: ${{ steps.prpatch.outputs.src_dir }} + run: | + set -euo pipefail + [[ -f gitleaks.json ]] || { echo "No gitleaks.json"; exit 0; } + + JQ_NORM=' + def norm: + if type=="object" and has("findings") then .findings + elif type=="array" then . + else [] end; + norm + | map({ + rule: (.RuleID // .Rule // .Description // ""), + file: (.File // .file // .Target // .Location.File // ""), + line: (.StartLine // .Line // .Location.StartLine // 0), + commit: (.Commit // .commit // "") + }) + ' + + COUNT="$(jq -r "$JQ_NORM | length" gitleaks.json)" + echo "Findings (stdout): $COUNT" + + if [[ "$COUNT" -eq 0 ]]; then + echo "No leaks found in PR patch." + exit 0 + fi + + jq -r "$JQ_NORM | .[] | [.rule, .file, (.line|tostring), .commit] | @tsv" gitleaks.json \ + | while IFS=$'\t' read -r rule file line commit; do + rel="${file#${SRC_DIR}/}" + printf '• [%s] %s:%s %s\n' "$rule" "${rel:-$file}" "$line" "${commit:-''}" + done + + - name: Generate PR summary + if: ${{ inputs.scan_mode == 'diff' }} + shell: bash + env: + SRC_DIR: ${{ steps.prpatch.outputs.src_dir }} + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + run: | + set -euo pipefail + [[ -f gitleaks.json ]] || { echo "No gitleaks.json produced"; exit 0; } + + JQ_NORM=' + def norm: + if type=="object" and has("findings") then .findings + elif type=="array" then . + else [] end; + norm + | map({ + rule: (.RuleID // .Rule // .Description // ""), + file: (.File // .file // .Target // .Location.File // ""), + line: (.StartLine // .Line // .Location.StartLine // 0), + commit: (.Commit // .commit // "") + }) + ' + + COUNT="$(jq -r "$JQ_NORM | length" gitleaks.json)" + echo "Findings: $COUNT" + + { + echo "### Secret findings :closed_lock_with_key:" + echo + echo "| Rule | File:Line | Commit | Link |" + echo "|---|---|---|---|" + jq -r "$JQ_NORM | .[] | [.rule, .file, (.line|tostring), .commit] | @tsv" gitleaks.json \ + | while IFS=$'\t' read -r rule file line commit; do + rel="${file#${SRC_DIR}/}" + printf '| `%s` | `%s:%s` | `%s` | %s |\n' "$rule" "${rel:-$file}" "$line" "${commit:--}" "-" + done + } >> "$GITHUB_STEP_SUMMARY" + - name: Print findings (always) if: always() shell: bash From a127849753ac302af8c54d90092f9b5e807fab1f Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Tue, 14 Oct 2025 13:35:53 +0500 Subject: [PATCH 18/23] output fix Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 192 ++++++++++++++++---------------------------- 1 file changed, 67 insertions(+), 125 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index a09dfcc..4649e32 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -40,33 +40,20 @@ runs: set -euo pipefail ver="${{ inputs.gitleaks_version }}" file_ver="${ver#v}" - arch="$(uname -m)" case "$arch" in x86_64|amd64) pkg_arch="linux_x64" ;; aarch64|arm64) pkg_arch="linux_arm64" ;; *) echo "Unsupported arch: $arch"; exit 1 ;; esac - base="https://github.com/gitleaks/gitleaks/releases/download/${ver}" tgz="gitleaks_${file_ver}_${pkg_arch}.tar.gz" - - echo "📥 Installing Gitleaks $ver..." curl -sSL "$base/$tgz" -o gitleaks.tgz tar -xzf gitleaks.tgz gitleaks chmod +x gitleaks - - install_dir="$HOME/.local/bin" - mkdir -p "$install_dir" + install_dir="$HOME/.local/bin"; mkdir -p "$install_dir" mv gitleaks "$install_dir/gitleaks" echo "$install_dir" >> "$GITHUB_PATH" - "$install_dir/gitleaks" version - - - name: Sanity check PATH (should find gitleaks) - shell: bash - run: | - set -euo pipefail - command -v gitleaks gitleaks version - name: Check for optional config @@ -82,6 +69,7 @@ runs: echo "⚠️ Config file not found. Proceeding with default rules." fi + # --- DIFF CONTEXT COLLECTION (only when scan_mode=diff) --- - name: Collect PR patch (files & added lines) id: prpatch if: ${{ inputs.scan_mode == 'diff' }} @@ -95,6 +83,10 @@ runs: git fetch --no-tags origin "${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" BASE_SHA="$(git rev-parse "origin/${GITHUB_BASE_REF}")" fi + + echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT" + echo "base_sha=${BASE_SHA}" >> "$GITHUB_OUTPUT" + if [[ -z "${BASE_SHA}" ]]; then echo "No BASE SHA for PR; empty patch." echo "src_dir=" >> "$GITHUB_OUTPUT" @@ -103,7 +95,6 @@ runs: fi mapfile -t FILES < <(git diff --name-only --diff-filter=AMR "${BASE_SHA}" "${HEAD_SHA}") - if (( ${#FILES[@]} == 0 )); then echo "No changed files." echo "src_dir=" >> "$GITHUB_OUTPUT" @@ -134,22 +125,22 @@ runs: done <<< "$HUNKS" if (( ${#RANGES[@]} > 0 )); then - jq --arg f "$file" --argjson arr "[$(IFS=,; echo "${RANGES[*]}")]" '. + {($f): $arr}' "$PATCH_JSON" > "$PATCH_JSON.tmp" + jq --arg f "$file" --argjson arr "[$(IFS=,; echo "${RANGES[*]}")]" \ + '. + {($f): $arr}' "$PATCH_JSON" > "$PATCH_JSON.tmp" mv "$PATCH_JSON.tmp" "$PATCH_JSON" fi done <<< "$(printf '%s\n' "${FILES[@]}")" - echo "Collected ${#FILES[@]} files into: ${SRC_DIR}" echo "src_dir=${SRC_DIR}" >> "$GITHUB_OUTPUT" echo "patch_map=${PATCH_JSON}" >> "$GITHUB_OUTPUT" + # --- DETECT --- - name: Gitleaks detect (full) if: ${{ inputs.scan_mode == 'full' }} shell: bash run: | set -euo pipefail CONFIG_ARG="${{ steps.config.outputs.config_arg }}" - echo "🕵️ Running full scan..." gitleaks detect --no-banner \ --report-format json --report-path gitleaks.json \ $CONFIG_ARG \ @@ -163,25 +154,22 @@ runs: SRC="${{ steps.prpatch.outputs.src_dir }}" CONFIG_ARG="${{ steps.config.outputs.config_arg }}" if [[ -z "${SRC}" ]]; then - echo "No patch to scan." - touch gitleaks.json echo "[]" > gitleaks.json exit 0 fi - - echo "🕵️ Scanning PR patch (tree-only) in ${SRC}..." gitleaks detect --no-banner --no-git \ --report-format json --report-path gitleaks.json \ $CONFIG_ARG \ --source "${SRC}" + # --- FILTER TO ADDED LINES (diff only) --- - name: Filter findings by added lines (patch) if: ${{ inputs.scan_mode == 'diff' }} shell: bash run: | set -euo pipefail PATCH_MAP_PATH="${{ steps.prpatch.outputs.patch_map }}" - [[ -f "gitleaks.json" ]] || { echo "No gitleaks.json"; exit 0; } + [[ -f "gitleaks.json" ]] || { echo "[]">gitleaks.json; exit 0; } if [[ -z "${PATCH_MAP_PATH}" || ! -s "${PATCH_MAP_PATH}" ]]; then echo "[]" > gitleaks.json @@ -191,12 +179,9 @@ runs: MAP_JSON="$(cat "${PATCH_MAP_PATH}")" jq --argjson map "${MAP_JSON}" ' - def norm: - if type=="object" and has("findings") then .findings - elif type=="array" then . - else [] end; - - norm + def arr: if type=="object" and has("findings") then .findings + elif type=="array" then . else [] end; + arr | map( . as $f | ($f.File // $f.file // $f.Target // $f.Location.File // "") as $file @@ -208,99 +193,27 @@ runs: ' gitleaks.json > gitleaks.filtered.json mv gitleaks.filtered.json gitleaks.json - echo "Filtered findings to only added lines in PR patch" + echo "Filtered to added lines." - - name: Print findings to stdout - if: ${{ inputs.scan_mode == 'diff' }} - shell: bash - env: - SRC_DIR: ${{ steps.prpatch.outputs.src_dir }} - run: | - set -euo pipefail - [[ -f gitleaks.json ]] || { echo "No gitleaks.json"; exit 0; } - - JQ_NORM=' - def norm: - if type=="object" and has("findings") then .findings - elif type=="array" then . - else [] end; - norm - | map({ - rule: (.RuleID // .Rule // .Description // ""), - file: (.File // .file // .Target // .Location.File // ""), - line: (.StartLine // .Line // .Location.StartLine // 0), - commit: (.Commit // .commit // "") - }) - ' - - COUNT="$(jq -r "$JQ_NORM | length" gitleaks.json)" - echo "Findings (stdout): $COUNT" - - if [[ "$COUNT" -eq 0 ]]; then - echo "No leaks found in PR patch." - exit 0 - fi - - jq -r "$JQ_NORM | .[] | [.rule, .file, (.line|tostring), .commit] | @tsv" gitleaks.json \ - | while IFS=$'\t' read -r rule file line commit; do - rel="${file#${SRC_DIR}/}" - printf '• [%s] %s:%s %s\n' "$rule" "${rel:-$file}" "$line" "${commit:-''}" - done - - - name: Generate PR summary - if: ${{ inputs.scan_mode == 'diff' }} + # --- SINGLE, UNIFIED OUTPUT (ALWAYS) --- + - name: Print findings (always) + if: always() shell: bash env: SRC_DIR: ${{ steps.prpatch.outputs.src_dir }} + HEAD_SHA: ${{ steps.prpatch.outputs.head_sha }} REPO_URL: ${{ github.server_url }}/${{ github.repository }} - run: | - set -euo pipefail - [[ -f gitleaks.json ]] || { echo "No gitleaks.json produced"; exit 0; } - - JQ_NORM=' - def norm: - if type=="object" and has("findings") then .findings - elif type=="array" then . - else [] end; - norm - | map({ - rule: (.RuleID // .Rule // .Description // ""), - file: (.File // .file // .Target // .Location.File // ""), - line: (.StartLine // .Line // .Location.StartLine // 0), - commit: (.Commit // .commit // "") - }) - ' - - COUNT="$(jq -r "$JQ_NORM | length" gitleaks.json)" - echo "Findings: $COUNT" - - { - echo "### Secret findings :closed_lock_with_key:" - echo - echo "| Rule | File:Line | Commit | Link |" - echo "|---|---|---|---|" - jq -r "$JQ_NORM | .[] | [.rule, .file, (.line|tostring), .commit] | @tsv" gitleaks.json \ - | while IFS=$'\t' read -r rule file line commit; do - rel="${file#${SRC_DIR}/}" - printf '| `%s` | `%s:%s` | `%s` | %s |\n' "$rule" "${rel:-$file}" "$line" "${commit:--}" "-" - done - } >> "$GITHUB_STEP_SUMMARY" - - - name: Print findings (always) - if: always() - shell: bash run: | set -euo pipefail [[ -f gitleaks.json ]] || { echo "No gitleaks.json produced"; exit 0; } LIMIT=200 - REPO_URL="${{ github.server_url }}/${{ github.repository }}" JQ_FILTER=' def norm: { - file: (.File // .file // .Target // .Location.File // ""), - line: (.StartLine // .Line // .Location.StartLine // 0), - rule: (.RuleID // .Rule // .Description // ""), + file: (.File // .file // .Target // .Location.File // ""), + line: (.StartLine // .Line // .Location.StartLine // 0), + rule: (.RuleID // .Rule // .Description // ""), commit: (.Commit // .commit // "") }; (if type=="object" and has("findings") then .findings @@ -310,27 +223,56 @@ runs: COUNT=$(jq -r 'if type=="object" and has("findings") then (.findings|length) elif type=="array" then length else 0 end' gitleaks.json) - echo "Findings: $COUNT" - echo "### Secret findings :closed_lock_with_key:" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "| Rule | File:Line | Commit | Link |" >> "$GITHUB_STEP_SUMMARY" - echo "|---|---|---|---|" >> "$GITHUB_STEP_SUMMARY" - jq -r "$JQ_FILTER | [.rule, (.file+\":\"+(.line|tostring)), (.commit[0:7]), - (if (.commit|length)>0 and (.file|length)>0 and (.line|tonumber)>0 - then \"$REPO_URL/blob/\"+.commit+\"/\"+.file+\"#L\"+(.line|tostring) - else \"\" end)] - | @tsv" gitleaks.json \ + # stdout (up to LIMIT) + jq -r "$JQ_FILTER | [.rule, .file, (.line|tostring), (.commit // \"\")] | @tsv" gitleaks.json \ | head -n "$LIMIT" \ - | while IFS=$'\t' read -r rule loc sha link; do - printf '• %s %s %s %s\n' "[$rule]" "$loc" "$sha" "$link" - printf '| `%s` | `%s` | `%s` | %s |\n' "$rule" "$loc" "$sha" "${link:-"-"}" >> "$GITHUB_STEP_SUMMARY" + | while IFS=$'\t' read -r rule file line commit; do + # repo-relative path: strip temp tree prefix for diff mode + rel="$file" + if [[ -n "${SRC_DIR:-}" && "$file" == "${SRC_DIR%/}/"* ]]; then + rel="${file#${SRC_DIR%/}/}" + fi + sha="${commit:-${HEAD_SHA:-}}" + link="" + if [[ -n "$sha" && -n "$rel" && "$line" =~ ^[0-9]+$ ]]; then + link="${REPO_URL}/blob/${sha}/${rel}#L${line}" + fi + printf '• %s %s %s %s\n' "[$rule]" "${rel}:${line}" "${sha:0:7}" "${link}" done - if (( COUNT > LIMIT )); then - MORE=$((COUNT - LIMIT)) - echo "... and $MORE more (see artifact gitleaks-report)." | tee -a "$GITHUB_STEP_SUMMARY" + # summary (single table) + { + echo "### Gitleaks findings (showing up to $LIMIT) :closed_lock_with_key:" + echo + echo "| Rule | File:Line | Commit | Link |" + echo "|---|---|---|---|" + jq -r "$JQ_FILTER | [.rule, .file, (.line|tostring), (.commit // \"\")] | @tsv" gitleaks.json \ + | head -n "$LIMIT" \ + | while IFS=$'\t' read -r rule file line commit; do + rel="$file" + if [[ -n "${SRC_DIR:-}" && "$file" == "${SRC_DIR%/}/"* ]]; then + rel="${file#${SRC_DIR%/}/}" + fi + sha="${commit:-${HEAD_SHA:-}}" + link="-" + if [[ -n "$sha" && -n "$rel" && "$line" =~ ^[0-9]+$ ]]; then + link="${REPO_URL}/blob/${sha}/${rel}#L${line}" + fi + printf '| `%s` | `%s:%s` | `%s` | %s |\n' "$rule" "$rel" "$line" "${sha:0:7}" "$link" + done + + if (( COUNT > LIMIT )); then + MORE=$((COUNT - LIMIT)) + echo + echo "_... and $MORE more (see artifact **gitleaks-report**)._" + fi + } >> "$GITHUB_STEP_SUMMARY" + + # non-zero exit if any findings remain + if (( COUNT > 0 )); then + exit 1 fi - name: Upload report (always) From 3ac6cf34ed41ca392351eb1242e475f535c0069e Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Tue, 14 Oct 2025 13:51:25 +0500 Subject: [PATCH 19/23] clean Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index 4649e32..afdd7a9 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -69,7 +69,6 @@ runs: echo "⚠️ Config file not found. Proceeding with default rules." fi - # --- DIFF CONTEXT COLLECTION (only when scan_mode=diff) --- - name: Collect PR patch (files & added lines) id: prpatch if: ${{ inputs.scan_mode == 'diff' }} @@ -134,7 +133,6 @@ runs: echo "src_dir=${SRC_DIR}" >> "$GITHUB_OUTPUT" echo "patch_map=${PATCH_JSON}" >> "$GITHUB_OUTPUT" - # --- DETECT --- - name: Gitleaks detect (full) if: ${{ inputs.scan_mode == 'full' }} shell: bash @@ -162,7 +160,6 @@ runs: $CONFIG_ARG \ --source "${SRC}" - # --- FILTER TO ADDED LINES (diff only) --- - name: Filter findings by added lines (patch) if: ${{ inputs.scan_mode == 'diff' }} shell: bash @@ -195,7 +192,6 @@ runs: mv gitleaks.filtered.json gitleaks.json echo "Filtered to added lines." - # --- SINGLE, UNIFIED OUTPUT (ALWAYS) --- - name: Print findings (always) if: always() shell: bash @@ -225,11 +221,9 @@ runs: elif type=="array" then length else 0 end' gitleaks.json) echo "Findings: $COUNT" - # stdout (up to LIMIT) jq -r "$JQ_FILTER | [.rule, .file, (.line|tostring), (.commit // \"\")] | @tsv" gitleaks.json \ | head -n "$LIMIT" \ | while IFS=$'\t' read -r rule file line commit; do - # repo-relative path: strip temp tree prefix for diff mode rel="$file" if [[ -n "${SRC_DIR:-}" && "$file" == "${SRC_DIR%/}/"* ]]; then rel="${file#${SRC_DIR%/}/}" @@ -242,9 +236,8 @@ runs: printf '• %s %s %s %s\n' "[$rule]" "${rel}:${line}" "${sha:0:7}" "${link}" done - # summary (single table) { - echo "### Gitleaks findings (showing up to $LIMIT) :closed_lock_with_key:" + echo "### Secret findings :closed_lock_with_key:" echo echo "| Rule | File:Line | Commit | Link |" echo "|---|---|---|---|" @@ -270,7 +263,6 @@ runs: fi } >> "$GITHUB_STEP_SUMMARY" - # non-zero exit if any findings remain if (( COUNT > 0 )); then exit 1 fi From bd128896c28343999740b66b42b9a6705858ca97 Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Tue, 14 Oct 2025 16:50:46 +0500 Subject: [PATCH 20/23] README update Signed-off-by: Roman Trofimenkov --- gitleaks/README.md | 120 +++++++++++++++++++-------------------------- 1 file changed, 50 insertions(+), 70 deletions(-) diff --git a/gitleaks/README.md b/gitleaks/README.md index 8ca21c9..c78fad7 100644 --- a/gitleaks/README.md +++ b/gitleaks/README.md @@ -1,105 +1,85 @@ # 🕵️ Gitleaks GitHub Action -## 📌 Что это такое +## 📌 Purpose -Этот GitHub Actions workflow автоматически запускает [Gitleaks](https://github.com/gitleaks/gitleaks) — инструмент для поиска секретов в коде (токены, ключи, пароли и т.п.). Он сканирует изменения либо полностью, либо в виде diff-а для Pull Request'ов. +GitHub Action for automatic secret scanning in code using [Gitleaks](https://github.com/gitleaks/gitleaks). Prevents leakage of tokens, keys, passwords, and other secrets into the repository. -Цель — предотвратить утечку секретов в публичные или приватные репозитории до попадания кода в основную ветку. +## ⚙️ Operation Modes ---- +### Diff scan (primary mode) +- **Automatically integrated** into general PR validation +- Scans **only changed files** and **only added lines** in PR +- Does not analyze commit history — eliminates false positives +- Does not check unchanged files — focuses on new code +- Uses `--no-git` for fast scanning -## ⚙️ Как работает +### Full scan (additional mode) +- Runs on schedule or manually +- Scans the entire repository +- Suitable for periodic security audits -Workflow состоит из двух режимов: +## 🚀 Usage -- **Diff scan** (режим по умолчанию для PR): - - Запускается при создании или обновлении Pull Request. - - Сканирует только изменения между текущей веткой и целевой (base) веткой. +### Automatic Integration -- **Full scan**: - - Запускается по расписанию или вручную. - - Сканирует весь репозиторий. +Diff scan is already integrated into general PR validation and works automatically. No additional configuration required. -Компоненты: +### Full Scanning (optional) -- 📄 `gitleaks.yml`: основной workflow с тремя триггерами — `pull_request`, `schedule`, `workflow_dispatch`. -- ⚙️ `deckhouse/modules-actions/gitleaks@feature/gitleaks`: composite action, устанавливающая и запускающая Gitleaks. -- 🛠 `gitleaks.toml` (опционально): конфигурационный файл правил для Gitleaks в корне репозитория. Если отсутствует — используются встроенные правила Gitleaks. - ---- - -## 🚀 Как подключить - -### 1. (Опционально) Добавьте конфиг Gitleaks - -Если вы хотите использовать собственные правила сканирования, создайте файл `gitleaks.toml` в **корне** вашего репозитория. Пример можно взять из официального репозитория Gitleaks: -📎 - -**Если файл отсутствует:** Gitleaks будет использовать встроенные дефолтные правила. - -### 2. Добавьте workflow-файл - -Создайте файл `.github/workflows/gitleaks.yml` со следующим содержимым: +If you need full scan, add to `.github/workflows/security-scan.yml`: ```yaml -name: Gitleaks +name: Security Scan on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] schedule: - - cron: "0 2 * * *" # ежедневно в 02:00 UTC - workflow_dispatch: {} # ручной запуск + - cron: "0 2 * * *" # daily at 02:00 UTC + workflow_dispatch: {} # manual trigger permissions: contents: read -concurrency: - group: "gitleaks-${{ github.ref }}" - cancel-in-progress: false - jobs: - gitleaks-diff: - if: ${{ github.event_name == 'pull_request' }} - runs-on: ubuntu-latest - steps: - - uses: deckhouse/modules-actions/gitleaks@feature/gitleaks - with: - scan_mode: diff - # gitleaks_version: v8.28.0 # опционально, по умолчанию v8.28.0 - gitleaks-full: - if: ${{ github.event_name != 'pull_request' }} runs-on: ubuntu-latest - continue-on-error: true steps: - - uses: deckhouse/modules-actions/gitleaks@feature/gitleaks + - uses: deckhouse/modules-actions/gitleaks@main with: scan_mode: full - # gitleaks_version: v8.28.0 # опционально, по умолчанию v8.28.0 ``` ---- +### Configuration (optional) -## 📝 Входные параметры (Inputs) +To configure scanning rules, create `gitleaks.toml` in the repository root: +📎 -| Параметр | Описание | Обязательный | Значение по умолчанию | -|----------|----------|--------------|----------------------| -| `scan_mode` | Режим сканирования: `full` или `diff` | Нет | `full` | -| `gitleaks_version` | Версия Gitleaks для установки | Нет | `v8.28.0` | +Without config, built-in Gitleaks rules are used. -### Примеры использования +## 📝 Parameters -**Использование конкретной версии Gitleaks:** +| Parameter | Description | Default | +|-----------|-------------|---------| +| `scan_mode` | Mode: `diff` or `full` | `full` | +| `gitleaks_version` | Gitleaks version | `v8.28.0` | +| `checkout_repo` | Repository for checkout | `${{ github.repository }}` | +| `checkout_ref` | SHA for checkout | `""` | +| `base_sha` | Base SHA for diff | `""` | -```yaml -- uses: deckhouse/modules-actions/gitleaks@feature/gitleaks - with: - scan_mode: full - gitleaks_version: v8.20.0 -``` +## 🔧 Technical Features -**Минимальная конфигурация (используются дефолты):** +### Patch-based scanning (diff mode) +- Collects only changed files from PR +- Creates temporary tree with these files +- Scans without git history (`--no-git`) +- Filters findings only by added lines -```yaml -- uses: deckhouse/modules-actions/gitleaks@feature/gitleaks +### Benefits +- **Minimal false positives** — doesn't find deleted secrets +- **Fast operation** — scans only changes +- **Accuracy** — focuses on new code in PR + +## 🐛 Troubleshooting + +**Many false positives**: use `diff` mode for PR checks +**Workflow fails**: check `contents: read` permissions +**Need configuration**: create `gitleaks.toml` in repository root From 8f4aa00320b3eca05f5d57b78b328e44205003a0 Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Fri, 17 Oct 2025 23:43:22 +0500 Subject: [PATCH 21/23] fix(gitleaks): update diff command to use three-dot syntax for accurate file changes Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index afdd7a9..23bfe81 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -93,7 +93,7 @@ runs: exit 0 fi - mapfile -t FILES < <(git diff --name-only --diff-filter=AMR "${BASE_SHA}" "${HEAD_SHA}") + mapfile -t FILES < <(git diff --name-only --diff-filter=AMR "${BASE_SHA}...${HEAD_SHA}") if (( ${#FILES[@]} == 0 )); then echo "No changed files." echo "src_dir=" >> "$GITHUB_OUTPUT" @@ -112,7 +112,7 @@ runs: echo '{}' > "$PATCH_JSON" while IFS= read -r file; do - HUNKS="$(git diff --unified=0 "${BASE_SHA}" "${HEAD_SHA}" -- "$file" \ + HUNKS="$(git diff --unified=0 "${BASE_SHA}...${HEAD_SHA}" -- "$file" \ | awk '/^@@/ {print}' \ | sed -n 's/.*+\([0-9]\+\),\([0-9]\+\).*/\1 \2/p')" @@ -163,6 +163,8 @@ runs: - name: Filter findings by added lines (patch) if: ${{ inputs.scan_mode == 'diff' }} shell: bash + env: + SRC_DIR: ${{ steps.prpatch.outputs.src_dir }} run: | set -euo pipefail PATCH_MAP_PATH="${{ steps.prpatch.outputs.patch_map }}" @@ -174,16 +176,19 @@ runs: fi MAP_JSON="$(cat "${PATCH_MAP_PATH}")" + SRC_PREFIX="${SRC_DIR%/}/" - jq --argjson map "${MAP_JSON}" ' + jq --argjson map "${MAP_JSON}" --arg src "${SRC_PREFIX}" ' def arr: if type=="object" and has("findings") then .findings elif type=="array" then . else [] end; + def strip($p; s): if s|startswith($p) then s[($p|length):] else s end; + arr | map( . as $f - | ($f.File // $f.file // $f.Target // $f.Location.File // "") as $file + | (strip($src; ($f.File // $f.file // $f.Target // $f.Location.File // ""))) as $rel | ($f.StartLine // $f.Line // $f.Location.StartLine // 0) as $line - | if ($map[$file] // empty) as $ranges + | if ($map[$rel] // []) as $ranges | any($ranges[]; $line >= .[0] and $line <= .[1]) then $f else empty end ) From 7f7d85d42e4b06c840502f0f1714a6987e0866e6 Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Sat, 18 Oct 2025 00:18:44 +0500 Subject: [PATCH 22/23] fix --- gitleaks/action.yml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index 23bfe81..15b2694 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -78,9 +78,22 @@ runs: HEAD_SHA="$(git rev-parse HEAD)" BASE_SHA="${{ inputs.base_sha }}" - if [[ -z "${BASE_SHA}" && -n "${GITHUB_BASE_REF:-}" ]]; then - git fetch --no-tags origin "${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" - BASE_SHA="$(git rev-parse "origin/${GITHUB_BASE_REF}")" + BASE_REPO_FULL="${{ github.event.pull_request.base.repo.full_name }}" + BASE_REF="${{ github.event.pull_request.base.ref }}" + BASE_REMOTE_URL="${{ github.server_url }}/${BASE_REPO_FULL}.git" + git remote add base "$BASE_REMOTE_URL" 2>/dev/null || git remote set-url base "$BASE_REMOTE_URL" + + if [[ -z "${BASE_SHA}" && -n "${BASE_REF}" ]]; then + git fetch --no-tags base "${BASE_REF}:${BASE_REF}" + BASE_SHA="$(git rev-parse "FETCH_HEAD")" + fi + + if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then + if [[ -n "${BASE_REF}" ]]; then + git fetch --no-tags base "${BASE_REF}:${BASE_REF}" + else + git fetch --no-tags base "${BASE_SHA}" || true + fi fi echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT" From 4253cd0cecd5d200244b7dc6f07ade5ff1215f07 Mon Sep 17 00:00:00 2001 From: Roman Trofimenkov Date: Mon, 20 Oct 2025 15:13:22 +0500 Subject: [PATCH 23/23] refactor Signed-off-by: Roman Trofimenkov --- gitleaks/action.yml | 196 ++++++++------------------------------------ 1 file changed, 34 insertions(+), 162 deletions(-) diff --git a/gitleaks/action.yml b/gitleaks/action.yml index 15b2694..2dff017 100644 --- a/gitleaks/action.yml +++ b/gitleaks/action.yml @@ -1,5 +1,5 @@ name: "Gitleaks scan" -description: "Run Gitleaks in full or diff mode (composite action, configurable version)" +description: "Run Gitleaks in full or diff mode (composite action, pull_request_target compatible)" inputs: scan_mode: @@ -10,27 +10,22 @@ inputs: description: "Gitleaks version to install" required: false default: "v8.28.0" - checkout_repo: - description: "owner/repo to checkout (PR head)" - required: false - default: "${{ github.repository }}" - checkout_ref: - description: "ref/sha to checkout (PR head SHA)" - required: false - default: "" - base_sha: - description: "Base SHA for diff (PR base SHA)" - required: false - default: "" runs: using: "composite" steps: - - name: Checkout (PR head or default) + - name: Checkout repository uses: actions/checkout@v4 + if: ${{ inputs.scan_mode == 'full' }} with: - repository: ${{ inputs.checkout_repo }} - ref: ${{ inputs.checkout_ref }} + fetch-depth: 0 + persist-credentials: false + + - name: Checkout PR merge commit + uses: actions/checkout@v4 + if: ${{ inputs.scan_mode == 'diff' }} + with: + ref: refs/pull/${{ github.event.number }}/merge fetch-depth: 0 persist-credentials: false @@ -69,153 +64,38 @@ runs: echo "⚠️ Config file not found. Proceeding with default rules." fi - - name: Collect PR patch (files & added lines) - id: prpatch - if: ${{ inputs.scan_mode == 'diff' }} - shell: bash - run: | - set -euo pipefail - - HEAD_SHA="$(git rev-parse HEAD)" - BASE_SHA="${{ inputs.base_sha }}" - BASE_REPO_FULL="${{ github.event.pull_request.base.repo.full_name }}" - BASE_REF="${{ github.event.pull_request.base.ref }}" - BASE_REMOTE_URL="${{ github.server_url }}/${BASE_REPO_FULL}.git" - git remote add base "$BASE_REMOTE_URL" 2>/dev/null || git remote set-url base "$BASE_REMOTE_URL" - - if [[ -z "${BASE_SHA}" && -n "${BASE_REF}" ]]; then - git fetch --no-tags base "${BASE_REF}:${BASE_REF}" - BASE_SHA="$(git rev-parse "FETCH_HEAD")" - fi - - if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then - if [[ -n "${BASE_REF}" ]]; then - git fetch --no-tags base "${BASE_REF}:${BASE_REF}" - else - git fetch --no-tags base "${BASE_SHA}" || true - fi - fi - - echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT" - echo "base_sha=${BASE_SHA}" >> "$GITHUB_OUTPUT" - - if [[ -z "${BASE_SHA}" ]]; then - echo "No BASE SHA for PR; empty patch." - echo "src_dir=" >> "$GITHUB_OUTPUT" - echo "patch_map=" >> "$GITHUB_OUTPUT" - exit 0 - fi - - mapfile -t FILES < <(git diff --name-only --diff-filter=AMR "${BASE_SHA}...${HEAD_SHA}") - if (( ${#FILES[@]} == 0 )); then - echo "No changed files." - echo "src_dir=" >> "$GITHUB_OUTPUT" - echo "patch_map=" >> "$GITHUB_OUTPUT" - exit 0 - fi - - SRC_DIR="$(mktemp -d)" - for f in "${FILES[@]}"; do - [[ -f "$f" ]] || continue - mkdir -p "${SRC_DIR}/$(dirname "$f")" - cp "$f" "${SRC_DIR}/$f" - done - - PATCH_JSON="$(mktemp)" - echo '{}' > "$PATCH_JSON" - - while IFS= read -r file; do - HUNKS="$(git diff --unified=0 "${BASE_SHA}...${HEAD_SHA}" -- "$file" \ - | awk '/^@@/ {print}' \ - | sed -n 's/.*+\([0-9]\+\),\([0-9]\+\).*/\1 \2/p')" - - RANGES=() - while read -r start count; do - [[ -z "${start:-}" || -z "${count:-}" ]] && continue - end=$((start + count - 1)) - RANGES+=("[${start},${end}]") - done <<< "$HUNKS" - - if (( ${#RANGES[@]} > 0 )); then - jq --arg f "$file" --argjson arr "[$(IFS=,; echo "${RANGES[*]}")]" \ - '. + {($f): $arr}' "$PATCH_JSON" > "$PATCH_JSON.tmp" - mv "$PATCH_JSON.tmp" "$PATCH_JSON" - fi - done <<< "$(printf '%s\n' "${FILES[@]}")" - - echo "src_dir=${SRC_DIR}" >> "$GITHUB_OUTPUT" - echo "patch_map=${PATCH_JSON}" >> "$GITHUB_OUTPUT" - - - name: Gitleaks detect (full) + - name: Gitleaks scan (full) if: ${{ inputs.scan_mode == 'full' }} shell: bash run: | set -euo pipefail CONFIG_ARG="${{ steps.config.outputs.config_arg }}" - gitleaks detect --no-banner \ + gitleaks detect --no-banner --redact \ --report-format json --report-path gitleaks.json \ $CONFIG_ARG \ --source . - - name: Gitleaks detect (patch tree-only) + - name: Gitleaks scan (diff) if: ${{ inputs.scan_mode == 'diff' }} shell: bash run: | set -euo pipefail - SRC="${{ steps.prpatch.outputs.src_dir }}" CONFIG_ARG="${{ steps.config.outputs.config_arg }}" - if [[ -z "${SRC}" ]]; then - echo "[]" > gitleaks.json - exit 0 - fi - gitleaks detect --no-banner --no-git \ + + BASE_COMMIT=$(git rev-parse HEAD^1) + echo "Base commit: $BASE_COMMIT" + echo "Scanning range: ${BASE_COMMIT}..HEAD" + + gitleaks detect --no-banner --redact \ --report-format json --report-path gitleaks.json \ + --log-opts="${BASE_COMMIT}..HEAD" \ $CONFIG_ARG \ - --source "${SRC}" - - - name: Filter findings by added lines (patch) - if: ${{ inputs.scan_mode == 'diff' }} - shell: bash - env: - SRC_DIR: ${{ steps.prpatch.outputs.src_dir }} - run: | - set -euo pipefail - PATCH_MAP_PATH="${{ steps.prpatch.outputs.patch_map }}" - [[ -f "gitleaks.json" ]] || { echo "[]">gitleaks.json; exit 0; } - - if [[ -z "${PATCH_MAP_PATH}" || ! -s "${PATCH_MAP_PATH}" ]]; then - echo "[]" > gitleaks.json - exit 0 - fi - - MAP_JSON="$(cat "${PATCH_MAP_PATH}")" - SRC_PREFIX="${SRC_DIR%/}/" - - jq --argjson map "${MAP_JSON}" --arg src "${SRC_PREFIX}" ' - def arr: if type=="object" and has("findings") then .findings - elif type=="array" then . else [] end; - def strip($p; s): if s|startswith($p) then s[($p|length):] else s end; - - arr - | map( - . as $f - | (strip($src; ($f.File // $f.file // $f.Target // $f.Location.File // ""))) as $rel - | ($f.StartLine // $f.Line // $f.Location.StartLine // 0) as $line - | if ($map[$rel] // []) as $ranges - | any($ranges[]; $line >= .[0] and $line <= .[1]) - then $f else empty end - ) - ' gitleaks.json > gitleaks.filtered.json - - mv gitleaks.filtered.json gitleaks.json - echo "Filtered to added lines." + --source . - - name: Print findings (always) + - name: Print findings if: always() shell: bash env: - SRC_DIR: ${{ steps.prpatch.outputs.src_dir }} - HEAD_SHA: ${{ steps.prpatch.outputs.head_sha }} REPO_URL: ${{ github.server_url }}/${{ github.repository }} run: | set -euo pipefail @@ -242,36 +122,28 @@ runs: jq -r "$JQ_FILTER | [.rule, .file, (.line|tostring), (.commit // \"\")] | @tsv" gitleaks.json \ | head -n "$LIMIT" \ | while IFS=$'\t' read -r rule file line commit; do - rel="$file" - if [[ -n "${SRC_DIR:-}" && "$file" == "${SRC_DIR%/}/"* ]]; then - rel="${file#${SRC_DIR%/}/}" - fi - sha="${commit:-${HEAD_SHA:-}}" + sha="${commit}" link="" - if [[ -n "$sha" && -n "$rel" && "$line" =~ ^[0-9]+$ ]]; then - link="${REPO_URL}/blob/${sha}/${rel}#L${line}" + if [[ -n "$sha" && -n "$file" && "$line" =~ ^[0-9]+$ ]]; then + link="${REPO_URL}/blob/${sha}/${file}#L${line}" fi - printf '• %s %s %s %s\n' "[$rule]" "${rel}:${line}" "${sha:0:7}" "${link}" + printf '• %s %s %s %s\n' "[$rule]" "${file}:${line}" "${sha:0:7}" "${link}" done { - echo "### Secret findings :closed_lock_with_key:" + echo "### Secret findings :closed_lock_with_key:" echo echo "| Rule | File:Line | Commit | Link |" echo "|---|---|---|---|" jq -r "$JQ_FILTER | [.rule, .file, (.line|tostring), (.commit // \"\")] | @tsv" gitleaks.json \ | head -n "$LIMIT" \ | while IFS=$'\t' read -r rule file line commit; do - rel="$file" - if [[ -n "${SRC_DIR:-}" && "$file" == "${SRC_DIR%/}/"* ]]; then - rel="${file#${SRC_DIR%/}/}" - fi - sha="${commit:-${HEAD_SHA:-}}" + sha="${commit}" link="-" - if [[ -n "$sha" && -n "$rel" && "$line" =~ ^[0-9]+$ ]]; then - link="${REPO_URL}/blob/${sha}/${rel}#L${line}" + if [[ -n "$sha" && -n "$file" && "$line" =~ ^[0-9]+$ ]]; then + link="${REPO_URL}/blob/${sha}/${file}#L${line}" fi - printf '| `%s` | `%s:%s` | `%s` | %s |\n' "$rule" "$rel" "$line" "${sha:0:7}" "$link" + printf '| `%s` | `%s:%s` | `%s` | %s |\n' "$rule" "$file" "$line" "${sha:0:7}" "$link" done if (( COUNT > LIMIT )); then @@ -285,10 +157,10 @@ runs: exit 1 fi - - name: Upload report (always) + - name: Upload report if: always() uses: actions/upload-artifact@v4 with: name: gitleaks-report path: gitleaks.json - if-no-files-found: ignore \ No newline at end of file + if-no-files-found: ignore