Connecting GitHub
Connect a GitHub repository and your SBOM refreshes itself. The portal reads the repository's dependency graph once a night, replaces the stored component list, and scans the new list against OSV in the same run. Nobody has to remember to export a file.
This matters more than it sounds. One SBOM is stored per company and correlation only knows the versions that SBOM pins, so an inventory nobody has replaced in months accumulates findings against versions you stopped shipping. See Managing the SBOM Registry for what goes wrong and why re-uploading fixes it. A connected repository removes the step a person forgets.
The connector is available on the Enterprise plan, and only company admins can configure it.
The token
Create a fine-grained personal access token at https://github.com/settings/personal-access-tokens/new. Four settings matter, and one of them catches almost everybody.
Resource owner
If the repository belongs to an organisation, set the resource owner to that organisation, not to your personal account.
This is the setting that goes wrong. A fine-grained token reaches the repositories of exactly one owner. Created against your personal account, it cannot see anything the organisation owns, and GitHub reports a private repository a token cannot see as missing rather than forbidden. So the symptom is "Repository not found" for a repository whose name is spelled perfectly.
What makes it stubborn is that the token is not broken. It authenticates, the card says Connected and names your account, and only the repository fails. Connecting successfully is not evidence that the repository is reachable.
Two things can happen when you pick an organisation:
- It is not in the list. The organisation has not enabled fine-grained tokens. An owner enables them under Organization settings, then Personal access tokens, then Settings.
- The token is created but marked pending. An owner has to approve it under Organization settings, then Personal access tokens, then Pending requests. Until they do, the repository keeps returning not found.
Repository access
Choose Only select repositories and pick the one repository. "All repositories" grants more than the connector will ever use.
Permissions
Exactly two, both read:
| Permission | Access | What it is for |
|---|---|---|
| Contents | Read-only | Reading the dependency graph. This is the one that matters. |
| Metadata | Read-only | Confirming the repository is reachable. GitHub adds it automatically with Contents and does not let you remove it. |
Grant nothing else. No Actions, no Administration, no Secrets, no Webhooks, and no write access anywhere. The connector calls two endpoints and no others: one to check it can see the repository, one to read the component list. It never writes to your repository, never opens issues or pull requests, and never reads your source code.
Expiry
Fine-grained tokens expire, up to a maximum of one year. When yours does, the nightly pull starts failing and the card says the token was rejected. To replace it, disconnect and connect again with the new token. Your SBOM is not affected by disconnecting.
Other requirements
The repository needs its dependency graph switched on. That is the default on public repositories. On a private repository it is a setting under Settings, then Code security.
GitHub Enterprise Server is not supported. The connector only talks to api.github.com, and the host is fixed in code rather than configurable, so a stored connection cannot be pointed anywhere else.
Connecting
- Go to Settings, then Integrations.
- On the GitHub card, choose Connect.
- Paste the token, and optionally the repository as
owner/name. A pasted GitHub URL works too. - Choose Connect. The token is verified against GitHub before it is stored.
The token is encrypted at rest and is never shown again, not even to you. To replace it, connect again with the new one.
Once a repository is set, choose Pull now to import the component list immediately rather than waiting for the overnight run.
What happens each night
At 06:30 UTC the portal:
- Reads the dependency graph of every connected repository.
- Replaces that company's stored SBOM with what it found, marked as sourced from GitHub.
- Scans the new component list against OSV, opens findings for anything newly vulnerable, and closes findings the new list no longer matches.
- Records the supply-chain verdicts in the control register, including the 90-day freshness check, which a connected repository keeps passing on its own.
Two kinds of entry are filtered out before anything is stored. The repository itself, which GitHub lists alongside your dependencies and which would otherwise appear as a component in its own inventory. And the actions your workflows use, such as actions/checkout, which are build infrastructure rather than parts of the product, and which GitHub reports at wildcard versions like 5.*.* that no advisory range can be matched against.
What remains carries a package URL for every entry, which is what makes vulnerability matching a lookup rather than a name guess. An SBOM assembled by hand from a bare manifest often does not, and components without one are silently skipped by the scan.
What the dependency graph does and does not tell you
The dependency graph is your whole dependency tree, and it does not distinguish build-time dependencies from the ones you ship. A test framework and a runtime library arrive the same way, because GitHub's output carries no marker separating them.
That has two consequences worth understanding before you connect.
The component count will be considerably larger than a hand-assembled SBOM. Connecting this repository, for example, yields around 2,070 components where the manually uploaded file listed 706. Neither number is wrong. They are answers to different questions.
More components means more findings, and some of them will be against packages that never reach a customer. A vulnerability in a test runner is a real vulnerability and a poor candidate for a CRA disclosure obligation, which concerns the product placed on the market.
If that distinction matters to you, and for a conformity assessment it eventually will, generate the SBOM in your build from the production dependency tree and push it through the API. The connector is the better answer when the alternative is an SBOM nobody refreshes, which is the usual situation. It is the weaker answer once you have a release pipeline that can tell the two trees apart.
Changing or removing the connection
Change the repository from the same card, without re-pasting the token. The portal checks that the token can reach the new repository before saving it, so a typo shows up straight away.
Clearing the repository stops the nightly pull and leaves everything else in place.
Disconnecting removes the token. The SBOM already pulled stays, because it is your component inventory and your findings history refers to it. It simply stops refreshing, and after 90 days the freshness check in the control register says so.
When a sync fails
The card shows the outcome of the last attempt, successful or not, along with the date. A connection that stopped working reads as failed rather than as quiet, which is the distinction that matters: a connector silently doing nothing looks exactly like a connector with nothing to do.
Common causes:
| What you see | What it usually means |
|---|---|
| GitHub rejected the token | The token expired. Fine-grained tokens have a maximum lifetime, so this is expected eventually. Disconnect and connect again with a new one. |
| Repository not found | Almost always the resource owner, see above: a token created against your personal account cannot see an organisation's repository, and a pending token cannot see it either. Failing that, check the name and that the token lists this repository. |
| GitHub refused the request | A missing permission, or the hourly API rate limit is exhausted. |
| No dependency data | The dependency graph is switched off for that repository. |
Uploading an SBOM by hand still works while a connection is failing, and a manual upload takes precedence until the next successful nightly pull replaces it.
Pushing instead of pulling
If your build pipeline already produces a CycloneDX or SPDX file, pushing it on every release stays the better option, because the freshness of your inventory then tracks your actual release cadence. See the API overview. The connector exists for the case where no such pipeline step exists yet, which is most of the time.