Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/ggshield-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Reusable GitGuardian (ggshield) secret scan.
#
# Runs `ggshield secret scan ci` over the caller's pushed/PR commit range so a
# leaked credential is caught *before* it merges — the pre-merge twin of the
# GitGuardian GitHub App, which only flags secrets retroactively after a push.
# Findings also land in the shared GitGuardian dashboard (policy + false-positive
# management live there — e.g. bws UUIDs are resolved as FPs in the dashboard,
# not ignored in code).
#
# Callers (one per repo) need the org Actions secret GITGUARDIAN_API_KEY and:
#
# jobs:
# ggshield:
# uses: cshuttle/workflows/.github/workflows/ggshield-scan.yml@main
# secrets: inherit
#
# `secrets: inherit` passes the org-level GITGUARDIAN_API_KEY through without
# re-declaring it per caller.
name: ggshield-scan

# Least privilege: the scan only reads the checkout + calls the GitGuardian API.
permissions:
contents: read

on:
workflow_call:
secrets:
GITGUARDIAN_API_KEY:
description: GitGuardian API key (scope `scan`); source of truth in bws Infrastructure.
required: true

jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
# ggshield diffs the commit range; it needs full history, not a shallow
# clone, to resolve the base of a push or PR.
fetch-depth: 0

- name: ggshield secret scan
uses: GitGuardian/ggshield-action@v1
env:
# ggshield-action reads these to compute the exact push/PR commit range
# to scan (see GitGuardian's GitHub Actions docs).
GITHUB_PUSH_BEFORE_SHA: ${{ github.event.before }}
GITHUB_PUSH_BASE_SHA: ${{ github.event.base }}
GITHUB_PULL_BASE_SHA: ${{ github.event.pull_request.base.sha }}
GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
GITGUARDIAN_API_KEY: ${{ secrets.GITGUARDIAN_API_KEY }}
32 changes: 29 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,36 @@ jobs:

Optional input `paths` (space-separated roots to scan; default `.`).

### `ggshield-scan.yml`

Runs a GitGuardian [`ggshield`](https://github.com/GitGuardian/ggshield) secret
scan over the caller's pushed/PR commit range — a **pre-merge** gate, unlike the
GitGuardian GitHub App which only flags leaks retroactively. Findings also appear
in the shared GitGuardian dashboard (where policy and false-positives — e.g. bws
UUIDs — are managed; don't obfuscate them in code).

```yaml
# .github/workflows/ggshield.yml in any repo
name: ggshield
on:
push:
pull_request:
jobs:
ggshield:
uses: cshuttle/workflows/.github/workflows/ggshield-scan.yml@main
secrets: inherit
```

Requires the org Actions secret **`GITGUARDIAN_API_KEY`** (scope `scan`; source
of truth in bws Infrastructure). `secrets: inherit` passes it through — no
per-repo secret needed.

## Git hooks

### `lefthook/base.yml`

Shared advisory pre-commit hooks (shellcheck, gitleaks, yamllint, whitespace /
merge-conflict). Consume from any repo with a tiny `lefthook.yml`:
Shared advisory pre-commit hooks (shellcheck, gitleaks, ggshield, yamllint,
whitespace / merge-conflict). Consume from any repo with a tiny `lefthook.yml`:

```yaml
remotes:
Expand All @@ -47,4 +71,6 @@ remotes:
```

Then `lefthook install` per clone. Tools expected on PATH: `lefthook`,
`shellcheck`, `gitleaks`, `yamllint`.
`shellcheck`, `gitleaks`, `ggshield`, `yamllint`. `ggshield` also needs a
GitGuardian token (`ggshield auth login` or `GITGUARDIAN_API_KEY`); without one
its hook self-skips (advisory) — `gitleaks` still runs offline.
14 changes: 13 additions & 1 deletion lefthook/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
# CI (server-side branch protection isn't available on the Free GitHub plan).
#
# Per clone: lefthook install (re-run after a pre-push change to wire the hook)
# Tools on PATH: shellcheck, gitleaks, yamllint (pre-commit);
# Tools on PATH: shellcheck, gitleaks, yamllint, ggshield (pre-commit);
# kustomize, helm, kubeconform (pre-push, GitOps repos only).
# ggshield additionally needs a GitGuardian token — `ggshield auth login` or
# GITGUARDIAN_API_KEY in env; without it the ggshield hook self-skips (advisory).
pre-commit:
parallel: true
commands:
Expand All @@ -15,6 +17,16 @@ pre-commit:
run: shellcheck {staged_files}
gitleaks:
run: gitleaks protect --staged --redact --no-banner
ggshield:
# GitGuardian secret scan of the staged changes — the local twin of the
# ggshield-scan CI gate, backed by the same dashboard/policy. Advisory: if
# ggshield isn't installed or has no token it self-skips (never wedges a
# dev who hasn't set up auth); gitleaks above is the always-offline net and
# CI is the hard gate. Auth: `ggshield auth login` or GITGUARDIAN_API_KEY.
run: |
command -v ggshield >/dev/null 2>&1 || { echo "ggshield not installed — skip"; exit 0; }
ggshield api-status >/dev/null 2>&1 || { echo "ggshield not authenticated — skip"; exit 0; }
ggshield secret scan pre-commit
yaml-lint:
glob: "*.{yml,yaml}"
# SOPS-encrypted / Helm-templated files aren't plain YAML
Expand Down