Skip to content

add BANDICOOT blog post #2

add BANDICOOT blog post

add BANDICOOT blog post #2

# Vendored variant of Astera-org/github-workflows secret-scan.yml for the
# diff-use org (Free plan: cannot call the private reusable workflow).
# Synced manually; edit upstream first.
name: Secret scan
on:
pull_request:
workflow_dispatch:
# checks:write lets the scan jobs post a separate, NON-required check run with
# conclusion=failure when findings exist. That check shows red on the PR for
# visibility, but nothing requires it, so it never blocks merges. The workflow's
# own (required) check stays green unless ENFORCE_CREDENTIALS is on.
permissions:
contents: read
checks: write
env:
GITLEAKS_VERSION: 8.30.1
# Rollout switch: "false" = credential findings warn but do not
# fail the check. Flip to "true" once the fleet-wide rollout is triaged.
# Lives here (not a workflow_call input) because required-workflow rulesets
# trigger this workflow directly, with no caller to pass inputs.
ENFORCE_CREDENTIALS: "false"
# Findings are announced here via chat.postMessage when the SECRET_SCAN_SLACK_BOT_TOKEN
# secret is available (bot needs chat:write and to be in the channel).
SLACK_CHANNEL_ID: C0BL6L7TS02
jobs:
credentials:
name: Credentials
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install gitleaks
run: |
curl -fsSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks
- name: Scan full history for credentials
env:
SECRET_SCAN_SLACK_BOT_TOKEN: ${{ secrets.SECRET_SCAN_SLACK_BOT_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set +e
gitleaks git --redact --verbose --exit-code 2 . \
--report-format json --report-path /tmp/credentials.json
status=$?
set -e
# exit-code 2 on findings; 0 clean; 1 reserved for scanner errors
if [ "$status" -eq 2 ]; then
count=$(jq length /tmp/credentials.json)
{
echo "## Credential findings: ${count}"
echo ""
echo "| Rule | File | Line | Commit |"
echo "|------|------|------|--------|"
jq -r '.[] | "| \(.RuleID) | \(.File) | \(.StartLine) | \(.Commit[:8]) |"' /tmp/credentials.json | head -50
echo ""
echo "**What to do:**"
echo "1. **Rotate the credential now.** It is compromised the moment it was pushed, even on an unmerged branch; deleting the line does not help, git history keeps it."
echo "2. Put the new value in a proper secret store (GitHub Actions secret, Pulumi secret config, 1Password), not in code."
echo "3. False positive, or historical and already rotated? Add the finding's fingerprint to \`.gitleaksignore\`."
} >> "$GITHUB_STEP_SUMMARY"
if [ "$ENFORCE_CREDENTIALS" = "true" ]; then
echo "::error title=Credentials found::${count} credential finding(s) in history. Rotate and add to .gitleaksignore."
exit 1
fi
echo "::warning title=Credentials found (not yet enforced)::${count} credential finding(s) in history. Rotate now; this check becomes blocking soon."
# Red, non-required check for visibility. Read-only token (fork PRs)
# cannot create check runs; ignore the failure there.
GH_TOKEN="${{ github.token }}" gh api "repos/${GITHUB_REPOSITORY}/check-runs" \
-f name="credential-findings" \
-f head_sha="${{ github.event.pull_request.head.sha || github.sha }}" \
-f status=completed -f conclusion=failure \
-f 'output[title]'="${count} credential finding(s) in git history" \
-f 'output[summary]'="Rotate the credential(s) NOW (pushed = compromised; deleting the line does not help). Details and remediation steps: see the step summary of the Secret scan workflow run. This check is informational and does not block merging." \
|| echo "could not create check run (read-only token?)"
# Slack dedup: on PRs, notify only when the PR's own
# commits contain findings. Historical findings alerted when they
# were introduced and stay visible in the check run + step summary;
# re-posting them on every push drowned the channel.
# Anchor on the current base tip, not event base.sha: base.sha is
# frozen at PR creation, so after a merge-from-main the stale range
# would re-attribute main's findings to this PR. gitleaks exits 0 on
# an unresolvable range, so validate it here and fall back to
# full-history alerting (fail closed) on any doubt.
notify=/tmp/credentials.json
scope="in git history"
if [ -n "$PR_HEAD_SHA" ] && [ -n "$SECRET_SCAN_SLACK_BOT_TOKEN" ]; then
base=$(git merge-base "origin/${GITHUB_BASE_REF}" "$PR_HEAD_SHA" 2>/dev/null || true)
if [ -n "$base" ]; then
set +e
gitleaks git --redact --exit-code 2 . \
--log-opts "${base}..${PR_HEAD_SHA}" \
--report-format json --report-path /tmp/credentials-new.json
inc_status=$?
set -e
if { [ "$inc_status" -eq 0 ] || [ "$inc_status" -eq 2 ]; } && [ -s /tmp/credentials-new.json ]; then
notify=/tmp/credentials-new.json
scope="new in this PR, ${count} total in history"
fi
fi
fi
notify_count=$(jq length "$notify" 2>/dev/null || echo 0)
notify_count=${notify_count:-0}
if [ -n "$SECRET_SCAN_SLACK_BOT_TOKEN" ] && [ "$notify_count" -gt 0 ]; then
# Escape Slack mrkdwn entities: a crafted file name could otherwise
# render as a disguised clickable link in the alert channel.
sample=$(jq -r 'def esc: tostring | gsub("&";"&amp;") | gsub("<";"&lt;") | gsub(">";"&gt;");
.[:5][] | "> \(.RuleID|esc) \(.File|esc):\(.StartLine)"' "$notify")
txt=":rotating_light: *Credential leak detected* in \`${GITHUB_REPOSITORY}\` (${notify_count} ${scope}). Rotate immediately.
${sample}
${PR_URL:+PR: ${PR_URL}
}Run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
curl -sf -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SECRET_SCAN_SLACK_BOT_TOKEN" \
-H "Content-Type: application/json; charset=utf-8" \
-d "$(jq -n --arg ch "$SLACK_CHANNEL_ID" --arg txt "$txt" \
'{channel: $ch, text: $txt}')" | jq -r '.ok // "post failed"'
else
echo "Slack: skipped (no token or nothing new to report; notify_count=${notify_count})"
fi
elif [ "$status" -ne 0 ]; then
exit "$status"
fi
internal-refs:
name: Internal references (warn-only)
runs-on: ubuntu-latest
# Per-repo opt-out: set the repo Actions variable
# SECRET_SCAN_SKIP_INTERNAL_REFS=true to skip this job.
if: ${{ vars.SECRET_SCAN_SKIP_INTERNAL_REFS != 'true' }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install gitleaks
run: |
curl -fsSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks
- name: Write internal-refs config from org secret
env:
GITLEAKS_INTERNAL_CONFIG: ${{ secrets.GITLEAKS_INTERNAL_CONFIG }}
run: |
# The pattern list enumerates internal hostnames/IP ranges, so it is
# NOT embedded in this (potentially public) file. It lives in the
# diff-use org-level Actions secret GITLEAKS_INTERNAL_CONFIG.
if [ -z "$GITLEAKS_INTERNAL_CONFIG" ]; then
echo "GITLEAKS_INTERNAL_CONFIG org secret not available (fork PR or unset); skipping internal-refs scan."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
printf '%s' "$GITLEAKS_INTERNAL_CONFIG" > /tmp/gitleaks-internal.toml
id: config
- name: Scan full history for internal references
if: ${{ steps.config.outputs.skip != 'true' }}
env:
SECRET_SCAN_SLACK_BOT_TOKEN: ${{ secrets.SECRET_SCAN_SLACK_BOT_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set +e
gitleaks git --config /tmp/gitleaks-internal.toml --verbose --exit-code 2 . \
--report-format json --report-path /tmp/internal-refs.json
status=$?
set -e
if [ "$status" -eq 2 ]; then
count=$(jq length /tmp/internal-refs.json)
echo "::warning title=Internal references found::${count} internal Astera reference(s) in history. Fine for a private repo; MUST be resolved before making this repo public."
{
echo "## Internal references (warn-only): ${count} finding(s)"
echo ""
echo "| Rule | File | Line |"
echo "|------|------|------|"
jq -r '.[] | "| \(.RuleID) | \(.File) | \(.StartLine) |"' /tmp/internal-refs.json | head -50
echo ""
echo "**What to do:** nothing, while this repo stays private. Before making it public, these must be removed or genericized, and note git history keeps old occurrences, so a repo with findings in history should be republished as a fresh single-commit repo. Intentional references (e.g. infra docs) go in \`.gitleaksignore\`."
} >> "$GITHUB_STEP_SUMMARY"
GH_TOKEN="${{ github.token }}" gh api "repos/${GITHUB_REPOSITORY}/check-runs" \
-f name="internal-ref-findings" \
-f head_sha="${{ github.event.pull_request.head.sha || github.sha }}" \
-f status=completed -f conclusion=failure \
-f 'output[title]'="${count} internal Astera reference(s) in git history" \
-f 'output[summary]'="Fine while this repo is private; must be resolved before making it public. Details: see the step summary of the Secret scan workflow run. This check is informational and does not block merging." \
|| echo "could not create check run (read-only token?)"
# Slack dedup: same rule as the credentials job: on PRs,
# notify only for findings introduced by the PR's own commits.
# Anchor on the current base tip, not event base.sha: base.sha is
# frozen at PR creation, so after a merge-from-main the stale range
# would re-attribute main's findings to this PR. gitleaks exits 0 on
# an unresolvable range, so validate it here and fall back to
# full-history alerting (fail closed) on any doubt.
notify=/tmp/internal-refs.json
scope="in git history"
if [ -n "$PR_HEAD_SHA" ] && [ -n "$SECRET_SCAN_SLACK_BOT_TOKEN" ]; then
base=$(git merge-base "origin/${GITHUB_BASE_REF}" "$PR_HEAD_SHA" 2>/dev/null || true)
if [ -n "$base" ]; then
set +e
gitleaks git --config /tmp/gitleaks-internal.toml --exit-code 2 . \
--log-opts "${base}..${PR_HEAD_SHA}" \
--report-format json --report-path /tmp/internal-refs-new.json
inc_status=$?
set -e
if { [ "$inc_status" -eq 0 ] || [ "$inc_status" -eq 2 ]; } && [ -s /tmp/internal-refs-new.json ]; then
notify=/tmp/internal-refs-new.json
scope="new in this PR, ${count} total in history"
fi
fi
fi
notify_count=$(jq length "$notify" 2>/dev/null || echo 0)
notify_count=${notify_count:-0}
if [ -n "$SECRET_SCAN_SLACK_BOT_TOKEN" ] && [ "$notify_count" -gt 0 ]; then
# Escape Slack mrkdwn entities: a crafted file name could otherwise
# render as a disguised clickable link in the alert channel.
sample=$(jq -r 'def esc: tostring | gsub("&";"&amp;") | gsub("<";"&lt;") | gsub(">";"&gt;");
.[:5][] | "> \(.RuleID|esc) \(.File|esc):\(.StartLine)"' "$notify")
txt=":warning: Internal Astera references in \`${GITHUB_REPOSITORY}\` (${notify_count} ${scope}). Fine while private; must be resolved before the repo goes public.
${sample}
${PR_URL:+PR: ${PR_URL}
}Run: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
curl -sf -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SECRET_SCAN_SLACK_BOT_TOKEN" \
-H "Content-Type: application/json; charset=utf-8" \
-d "$(jq -n --arg ch "$SLACK_CHANNEL_ID" --arg txt "$txt" \
'{channel: $ch, text: $txt}')" | jq -r '.ok // "post failed"'
else
echo "Slack: skipped (no token or nothing new to report; notify_count=${notify_count})"
fi
elif [ "$status" -ne 0 ]; then
echo "gitleaks failed with status $status" >&2
exit "$status"
fi