Skip to main content
Response · Secrets in public code

Anyone can find an exposed key. Proving it is yours is the job.

“A key is sitting in a public repository. Is it live, what does it reach, and whose is it?”

Searching public code for your name returns thousands of matches, and almost none of them are yours. This is the sequence that turns one of them into a closed incident: attribute it on identity rather than on naming, establish what the key opens while it is still live, then rotate in an order that survives the log review.

Type + length
How a credential value is characterised in the report — never reprinted
Audit ID
Carried by every validation request, so you can reconcile it in your own logs
0.09%

Of an exposed corpus read to establish what the key opened

From one engagement, and the restraint is the point rather than the finding. Six day-files out of several thousand were enough to characterise what sat behind an exposed storage key, and the counts that could not be trusted were labelled upper bounds and said so — internal conversation identifiers can pass a national identity-number checksum by chance. A tool maximising its own severity score does not mention that.

The constraint

Detection is cheap. Attribution is the whole job.

Sweep a public code host for your brand and you get a firehose. Almost none of it is yours, and the row that is will not have your name anywhere in it.

A keyword sweep returns forks of your SDK, sample integrations, tutorials, CVs and other companies with a similar name. Filter hard enough to make that queue readable and you discard the finding that mattered, because the exposures that hurt are rarely published under a corporate account at all — they sit in a contractor's namespace, a former employee's fork, or the personal account of a developer on an outsourced team. So attribution is not enrichment applied to a finding after it is made; it is the test that decides whether there was a finding. It runs on identity rather than naming: the address domain a commit was authored from, an internal hostname a credential points at, fork lineage, and the identifiers left in the code itself. That is what turns somebody else's public file into your incident — and where those same signals put the credential with the repository's own author instead, the row says so rather than routing it to you.

How a finding is filed

Every row states whose credential it is, or states that we cannot tell

The distance between "rotate this now" and "please confirm this is yours" is the distance between a usable queue and a liability. Attribution is published as a state with a routing consequence, not assumed silently in the background.

Every row states whose credential it is, or states that we cannot tell
StateWhat it meansWhat follows
Linked to your organisation Published under a corporate account, committed from an address on a mail domain you issue, or carrying a credential that points at infrastructure already attributed to you. Rotate or revoke. The credential is treated as compromised regardless of how briefly it was public — there is no exposure-window argument to have.
References you, credential is not yours The repository names you or your products, but the secret in it appears to belong to its own author or to a third party — a partner's sandbox key, a developer's personal token, an integration built against your API. Surfaced for awareness and never actioned on your behalf. ShadowMap does not adjudicate ownership: confirm it as yours, or confirm it is not. These are surfaced, not dismissed.
Not attributable The material matched a pattern and nothing links it to you or to your people — a similarly named company, a coincidence of internal terms, a public dataset that happens to mention you. Not routed and not counted for you. Whatever it matched, it is not your exposure, and a queue that counts it is inflating itself.
Key
  • Rotate or revoke
  • Your ownership call
  • Not yours, not counted

A worked case

One configuration file, in a repository nobody had a reason to look at

A real engagement, redrawn with the customer removed. A customer-facing conversational-support platform, built and operated substantially by an external development team. The exposure was not on the group network, not on its domains and not in its repositories, and produced no alert anywhere.

01 Attribution

Nothing pointed inward would ever have surfaced it

Where it actually sat
A committed configuration file in a public repository owned by an individual developer. No company name in the account, in the repository or in the code. An ownership search built on the organisation name returns nothing here, and internal secret scanning never reached it either, because the repository was never inside the boundary those controls govern.
What made the link
Nothing on the surface of the account connected it to the group. What the commits carried underneath did: they were authored from an address on a mail domain the group itself issues — identity rather than naming, which is the only test that was ever going to reach this file.
What the file held
A cloud object-storage account key, two third-party platform API keys and two RSA application signing keys. One file, committed once, by somebody with no relationship to the security team that would end up owning it.
02 Validation and blast radius

What the key turned out to open

Live, not inferred
A single authenticated request returned a real success response rather than a guess from the shape of the key. The storage key was live and unrotated with read, write and delete across every container. Of the two platform keys, one was live and unrestricted; the other had expired, and was reported as expired.
Quantified, not asserted
Container and object metadata were enumerated to size the exposure: well over 100 GB of daily conversation logs across several thousand files, spanning roughly eight months and still being written to — live, not historical. A bounded read-only sample, well under a tenth of one percent of the corpus, then characterised what the data was, and every count taken from it was reported as a floor.
Where it stopped being a hygiene ticket
The same key held write access to the secret key stores behind the platform's serverless functions. An attacker writing their own key material there is adopted by the runtime — and control of that platform is the ability to message customers from the brand's own verified channel, at the brand's scale. The first step was a credential in a repository. The last was a board-level brand-integrity event. Nothing changed except that the context was connected to the same credential.

By class

What a safe check can establish, and what rotation actually costs, differ by secret

A secret is not a generic finding. What can be tested, what the test proves, and what has to happen afterwards are all functions of the class — and it is the third column that surprises people, because it is where the work actually is.

Secret classWhat a safe check can establishWhat rotation actually requires
Cloud object-storage key Whether the key authenticates, and which containers and objects it reaches. Metadata enumeration sizes the exposure without reading anything inside it. Rotating the key does not invalidate anything already copied out of the containers it opened. Reviewing activity and diagnostic logs to scope access is a separate and mandatory step, and its output feeds a breach-notification decision rather than a ticket.
Where a sample is genuinely needed to establish what the data is, it is bounded and read-only. Nothing is modified or deleted, personal data and secret values are redacted before anything is written down, and every request carries an audit identifier you can reconcile against your own cloud logs.
Cloud IAM access key Whether it authenticates, and what the identity behind it is permitted to do. Permissions are enumerated, not exercised. Deactivate before delete, so a dependency that breaks is recoverable. Then find every place the key was deployed — the second copy sitting in a build variable is what reinstates the exposure a week later.
Enumerating a permission is not using it. Where the enumeration itself falls outside authorised scope, the finding stays untested and says so on the row.
Third-party platform API key Whether the issuer still honours it, and whether it is scoped or unrestricted. Issuers differ sharply in what they will disclose without the key being exercised. The issuer revokes and you redeploy. The slow part is the inventory of consumers, which is routinely undocumented for a platform an external team built and runs.
Some issuers publish an introspection or metadata endpoint that answers this with no privileged call at all. Where none exists, no safe check exists either, and the key is reported as untested rather than assumed dead.
Application signing key Whether the key material is well-formed and matches a certificate or artefact you publish. Validity is a property of the material, so establishing it requires no call to anybody. Re-issue, re-sign and redistribute. Everything already signed with the exposed key stays trusted by every client that has not yet received the new material, which makes this the class where rotation is a release programme rather than a console action.
A signing key is the class most often graded low by a scanner, because nothing about it authenticates to a service. Blast radius here is a function of what trusts it, not of what it opens.
Database connection string Whether the host answers from the public internet at all. Many do not, which changes the grade without changing the fact that the credential is public. Change the credential, then find every application, scheduled job and dashboard holding it. Unreachable today is not unreachable after the next network change, so the grade is not a reason to leave it.
Where the host does answer publicly, whether it can be authenticated into is a separate question, answered only where testing is safe and authorised.
OAuth refresh or build token Whether the token still exchanges for an access token, and which scopes come back with it. Revoke the token and terminate the sessions it has already minted. A refresh token revoked on its own leaves the access it granted alive until those sessions expire by themselves.
This is the class most likely to be re-surfaced later by dark-web collection: the same token, traded, long after the repository itself is gone.

Cloud object-storage key

What a safe check can establish
Whether the key authenticates, and which containers and objects it reaches. Metadata enumeration sizes the exposure without reading anything inside it.
What rotation actually requires
Rotating the key does not invalidate anything already copied out of the containers it opened. Reviewing activity and diagnostic logs to scope access is a separate and mandatory step, and its output feeds a breach-notification decision rather than a ticket.

Where a sample is genuinely needed to establish what the data is, it is bounded and read-only. Nothing is modified or deleted, personal data and secret values are redacted before anything is written down, and every request carries an audit identifier you can reconcile against your own cloud logs.

Cloud IAM access key

What a safe check can establish
Whether it authenticates, and what the identity behind it is permitted to do. Permissions are enumerated, not exercised.
What rotation actually requires
Deactivate before delete, so a dependency that breaks is recoverable. Then find every place the key was deployed — the second copy sitting in a build variable is what reinstates the exposure a week later.

Enumerating a permission is not using it. Where the enumeration itself falls outside authorised scope, the finding stays untested and says so on the row.

Third-party platform API key

What a safe check can establish
Whether the issuer still honours it, and whether it is scoped or unrestricted. Issuers differ sharply in what they will disclose without the key being exercised.
What rotation actually requires
The issuer revokes and you redeploy. The slow part is the inventory of consumers, which is routinely undocumented for a platform an external team built and runs.

Some issuers publish an introspection or metadata endpoint that answers this with no privileged call at all. Where none exists, no safe check exists either, and the key is reported as untested rather than assumed dead.

Application signing key

What a safe check can establish
Whether the key material is well-formed and matches a certificate or artefact you publish. Validity is a property of the material, so establishing it requires no call to anybody.
What rotation actually requires
Re-issue, re-sign and redistribute. Everything already signed with the exposed key stays trusted by every client that has not yet received the new material, which makes this the class where rotation is a release programme rather than a console action.

A signing key is the class most often graded low by a scanner, because nothing about it authenticates to a service. Blast radius here is a function of what trusts it, not of what it opens.

Database connection string

What a safe check can establish
Whether the host answers from the public internet at all. Many do not, which changes the grade without changing the fact that the credential is public.
What rotation actually requires
Change the credential, then find every application, scheduled job and dashboard holding it. Unreachable today is not unreachable after the next network change, so the grade is not a reason to leave it.

Where the host does answer publicly, whether it can be authenticated into is a separate question, answered only where testing is safe and authorised.

OAuth refresh or build token

What a safe check can establish
Whether the token still exchanges for an access token, and which scopes come back with it.
What rotation actually requires
Revoke the token and terminate the sessions it has already minted. A refresh token revoked on its own leaves the access it granted alive until those sessions expire by themselves.

This is the class most likely to be re-surfaced later by dark-web collection: the same token, traded, long after the repository itself is gone.

The sequence

The order is not the order most teams run

Each move exists because the next one depends on its output. The one routinely skipped is the third, and skipping it is irreversible: once the key is dead you can no longer establish what it opened, and the notification decision needs that answer far more than the ticket does.

Scope

Adjacent jobs that arrive in the same inbox and take a different answer

This page answers one job: a secret is public, establish what it opens and close it. The rest are routinely mistaken for it, and naming them here is cheaper than discovering the mismatch halfway through an evaluation.

The adjacent jobWhy it is a different jobWhere it is answered
Standing coverage, rather than one incident Everything above starts from a finding. Producing that finding is continuous work across the public surface outside your organisation — repositories you do not administer, storage containers, paste sites and indexed documents. Data Exposure Monitoring
Native secret scanning covers the repositories you administer and does that job well. It has no view of a personal account, a contractor namespace or a fork made before somebody left, which is where most of what matters actually sits.
Establishing what a credential opens Whether a key is live, what it reaches and how far is a testing question rather than a scanning one. It runs where it is safe and authorised, and hands over the scope, the evidence and the audit identifier with the result. Continuous Automated Red-Teaming
Not every secret is testable. Where no safe check exists, or the check falls outside authorised scope, the finding stays untested — an absent result is never reported as a negative one.
A password that leaked, not a key that was committed A credential out of a stealer log is a different artefact with a different life. It belongs to a compromised device, it is probed rather than validated, and it carries its own published state vocabulary. Dark Web Monitoring
The two meet more often than they should. A secret first found in a repository and later traded attaches to the exposure you already have, rather than opening a second, unrelated alert.
When your own release velocity is the source of the risk For a software business the exposure is structural rather than accidental: contractor and vendor repositories, short-lived infrastructure, and a build system that outruns the inventory. The response is the same; the frequency and the ownership are not. Technology and software
The repeat-offender pattern across a quarter is a different finding from any single leak in it, and it is the one that engineering leadership can actually act on.
When a regulator is the party asking The response does not change. What changes is that every step has to leave a dated artefact somebody outside your team can reproduce — the scan window, the validation record, the rotation actor and the timestamp. Banking and financial services
Negative assurance counts here too: a surface that was checked and found clean is evidence that it was checked, and it belongs in the record beside the findings rather than being omitted from it.

Standing coverage, rather than one incident

Why it is a different job
Everything above starts from a finding. Producing that finding is continuous work across the public surface outside your organisation — repositories you do not administer, storage containers, paste sites and indexed documents.
Where it is answered
Data Exposure Monitoring

Native secret scanning covers the repositories you administer and does that job well. It has no view of a personal account, a contractor namespace or a fork made before somebody left, which is where most of what matters actually sits.

Establishing what a credential opens

Why it is a different job
Whether a key is live, what it reaches and how far is a testing question rather than a scanning one. It runs where it is safe and authorised, and hands over the scope, the evidence and the audit identifier with the result.
Where it is answered
Continuous Automated Red-Teaming

Not every secret is testable. Where no safe check exists, or the check falls outside authorised scope, the finding stays untested — an absent result is never reported as a negative one.

A password that leaked, not a key that was committed

Why it is a different job
A credential out of a stealer log is a different artefact with a different life. It belongs to a compromised device, it is probed rather than validated, and it carries its own published state vocabulary.
Where it is answered
Dark Web Monitoring

The two meet more often than they should. A secret first found in a repository and later traded attaches to the exposure you already have, rather than opening a second, unrelated alert.

When your own release velocity is the source of the risk

Why it is a different job
For a software business the exposure is structural rather than accidental: contractor and vendor repositories, short-lived infrastructure, and a build system that outruns the inventory. The response is the same; the frequency and the ownership are not.
Where it is answered
Technology and software

The repeat-offender pattern across a quarter is a different finding from any single leak in it, and it is the one that engineering leadership can actually act on.

When a regulator is the party asking

Why it is a different job
The response does not change. What changes is that every step has to leave a dated artefact somebody outside your team can reproduce — the scan window, the validation record, the rotation actor and the timestamp.
Where it is answered
Banking and financial services

Negative assurance counts here too: a surface that was checked and found clean is evidence that it was checked, and it belongs in the record beside the findings rather than being omitted from it.

Questions buyers actually ask

Before you evaluate this

The repository belongs to a contractor. Who is actually supposed to fix it?

The division of labour matters more than it sounds. ShadowMap attributes the finding, establishes whether the key is live where that is safe and authorised, grades it by what it opens and hands over the evidence. Rotation stays with the team that owns the account at the issuer, which is almost never the person who committed the file — and you do not need the contractor to rotate. You need them to purge the history, which is a slower conversation and must not hold up the first step. If the credential itself turns out to be the contractor's rather than yours, it is filed in the second attribution state and we will not rotate somebody else's key on your behalf.

How can you say it is ours when our name is nowhere in the repository?

On identity, not on naming. The signals are the address domain a commit was authored from, an internal hostname or namespace a credential points at, fork lineage back to code you published, and identifiers left in the code itself. Where those signals put the credential with you, the row says so and the credential is treated as compromised. Where they put it with the repository's own author or a third party, the row says that instead — surfaced with the source link attached, never actioned on your behalf, and you make the call. That second state is not a hedge; it is what makes the queue usable for an organisation with hundreds of contractors, resellers and integration partners legitimately holding material that references it.

Do you use the key once you have found it?

Only as far as establishing that it is live and what it reaches requires, and only where that is authorised. In practice: one authenticated request to confirm the key answers, metadata enumeration to size what sits behind it, and a bounded read-only sample where the content itself is the open question. Secondary key material recovered along the way is read to confirm the exposure and then deliberately not used to invoke anything — no lateral movement, stated explicitly in the report. Nothing is modified or deleted, values are redacted before anything is written down, and every request carries an audit identifier so you can reconcile our activity against your own logs. Where any of that falls outside authorised scope, the finding stays untested and says so.

The key was public for twenty minutes. Do we really have to rotate it?

Yes, and the principle is worth stating once rather than re-arguing per finding: an exposed credential is considered compromised regardless of how briefly it was public. Opportunistic harvesting of new public commits runs continuously and is not something you get to be lucky about. The exposure-window argument is also one you would have to win later, in front of an auditor or a regulator, using logs you may not have retained. Scope the window by all means — but scope it after the rotation, from the activity logs, as input to the notification decision. Not instead of it.

Find out whose accounts are holding your keys

One apex domain and your public code footprint, two business days, a written snapshot — every finding filed under whether it is yours, somebody else's, or not attributable at all.