Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

451 linesCodeBlameRaw
1---
2title: Security
3description: How g1t keeps secrets out of your repositories, finds vulnerable dependencies, and opens the pull requests that fix them.
4---
5
6import Aside from '../../../components/Aside.astro';
7
8Every project has a **Security** section at `g1t.sh/<owner>/<project>/security`.
9It shows what g1t has found, and what is being done about each alert:
10
11- **Secrets.** A push that adds a key or a token is refused before it lands,
12 and each repository's history is scanned once, in the background. See
13 also [secret protection](/guides/security/secret-protection/): custom
14 patterns, bypassing with a reason, and validity checks.
15- **Dependencies.** Every package your lockfiles resolve is checked against
16 the [OSV](https://osv.dev) database of known vulnerabilities. For each
17 vulnerable package that has a fix, g1t opens a pull request that raises it
18 to the fixed version, and the pull request lands through your branch's
19 required checks and merge queue. See also the
20 [supply chain](/guides/security/supply-chain/): the dependency graph, its
21 SBOM, and dependency review on pull requests.
22- **Code.** Static analysis results uploaded as SARIF become alerts, and
23 checks on pull requests. See [code scanning](/guides/security/code-scanning/).
24- **Reporting.** What the repository tells people about vulnerabilities in
25 what it ships, and how people tell its maintainers about one privately.
26 See [advisories and private reporting](/guides/security/advisories/).
27
28Its pages are **Overview**, **Secret scanning**, **Code scanning**,
29**Vulnerabilities**, **Dependency graph**, **Dependency updates**,
30**Advisories** and **Settings**. Secret scanning,
31push protection, vulnerability alerts, security updates and the dependency
32graph are free everywhere; the rest is free on public repositories and part
33of the g1t plan, as Security and quality, on private ones. See
34[what's free and what's paid](/guides/security/pricing/), and the
35workspace's [security overview](/guides/security/security-overview/).
36
37Alerts are the workspace's to fix, so the Security page needs a
38[role](/guides/access-and-roles/) on the repository, whether the project
39is public or private:
40
41| | Needs |
42| --- | --- |
43| See alerts, **Re-scan now** | Write |
44| **Dismiss** or **Reopen** an alert: a secret, a dependency or code scanning | Write |
45| **Bypass** push protection for a blocked secret | Write (or a request, with [delegated bypass](/guides/security/secret-protection/#delegated-bypass)) |
46| **Security updates** on or off, **Set up code scanning**, **Set up a security policy** | Maintain |
47| **Security settings**, **Custom patterns**, reviewing bypass requests, writing and publishing advisories, reviewing reports | Admin |
48
49A workspace's [security managers](/guides/workspaces/#roles-that-add-to-a-member)
50can do all of this on every repository in it, and change the workspace's
51security settings, whatever their role; owners can too.
52
53Someone with Read or Triage is told the page needs Write. The workspace's
54own page, `g1t.sh/<owner>/-/security`, lists the open alerts of every
55project the member can see them on, most severe first.
56
57Anyone else who can see a public repository sees its public face at the
58same address: the security policy, the published advisories, and, when the
59repository takes them, **Report a vulnerability**.
60
61## The overview
62
63The **Overview** is one row per feature, in three groups, each with a
64status word (**Enabled**, **Disabled**, **Needs setup** or **Coming**), a
65line on what it does, and the one thing to do about it:
66
67| Group | Row | Status | To the right |
68| --- | --- | --- | --- |
69| Policy | **Security policy** | Enabled when a `SECURITY.md` (or `.github/SECURITY.md`, `docs/SECURITY.md`) is on the default branch; Needs setup otherwise. | **View policy**, or **Set up a security policy**, which opens a pull request with a starting one. |
70| Findings | **Dependency alerts** | Enabled. | **View alerts**. |
71| Findings | **Security updates** | Enabled or Disabled. | The switch, for Maintain and up. The same switch is on **Vulnerabilities** and in **Settings**. |
72| Findings | **Code scanning** | Enabled once an analysis was uploaded; Needs setup before. | **View alerts**, or **Set up code scanning**. |
73| Findings | **Secret scanning** | Enabled; push protection is always on. | **View alerts**. |
74| Findings | **Code quality** | Coming: not built yet. | |
75| Reporting | **Security advisories** | Enabled. | **Manage advisories** or **View advisories**. |
76| Reporting | **Private vulnerability reporting** | Enabled or Disabled. | The switch, for Admins and security managers. |
77
78Below the rows, the open alerts are counted by severity: critical, high,
79medium, low and unrated. The counts are:
80
81- each open vulnerability, by its severity,
82- each open code scanning alert, by its severity, and
83- each secret in the repository's history that looks real, as critical.
84
85A secret that a push carried but that never landed (push blocked) is not
86counted, and neither is a [likely test value](#likely-test-values). A
87dismissed alert is not counted. Below the counts:
88
89- **Re-scan now** (on **Vulnerabilities**) reads the dependencies again at
90 once and scans the history again from the start.
91- **Security updates** (on **Vulnerabilities**) turns
92 [security updates](#security-updates) on or off for the project. It is on
93 unless someone turns it off.
94
95**Secret scanning** and **Vulnerabilities** list each alert. Filter either
96by state:
97
98| State | Meaning |
99| --- | --- |
100| Open | Needs attention. |
101| Dismissed | Someone dismissed it with a reason. |
102| Fixed | The secret was revoked, or the vulnerable version is no longer resolved by any lockfile. |
103
104## Secret scanning
105
106g1t looks for credentials whose format their issuer made recognisable, so a
107match is nearly always a real secret or a fake made to look like one:
108
109| What | What it looks like |
110| --- | --- |
111| AWS access keys | `AKIA`, `ASIA`, `ABIA` or `ACCA` and 16 more characters |
112| AWS secret access keys | 40 characters on a line that names an AWS secret |
113| GitHub tokens | `ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_`, `github_pat_` |
114| GitLab tokens | `glpat-`, `gloas-`, `glrt-`, `glptt-`, `gldt-` |
115| Stripe live keys | `sk_live_`, `rk_live_` (test keys are left alone) |
116| Slack tokens | `xoxb-`, `xoxp-`, `xoxa-`, `xoxr-`, `xoxs-`, `xoxe-` |
117| Slack webhooks | `https://hooks.slack.com/services/` addresses |
118| Google API keys | `AIza` and 35 more characters |
119| Anthropic API keys | `sk-ant-` |
120| OpenAI API keys | `sk-proj-`, `sk-svcacct-`, `sk-admin-`, and older `sk-` keys |
121| Private keys | A PEM `BEGIN … PRIVATE KEY` header followed by the key |
122| Service-role JWTs | A JWT whose claims carry `service_role` |
123| npm tokens | `npm_` |
124| g1t tokens | `g1t_` and 40 hex characters |
125| SendGrid keys | `SG.` and two dotted parts |
126
127Placeholders made of a handful of characters, such as `ghp_xxxxxxxx…`, are
128not reported. Lockfiles, and files under `node_modules/` and `vendor/`, are
129not scanned.
130
131g1t never stores a secret it finds. It keeps a fingerprint, so it can
132recognise the same secret again, and a short preview, such as `AKIA…`, so
133you can recognise it.
134
135### Likely test values
136
137g1t judges each value it finds by the value alone, never by the file it is
138in: a key under `tests/` is as real as one under `src/` if it was ever
139issued. A value is a **likely test value** when it:
140
141- is a key its issuer publishes as an example, such as AWS's documented
142 example keys;
143- says it is an example: it contains `example`, `sample`, `dummy`, `fake`,
144 `placeholder`, `changeme`, `notreal`, `redacted` or `xxxxx`;
145- counts up for six or more characters in a row, like `abcdef` or `123456`;
146- repeats one character five or more times in a row;
147- is a short piece repeated; or
148- has too little randomness to be a real key.
149
150A likely test value is still listed, in its own group on **Secret
151scanning**, with the reason. It never stops a push and is never counted as
152critical. Private keys are judged by the words only.
153
154### Push protection
155
156When a push over HTTPS adds a secret, g1t refuses the whole push and nothing
157is stored. Only the lines the push adds are checked, so a secret that is
158already in the repository does not block every later push to the same file.
159This applies to every push, including the pushes g1t makes to pull
160requests.
161
162Very large pushes are scanned after they land, not before. Such a push is
163too large to read in full before it is stored, so it goes through, and g1t
164then scans every commit it added, on whichever branch, in the
165background. A secret found there is an open alert, as if it had been found
166in history, and the workspace's owners get an email when one looks real.
167To have a large push checked before it lands, push it in parts, as
168[Size limits](/guides/git/#size-limits) shows.
169
170Git lists every secret it found, by file and line, with a link to allow
171each one:
172
173```text
174$ git push
175remote: g1t found a secret in this push, so nothing was pushed.
176remote:
177remote: config/prod.env:3 an AWS access key (commit 4807077)
178remote:
179remote: Take the secret out of the commit that adds it (git commit --amend, or
180remote: git rebase -i for an older commit), rotate it if it was ever real, and
181remote: push again.
182remote:
183remote: If it is not a real secret (a test fixture), or you will rotate it later:
184remote: - add g1t:allow-secret in a comment on its line, or
185remote: - bypass it with a reason at https://g1t.sh/acme/rocket/security/secret-scanning/sec_…
186remote: A bypass is recorded with your name and reason (or goes to an owner
187remote: to approve, if your workspace asks), then the same push goes through.
188To https://g1t.sh/acme/rocket.git
189 ! [remote rejected] main -> main (secret found: config/prod.env:3 has an AWS access key)
190```
191
192To fix a real secret:
193
1941. Remove it from the commit that adds it: `git commit --amend` for the last
195 commit, `git rebase -i` for an older one.
1962. Rotate it with whoever issued it. Once a secret has been on any machine
197 but yours, treat it as known.
1983. Push again.
199
200### Letting a secret through
201
202A fake key that does not look made up is still reported, on purpose: it
203looks exactly like a real one. Two ways to let it through:
204
205- **Mark the line.** Put `g1t:allow-secret` anywhere on the line, usually in
206 a comment. The line is never reported, in any push or in history.
207
208 ```ts
209 const FIXTURE_KEY = "AKIA…"; // g1t:allow-secret
210 ```
211
212- **Bypass it.** Open the link in git's message and choose **Bypass push
213 protection** with a reason, then push again, unchanged. Anyone with Write
214 can; see [pushing past push protection](/guides/security/secret-protection/#pushing-past-push-protection).
215- **Dismiss the alert.** Open the alert on **Secret scanning**, choose
216 **Dismiss** with the reason **False positive** or **Used in tests**, and
217 push again, unchanged: that secret no longer stops a push to this
218 project.
219
220Dismissing a secret alert needs the Admin [role](/guides/access-and-roles/)
221on the repository. Someone without it sees the same message and asks
222someone who has it.
223
224### Secrets in history
225
226The first time g1t sees a repository (when it is created, on its next push
227to the default branch, or when its Security page is first opened) it scans
228the default branch's history in the background, a page of commits at a
229time, comparing each commit with its first parent. The page shows how far
230it has got. Scanning is metered to the workspace like other usage, and it
231pauses if the workspace reaches its spending limit.
232
233A secret found in history is open: it is in the repository, and anyone
234who could clone it may have it. Rotate it, then dismiss it as **Revoked**.
235Removing it from the code is not enough, since it stays in history.
236
237Each page of the scan that finds secrets is one
238[notification](/guides/notifications/#security) to the workspace's owners
239and security managers, with how many it found; a repository whose history
240holds old test fixtures or rotated keys can turn **Alerts for secrets in
241the history** off in its Security settings, and the secrets are still
242listed here but tell nobody. Push protection is never off: a push that
243adds a secret is refused either way.
244
245### Dismissing a secret alert
246
247Choose **Dismiss**, pick a reason, and add a comment if you like (up to 500
248characters):
249
250| Reason | API value | What happens |
251| --- | --- | --- |
252| False positive | `false_positive` | Dismissed. Pushes carrying the secret go through. |
253| Used in tests | `used_in_tests` | Dismissed. Pushes carrying the secret go through. |
254| Revoked | `revoked` | Fixed. The secret was real and has been revoked or rotated. |
255| Won't fix | `wont_fix` | Dismissed. Pushes carrying the secret go through. |
256
257Who dismissed it, when, the reason and the comment are kept in the alert's
258activity log. **Reopen** puts the alert back to open; a secret that never
259landed goes back to blocking pushes.
260
261## Dependencies
262
263g1t reads these lockfiles on the default branch, up to four directories
264deep, skipping `node_modules`, `vendor`, `target`, `dist` and `build`:
265
266| Ecosystem | Files |
267| --- | --- |
268| npm | `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock` |
269| Cargo | `Cargo.lock` |
270| Go | `go.mod` (or `go.sum` where there is no `go.mod`) |
271| Python | `poetry.lock`, and pinned lines (`name==1.2.3`) in `requirements.txt` |
272
273It reads them on every push to the default branch and again every day, and
274asks OSV about every package at the exact version locked. Each advisory
275found is listed with its id (its GHSA id when it has one), severity, the
276version that fixes it, and the lockfile that resolves the vulnerable
277version. A vulnerability that a later scan no longer finds is marked fixed.
278
279### Dismissing a dependency alert
280
281Dismissing a dependency alert needs the Write role. Pick a reason, and add
282a comment if you like (up to 500 characters):
283
284| Reason | API value |
285| --- | --- |
286| A fix has already been started | `fix_started` |
287| No bandwidth to fix this | `no_bandwidth` |
288| Risk is tolerable to this project | `tolerable_risk` |
289| This alert is inaccurate or incorrect | `inaccurate` |
290| Vulnerable code is not actually used | `not_used` |
291
292A dismissed vulnerability that a later scan finds again stays dismissed
293until someone reopens it. The activity log keeps who dismissed it, when,
294the reason and the comment.
295
296### When there is no fix yet
297
298When no patched version has been published, the alert links the advisory
299and nothing is opened for it. You can dismiss it as **Risk is tolerable to
300this project**. g1t checks your dependencies again every day, fetching
301OSV's record for each advisory without a fix again, and opens a security
302update as soon as a fix is published.
303
304## Security updates
305
306With **Security updates** on, g1t opens a pull request to upgrade each
307vulnerable dependency that has a fix. It lands through your branch's
308required checks. A [dependency update file](/guides/dependency-updates/#security-updates-follow-the-same-file)
309can skip packages, group fixes into one pull request, and set the title's
310prefix, assignees and reviewers.
311
3121. g1t starts a sandbox, which raises the package to the fixed version in
313 each lockfile that resolves it, using the ecosystem's own tool.
3142. It commits as `g1t <g1t@users.noreply.g1t.sh>` and pushes a branch named
315 `g1t/security/<package>-<version>`.
3163. It opens a pull request, authored by `g1t`, that names the advisories it
317 fixes, the old and new versions, and the lockfiles it changed.
3184. The pull request merges through the branch's
319 [required status checks](/guides/pull-requests/#required-status-checks)
320 and the [merge queue](/guides/merge-queue/), like any other. When it
321 merges, the next scan finds the vulnerability fixed.
322
323Each alert shows its update's status and links the pull request:
324
325| Status | Meaning |
326| --- | --- |
327| Making the change | The sandbox is raising the version. |
328| Open | The pull request is open. |
329| Merged | The pull request merged. |
330| Closed | The pull request was closed without merging. |
331| Superseded | A newer security update for the same package replaced it, or the alerts it fixes were fixed another way or dismissed. |
332| Needs code changes | An earlier update that g1t opened an issue for. g1t closes that issue, and the update shows as failed. |
333| Failed | The update could not be made. |
334
335### When an update is no longer needed
336
337A newer security update for the same package closes the older pull request
338with the comment "Closed: superseded by #N, which upgrades `<package>` to
339`<version>`.".
340
341g1t also closes a security update once none of the alerts it fixes is
342open. An alert counts as one the update fixes when it is on the update's
343package, at a version below the one the update raises it to. Each of those
344alerts must be:
345
346- **Fixed on the default branch.** You raised the version another way, for
347 example with an `overrides` entry in `package.json` or by updating the
348 lockfile yourself, and pushed it. The next scan no longer finds the
349 vulnerable version in the lockfiles.
350- **Dismissed.** g1t checks when you dismiss an alert, without waiting for a
351 push.
352
353g1t comments why, closes the pull request and deletes its branch:
354
355> Closed: the alert this fixed is resolved on `main` (`sharp` 0.34.1 is no
356> longer in `package-lock.json`).
357
358> Closed: the alert this fixed was dismissed, so this update is no longer
359> needed.
360
361A grouped security update closes only when the alerts of every package in
362it are fixed or dismissed. If a package is still vulnerable at a higher
363version than the pull request reached, g1t closes the pull request and
364asks for a new one for the higher version in the same scan. A later scan never
365opens the closed pull request again. g1t opens a new one only if the
366package becomes vulnerable again, at a version that update would fix.
367
368When a security update pull request merges or closes, by g1t or by a
369person, g1t deletes its `g1t/security/…` branch, unless someone pushed to
370it after its last commit in the pull request. A branch left from an
371update pull request that was already closed is removed on a later push to
372the default branch. See
373[branches](/guides/dependency-updates/#branches).
374
375How each lockfile is changed:
376
377| Lockfile | How the version is raised |
378| --- | --- |
379| `package-lock.json` | `npm install --package-lock-only --ignore-scripts` for a direct dependency, keeping its range style; for an indirect one, `npm update`, then an `overrides` entry if that is not enough |
380| `pnpm-lock.yaml` | `pnpm update --lockfile-only`, then `pnpm.overrides` if needed |
381| `yarn.lock` | `yarn up` (Yarn 2 and later) or `yarn upgrade` (Yarn 1), then `resolutions` if needed |
382| `Cargo.lock` | `cargo update -p <package> --precise <version>` |
383| `go.mod`, `go.sum` | `go get <module>@<version>`, then `go mod tidy` |
384| `poetry.lock` | `poetry add <package>@^<version> --lock`, or `poetry update --lock` for an indirect one |
385| `requirements.txt` | The `==` pin is rewritten |
386
387Install scripts never run. A lockfile the sandbox cannot change this way,
388such as a `requirements.txt` that pins with `--hash`, is handled as
389[when an update cannot be made](#when-an-update-cannot-be-made): if no
390branch has been pushed 45 minutes after the update started, the update
391fails.
392
393### When an update cannot be made
394
395Sometimes raising the version is not enough, and g1t does not open an issue
396for it:
397
398- **The version cannot be raised.** A package that depends on it may hold
399 it back, or the lockfile cannot be changed in the sandbox. The update is
400 marked **Failed**, the alert says why, and g1t tries again a day later.
401- **The pull request's required checks fail.** It stays open with the
402 failing checks for you to decide on: push the code changes it needs to
403 its branch, or close it.
404
405If the vulnerable code is never reached, dismiss the alert with a reason.
406
407Turn **Security updates** off under **Vulnerabilities** to stop g1t opening
408these pull requests for a project. Alerts are still listed.
409
410## Version updates
411
412g1t can also keep dependencies at their newest versions. Add a
413`dependabot.yml` file (version 2) at `.g1t/dependabot.yml` or
414`.github/dependabot.yml` on the default branch, and g1t opens pull requests
415that raise your npm, Cargo, Go and pip dependencies on the schedule it
416sets. The same file shapes security updates: its `ignore`, `allow`,
417`groups`, `commit-message`, `assignees` and `reviewers` apply to them.
418
419The Security section's **Dependency updates** tab shows the
420file g1t read, any problems in it, each entry's next run and what its last
421run found, and a **Check for updates** button per entry. See
422[Dependency updates](/guides/dependency-updates/).
423
424## The g1t identity
425
426Pull requests, issues and merges that g1t makes itself, such as security
427updates, are authored by `g1t`, shown with the pixel 1 avatar. `g1t` is not
428an account: no one can sign in to it, and the name cannot be registered.
429
430## API and MCP
431
432| Route | What it does | Scope |
433| --- | --- | --- |
434| `GET /repos/{owner}/{name}/security/alerts` | Lists alerts. Filter with `state` (`open`, `dismissed`, `fixed`) and `kind`. | `repo:read` |
435| `POST /repos/{owner}/{name}/security/alerts/{id}/dismiss` | Dismisses an alert, with `reason` and an optional `comment`. | `repo:admin` |
436| `POST /repos/{owner}/{name}/security/alerts/{id}/reopen` | Reopens a dismissed alert. | `repo:admin` |
437
438`reason` is one of the API values in the tables above. Over MCP, the
439[`repository` tool](/reference/mcp/#repository) has the actions
440`security_alerts`, `dismiss_alert` and `reopen_alert`.
441
442Each kind of alert also has its own routes, with filters, under the
443`security:read` and `security:write` scopes: secret scanning, code
444scanning, vulnerability alerts, the dependency graph, settings and the
445workspace's overview, and the [`security` MCP tool](/reference/mcp/#security).
446See [secret protection](/guides/security/secret-protection/#api-and-mcp),
447[code scanning](/guides/security/code-scanning/#api-and-mcp) and the
448[supply chain](/guides/security/supply-chain/#api-and-mcp). The token's scope
449does not replace the role: dismissing still needs Admin for a secret and
450Write for a dependency. See the [API reference](/reference/api/) for the
451fields of each.