Leaked secrets
dagsec reads every line added in every commit of the repository's history and matches it against the rules below. A secret that was committed and later deleted is still found, because it stays in git history and anyone with the repository can read it.
Rules
| Rule ID | Finds | Pattern |
|---|---|---|
aws-access-key-id | AWS access key ID | AKIA or ASIA followed by 16 uppercase letters or digits |
github-token | GitHub token | ghp_, gho_, ghu_, ghs_ or ghr_ followed by 36 characters |
github-fine-grained-pat | GitHub fine-grained personal access token | github_pat_ followed by 82 characters |
slack-token | Slack token | xoxb-, xoxa-, xoxp-, xoxr- or xoxs- followed by at least 10 characters |
stripe-live-key | Stripe live secret or restricted key | sk_live_ or rk_live_ followed by at least 24 characters |
google-api-key | Google API key | AIza followed by 35 characters |
private-key | Private key block | A -----BEGIN ... PRIVATE KEY----- header (RSA, EC, DSA, OPENSSH, PGP or plain) |
generic-secret | High-entropy value assigned to a secret-like name | A quoted value of 16+ characters assigned to a name containing secret, token, password, passwd, api_key or access_key, with Shannon entropy of at least 3.5 |
Values that contain EXAMPLE, PLACEHOLDER, DUMMY, CHANGEME or REDACTED (in any case) are treated as documentation placeholders and skipped, such as AWS's AKIAIOSFODNN7EXAMPLE. Low-entropy values such as password = "changemechangeme" don't match the generic rule.
Where a secret is
Every finding gets a location from its file path. Only secrets in code fail a scan; the others are listed in a collapsed section, because they are usually test fixtures.
| Location | The path |
|---|---|
test | is in a directory named test, tests, __tests__, spec, specs, testdata, fixtures, __fixtures__, __mocks__ or mocks; or the file name starts with test_, ends with _test, or contains .test., .spec. or _spec. |
example | is in example, examples, sample, samples, demo or demos; or the file ends with .example, .sample, .template or .dist |
docs | is in doc, docs or documentation; or the file ends with .md, .mdx, .rst, .adoc or .ipynb |
code | anything else, including .env and config files |
Checks are made in that order and names are compared case-insensitively. For example, tests/test_api.py is test, .env.example is example, README.md is docs, and config/deploy.env is code.
A fixture can still be real
If a "test" secret is a real credential, rotate it anyway. The location only decides whether the check fails.
Masked values
Reports never contain the full secret. They show its first four characters followed by asterisks, such as AKIA******** or ghp_********. For private-key, the report shows the key type from the header (for example BEGIN RSA PRIVATE KEY), which is not secret.
The full value exists only in memory while the scan runs. It is never written to a report, the dagsec database or logs.
Is it still active?
For GitHub, Stripe and Slack credentials, dagsec asks the provider whether the credential still works, with a read-only call that changes nothing:
| Rule | Call | Active when | Revoked when |
|---|---|---|---|
github-token, github-fine-grained-pat | GET https://api.github.com/rate_limit | 2xx or 403 | 401 |
stripe-live-key | GET https://api.stripe.com/v1/balance | 2xx or 403 (restricted key) | 401 |
slack-token | POST https://slack.com/api/auth.test | "ok": true | invalid_auth, token_revoked, account_inactive, token_expired, not_authed |
The report marks each checked finding ACTIVE or (revoked), and the summary line says how many are still active. Anything else (a timeout, a rate limit, another rule) leaves the finding unchecked. At most 25 distinct credentials are checked per scan, with a 10-second timeout each.
When checks run. Only where the repository's owner has authorized the scan:
- In your own CI and on the command line: on by default. Turn off with
--no-verify. - In GitHub App scans: on, because the repository's owner installed the App.
- In dashboard scans: never, because anyone can scan any public repository there.
AWS access key IDs can't be checked without the secret half of the key pair; Google API keys and private keys have no neutral check.
Fingerprints and false positives
Each finding has a stable fingerprint:
<rule id>:<first 12 characters of the commit>:<path>:<line>for example aws-access-key-id:3f2a9c1d7e04:config/deploy.env:1. To accept a finding, add its fingerprint to .dagsecignore on the default branch. The dashboard has a Copy ignore line button on each finding.
What to do about a real secret
- Revoke or rotate the credential at its provider first. Deleting it from the code doesn't remove it from git history, and history is copied to every clone and fork.
- Remove it from the code and load it from your CI's secret store or environment instead.
- Rewriting history (
git filter-repo) is optional once the credential is revoked, and doesn't reach existing clones.