Skip to content

chore(ci): take fork-reachable jobs off the self-hosted runners (ENG-2017) - #88

Merged
mindsdb-devops merged 4 commits into
mainfrom
chore/eng-2017-fork-jobs-off-self-hosted-runners
Aug 28, 2026
Merged

chore(ci): take fork-reachable jobs off the self-hosted runners (ENG-2017)#88
mindsdb-devops merged 4 commits into
mainfrom
chore/eng-2017-fork-jobs-off-self-hosted-runners

Conversation

@lucas-koontz

@lucas-koontz lucas-koontz commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

User story

As an engineer who reviews external contributions to this repository
I want approving CI on a stranger's pull request to be a harmless act
So that a plausible-looking patch cannot execute its own code on a pod inside our clusters

Why this matters

test_on_pr.yml runs on pull_request with runs-on: mdb-dev, which is a pod inside our newdev EKS cluster and not a GitHub VM. It checks out the pull request's head and runs pip install -r requirements.txt against it. A fork owns that file, so an approved fork pull request installed and executed whatever it named, on a pod holding a Kubernetes identity, an AWS role and a filesystem other repositories' credentialed jobs write into.

Separately, cla.yml ran on mdb-dev too. Its triggers, pull_request_target and issue_comment, run in base-repo context, so GitHub's fork-approval gate never applied and any account could start that pod with no human in the loop.

What happens today

flowchart TD
    F["Fork pull request"] --> APP{"Someone with push clicks Approve and run"}
    APP --> J["test job on mdb-dev"]
    J --> CO["Checks out the fork's tree"]
    CO --> PI["pip install -r requirements.txt, fork-controlled"]
Loading

What should happen

flowchart TD
    F["Fork pull request"] --> APP{"Someone with push clicks Approve and run"}
    APP --> J["test job on ubuntu-latest"]
    J --> CO["Checks out the fork's tree"]
    CO --> PI["pip install runs on a disposable VM"]
Loading

Acceptance criteria

  • test_on_pr.yml no longer requests mdb-dev.
  • The CLA job no longer requests mdb-dev and skips outright for a comment that is not a CLA comment.
  • The test job still runs across Python 3.8, 3.9, 3.10 and 3.11.
  • Negative: deploy.yml and test_on_deploy.yml are untouched. They are release-triggered, not fork-reachable, and moving the publish job is a separate piece of work.
  • Negative: the existing signature ledger, 10 signers on the cla branch, is unchanged.

How to test

  1. Open a pull request from a fork. Confirm the test matrix runs on ubuntu-latest for all four Python versions and that no self-hosted job is offered a runner.
  2. Open a pull request from a fork using an account not in the CLA allowlist and confirm the CLA job runs on a hosted runner and asks for a signature.
  3. Cut a release and confirm the publish path is unchanged.

Notes for the reviewer

The allowlist is gone from this file, and staff now sign like everybody else. Fifteen repositories carried six different hand-maintained copies naming 25 people between them, and 11 of those 25 had already left the org while still being exempt from signing. It now defaults in the reusable and is bots only: dependabot[bot] and mindsdb-release-train[bot], the two that actually open pull requests in this org. A bot cannot post the agreement sentence, so without an exemption its pull request is red forever.

Dropping the line also drops two live defects that rode inside it. bot* compiled to an unanchored new RegExp("bot.*").test(login), so it exempted any login containing "bot", including robotnik and sabotage; anyone could opt out of the CLA by choosing a username. And Stpmax never matched the real login StpMax, because non-wildcard entries use a case-sensitive ===. Needs mindsdb/github-actions#56, which gives the input its default.

.github/ gets a code owner. This repo had no CODEOWNERS, so a change to what runs in CI, on which runner, and with which secrets in scope needed no particular reviewer, on a repo that accepts outside pull requests. Auto-request only for now: a hard gate needs require_code_owner_reviews in branch protection, which this repo does not set and which rides ENG-2013's org ruleset. Faking it here would be a file that looks like a control and is not one.

The green CLAssistant row on this pull request does not exercise this change. pull_request_target loads the workflow from the base branch, so the check that passed here is the copy this PR replaces, running on mdb-dev. The new job cannot test itself on its own pull request; it is exercised on the first pull request opened after this merges.

The on: and permissions: blocks swap places, which is why the diff looks bigger than it is. Every converted wrapper now reads name, permissions, triggers, job, in that order. No trigger and no scope changes as a result of the move itself.

The matrix is unchanged, which needed checking rather than assuming. Python 3.8 has been end of life since 2024 and is not in the ubuntu-24.04 image, so the obvious worry is that ubuntu-latest drops half the matrix. I read actions/python-versions' manifest: 3.8, 3.9, 3.10 and 3.11 all publish ubuntu-24.04 x64 builds, so setup-python resolves every one of them. Worth knowing that ubuntu-latest will move to 26.04, and 3.8 and 3.9 have no 26.04 builds; that is a future matrix decision, not this one.

deploy.yml still runs on mdb-dev and this PR leaves it there. It publishes to PyPI with the shared PYPI_PASSWORD org secret, it is triggered by workflow_run off a release, and a fork cannot reach it. Moving it is the trusted-publishing work, which is its own ticket.

The CLA change is the same one landing in nine sibling repositories, replacing a hand-rolled job with the reusable anton, cowork and cowork-server already call. The reusable is hardened in the anchor PR: the action pinned to a commit, the event filter moved from the step to the job, the runs-on input deleted, and actions: write reduced to actions: read.

The agreement link names the canonical repository rather than a redirect. Every URL the org used for this document was a rename redirect. They all resolve to the same file and serve byte-identical content, so nothing changes for a reader today. A redirect stops being harmless the moment somebody creates a repository at the old name, and this is the page a contributor reads before agreeing to it.

Verified locally

Check What I observed
actionlint on both changed files Clean, v1.7.12. The two findings this repo still reports are pre-existing, in deploy.yml and test_on_deploy.yml, which this PR does not touch.
Hosted Python availability actions/python-versions versions-manifest.json lists ubuntu-24.04 x64 builds for 3.8.16+, 3.9.23+, 3.10.19+ and 3.11.14+. So the matrix survives the move with no edit.
The reusable's runner default Read from mindsdb/github-actions@main: ubuntu-latest, and after the anchor PR the input does not exist.
The existing ledger Still resolves on the cla branch, 10 signers, unchanged by this PR.

Ships with

One behaviour across eleven repositories, one pull request each. The anchor is
mindsdb/github-actions, which hardens the shared CLA reusable this repo now
calls. No Deploys: line and no pull request environment: nothing here brings
one up.

Merge order: the anchor first, and that is a requirement rather than a
preference.
This wrapper grants actions: read, and until
mindsdb/github-actions#55 merges the reusable still declares actions: write.
A called workflow's permissions are enforced when its file is loaded, so a
callee naming a scope its caller has not granted rejects the whole run as a
startup_failure with zero jobs, and there is no job left to report it. The
run is red, the CLA gate is down, and nothing announces either.

Refs: ENG-2017

…2017)

test_on_pr runs on pull_request and installed the pull request's own
requirements.txt on mdb-dev, so an approved fork PR ran its own code on a pod in
the newdev cluster. Move the matrix to ubuntu-latest. The hosted image carries
builds for 3.8 through 3.11, so the matrix is unchanged.

The CLA workflow moves to the shared reusable in the same change, which takes it
off mdb-dev and pins the third-party action it runs.

Refs: ENG-2017
…017)

Both URLs the org uses for this document are rename redirects that resolve to
the same repository and serve byte-identical content. A redirect is fine until
somebody creates a repository at the old name, and this is the page a
contributor reads before agreeing to it. Name the repository that actually
holds the file.

Refs: ENG-2017
@lucas-koontz lucas-koontz self-assigned this Aug 27, 2026
…2017)

The allowlist moved into mindsdb/github-actions and is bots only. This repo
carried one of six hand-maintained copies naming people, and 11 of the 25 names
across those copies had already left the org while still being exempt from
signing. Staff sign like everybody else now. A bot cannot sign, so the two that
actually open pull requests in this org stay exempt in the reusable.

Dropping the line also drops two defects that rode inside it. `bot*` compiled to
an unanchored `new RegExp("bot.*").test(login)`, so it exempted any login
containing "bot", `robotnik` and `sabotage` included. And `Stpmax` never matched
the real login `StpMax`, because non-wildcard entries are compared with a
case-sensitive `===`.

Needs mindsdb/github-actions#56 first, which gives the input a default.
This repo accepts pull requests from outside contributors and had no CODEOWNERS
at all, so a workflow change here needed no particular reviewer. Everything
under .github/ decides what runs in CI, on which runner, and with which secrets
in scope, which is a trust boundary rather than product code.

Auto-request only for now. Making it a hard gate needs require_code_owner_reviews
in branch protection, which this repo does not have; that rides ENG-2013's org
ruleset.
@mindsdb-devops
mindsdb-devops merged commit b1b5cc7 into main Aug 28, 2026
5 checks passed
@mindsdb-devops
mindsdb-devops deleted the chore/eng-2017-fork-jobs-off-self-hosted-runners branch August 28, 2026 00:33
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 28, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants