Secrets Sprawl Detector showing 7 exposed credentials: 1 critical, 5 high, 1 low, with masked previews and fix commands
Scan of a project with planted canaries: 3,050 items scanned, 7 credentials found in buckets, Cloud Run env vars and logs.

Secret scanners usually watch Git. In a GCP project credentials also end up in Cloud Run environment variables, bucket objects (Terraform state, .env files), application logs and VM metadata. gcp-secrets-sprawl-detector scans those places read-only, finds credentials, and ranks each by two things a plain grep cannot know: who can read the place it was found, and what the credential can do. Secret values never leave the scanner: findings carry a masked preview only, and models see redacted metadata.

Where it looks and how exposure is judged

SourceHowExposure means
Cloud Run env varsAdmin API v2; Secret Manager references are skipped (that is the right pattern)Holders of run viewer/developer/admin or project viewer/editor/owner
Bucket objectsAllow-listed buckets only, text-like files up to 512 KBBucket and project storage roles; public if allUsers is granted
Cloud LoggingRecent text and JSON payloadsLogging viewer roles
VM / project metadataCompute APICompute viewer roles
Local directorysprawl scan-dirUnknown

Detectors cover AWS and Google keys (including service-account key JSON and private keys), GitHub, GitLab, Slack, Stripe, OpenAI, Anthropic, SendGrid and npm tokens, JWTs, database URLs with passwords, and generic high-entropy assignments, skipping placeholders and code that merely handles secrets.

Is the leaked key still alive?

For a leaked service-account key the scanner asks IAM, using its own identity and never the leaked key, whether the key is still active and what roles the account holds. An active key on an Editor or Owner account is critical; a key that no longer exists is low. In the screenshot the deployer.json key found in the canary bucket is rated low, "key deleted or disabled", with the advice to remove the file because there is nothing to rotate. The other six findings are rated by type and exposure. Only service-account keys get a liveness check; GitHub, Stripe, AWS and database credentials are not validated as live.

Reading the live scan

The scan covered 47 Cloud Run env vars, 3 bucket objects and 3,000 log entries (639 skipped), 3,050 items in all. It found 7 credentials: a Stripe live key (critical), an AWS access key id and a database URL with password in config/app.env, a GITHUB_TOKEN and a REDIS_URL in the ss-canary service's env vars, a GitHub token that appeared in a log entry, and the dead service-account key. Each shows a masked preview such as sk_l…dc (32 chars), a readers count ("exposure: limited (5 readers)"), the rotation advice, and a concrete fix: gcloud run services update ss-canary --update-secrets=GITHUB_TOKEN=... for env vars, gcloud storage rm for bucket objects, and a log exclusion for log entries.

How I validated it

CheckResult
Offline tests52 pass, including no-leak properties across API, CLI and model payloads
Planted canaries in real GCP7 of 7 found; 0 unexpected findings in 47 env vars and 3,000 log entries; benign controls not flagged; local and deployed identical
False-positive sweep, 1,335 real files in 16 of my reposFirst version: 18 false positives from the generic rule. After the fix: 0, plus one real private key correctly found in a gitignored .secrets/ directory
Model evalFindings identical to model-free; no secret value, preview or fingerprint ever sent to a model (audited)

Limits

It is pattern-based: low-entropy passwords made only of letters, secrets split across lines and encoded or encrypted blobs are missed. The "key is active" path was verified with a fake IAM in unit tests only, because this project's org policy blocks creating real keys; the live canary was a fake key file for a real throwaway account. Exposure counts principals holding a role that can read the source, without expanding groups or evaluating conditions. Coverage is bounded to 200 objects per bucket, 512 KB per object and 3,000 log entries per scan; Terraform state in other buckets, Secret Manager contents, image layers and Cloud Build logs are not scanned. The runtime identity needs broad read roles on logs, Cloud Run config and VM metadata, so keep the invoker role tight.

Try it yourself

git clone https://github.com/soodrajesh/gcp-secrets-sprawl-detector
cd gcp-secrets-sprawl-detector
.venv/bin/sprawl scan-dir ./my-repo
.venv/bin/sprawl scan my-project --sources cloudrun_env,log_entry --hours 24
.venv/bin/sprawl scan my-project --bucket my-bucket --llm vertex # bucket scanning is opt-in, per bucket

📢 Have questions or feedback? Drop a comment below or connect with me on Twitter/X@spysood!