chore(ci): remove two fork-owned publish workflows that rode in by accident (#8967)

* chore(ci): remove two fork-owned publish workflows that rode in by accident

Both files publish to a DIFFERENT owner's GHCR namespace, and both arrived as an
unrelated extra file inside an otherwise on-topic PR:

    build-fork.yml            added by #1528 (scope: SSE translator, Qiwen Chen)
                              env IMAGE_NAME: ghcr.io/kang-heewon/omniroute
                              if: github.repository == 'kang-heewon/OmniRoute'
                              100+ runs instantiated here

    build-rinseaid-image.yml  added by #8729 (scope: SSE reasoning)
                              tags: ghcr.io/rinseaid/omniroute:...
                              no repository guard at all — 0 runs

Neither can ever succeed: this repository's GITHUB_TOKEN cannot write to another
owner's namespace. The cost is not a breach, it is noise. build-fork.yml's guard
sits on the JOB, not the workflow, so GitHub instantiates a run on every push to
main and every v* tag and then skips the job — which is why every release check
board has carried a permanently skipped "Publish Fork Image to GHCR" entry.
build-rinseaid-image.yml never fires because its trigger branch
(`build-k3-reasoning-image`) does not exist in this repo.

Only build-fork.yml was pre-approved (2026-07-30). The second was found while
executing: grepping the workflow directory for registry namespaces turned up
ghcr.io/rinseaid alongside ghcr.io/kang-heewon. Same defect, same remedy, so both
go — easy to split if that is preferred.

Nothing else is touched. Specifically NOT touched: the 14 `kang-heewon` credit
links in CHANGELOG.md (real contributions), their 42 i18n mirrors, and the
historical `- **ci:** update build-fork workflow…` entry from #2055. Measured: 0
of the 14 credit mentions concern build-fork, so no credit line is involved
either way. `git status` shows exactly two deletions and one new test.

TDD — the guard names both offenders before the removal and passes after:

    node --import tsx/esm --test tests/unit/workflows-no-foreign-fork-publishers.test.ts
    # before: 0 pass, 2 fail  →  build-fork.yml → ghcr.io/kang-heewon
    #                            build-rinseaid-image.yml → ghcr.io/rinseaid
    # after:  2 pass, 0 fail

Zizmor findings drop 190 → 178 (both files use unpinned docker/* and checkout
actions). The baseline is deliberately NOT rebaselined here: the metric direction
is `down` so a drop cannot break the ratchet, tightening it to exactly 178 would
leave zero headroom and self-break on drift, and validate-release-green states
the convention outright — "Any drift above is rebaselined at release, not a
contributor concern."

* docs(changelog): fragment for #8967

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-30 09:07:23 -03:00
committed by GitHub
parent 14992ceebe
commit 321488abee
4 changed files with 94 additions and 110 deletions

View File

@@ -1,71 +0,0 @@
name: Publish Fork Image to GHCR
on:
push:
branches: [main]
tags:
- "v*"
workflow_dispatch:
# Least-privilege default: read-only at the top level; the build job that pushes to
# GHCR grants packages: write itself (Scorecard TokenPermissions).
permissions:
contents: read
env:
IMAGE_NAME: ghcr.io/kang-heewon/omniroute
jobs:
build:
name: Build and Push Fork Image
if: github.repository == 'kang-heewon/OmniRoute'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=sha,prefix=sha-
type=ref,event=tag
labels: |
org.opencontainers.image.title=omniroute
org.opencontainers.image.description=Unified AI proxy/router — fork image
org.opencontainers.image.url=https://github.com/kang-heewon/OmniRoute
org.opencontainers.image.source=https://github.com/kang-heewon/OmniRoute
org.opencontainers.image.licenses=MIT
- name: Build and push
uses: docker/build-push-action@v7
with:
context: .
target: runner-base
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@@ -1,39 +0,0 @@
name: Build Rinseaid OmniRoute image
on:
push:
branches: [build-k3-reasoning-image]
paths:
- Dockerfile
- package-lock.json
- package.json
- open-sse/**
- scripts/build/**
- .github/workflows/build-rinseaid-image.yml
workflow_dispatch:
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v6
with:
context: .
target: runner-base
platforms: linux/amd64
push: true
tags: ghcr.io/rinseaid/omniroute:k3-reasoning-${{ github.sha }}

View File

@@ -0,0 +1 @@
- **chore(ci):** removed two fork-owned image-publish workflows that had ridden into the repo as unrelated extra files in on-topic PRs — `build-fork.yml` (`ghcr.io/kang-heewon`, job-level guard, so it instantiated a skipped run on every push to main and every tag) and `build-rinseaid-image.yml` (`ghcr.io/rinseaid`, no guard, never fired). Neither could authenticate against this repository's token; a new policy guard now fails CI on any workflow targeting a foreign registry namespace ([#8967](https://github.com/diegosouzapw/OmniRoute/pull/8967))

View File

@@ -0,0 +1,93 @@
/**
* Policy guard: no workflow in this repository may publish to — or be gated on — a
* DIFFERENT repository's namespace.
*
* Twice now a contributor's own fork CI has ridden into the canonical repo as an
* unrelated extra file in an otherwise on-topic PR:
*
* .github/workflows/build-fork.yml added by #1528 (scope: SSE translator)
* env: IMAGE_NAME: ghcr.io/kang-heewon/omniroute
* if: github.repository == 'kang-heewon/OmniRoute'
* → the guard sits on the JOB, not the workflow, so GitHub instantiated a run on
* every push to main and every v* tag and skipped the job: 100+ runs, zero
* runner cost, and a permanently noisy check board on every release.
*
* .github/workflows/build-rinseaid-image.yml added by #8729 (scope: SSE reasoning)
* tags: ghcr.io/rinseaid/omniroute:...
* → no repository guard at all; it simply never fires because its trigger branch
* (`build-k3-reasoning-image`) does not exist here. 0 runs.
*
* Neither can succeed: this repo's GITHUB_TOKEN cannot write to another owner's GHCR
* namespace. So the cost is not a breach, it is dead configuration that review keeps
* waving through because the PR it arrives in is about something else entirely.
*
* This test is the cheap check that review is not: it reads the workflow directory and
* fails on any foreign owner, so the next accidental inclusion is caught by CI instead
* of surviving until someone wonders why a release board shows a skipped fork job.
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const workflowDir = path.join(repoRoot, ".github/workflows");
/** The only owner whose namespaces this repository may publish to or gate on. */
const OWNER = "diegosouzapw";
function workflowFiles(): string[] {
return fs
.readdirSync(workflowDir)
.filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"))
.map((f) => path.join(workflowDir, f));
}
test("no workflow publishes to another owner's container registry", () => {
const offenders: string[] = [];
for (const file of workflowFiles()) {
const text = fs.readFileSync(file, "utf-8");
// ghcr.io/<owner>/... and index.docker.io/<owner>/... — the owner is the segment
// right after the registry host.
for (const m of text.matchAll(/\b(?:ghcr\.io|(?:index\.)?docker\.io)\/([A-Za-z0-9_.-]+)/g)) {
const owner = m[1];
if (owner.toLowerCase() !== OWNER) {
offenders.push(`${path.basename(file)}${m[0]}`);
}
}
}
assert.deepEqual(
offenders,
[],
`workflow(s) target a registry namespace that is not ${OWNER}'s:\n ${offenders.join("\n ")}\n` +
`A fork's publish workflow does not belong in the canonical repository — it cannot ` +
`authenticate anyway, and it pollutes every release check board.`
);
});
test("no workflow job is gated on a different repository", () => {
const offenders: string[] = [];
for (const file of workflowFiles()) {
const text = fs.readFileSync(file, "utf-8");
// `if: github.repository == 'owner/name'` — a guard naming someone else's repo means
// the workflow was written for a fork.
for (const m of text.matchAll(/github\.repository\s*[=!]=\s*['"]([^'"]+)['"]/g)) {
const [owner] = m[1].split("/");
if (owner.toLowerCase() !== OWNER) {
offenders.push(`${path.basename(file)}${m[0]}`);
}
}
}
assert.deepEqual(
offenders,
[],
`workflow(s) gate on a foreign repository:\n ${offenders.join("\n ")}\n` +
`Note the failure mode: a job-level guard still instantiates a run on every ` +
`matching trigger, so the workflow shows up as a skipped check forever.`
);
});