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