Plan: mirror by default, opt-in per org, the scanner shipped on its own, dogfood on our own findings

f98b9a7 · Gabriel Morell · 2026-10-22 19:52

3 files +69 -2
Message
{commit_body(@commit)}

Files changed

modified docs/plans/dependency-security.md
+69 −2
@@ -1,8 +1,8 @@
1 1 # Plan: dependency security for repositories on the forge
2 2
3 3 Status: **proposed** (2026-10-22). Nothing here is built yet except
4 −step 0. Decisions so far are under "Decisions"; open questions 3, 5, 6,
5 −7 and 8 still have only recommended defaults.
4 +step 0. Decisions are under "Decisions"; open questions 3 and 8 still
5 +have only recommended defaults.
6 6
7 7 Three things, in the order they depend on each other:
8 8
@@ -196,6 +196,57 @@ the EEF's for Hex, PyPA, Go, npm and more. Two ways to ask it:
196 196 Config: `config :git_gud, GitGud.Advisories, source: :mirror | :api |
197 197 :off` (default `:mirror`), plus the ecosystems to fetch.
198 198
199 +### Who it's on for
200 +
201 +Off until an **organization turns it on**: a "Dependency alerts" switch
202 +in the org's settings, for its admins (`org.advisories_enabled` /
203 +`.advisories_disabled` in the audit log). On, every repository the org
204 +owns is scanned; a repository can't opt out of its org's choice. A
205 +personal repository has the same switch in its own settings, for its
206 +owner. The operator's `source: :off` turns it off for the instance
207 +whatever anyone chose. The mirror only downloads anything once at least
208 +one org or repository has it on.
209 +
210 +### Where the scanner comes from
211 +
212 +osv-scanner is a command, not a service, so the app runs it as a
213 +subprocess wherever it's installed. It looks for it at
214 +`GITGUD_OSV_SCANNER`, then `/usr/libexec/gitgud/osv-scanner`, then
215 +`PATH`; absent, the feature says so on the admin page and in each org's
216 +settings, and nothing else breaks. It is **not** baked into the app's
217 +image or the `gitgud` .deb, so the two move on separately:
218 +
219 +- **.deb:** a separate package, `gitgud-osv-scanner`, built by CI from
220 + the pinned upstream release (checked against its SHA-256), published
221 + to the forge's own apt repository beside `gitgud`, installing
222 + `/usr/libexec/gitgud/osv-scanner`. `gitgud` *Suggests* it. Bumping
223 + the scanner is a new `gitgud-osv-scanner` version, not a forge
224 + release.
225 +- **Kubernetes (this instance):** an init container from upstream's own
226 + image, pinned by digest, copies the binary into a shared `emptyDir`
227 + the app container mounts:
228 +
229 + ```yaml
230 + initContainers:
231 + - name: osv-scanner
232 + image: ghcr.io/google/osv-scanner:v2.6.0@sha256:<index digest>
233 + command: ["cp", "/osv-scanner", "/tools/osv-scanner"]
234 + volumeMounts: [{ name: tools, mountPath: /tools }]
235 + containers:
236 + - name: git-gud
237 + env: [{ name: GITGUD_OSV_SCANNER, value: /tools/osv-scanner }]
238 + volumeMounts: [{ name: tools, mountPath: /tools, readOnly: true }]
239 + volumes: [{ name: tools, emptyDir: {} }]
240 + ```
241 +
242 + A long-running sidecar would need a small RPC wrapper around a CLI
243 + for no gain; the init container is the sidecar pattern's simpler
244 + cousin. The OSV dumps live on the existing data volume
245 + (`GITGUD_DATA_DIR/osv`), so a restart doesn't re-download them.
246 +- **Podman / Docker (`/run-your-own`):** the same: a named volume
247 + filled once from the upstream image, mounted read-only, the env
248 + variable pointing at it.
249 +
199 250 ### What the mirror costs (measured 2026-10-22, osv-scanner 2.6.0)
200 251
201 252 OSV's per-ecosystem dumps, zipped, as osv-scanner keeps them:
@@ -312,6 +363,22 @@ nothing pushed without asking (pushing deploys).
312 363
313 364 ## Decisions (2026-10-22)
314 365
366 +- **OSV mirror by default** (open question 5); the API stays an opt-in
367 + for an operator who'd rather not store the dumps.
368 +- **Opt-in per organization** (and per personal repository), off until
369 + turned on.
370 +- **The scanner ships separately**: its own `gitgud-osv-scanner` .deb,
371 + and on Kubernetes an init container from upstream's pinned image —
372 + not inside the app's image or .deb (open question 6).
373 +- **Notifications as proposed** (open question 7): immediately for high
374 + and critical, a daily digest for the rest.
375 +- **Dogfood the whole lifecycle on the forge's own findings** before
376 + calling it done: `neiam` turns alerts on → the 21 findings of
377 + 2026-10-22 appear as alerts → the update workflow (B) opens PRs as
378 + `gg-security` → their CI, `audit.yml` included, goes green → merged →
379 + the alerts close as `fixed` on the next scan. Until then those
380 + dependencies stay as they are, by choice.
381 +
315 382 - **The CI account is `gg-security`**, one for the whole forge (open
316 383 question 1).
317 384 - **Job-token pull requests trigger `pull_request` workflows**, with the

Parents: 29569b7