Secrets Detection for Engineering Teams
Secrets detection that finds leaked credentials first. Git history, CI logs, and container layers, with pattern and entropy analytics plus a rotation-first runbook.
Secrets detection is the least glamorous control in security and one of the highest-yield. Credentials leak constantly through entirely ordinary engineering work: a key committed while debugging, a token echoed into CI output, an environment file baked into a container layer, an authorization header captured in an error trace. None of these require an attacker to do anything. The credential is simply sitting somewhere readable, and whoever finds it first wins. This guide covers finding them first.
Leaked credentials map to MITRE ATT&CK T1552.001 — Unsecured Credentials: Credentials In Files and T1552.004 — Private Keys, and they lead directly to T1078.004 — Valid Accounts: Cloud Accounts. This is the quiet path into most cloud estates, and it rarely produces a dramatic detection because nothing was broken into.
What is secrets detection?
Secrets detection is the practice of finding credentials in places they were never meant to live, before someone else does. The credential classes that matter are broader than “API keys”:
| Secret type | Where it leaks | Blast radius | Rotation difficulty |
|---|---|---|---|
| Cloud access keys | Git, CI logs, image layers | Often the entire account | Low |
| Provider API keys | Git, client bundles, error traces | The provider’s data and spend | Low |
| Database credentials | Config files, image layers | All stored data | Medium |
| Private keys (TLS, SSH) | Git, backups, image layers | Impersonation and interception | High |
| Signing keys | CI config, artifact pipelines | Every artifact you ever signed | Very high |
| OAuth client secrets | Client-side bundles, git | The app’s whole identity | Medium |
| Webhook signing secrets | Git, documentation | Forged inbound events | Low |
| Internal service tokens | Source, container env | Lateral movement across services | Medium |
The two rows worth dwelling on are signing keys and private keys, because their rotation cost is what makes teams hesitate, and hesitation is exactly what an attacker needs. A leaked signing key potentially invalidates the trust of every artifact you have ever published, which is why it belongs in a hardware security module or a managed signing service rather than anywhere a detection rule could find it.
Where do secrets actually leak?
Four surfaces account for the overwhelming majority, and they need different tooling.
Git history is the best known and still the largest. The failure mode is subtle: a developer commits a key, notices, and removes it in the next commit. The working tree is clean, so working-tree scanners report nothing, while the secret remains trivially readable in the earlier commit forever. Any scanner that does not walk full history is providing false assurance.
CI job logs are the most underestimated. Build systems echo commands, print environment state on failure, and emit verbose diagnostics precisely when something is going wrong and someone turned on debug output. Job logs are frequently readable by everyone in the organisation and retained for months. Masking helps only for values the platform already knows are secret, which excludes anything derived, decoded, or constructed at runtime.
Container image layers leak because layers are immutable and additive. Copying a .env file
in one layer and deleting it in a later one leaves it fully present in the earlier layer, and
anyone who pulls the image can read it. This is the exact same logical error as the git case,
reappearing in a different system, and it is just as common.
Error traces and APM payloads leak because exception handlers serialise context. A failed HTTP call helpfully includes the request headers, and the request headers include the authorization header. That trace then flows to an observability vendor and sits in a system with a different access model than the one holding the credential.
Two smaller surfaces are worth naming because they are rarely scanned at all. Client-side bundles ship whatever the build embedded, so any key referenced in front-end code is public the moment it deploys, regardless of minification or obfuscation. And infrastructure state files frequently store the plaintext values of every resource they manage, including database passwords and generated keys, which makes a state file committed to a repository one of the highest-value single artifacts an attacker can find. Neither appears in a default scanner configuration.
What telemetry and coverage do you need?
| Requirement | Why it matters | Common failure |
|---|---|---|
| Full git history scanning | Working-tree scans miss deleted-then-committed secrets | Scanner runs on HEAD only |
| Pre-receive or pre-commit hook | Prevents the push rather than reporting it | Detection only, no prevention |
| CI job log scanning | Second-largest leak surface | Never scanned at all |
| Container layer scanning | Layers retain deleted files | Only the final image inspected |
| Live verification of candidates | Separates real from expired | Every match treated as critical |
| Credential-use telemetry | Detects exposure becoming compromise | Cloud audit logs not correlated |
| Rotation automation | Response speed is the only variable | Manual rotation taking days |
The fifth row is what separates a usable programme from an ignored one. A scanner that reports every high-entropy string produces thousands of findings, and a team that receives thousands of findings stops reading them. Verifying whether a candidate credential is actually live collapses that list by an order of magnitude and turns the remainder into genuine incidents.
How to detect leaked secrets
Three complementary analytics: one preventing the leak at CI, one finding material at rest, and one catching the moment exposure becomes compromise.
Secret material written to CI job output
title: Secret Material Written to CI Job Output
id: 5d9a1c37-2e64-4b08-a7f1-6c3b8e2d94a0
status: experimental
description: >
Detects credential-shaped strings appearing in CI job output. Covers debug mode
enabled mid-pipeline, environment dumps on failure, and verbose tooling that
echoes constructed credentials the platform masker does not recognise.
references:
- https://attack.mitre.org/techniques/T1552/001/
author: Colson
date: 2026/07/28
logsource:
category: application
product: cicd
detection:
provider_patterns:
log_line|re:
- 'AKIA[0-9A-Z]{16}'
- 'ASIA[0-9A-Z]{16}'
- 'ghp_[A-Za-z0-9]{36}'
- 'github_pat_[A-Za-z0-9_]{82}'
- 'xox[baprs]-[A-Za-z0-9-]{10,}'
- 'sk-[A-Za-z0-9]{32,}'
- '-----BEGIN (RSA |EC |OPENSSH |PGP )?PRIVATE KEY-----'
- 'eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.'
env_dump:
log_line|re:
- '(?i)(SECRET|TOKEN|PASSWORD|APIKEY|API_KEY|PRIVATE_KEY)\s*[=:]\s*\S{8,}'
masked:
log_line|contains:
- '***'
- '[MASKED]'
- '[REDACTED]'
condition: (provider_patterns or env_dump) and not masked
fields:
- repo
- pipeline_id
- job_id
- step_name
- actor
- log_line
falsepositives:
- Example or documentation keys in test fixtures
- Deliberately revoked keys used in negative test cases
- Placeholder values in template configuration
level: critical Note the masked exclusion. Platform maskers do catch registered secrets, and alerting on
already-masked output wastes the rule’s credibility. What this catches is the residue: values the
masker never knew about because they were decoded, concatenated, or fetched at runtime.
Provider-pattern key material at rest
For repositories, image layers, and archives, a YARA ruleset gives you a portable scanner that runs anywhere, including across artifacts pulled during incident response.
rule Leaked_Credential_Material
{
meta:
description = "Provider-pattern credential material in repos or image layers"
author = "Colson"
date = "2026-07-28"
reference = "https://attack.mitre.org/techniques/T1552/001/"
severity = "high"
strings:
$aws_akia = /AKIA[0-9A-Z]{16}/
$aws_asia = /ASIA[0-9A-Z]{16}/
$gh_pat = /ghp_[A-Za-z0-9]{36}/
$gh_fine = /github_pat_[A-Za-z0-9_]{82}/
$slack = /xox[baprs]-[A-Za-z0-9-]{10,}/
$pem_rsa = "-----BEGIN RSA PRIVATE KEY-----"
$pem_ec = "-----BEGIN EC PRIVATE KEY-----"
$pem_ssh = "-----BEGIN OPENSSH PRIVATE KEY-----"
$env_assign = /(?i)(SECRET|TOKEN|PASSWORD|API_KEY)[ ]*=[ ]*[A-Za-z0-9\/+_-]{20,}/
$fp_example = "EXAMPLE" nocase
$fp_sample = "your-api-key-here" nocase
$fp_test = "AKIAIOSFODNN7EXAMPLE"
condition:
filesize < 20MB
and any of ($aws_*, $gh_*, $slack, $pem_*, $env_assign)
and not any of ($fp_*)
} AKIAIOSFODNN7EXAMPLE is the documented example key that appears in a great deal of tutorial
material, and excluding it explicitly removes a meaningful share of false positives on its own.
Extend the exclusion set with your own placeholder conventions as you discover them.
Credential use from an unexpected principal or network
Exposure is a risk; use is an incident. This analytic catches the transition.
index=cloud sourcetype=aws:cloudtrail earliest=-24h
| search userIdentity.type IN ("IAMUser","AssumedRole")
| eval principal = coalesce('userIdentity.userName', 'userIdentity.arn')
| lookup principal_baseline principal
OUTPUT usual_asn, usual_regions, usual_events, usual_ua, first_seen
| eval anomaly = 0
| eval anomaly = anomaly + if(src_asn != usual_asn, 3, 0)
| eval anomaly = anomaly + if(NOT match(awsRegion, usual_regions), 2, 0)
| eval anomaly = anomaly + if(NOT match(eventName, usual_events), 2, 0)
| eval anomaly = anomaly + if(match(userAgent, "(?i)(curl|python|boto|go-http)"), 1, 0)
| eval anomaly = anomaly + if(eventName IN ("CreateUser","CreateAccessKey",
"AttachUserPolicy","PutUserPolicy",
"CreateLoginProfile","GetSecretValue"), 4, 0)
| where anomaly >= 5
| stats min(_time) AS first_anomaly, max(_time) AS last_anomaly,
values(eventName) AS actions, values(src_ip) AS source_ips,
values(awsRegion) AS regions, max(anomaly) AS peak_score
by principal
| sort - peak_score The CreateAccessKey and AttachUserPolicy weighting is deliberate. An attacker who finds a
leaked key rarely uses it directly for long; they use it to mint durable, less-monitored access.
Those two events immediately following an unusual-origin authentication are close to definitive
and connect directly to the patterns in
CloudTrail monitoring that matters.
Which false positives will you actually see?
| False positive | Which rule it hits | Why it happens | Resolution |
|---|---|---|---|
| Documentation example keys | YARA, Sigma | Tutorials use real-format placeholders | Exclude known example values explicitly |
| Test fixtures with fake creds | YARA, Sigma | Negative test cases need key-shaped input | Path-exclude fixture directories |
| Base64 blobs and hashes | Entropy scanning | High entropy, not a credential | Require pattern or verification, not entropy alone |
| Already-revoked keys | All scanning | History full of old rotated keys | Live verification resolves this entirely |
| Minified JS bundles | YARA | Random-looking identifiers | Exclude build output paths |
| Platform-masked output | Sigma CI rule | Masker already handled it | The masked exclusion covers it |
| Automation from a new region | Credential use | Legitimate infrastructure change | Baseline per principal, re-baseline after changes |
| Lock and checksum files | YARA | Long random-looking hashes | Exclude by filename pattern |
Rows three and four together explain most of the noise in an untuned programme, and both are resolved by the same practice. Entropy without verification generates the volume; verification removes it. Build verification before you widen coverage, not after.
How do you respond to a leaked secret?
Order matters more here than in almost any other incident type, because the clock started when the secret was pushed rather than when you found it.
- Rotate immediately. Before investigating scope, before notifying, before writing a timeline. If the secret is live, every minute of analysis is a minute of attacker opportunity.
- Revoke the old credential explicitly. Issuing a replacement does not disable the original on many providers, and an unrevoked old key remains fully valid.
- Determine exposure duration. From the introducing commit or build to revocation. Public exposure should be treated as compromised from the first moment.
- Pull all usage of the credential across its lifetime. Cloud audit logs, provider access logs, application logs. This establishes whether exposure became use.
- Look for durable access established with it. New keys, new users, new policies, new OAuth grants. This determines whether rotation actually ended the incident.
- Assess the blast radius by permission, not by intent. What the credential could reach, not what it was created for.
- Purge the material where you can, after rotation. History rewriting, log purging, image rebuilds. This is cleanup, not remediation.
- Fix the introducing path. A pre-receive hook, a masking gap, a
.dockerignoreentry. A leak that recurs is a process defect rather than an individual mistake.
Step 1 before step 3 is the part that feels wrong to incident responders trained to preserve evidence and scope before acting. For live credentials it is correct, because the credential’s continued validity is itself the ongoing harm and the logs you need to scope it are not going anywhere.
How to test your secrets detection
In a repository and pipeline you own, using credentials you have already revoked:
- Commit a revoked provider key, then delete it in a follow-up commit. Confirm the history scan still finds it, since this is the exact failure mode that matters.
- Attempt to push a live-format test credential. Confirm the pre-receive hook blocks the push rather than merely reporting it after the fact.
- Echo a constructed credential in a CI step, built by concatenation so the platform masker does not recognise it. Confirm the Sigma rule fires.
- Build an image that copies a secret file in one layer and deletes it in a later one. Confirm the layer scan finds it in the earlier layer.
- Include a documentation example key. Confirm it does not alert.
- Use a revoked key from an unusual network and confirm the credential-use analytic scores it.
Items 1 and 4 are the same logical error in two systems, and a programme that catches one but not the other is extremely common.
How to prevent secrets from leaking
Four further controls carry real weight:
- Scope every credential minimally. A leaked key limited to reading one bucket is an inconvenience; an administrative key is an outage and a disclosure.
- Never put a secret in a client bundle. Anything shipped to a browser or mobile app is public by definition, regardless of obfuscation.
- Strip authorization headers before telemetry export. Do it at the SDK boundary rather than trusting each exception handler.
- Keep signing keys in an HSM or managed signing service. Their rotation cost makes exposure disproportionately expensive, which links directly to supply chain attack detection.
Which secrets control should you deploy first?
| Control | Stops | Effort | Coverage | Deploy when |
|---|---|---|---|---|
| Pre-receive secret blocking | New git leaks | Low | High | Always, first |
| Full-history scan with verification | Existing leaks | Low | High | Always, second |
| CI log scanning | The second-biggest surface | Low | Medium | Always |
| Container layer scanning | Image-baked secrets | Medium | Medium | You ship images |
| Platform env secrets only | Config-file leaks | Low | High | Always |
| Short-lived dynamic credentials | Static secrets entirely | High | Very high | You have a secrets manager |
| Workload identity federation | Cloud keys entirely | High | Very high | Cloud-native workloads |
Pre-receive blocking plus a verified full-history scan is a weekend of work and covers the majority of realistic exposure. Dynamic credentials and workload identity are the endgame that makes the whole category mostly disappear.
Common secrets detection mistakes
- Scanning the working tree only. The deleted-then-committed secret is the common case.
- Rewriting history instead of rotating. The copies you do not control are already gone.
- Skipping live verification. Unverified findings become an ignored backlog.
- Trusting the CI masker completely. It only masks values it was told about.
- Ignoring container layers. Same error as git, different system.
- Leaving old keys active after issuing new ones. Replacement is not revocation.
- Alerting on entropy alone. Every checksum file becomes a finding.
- Treating a leak as an individual mistake. The introducing path is the actual defect.
Secrets detection checklist
- Scan full git history, not just the working tree, across every repository.
- Add pre-receive or pre-commit blocking so leaks are prevented rather than reported.
- Build live verification so findings are separated into real and expired.
- Scan CI job logs continuously, excluding already-masked output.
- Scan container image layers individually, not just the final flattened image.
- Strip authorization headers at the telemetry SDK boundary.
- Exclude documented example keys, fixtures, lockfiles, and build output from scanning.
- Correlate cloud audit logs to detect credential use from unexpected principals or networks.
- Weight key-creation and policy-attachment events heavily in that analytic.
- Rotate first and revoke explicitly; treat purging as cleanup afterwards.
- Automate rotation so response speed is not gated on a manual runbook.
- Scope every credential to the minimum permission it needs.
- Move to short-lived dynamic credentials or workload identity where the platform supports it.
- Keep signing keys in an HSM or managed signing service, never in scannable storage.
- Fix the introducing path after every leak, since recurrence is a process defect.
The takeaway
Secrets detection works when it scans history rather than the working tree, verifies findings rather than counting them, and covers CI logs and container layers alongside git. Rotate first and treat purging as cleanup, because the copies you cannot see are already outside your control. Then remove the category structurally with short-lived dynamic credentials and workload identity, so the secret that leaks does not exist to leak. Continue with supply chain attack detection, CloudTrail monitoring patterns that matter, and OAuth misconfiguration defensive review, or browse the full Security Tools pillar.
Training & tools referenced
Disclosure: Some links below are affiliate links. If you buy through them, darkpwn may earn a commission at no extra cost to you. We only recommend training and tools we actually use in our own lab, and affiliate links never influence editorial coverage.
- 1PasswordSecrets management and team credential storagePassword ManagerCompare plans
- TryHackMeAuthorized labs to practice credential-exposure analysisSecurity TrainingStart training
Frequently asked questions
What is secrets detection?
Secrets detection is the practice of finding credentials that have leaked into places they were never meant to live — git history, CI job logs, container image layers, error traces, and configuration files — before an attacker finds them. It combines provider-specific pattern matching with entropy analysis and, critically, verification that the found credential is actually live.
How do you find leaked secrets in git history?
Scan the full commit history rather than the working tree, because deleting a secret in a later commit leaves it permanently readable in the earlier one. Use provider-specific patterns for known key formats plus entropy analysis for opaque tokens, then verify each candidate against its provider to separate live credentials from expired noise.
Is deleting the commit enough to fix a leaked secret?
No. Rewriting history does not help once the commit has been pushed, cloned, forked, or indexed by any platform or scanner. Assume any secret that reached a remote is public permanently. The only real remediation is rotating the credential, and history rewriting is at best cleanup performed after rotation.
How fast do attackers find leaked keys?
Fast enough that rotation speed is the only variable you control. Public repository push events are streamed continuously and scanned automatically by both defenders and attackers, so exposure is measured in minutes rather than days. Plan your response around the assumption that a public leak is compromised at the moment of the push.
What is the difference between entropy and pattern detection?
Pattern detection matches known credential formats such as a provider's key prefix, which gives high precision and misses anything unrecognised. Entropy detection flags strings that are statistically random, catching unknown formats at the cost of many false positives from hashes and encoded blobs. Production tooling needs both plus live verification to arbitrate.