Pin what we build and scan what we ship: Renovate, OSV-Scanner, Gitleaks and ruff on Gitea Actions (phase one, rides the migration) #41

Open
opened 2026-11-01 06:41:16 +00:00 by solo.ist · 1 comment

What's the idea?

Once #30 gives us a CI runner, put four open tools on it so that the things we build ourselves are pinned, scanned and linted without anyone remembering to do it:

  1. Renovate opens pull requests to update dependencies and, more importantly, to pin container images by digest and Actions by commit SHA. It runs natively against Gitea.
  2. OSV-Scanner checks our dependency files and images against the OSV database on every pull request, and on a schedule against main, so a CVE published next month still reaches us.
  3. Gitleaks fails a pull request that contains something shaped like a key or token.
  4. ruff lints the Python apps for the class of bug that only shows up at runtime.

Everything runs as a Gitea Actions job. Nothing here needs a server of its own, and all four are Apache, MIT or AGPL licensed with no paid tier involved.

Why does it matter?

Two things make this timely and urgent. Attackers now use LLMs to find and weaponize exploits faster than before, and member contributions from forks are becoming normal here, so a weak link in the supply chain or an accidental leak in a member PR carries a real cost to the stability and reputation of the service.

The one real outage we've had so far was a pinning problem. On October 2 the gateway rebuilt against a floating image tag that had silently moved, which pulled in a Prisma migration, an out-of-memory loop and about 45 minutes of downtime (write-up in co-op/docs PR #9). The fix was an exact pin. The same lesson arrived from the other direction in March, when attackers who had compromised Trivy's release pipeline re-pointed its Docker Hub and GitHub Action tags at code that stole CI credentials (GHSA-69fq-xp46-6x23, Apache Infrastructure's account). No bug in Trivy was exploited; mutable tags were the weak point.

Today our custom images still carry that shape. The gateway pins LiteLLM by version tag, not digest. The three Python apps build on a floating python:3.12-slim. The portal's requirements are pinned to versions that are over a year old, with nobody watching for advisories against them. And the portal's commit log from October 20 records two latent NameErrors and an async def that was never awaited, each of which crashed or silently failed on first use. A linter catches all three in seconds.

The migration (#2) is the moment to do this. The gateway and the custom apps get rebuilt and redeployed during the move anyway, so pinning by digest costs nothing extra right then, and a migration that rebuilds from git lands on the same bits only if the pins live in git.

Who is it for?

Whoever operates the stack, first of all: fewer surprises at rebuild time, and a pull request to review instead of a silent change. Members, because the three apps that hold their keys and credits get continuous checks rather than periodic audits (#24 found the same open item across three passes, which says how much standing capacity there is for manual sweeps). And anyone who later reuses the portal kit, since the configs travel with the repos.

How might it work?

Phase one, all serverless, in this order:

  • Renovate as a scheduled Actions job (or a container on the agent host until the runner exists), with its own pull-only Gitea account, exactly the standing a member has. Config per repo in renovate.json: digest pinning for FROM lines, SHA pinning for Actions, grouped minor and patch updates, weekly schedule so it doesn't flood. It would open the LiteLLM digest pin and the python:3.12-slim pin as its first two PRs.
  • OSV-Scanner on pull requests against requirements.txt and the built image, failing only on high and critical at first to avoid alert fatigue; plus a weekly run against main of each custom repo that posts findings to an issue or the updates feed. That weekly run is the cheap substitute for a central inventory while we have five images.
  • Gitleaks on pull requests, with the default rules and an allowlist file for the few fixtures that legitimately look like secrets.
  • ruff on pull requests for the Python apps, errors only, no style rules.

Later, if wanted: Semgrep CE for pattern-level checks on our own code (also serverless; the only cost is reading its findings). OWASP Dependency-Track as a hosted inventory only if the custom-image count grows past what a weekly scan covers; it is another Postgres-backed server to upgrade and back up. Prowler and the Kubernetes tools don't apply: we have Cloudron on VPSes, not a cloud account they can read.

Rules for the tools themselves, since scanners run with access to code and CI secrets and are a supply-chain target too: pin their own images and Actions by digest and SHA; give each job the narrowest token it needs, which for all four is read-only plus pull-request creation for Renovate; keep the account that opens PRs separate from any credential that can merge or deploy.

Who does the work: the operator or the bot, on the runner #30 chooses. I'm happy to write the renovate.json files and the four workflow files as PRs from my fork, one repo at a time, and leave the runner secrets and the first merge to whoever holds them.

Anything else?

  • Depends on #30 (runner). Everything above works on either route it picks, Gitea Actions or Woodpecker, with the YAML adjusted.
  • Pairs with #2 (migration): the digest pins should land before or during the rebuild, not after.
  • Related: #24 (audit), and the incident write-up in co-op/docs PR #9, which this would make a non-event next time.
  • Findings need a home. My suggestion is one issue per repo that the weekly scan updates, plus a line in the updates feed when something is high or critical, but that's a norm for whoever triages, and I'd rather hear what the operator prefers.
  • Sources: Renovate's Gitea platform docs, OSV-Scanner, Gitleaks, ruff, Gitea Actions.
## What's the idea? Once #30 gives us a CI runner, put four open tools on it so that the things we build ourselves are pinned, scanned and linted without anyone remembering to do it: 1. **Renovate** opens pull requests to update dependencies and, more importantly, to pin container images by digest and Actions by commit SHA. It runs natively against Gitea. 2. **OSV-Scanner** checks our dependency files and images against the OSV database on every pull request, and on a schedule against `main`, so a CVE published next month still reaches us. 3. **Gitleaks** fails a pull request that contains something shaped like a key or token. 4. **ruff** lints the Python apps for the class of bug that only shows up at runtime. Everything runs as a Gitea Actions job. Nothing here needs a server of its own, and all four are Apache, MIT or AGPL licensed with no paid tier involved. ## Why does it matter? Two things make this timely and urgent. Attackers now use LLMs to find and weaponize exploits faster than before, and member contributions from forks are becoming normal here, so a weak link in the supply chain or an accidental leak in a member PR carries a real cost to the stability and reputation of the service. The one real outage we've had so far was a pinning problem. On October 2 the gateway rebuilt against a floating image tag that had silently moved, which pulled in a Prisma migration, an out-of-memory loop and about 45 minutes of downtime (write-up in co-op/docs PR #9). The fix was an exact pin. The same lesson arrived from the other direction in March, when attackers who had compromised Trivy's release pipeline re-pointed its Docker Hub and GitHub Action tags at code that stole CI credentials ([GHSA-69fq-xp46-6x23](https://db.gcve.eu/vuln/GHSA-69fq-xp46-6x23), [Apache Infrastructure's account](https://infra.apache.org/blog/trivy_security_incident.html)). No bug in Trivy was exploited; mutable tags were the weak point. Today our custom images still carry that shape. The gateway pins LiteLLM by version tag, not digest. The three Python apps build on a floating `python:3.12-slim`. The portal's requirements are pinned to versions that are over a year old, with nobody watching for advisories against them. And the portal's commit log from October 20 records two latent `NameError`s and an `async def` that was never awaited, each of which crashed or silently failed on first use. A linter catches all three in seconds. The migration (#2) is the moment to do this. The gateway and the custom apps get rebuilt and redeployed during the move anyway, so pinning by digest costs nothing extra right then, and a migration that rebuilds from git lands on the same bits only if the pins live in git. ## Who is it for? Whoever operates the stack, first of all: fewer surprises at rebuild time, and a pull request to review instead of a silent change. Members, because the three apps that hold their keys and credits get continuous checks rather than periodic audits (#24 found the same open item across three passes, which says how much standing capacity there is for manual sweeps). And anyone who later reuses the portal kit, since the configs travel with the repos. ## How might it work? **Phase one, all serverless, in this order:** * **Renovate** as a scheduled Actions job (or a container on the agent host until the runner exists), with its own pull-only Gitea account, exactly the standing a member has. Config per repo in `renovate.json`: digest pinning for `FROM` lines, SHA pinning for Actions, grouped minor and patch updates, weekly schedule so it doesn't flood. It would open the LiteLLM digest pin and the `python:3.12-slim` pin as its first two PRs. * **OSV-Scanner** on pull requests against `requirements.txt` and the built image, failing only on high and critical at first to avoid alert fatigue; plus a weekly run against `main` of each custom repo that posts findings to an issue or the updates feed. That weekly run is the cheap substitute for a central inventory while we have five images. * **Gitleaks** on pull requests, with the default rules and an allowlist file for the few fixtures that legitimately look like secrets. * **ruff** on pull requests for the Python apps, errors only, no style rules. **Later, if wanted:** Semgrep CE for pattern-level checks on our own code (also serverless; the only cost is reading its findings). OWASP Dependency-Track as a hosted inventory only if the custom-image count grows past what a weekly scan covers; it is another Postgres-backed server to upgrade and back up. Prowler and the Kubernetes tools don't apply: we have Cloudron on VPSes, not a cloud account they can read. **Rules for the tools themselves**, since scanners run with access to code and CI secrets and are a supply-chain target too: pin their own images and Actions by digest and SHA; give each job the narrowest token it needs, which for all four is read-only plus pull-request creation for Renovate; keep the account that opens PRs separate from any credential that can merge or deploy. **Who does the work:** the operator or the bot, on the runner #30 chooses. I'm happy to write the `renovate.json` files and the four workflow files as PRs from my fork, one repo at a time, and leave the runner secrets and the first merge to whoever holds them. ## Anything else? * Depends on #30 (runner). Everything above works on either route it picks, Gitea Actions or Woodpecker, with the YAML adjusted. * Pairs with #2 (migration): the digest pins should land before or during the rebuild, not after. * Related: #24 (audit), and the incident write-up in co-op/docs PR #9, which this would make a non-event next time. * Findings need a home. My suggestion is one issue per repo that the weekly scan updates, plus a line in the updates feed when something is high or critical, but that's a norm for whoever triages, and I'd rather hear what the operator prefers. * Sources: [Renovate's Gitea platform docs](https://docs.renovatebot.com/modules/platform/gitea/), [OSV-Scanner](https://google.github.io/osv-scanner/), [Gitleaks](https://github.com/gitleaks/gitleaks), [ruff](https://docs.astral.sh/ruff/), [Gitea Actions](https://docs.gitea.com/usage/actions/overview).
Owner

Endorsing this. It's the strongest proposal on the table, and it names the real weak point correctly: our one actual outage was a pinning failure. On 2 October the gateway rebuilt against a floating tag that had silently moved, and our custom images still have that shape — the gateway pins LiteLLM by version, not digest, and the Python apps build on a floating base.

Riding the migration is the right call. Those images get rebuilt and redeployed during the move anyway, so pinning by digest then costs nothing extra. And digest-pinning the gateway is worth doing at cutover whether or not the runner exists yet — that is the specific fix for the October failure, and it shouldn't wait on #30.

Three things I'd settle before phase one starts:

  • The fallback needs adjusting. Running Renovate as a container on the agent host conflicts with a standing rule here: no Docker on that host. Renovate runs fine as a plain CLI with a scheduler, so phase one can still start before the runner exists — just not that way.
  • Renovate needs its own Gitea account, and it should be an agent account, not a member one, with the same budget and visibility treatment as the co-op's other agents. Otherwise it shows up in member surfaces where it doesn't belong.
  • Plan for review volume. The tools are free; the reviewing isn't. Grouped weekly updates beat a daily stream of dependency PRs, especially while the migration is in flight.

Also worth aligning: Gitleaks and the secret check already running in our state-mirror job should share one allowlist convention, or the two will disagree about the same fixture.

The calibration in the proposal is right — high and critical only at first, ruff errors only, an allowlist for legitimate fixtures. And the part that shows this was written by someone who knows the stack: treating the scanners themselves as supply-chain targets that need pinning. I'd support taking this up in this shape.

(Inferencebot — the co-op's steward bot. Proposals and member-facing changes here go through the GM before they're acted on.)

Endorsing this. It's the strongest proposal on the table, and it names the real weak point correctly: our one actual outage was a pinning failure. On 2 October the gateway rebuilt against a floating tag that had silently moved, and our custom images still have that shape — the gateway pins LiteLLM by version, not digest, and the Python apps build on a floating base. **Riding the migration is the right call.** Those images get rebuilt and redeployed during the move anyway, so pinning by digest then costs nothing extra. And digest-pinning the gateway is worth doing at cutover **whether or not the runner exists yet** — that is the specific fix for the October failure, and it shouldn't wait on #30. Three things I'd settle before phase one starts: - **The fallback needs adjusting.** Running Renovate as a container on the agent host conflicts with a standing rule here: no Docker on that host. Renovate runs fine as a plain CLI with a scheduler, so phase one can still start before the runner exists — just not that way. - **Renovate needs its own Gitea account, and it should be an agent account**, not a member one, with the same budget and visibility treatment as the co-op's other agents. Otherwise it shows up in member surfaces where it doesn't belong. - **Plan for review volume.** The tools are free; the reviewing isn't. Grouped weekly updates beat a daily stream of dependency PRs, especially while the migration is in flight. Also worth aligning: Gitleaks and the secret check already running in our state-mirror job should share one allowlist convention, or the two will disagree about the same fixture. The calibration in the proposal is right — high and critical only at first, ruff errors only, an allowlist for legitimate fixtures. And the part that shows this was written by someone who knows the stack: treating the scanners themselves as supply-chain targets that need pinning. I'd support taking this up in this shape. *(Inferencebot — the co-op's steward bot. Proposals and member-facing changes here go through the GM before they're acted on.)*
inference-admin added the Infrastructure label 2026-11-09 16:23:41 +00:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: co-op/support#41