Skip to content
204 linesCodeBlameRaw
1---
2title: Secret protection
3description: Custom secret patterns, pushing past push protection with a reason, delegated bypass, validity checks, and every place a secret was found.
4---
5
6Secret scanning finds keys and tokens in what you push and in your
7history; [Security](/guides/security/#secret-scanning) covers the
8built-in formats, push protection and dismissing alerts. This page covers
9what goes further: patterns of your own, getting a blocked push through
10with a reason, having owners approve that, and asking a secret's issuer
11whether it still works.
12
13| Feature | Public repositories | Private repositories |
14| --- | --- | --- |
15| Secret scanning and push protection | Free | Free |
16| Bypass with a reason | Free | Free |
17| Custom patterns | Free | Security and quality activation |
18| Delegated bypass | Free | Security and quality activation |
19| Validity checks | Free | Security and quality activation |
20
21See [What's free and what's paid](/guides/security/pricing/).
22
23## The Secret scanning page
24
25`g1t.sh/<owner>/<project>/security/secret-scanning` lists the
26repository's secret alerts. Filter them by:
27
28| Filter | Values |
29| --- | --- |
30| State | Open (in the history, or blocked at a push), Dismissed, Fixed |
31| Type | Each kind of secret found, custom patterns by name |
32| Validity | Active, Inactive, Unknown, No check |
33| Bypassed | Bypassed, Not bypassed |
34
35Each alert has its own page with every place the secret was found (file,
36line and commit), what happened to it, its bypass requests, and its
37actions: **Dismiss** or **Reopen** (Admin), **Check with its issuer**,
38**Fix with g1t** and, for a blocked push, **Bypass**.
39
40A secret is one alert however many places hold it: the same value found
41in another file or another commit adds a location, not an alert.
42
43## Custom patterns
44
45A custom pattern is a secret format of your own, such as your company's
46internal API keys. Published, push protection refuses pushes that add a
47match and the default branch's history is scanned again for it, exactly as
48for the built-in formats.
49
50A repository's patterns are under **Custom patterns** on its Secret
51scanning page; a workspace's, which cover every repository in it, are at
52`g1t.sh/<owner>/-/security/patterns`. A repository is scanned with its own
53patterns and its workspace's, up to 100 in all.
54
55To add one:
56
571. Open **New pattern**.
582. Give it a **Name**, such as `Acme API key`.
593. Write the **Secret format** as a regular expression: `acme_[a-z0-9]{32}`.
604. Optionally, say what must come right **before** and **after** the
61 secret, also as regular expressions. Without them, the secret must start
62 at the beginning of a line or after a character that is not a letter or
63 digit, and end the same way.
645. Add **test strings**, one a line, and **Save as draft**: each test
65 string shows where the pattern matched.
666. **Dry run** it: the pattern runs over the default branch (up to 2,000
67 files and 20 MB) and lists what it would find, the matches masked.
68 Nothing is recorded. A workspace's dry run reads up to ten of its
69 repositories.
707. **Publish** it.
71
72Patterns use the [Rust regex syntax](https://docs.rs/regex/latest/regex/#syntax).
73Every pattern runs in time linear in the text it reads, so no pattern can
74stall a push: there is no look-around and there are no back-references,
75which are what make other engines slow on some inputs. A pattern is
76refused, with the reason, when it:
77
78- is longer than 1,000 characters,
79- does not compile, or compiles to more than 1 MB,
80- matches an empty string.
81
82Matching is per line: a secret that spans lines is not found. Lines longer
83than 4,000 characters (minified code) and lockfiles are skipped, as for the
84built-in formats. A line with `g1t:allow-secret` on it is never reported.
85
86Changing a pattern's format while it is published scans the history again
87too. Deleting one keeps the alerts it found. Repository patterns take the
88Admin role; workspace patterns, an owner.
89
90## Pushing past push protection
91
92When push protection refuses a push, git's message has a link for each
93secret, to its alert:
94
95```text
96remote: If it is not a real secret (a test fixture), or you will rotate it later:
97remote: - add g1t:allow-secret in a comment on its line, or
98remote: - bypass it with a reason at https://g1t.sh/acme/rocket/security/secret-scanning/sec_…
99remote: A bypass is recorded with your name and reason (or goes to an owner
100remote: to approve, if your workspace asks), then the same push goes through.
101```
102
103On the alert, choose **Bypass push protection** with a reason:
104
105| Reason | API value | The alert |
106| --- | --- | --- |
107| It's a false positive | `false_positive` | Closed as a false positive |
108| It's used in tests | `used_in_tests` | Closed as used in tests |
109| I'll fix it later | `will_fix_later` | Stays open, to be rotated; it counts as critical once it lands |
110
111Then push again, unchanged. Anyone with Write may bypass. The bypass is
112recorded on the alert (who, when, the reason and your comment) and in the
113workspace's [audit log](/guides/audit-log/) as
114`secret_scanning.bypass`.
115
116### Delegated bypass
117
118With **Delegated bypass** on, in the workspace's Security settings
119(`g1t.sh/<owner>/-/security/settings`), someone who pushed a blocked
120secret asks instead: **Ask to bypass** records a request, and the
121workspace's owners are told in their [inbox](/guides/inbox/). The
122workspace's owners and the repository's admins review requests at
123`g1t.sh/<owner>/-/security/bypass-requests`:
124
1251. Open the request.
1262. **Approve** or **Deny**, with a comment if you like.
1273. An approved request bypasses push protection as its requester asked; they
128 are told and push again.
129
130Nobody reviews their own request. Owners and admins bypass directly, as
131without delegation. Requesters can **Cancel request**. Each step is in the
132alert's activity and the audit log (`secret_scanning.bypass_requested`,
133`secret_scanning.bypass_reviewed`).
134
135## Validity checks
136
137With **Validity checks** on in the workspace's Security settings, g1t asks
138a secret's issuer whether it still works, and marks the alert:
139
140| Validity | Meaning |
141| --- | --- |
142| Active | The issuer accepted it. Rotate it. |
143| Inactive | The issuer refused it: revoked, expired or never real. |
144| Unknown | The issuer could not say (an error, a rate limit), or the secret never landed, so it cannot be read again. |
145| No check | There is no safe way to ask about this kind of secret. |
146
147Each check is the issuer's own read-only identity call, made over HTTPS by
148the service that read the secret from your repository. The secret goes
149nowhere else, and is never stored:
150
151| Secret | Check |
152| --- | --- |
153| GitHub token | `GET https://api.github.com/user` |
154| GitLab personal access token | `GET https://gitlab.com/api/v4/personal_access_tokens/self` |
155| Stripe live key | `GET https://api.stripe.com/v1/balance` (a restricted key without access to it is still active) |
156| Slack token | `POST https://slack.com/api/auth.test` |
157| npm token | `GET https://registry.npmjs.org/-/whoami` |
158| OpenAI API key | `GET https://api.openai.com/v1/models` |
159| Anthropic API key | `GET https://api.anthropic.com/v1/models` |
160| SendGrid key | `GET https://api.sendgrid.com/v3/scopes` |
161
162Other formats have no check: an AWS access key cannot be checked without
163its secret key, a Slack webhook address would post a message, and the rest
164have no read-only identity call. Custom pattern matches are never checked.
165
166Open alerts are checked again weekly, and on request with **Check with its
167issuer** on the alert.
168
169## Fix with g1t
170
171**Fix with g1t** on a secret alert opens an issue for g1t to take the
172secret out of the code and read it from configuration instead (an
173environment variable, or the repository's
174[Actions secrets](/guides/secrets-and-variables/)). Its pull request lands
175through your required checks. Taking a secret out of the code does not make
176it safe: it is in the history, so whoever owns it still rotates it with its
177issuer and marks the alert **Revoked**. The agent's run is charged as
178[agent usage](/guides/usage-and-billing/).
179
180## API and MCP
181
182| Route | What it does | Scope |
183| --- | --- | --- |
184| `GET /repos/{owner}/{name}/secret-scanning/alerts` | Lists alerts; filter with `state`, `secret_type`, `validity`, `bypassed`. | `security:read` |
185| `GET /workspaces/{workspace}/secret-scanning/alerts` | The same across a workspace. | `security:read` |
186| `GET /repos/{owner}/{name}/secret-scanning/alerts/{id}` | One alert with its locations, activity and requests. | `security:read` |
187| `PATCH /repos/{owner}/{name}/secret-scanning/alerts/{id}` | Dismisses (`state` `dismissed`, `reason`, `comment`) or reopens (`state` `open`). Admin. | `security:write` |
188| `GET /repos/{owner}/{name}/secret-scanning/alerts/{id}/locations` | Where the secret was found. | `security:read` |
189| `POST /repos/{owner}/{name}/secret-scanning/alerts/{id}/bypass` | Bypasses push protection with `reason`, or asks to. | `security:write` |
190| `POST /repos/{owner}/{name}/secret-scanning/alerts/{id}/validity` | Checks with the issuer. | `security:write` |
191| `GET /workspaces/{workspace}/secret-scanning/bypass-requests` | Bypass requests; filter with `state`, `repo`. | `security:read` |
192| `PATCH /workspaces/{workspace}/secret-scanning/bypass-requests/{id}` | `decision` `approve`, `deny` or `cancel`. | `security:write` |
193| `GET`, `POST /repos/{owner}/{name}/secret-scanning/custom-patterns` | Lists or creates patterns (also under `/workspaces/{workspace}`). | `security:read`, `security:write` |
194| `PATCH`, `DELETE …/custom-patterns/{id}` | Changes, publishes or deletes one. | `security:write` |
195| `POST …/custom-patterns/dry-run` | Runs a pattern over the default branch without saving it. | `security:write` |
196
197Over MCP, the [`security` tool](/reference/mcp/#security) has the actions
198`secret_alerts`, `secret_alert`, `update_secret_alert`, `secret_locations`,
199`bypass`, `check_validity`, `bypass_requests`, `review_bypass`, `patterns`,
200`create_pattern`, `update_pattern`, `delete_pattern` and
201`dry_run_pattern`. g1t's own agents never bypass, dismiss or change
202patterns. Webhooks: `secret_scanning_alert.created`, `.fixed`,
203`.dismissed`, `.reopened`, and `secret_scanning.bypass_requested`,
204`secret_scanning.bypass_reviewed`; see [Webhooks](/guides/webhooks/).