Track repositories for code safety
Connect a repository host, assign repositories to tenants, and keep the code-safety auditor current with a baseline scan and an incremental scan on every push.
- Version: 0.4
- Feature: codesafety
- Role: superadmin
- Type: task
Administration → Repository Tracking points the CID222 code-safety auditor at your source code. You add a connector for a repository host, assign individual repositories to tenants, and CID222 runs a baseline scan on assignment and an incremental scan on every push. Findings are readable per repository, and you can ask a model to analyse selected files afterwards.
What do I need?
- Licence
- Enterprise
- Role
superadmin
Prerequisites
- You are signed in as the superadmin. An admin_user is offered this page and refused by every call it makes.
- `REPO_TRACKING_ENABLED` is `true` in the appliance's compose environment. Off, the module registers nothing and every route on this page returns 404.
- A cid-code-safety auditor is reachable at `CODE_SAFETY_SERVICE_URL`, with `CODE_SAFETY_SERVICE_ENABLED` set to `true` and the deep path enabled on the auditor itself.
- `JWT_SECRET` is set and persistent. Connector credentials are encrypted with a key derived from it, and the gateway refuses to store them otherwise.
- `PUBLIC_BASE_URL` is set if you want webhook-driven scans rather than polling.
Warning
This feature is off by default at three independent gates: REPO_TRACKING_ENABLED
(default false), CODE_SAFETY_SERVICE_ENABLED (default false), and the auditor's own
deep-path switch. All three must be on, on top of an Enterprise licence.
/assets/screenshots/repo-connector@0.4.pngWho can use this page
The page registry offers Repository Tracking to superadmin, admin_user and viewer, but
the backend admits only superadmin — a connector reads every repository assigned through it, and
an assignment decides whose scope the findings land in. An admin_user who opens the page is
refused by the page itself with Only the superadmin can manage repository connectors, because an
assignment decides whose scope findings land in. A viewer sees the page and its live-looking
buttons, and discovers on click that every write is refused with READ_ONLY_ROLE.
Add a connector
A connector is one repository host plus the credentials that read every repository assigned through it. Four types exist: GitHub / GHES, GitLab, Gitea / Forgejo and Generic git (polled). There is no Bitbucket and no Azure DevOps.
Open the dialog
Select Administration → Repository Tracking, then Add connector.
The Add repository connector dialog opens with GitHub / GHES selected.
Choose the type
Set Type.
The dialog's description and the host field change with the type. For Generic git (polled) the note reads: CID generates the deploy key itself and shows you only the public half; there is no API and no webhooks, so changes are found by polling.
Name it and set the host
Enter a Display name, then fill the host field.
For GitHub / GHES the field is GitHub Enterprise Server URL and you leave it blank for github.com — the host only, never a repository URL. For Generic git (polled) the field is Clone URL and it is the repository itself. Entering a repository URL where a host is expected raises an inline warning before you submit.
Supply credentials, or let GitHub create them
Leave the credential fields alone and optionally set GitHub organization. The button
reads Continue to GitHub; selecting it stages a disabled connector and hands you to
GitHub, which creates the App, asks you to approve it, and then asks which repositories it
may read. GitHub sends back the App id, private key and webhook secret — nothing is copied
by hand. The App requests read-only access to contents and metadata, and the push event.
The submit button reads Create, or Continue to GitHub on the manifest path.
Take the one-time secrets
Read the Connector created dialog and copy what it shows before closing it.
Depending on the type it shows a Deploy key (public half) to add to the repository as a
read-only deploy key, or a Payload URL and Secret to add as a webhook. Both are shown
once. Without PUBLIC_BASE_URL the dialog says so instead of inventing a URL.
Test the connection
Select Test on the connector's row.
Success toasts Connected — {n} repositories visible and the Status tag turns
active. Failure shows the host's own error text and stores it against the row, where the
error tag exposes it as a tooltip.
Danger
There is no edit dialog and no key-rotation screen. PATCH /admin/repo-connectors/:id exists in
the API, but the dashboard never calls it. To change a host, a token or a key you delete the
connector — which removes every repository tracked through it, along with their findings and
stored analyses — and create it again.
Assign a repository
An assignment binds one repository, through one connector, to one tenant. It cannot be bound to a tenant group. The tenant is the scope the auditor files findings under.
Start the assignment
Select Assign repo on the connector's row.
The button is disabled when no provider is available for the connector type. The Assign a repository dialog states: a baseline scan is queued immediately, and findings land in the chosen tenant's scope on the auditor.
Pick the repository and the tenant
Choose Repository from the list the connector can see, then choose Tenant.
The repository list comes from the host. If the connector cannot read it, an amber banner shows the host's own message instead of a list.
Queue the baseline
Select Assign.
A Repository assigned — baseline queued toast appears and the row joins Tracked
repositories with Baseline showing pending, then running, then complete.
While a baseline or an incremental scan is live, the list refreshes every 5 seconds and the Baseline cell carries a progress string — findings so far, files scanned against the total, and an estimate derived from this repository's own previous run. It never invents a denominator it does not have.
Keep it current
Once the baseline completes, CID222 tracks the repository's default branch only.
| Trigger | What happens |
|---|---|
| A push webhook arrives | CID222 verifies the HMAC signature over the raw body, then queues an increment |
| A generic-git repository is polled | A cluster-wide poll runs every 30 seconds and backs off to 30 minutes for a repository that has been quiet for a day |
| You select Rebaseline | The indexed commit is cleared and a full baseline is queued again |
An increment never scans the commits the webhook named. It always diffs the last indexed commit against the current head, which is why an outage catches up by itself, why several pushes coalesce into one scan, and why Rebaseline is also the fix after a force-push. The indexed commit advances only when the auditor reports the job completed.
Two caps apply. More than 500 changed files rebaselines instead of incrementing, and an individual file over 1 MiB is skipped.
Warning
Every webhook failure answers 404 — a wrong signature, a disabled connector, a connector with no secret, an unparseable body and rate limiting are deliberately indistinguishable, so an unauthenticated caller cannot enumerate connectors. Use the History dialog on the assignment, not the HTTP status, to find out why a push produced no scan.
Read the findings
Select Findings on a tracked repository — enabled only once a scan has completed. The dialog has three tabs.
- Findings — the auditor's findings, filterable by text and by the severities the response
itself declares. The header line names the finding count, the job's
decisionand the job id. Select the checkboxes to choose files, then Analyze with the model. - Files read — what the scan read and what it excluded, with the reason for each exclusion.
- Structure — the outline of the repository the model was shown, as files, symbols and import edges.
Danger
A scan marked degraded means unknown, not clean. The dialog says so: an analyzer was unavailable, so treat the result as unknown. A known auditor-side defect makes nearly every baseline report degraded today, so read that banner as a real caveat rather than noise.
The scan itself never runs the model. Analyze with the model is what does, one pass per file, at most 10 files per request, roughly a minute each. Three output formats are offered: Model summary, Full report and Compressed. Results are stored and listed in the dialog's history.
Fields reference
Connectors table
| Column | Shows |
|---|---|
| Name | The connector's display name |
| Type | GitHub / GHES, GitLab, Gitea / Forgejo or Generic git (polled) |
| Host | The base URL, or public host |
| Credentials | A key fingerprint, token set, or none |
| Status | active, disabled, error (tooltip carries the host's message) or no secret |
Row actions: Test, Assign repo, Delete.
Tracked repositories table
| Column | Shows |
|---|---|
| Repository | The repository name, with its last error underneath in red |
| Branch | The default branch — the only branch tracked |
| Baseline | pending, running, complete or failed, plus the live progress string |
| Indexed commit | The first eight characters of the anchor every increment diffs from |
| Last event | When the last tracking event was recorded |
| Enabled | A switch that stops and starts tracking |
Row actions: Findings, History, Rebaseline, Stop.
Create-connector fields
| Field | Types | Rule |
|---|---|---|
| Type | All | One of the four connector types. Cannot be changed afterwards |
| Display name | All | Required, up to 255 characters |
| GitHub Enterprise Server URL | GitHub | Blank for github.com. Host only, up to 2048 characters |
| GitLab URL / Gitea / Forgejo URL | GitLab, Gitea | Host only |
| Clone URL | Generic git | An ssh, ssh://, git:// or https:// clone URL |
| GitHub organization | GitHub | Blank creates the App on your own account |
| App ID | GitHub with existing App | Up to 64 characters |
| App private key (PEM) | GitHub with existing App | Up to 16384 characters |
| Access token | GitLab, Gitea | Up to 4096 characters, stored encrypted and never returned |
Verify
- The connector's Status reads active after Test, and the toast names a non-zero repository count.
- The assigned repository reaches Baseline
completeand shows an Indexed commit. - Findings opens and the header names a job id and a decision.
- Push a commit to the tracked branch. Within a poll or a webhook delivery, Last event moves
and History shows an
incrementrow that endscompleted. - This module writes nothing to Event logs. Creating a connector, deleting one, and reassigning a repository to another tenant leave no audit row. The only trail is the History dialog, which the code describes as a troubleshooting log rather than an audit record, and which is pruned after 30 days.
If it fails
- Refusing to store connector credentials: JWT_SECRET is not set, so they would be written
unencrypted. Set a persistent JWT_SECRET and restart. — the appliance is running with an
ephemeral secret. Set
JWT_SECRETand restart before creating a connector. - Leave the host URL blank for github.com — it is only for GitHub Enterprise Server. / Host URL must be the GHES host only (https://…), not a repository URL. — you entered a repository URL where the host was expected.
- That repository is not readable without credentials, and an https clone URL cannot use a deploy key. Use an ssh URL (git@host:org/repo.git) so a key can be generated, or put a token in the https URL. — a private generic-git repository behind an https clone URL.
- A clone URL cannot begin with "-" — git would read it as an option. / Clone URL must be ssh (git@host:org/repo.git), ssh://, git:// or https://. — the clone URL failed validation.
- Repository … is not visible to this connector — the repository is not in what the connector's credentials can list. On the GitHub App path, add it to the installation.
- No completed scan for this repository yet — check the event log — you opened findings, files or structure before any scan finished. Open History.
- This repository has no completed baseline yet, so there is no commit to read the files at. — model analysis needs an indexed commit.
- Select findings in at most 10 files — … were chosen. Each file is a separate model round trip. — narrow the selection.
- Scan … has expired from the auditor's retention window — rebaseline the repository to scan it again — the auditor pruned the job. Select Rebaseline.
- Not queued — Code Safety is not licensed on this deployment on Rebaseline — the auditor
client is disabled. Check
CODE_SAFETY_SERVICE_ENABLEDand the licence. - Every route on the page returns 404 while the sidebar link is present —
REPO_TRACKING_ENABLEDis nottrue. The sidebar hides this page on the licence key only; it has no probe for the feature flag, so a licensed appliance with the flag off shows a page that cannot work. - This feature (…) is not included in the installed license tier. Contact your CID
representative to upgrade. with
FEATURE_NOT_LICENSED— code safety is Enterprise only. - This is a read-only demo account (viewer role) — actions and changes are disabled. with
READ_ONLY_ROLE— a viewer selected a control that looked live.
Known gaps
- No audit trail. Nothing this page does is written to the event log.
- No connector editing or credential rotation in the UI, despite the module README describing rotation.
- No report or SARIF download. Both exist in the API; neither has a button.
- Findings are not reconciled between scans. Each job reports independently, so CID222 cannot yet say which findings are new since last week.
- Most of this page is untranslated. Only the page title, subtitle and the access-denied message go through the translation files; every dialog, table header, tag and toast is hard-coded English, including on a Turkish appliance.
- Neither list is paginated. Both tables return every row.
Next steps
- Access control — why this page is superadmin-only and what a
viewermay read. - License — confirm the appliance carries the code-safety capability.
- Claude Code — the other code-safety surface: what developers using the coding assistant did, and which filters fired.
Last updated on