How To

How to Run Secrets Detection Automation with GitGuardian's Native Tools

A hands-on build of secrets detection on GitGuardian's own tooling: ggshield in pre-commit and CI, validity-first triage over the API, and honeytokens. Then one valid AWS key worked by hand, which shows exactly where native automation stops.

10 min read
Cover Image for How to Run Secrets Detection Automation with GitGuardian's Native Tools

How to Run Secrets Detection Automation with GitGuardian's Native Tools

Six lines in a pre-commit config stop the next credential from ever reaching your history:

repos:
  - repo: https://github.com/gitguardian/ggshield
    rev: v1.34.0
    hooks:
      - id: ggshield
        stages: [pre-commit]

That block is cheap insurance for everything committed from today forward. It does nothing for the backlog: the hundreds of incidents that landed before anyone wired it up. In part one of this series we covered why that backlog exists. Env files, debug commits, notebooks and CI logs keep leaking credentials, deletion doesn't remediate anything once a secret is pushed, and validity checking is what separates a live key from dead noise.

This part is the hands-on half: secrets detection automation built entirely on GitGuardian's own tooling. You'll wire ggshield into pre-commit and CI, drive the incidents workflow, prioritize by validity and severity, query the GitGuardian API with curl, and plant honeytokens as tripwires. Nothing beyond a GitGuardian account and an API key. We'll go tool by tool, work one live incident end to end by hand, then name the ceiling every DIY setup hits.

01Laptop

Pre-commit hook

ggshield blocks the staged diff before the secret enters history.

02Pipeline

CI scan

The net nobody can skip with --no-verify. Scans the full push range.

03Queue

Incidents and API

Every hit becomes an owned incident, filtered by validity and severity.

04Perimeter

Honeytokens

Decoy keys that fire only when someone acts on stolen access.

Four native layers, each catching what the one before it can't see.

Tool 1: ggshield, shift detection left

ggshield is GitGuardian's CLI. Install it, authenticate once, and it scans locally against the same secrets engine the platform uses (GitGuardian docs).

pip install ggshield
ggshield auth login

Pre-commit: block the leak before it's a commit

Add the hook block from the top of this article to .pre-commit-config.yaml, then activate it:

pip install pre-commit
pre-commit install

Now every git commit runs the staged diff through GitGuardian first. A matched secret fails the commit with the file, line and detector name. This is the cheapest remediation there is, because there's nothing to remediate: the secret never enters history.

If you'd rather not depend on the pre-commit framework, install the hook directly:

ggshield install --mode local -t pre-commit

How to read it. Pre-commit is developer-side and skippable (git commit --no-verify), so treat it as the first net, not the only one. It also can't see what's already committed, only the diff in front of it. That's why you also scan in CI, where nobody can bypass it.

CI: the net nobody can skip

In CI, scan the full push range so a secret buried in an intermediate commit still surfaces. A GitHub Actions job:

name: gitguardian
on: [push, pull_request]
jobs:
  scanning:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: GitGuardian/ggshield-action@v1
        env:
          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 }}

The equivalent bare command, for GitLab or any other runner:

ggshield secret scan ci

To scan a repository's whole history in one pass (the right move when you first onboard a repo):

ggshield secret scan repo .

How to read it. fetch-depth: 0 matters: a shallow clone hides the commits where secrets usually hide. The full-history scan is your backlog discovery pass. Run it once per repo, and every hit becomes a tracked incident you work in the next tool.

Tool 2: The incidents workflow, turning detections into owned work

Every secret GitGuardian finds becomes an incident on the dashboard (Internal Monitoring → Incidents → Secrets). An unmanaged incident list is just anxiety. The workflow is what turns it into progress.

Each incident carries a status you drive explicitly:

  • Assign it to the engineer who owns the service the secret belongs to. Unassigned incidents are the ones that rot.
  • Resolve it once the credential is revoked and rotated, not merely deleted from the file.
  • Ignore it only with a reason: test_credential, false_positive or low_risk. The reason is the audit trail. A silent ignore looks exactly like neglect.

Prioritize by validity, then severity. GitGuardian tests detected credentials against the provider and tags each incident's validity as valid, invalid or unknown. A valid secret is a live key an attacker can use right now, so it jumps the queue. Within the valid set, sort by severity, which GitGuardian derives from the secret type and the permissions it likely grants (a cloud provider root key outranks a read-only third-party token). Work valid-and-critical first. Everything else is backlog you schedule, not a fire you fight.

For recurring, low-judgment patterns, set playbooks (Settings → Playbooks) to act automatically. For example, auto-resolve an incident when GitGuardian confirms the credential has become invalid, or notify a Slack channel the moment a valid incident of critical severity appears. Auto-resolve on invalidity is safe because an invalid key is no longer a live risk. Auto-resolving valid incidents is something you never want to configure.

Tool 3: The GitGuardian API, querying incidents at scale

The dashboard is fine for a handful of incidents. The API is how you triage hundreds. Everything the UI shows is available over REST (API reference). Create a personal access token (API → Personal access tokens) with incidents:read, then:

export GG_TOKEN="your_token_here"

List the open incidents, newest first:

curl -s -H "Authorization: Token $GG_TOKEN" \
  "https://api.gitguardian.com/v1/incidents/secrets?status=TRIGGERED&ordering=-date&per_page=50"

The triage query that matters: valid credentials at critical severity, the ones you work today.

curl -s -H "Authorization: Token $GG_TOKEN" \
  "https://api.gitguardian.com/v1/incidents/secrets?validity=valid&severity=critical&status=TRIGGERED"

Pipe it through jq to get a scannable worklist instead of a wall of JSON:

curl -s -H "Authorization: Token $GG_TOKEN" \
  "https://api.gitguardian.com/v1/incidents/secrets?validity=valid&status=TRIGGERED" \
  | jq -r '.[] | [.id, .severity, .detector.name, .gitguardian_url] | @tsv'

Drive workflow actions the same way (these need incidents:write). Assign an incident, then resolve it once you've rotated the key:

# Assign incident 12345 to an owner by email
curl -s -X POST -H "Authorization: Token $GG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email":"owner@yourco.com"}' \
  "https://api.gitguardian.com/v1/incidents/secrets/12345/assign"

# Resolve it after revoke and rotate
curl -s -X POST -H "Authorization: Token $GG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"secret_revoked":true}' \
  "https://api.gitguardian.com/v1/incidents/secrets/12345/resolve"

How to read it. The validity and severity filters do the real work: they collapse a 400-incident list into the five that can hurt you before lunch. Wire the valid-and-critical query into a scheduled job and page on a non-empty result, and you have a live-key alarm built from two curl calls. The write endpoints let you script bulk cleanup (mass-ignore a batch of confirmed test credentials with a reason), but the part of remediation that changes anything lives outside GitGuardian. More on that below.

Tool 4: Honeytokens, tripwires for the breach you can't see

Detection tells you a secret leaked. A honeytoken tells you someone is using leaked access. It's a decoy AWS credential GitGuardian generates that grants nothing, but alerts the instant anyone tries to authenticate with it (honeytoken docs).

Create one from the API:

curl -s -X POST -H "Authorization: Token $GG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"ci-deploy-decoy","type":"AWS","description":"planted in build config"}' \
  "https://api.gitguardian.com/v1/honeytokens"

Placement is the whole strategy. A honeytoken only fires if an intruder finds it while poking around, so plant it where a real credential would plausibly live and where a legitimate engineer would never call it:

  • In a .env.example, a Terraform variables file or a CI config, the spots an attacker scrapes first.
  • Inside a private repo you want to know is breached the moment the wrong person clones it.
  • In an internal wiki page or S3 bucket that stores "deployment credentials."

A honeytoken trip is a different category from a detection incident. A detection means a secret might be exposed. A trip means someone acted on stolen access. Route trips to an immediate page, not the backlog queue. The alert carries the source IP and user agent of whoever used it, which is your first evidence of the breach.

ToolCatchesMisses
Pre-commit hookNew secrets in the staged diffAnything already committed; anyone using --no-verify
CI scanEvery pushed commit in the rangeSecrets that never touch a scanned repo
Incidents and APIThe full backlog, sorted by validity and severityWhat the key can reach in your account
HoneytokensActive use of stolen accessLeaks nobody has exploited yet

Working one valid incident by hand

The tools above get you to a short list. Here's what happens next, for one incident, with nothing but native tooling. The worklist query returns a critical AWS key, valid, committed to a service repo six days ago.

There are three readings, and each one predicts something different:

  • A sandbox key nobody uses. The principal lives in a dev account, get-access-key-last-used shows nothing recent, and revoking it breaks nothing.
  • A live production key, not yet abused. The principal is a real service user, recent use comes only from your own infrastructure's IPs, and revoking it will break that service until the replacement lands.
  • A key someone else found. Recent use comes from an address you don't recognize, or from API calls your service never makes.

Check identity and last use from an admin session:

aws iam get-access-key-last-used --access-key-id AKIAEXAMPLE123456789

Then pull what the key has called since the commit, from CloudTrail's 90-day event history:

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAEXAMPLE123456789 \
  --start-time 2026-10-01T00:00:00Z \
  --query 'Events[].{time:EventTime,call:EventName,source:EventSource}'

Say the calls are all GetObject from your own NAT gateway: reading two, a production key in active legitimate use. The response is then a sequence that crosses three systems. Issue a second key for the IAM user, write it into the secrets manager, redeploy the consumers, deactivate the leaked key, confirm nothing broke, delete it, and resolve the incident in GitGuardian with secret_revoked: true:

aws iam create-access-key --user-name billing-export-svc
aws secretsmanager put-secret-value --secret-id prod/billing-export/aws --secret-string file://new-key.json
# redeploy the consumers, then:
aws iam update-access-key --user-name billing-export-svc \
  --access-key-id AKIAEXAMPLE123456789 --status Inactive

Each step is easy. Doing them in order, for the right principal, without breaking the service, while the rest of the queue waits, is the slow part.

The honest part: where native tooling stops

01Automated

Detection

Hooks, CI and repo scans find the secret reliably and fast.

02Automated

Routing

Playbooks notify, assign and auto-resolve keys that are already invalid.

03Still manual

Response

Blast radius, revoke, rotate, redeploy and verify all happen outside GitGuardian.

The native stack ends where the credential's own provider begins.

Wire all four tools together and you have strong secrets detection automation: leaks blocked at commit, a CI net nobody can skip, a prioritized incident queue, scriptable triage, and tripwires for active abuse. Run it. But know the ceiling before you call it a program.

Detection and workflow are automated. Remediation isn't. GitGuardian tells you a valid AWS key leaked, who should own it and how bad it is. It can't revoke that key in your AWS account, mint a replacement, push the new value into your secrets manager, redeploy the services that read it, and confirm the old one is dead. That revoke, rotate, redeploy and verify loop is the part that closes the exposure, and it's a human sprint across your cloud console, your secrets store and your deploy pipeline, once per incident.

The queue moves at human speed. Playbooks auto-resolve invalidated keys and route alerts, but a valid critical incident still waits on a person to pick it up, trace which account the key opens, and do the rotation by hand. On a busy repo the gap between "GitGuardian alerted" and "credential rotated" is measured in hours or days, which is exactly the window an attacker needs.

Blast radius is off-platform. GitGuardian scores severity from the secret's type, not from what that specific key can reach in your environment. Whether a leaked token opens a sandbox or your production billing account is a question you answer by hand, in the cloud console, every time. The walkthrough above is that question, and it doesn't scale to forty incidents a week.

None of this makes the native tooling optional. It makes it the detection layer, not the response layer. Which leads to the next question: how do you attach blast-radius-aware remediation to every valid incident and every honeytoken trip, without handing a tool unchecked write access to your cloud?

What comes next

Everything you scripted here (the valid-and-critical triage, the honeytoken watch, the revoke, rotate, redeploy loop) can run continuously with an agent attached. That's part three of this series: CloudThinker watches new incidents and honeytoken trips over a read-only GitGuardian token, triages by validity and blast radius, drafts the remediation plan, and executes it step by step in Manual mode, with a named person approving every write.

Or see your own open incidents triaged: Try CloudThinker free with 100 premium credits and no card required.

gitguardiansecuritysecretssecretsdetectionggshieldhoneytokensdevsecops