Skip to content

fix(ci): publish @cosmo/ai to JSR only on manual dispatch with confirmation - #13

Open
polylane[bot] wants to merge 1 commit into
mainfrom
polylane/autofix/k1es6s2kooay
Open

polylane[bot] wants to merge 1 commit into
mainfrom
polylane/autofix/k1es6s2kooay

Conversation

@polylane

@polylane polylane Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Fixes: ai-sdk auto-publishes @cosmo/ai to public JSR via OIDC on every main push with no gate

A push to main in apex-cc/ai-sdk currently runs a publish workflow that ships the package to the public JSR registry as @cosmo/ai with no review, no environment, and no secret gate, authenticated by the repository's OIDC identity. Any of the repo's 14 pushers (or an unreviewed PR merge) could permanently publish a compromised package. This change removes the push trigger entirely: publishing now requires a manual dispatch on main with the confirm input set to "publish", mirroring the gate the org already applies to its other production write paths.

What caused this

Affected: int_f66b1a871001vhnlqpa1i3ze · severity high

What changed

  • .github/workflows/publish.yml: replaced the on: push (main) trigger with on: workflow_dispatch requiring a confirm_publish input, and guarded the publish job to github.ref == 'refs/heads/main' plus the confirm value, so no push to main can reach the JSR registry anymore.

Why it's safe

  • Legitimate publishes still work: a maintainer dispatches the workflow on the main branch with confirm_publish set to publish; the release flow is unchanged apart from requiring the deliberate step, so the owner's existing publishing capability is preserved.
  • The main-ref guard prevents off-main publishes: a dispatch against a feature branch would otherwise run jsr publish with that branch's content; the job-level if now requires refs/heads/main, so only the main branch can ever publish.
  • No permissions or secrets changed: id-token: write remains exactly where JSR's OIDC flow needs it, scoped to the publish job; contents: read is unchanged.
  • Blast radius of the change itself is nil: publish.yml is the only file touched, and with dependentCount: 0 on the public package no consumer observes any behavior change until someone deliberately publishes.

Validation

  • PyYAML structural validation of .github/workflows/publish.yml: PASS — no push trigger present, workflow_dispatch with required string input confirm_publish, job if guard on refs/heads/main and the confirm value, permissions unchanged (id-token: write, contents: read), npx jsr publish step intact.
  • jsr.json parses as valid JSON (unchanged).
  • git status / git diff: only .github/workflows/publish.yml modified; node_modules/ ignored and untracked.
  • not run: actionlint — the release binary could not be downloaded from the sandbox (GitHub release asset redirects are outside the sandbox's allowed network; two attempts failed).
  • not run: repo test/lint/typecheck scripts — the repository declares none (package.json has only dependencies and devDependencies; this is a bare JSR package).
Root cause and scoping notes

Root cause

  • Symptom: apex-cc/ai-sdk's only workflow, .github/workflows/publish.yml, triggers on push to main and runs npx jsr publish with permissions: id-token: write and no environment, approval, or secret gate. The repo is public with 14 collaborators (4 admin, 10 write, including the account whose PAT is committed elsewhere in the org) and main has no branch protection (GitHub Free).
  • Mechanism: JSR's documentation confirms that publishing from GitHub Actions authenticates via the GitHub OIDC ID token, so the workflow needs no secret: any commit landing on main is permanently published as public @cosmo/ai.
  • Evidence the path is real: the JSR API shows @cosmo/ai live with 8 versions, latest 0.2.3 published 2024-08-21 by Nicolai Schmid, the same day as main's last commit. The package's recorded GitHub link (repo id 818333217) resolves to this repo, transferred from CosmoInnovationFactory. The Actions runs API returns 0 only because run retention pruned the 2024 runs.
  • Why this fixes the root: the producer is the unguarded push trigger. Removing it means a push to main no longer schedules the publish job at all; the job runs only when someone explicitly dispatches the workflow on main and types the confirm value, the same gate the org already adopted for transcript-master (#20) and n8n-agents (chore: Configure Renovate #1).

Out of scope / follow-ups

  • README: no publishing documentation exists there (it is a 4-line env snippet), so nothing to update, unlike the n8n-agents fix.
  • JSR-side OIDC identity for the @Cosmo scope: a JSR-console action for the scope owner (Nicolai Schmid), outside any repository; tracked as a follow-up on the issue timeline.
Causal chain
  • Signal (alert): ai-sdk auto-publishes @cosmo/ai to public JSR via OIDC on every main push with no gate
  • Surfacing site: apex-cc/ai-sdk at .github/workflows/publish.yml
  • Mechanism: Every push to main runs job publish with permissions: id-token: write, executing npx jsr publish; JSR's documentation confirms publishing from GitHub Actions authenticates via the GitHub OIDC ID token, so the pushed commit's content is permanently published as public @cosmo/ai with no secret and no approval.
  • Producer: apex-cc/ai-sdk workflow declaration (publish.yml, committed 2024-06-21, never modified), instance main branch, at apex-cc/ai-sdk:.github/workflows/publish.yml
  • Trigger: Latent but proven: Actions runs API returns 0 only due to retention pruning; JSR shows publishes through 2024-08-21 matching main's last commit 'chore: v0.2.3'. The next push to main, direct or via an unreviewed PR merge, fires the publish.
  • What happens to the failed unit today: Today a push to main automatically publishes with no review; there is no retry or dead-letter machinery (CI). After the change, pushes create no workflow run; the publish job runs only on a manual dispatch with confirm_publish == 'publish' on the main branch.
  • Cadence check: Dormant main (last commit 2024-08-21) predicts no publishes since that date, matching JSR exactly (8 versions, last 0.2.3 on 2024-08-21); the unguarded path fired for every release before it.
  • Blast radius: 0 other resource(s), 0 other tenant(s); data at risk: Integrity of the public package @cosmo/ai: a malicious push would be permanently published to the public JSR registry, where downstream consumers (dependentCount 0 today, but the package is public and fetchable by anyone) would receive it.
  • Producer evidence:
    • Cloned workflow read: on: push: branches: [main], job publish with id-token: write, steps npm i + npx jsr publish, no environment
    • JSR API: @cosmo/ai live with 8 versions, latest 0.2.3 published 2024-08-21T09:24:44Z by Nicolai Schmid; package linked to GitHub repo id 818333217 (this repo, transferred from CosmoInnovationFactory)
    • JSR docs: 'OIDC ID token is used for authentication with JSR' for GitHub Actions publishing
    • GitHub API: repo public, main unprotected, 14 collaborators (4 admin incl. NicolaiSchmid, 10 write incl. alireza-alg)

Detection outcome

The finding's condition — "publish.yml publishes @cosmo/ai to the public JSR registry on every push to main, via OIDC, with zero approval gate" — stops holding once this change is deployed: the workflow no longer triggers on any push, so no commit to main schedules npx jsr publish. The publish job can only be started by a manual workflow_dispatch on the main branch with the confirm_publish input set to publish; any other trigger path, including a direct push or an unreviewed PR merge, creates no run and reaches no registry. The exploration finding will not reproduce on the next main push because there is no longer a workflow listening to it.

Fix chosen

Chosen: cause (removes the mechanism that produces the failure): Replace the push-to-main trigger with a manual workflow_dispatch gated on a confirm_publish input and guarded to the main ref, so pushes never publish and the publish job runs only on an explicit, deliberate dispatch.

Considered and not chosen:

  • disable (turns a feature, guard, check, test, or telemetry off): Delete .github/workflows/publish.yml entirely, removing the publish capability. Not chosen: Publishing is legitimate and in active use: @cosmo/ai is live on public JSR with 8 versions published through 2024-08-21 by the repo owner. The defect is the unguarded trigger, not the publish itself.
  • loosen (raises a limit, threshold, or timeout, or widens a retry): Keep the push trigger but route the job through a GitHub environment with required reviewers. Not chosen: Environment protection rules live in the GitHub console/API, not in repository files, so they cannot ship in this pull request; the dispatch-plus-confirm gate is the enforceable-in-file fix and is the pattern the org already accepted on transcript-master and n8n-agents.
  • suppress (silences, downgrades, or reroutes the signal without changing what produces it): Leave the workflow as-is and unlink or restrict the JSR OIDC publish identity on the JSR side. Not chosen: The JSR console action belongs to the @Cosmo scope owner and is outside every repository; it is recorded as a follow-up on the issue timeline, and it would not by itself stop a pusher from adding a publish token or changing the package config.

Outcome after fix

Before: a direct push to main, or a merged PR (no review required on this repo), or a push by any of the 14 collaborators runs npx jsr publish with the repository's OIDC identity and permanently publishes the pushed content as public @cosmo/ai with zero approval. The pushed commit becomes public registry state immediately.

After: a push to main creates no workflow run whatsoever. A maintainer who wants to release dispatches the Publish workflow on the main branch and must set confirm_publish to publish; any other value or any other branch makes the job skip. The at-risk unit, the package publish, now happens only after a human deliberately invokes it on main, and a poisoned push can no longer reach the public registry by itself.

1 file changed (+11/-3)
  • .github/workflows/publish.yml: modified, +11/-3

Repository lint: the repository declares no lint command in its instruction files, Taskfile, justfile, Makefile, package.json scripts, or CONTRIBUTING.md, so none ran on the 1 changed file.

View autofix View thread


Generated by Polylane.

Co-authored-by: polylane[bot] <277585245+polylane[bot]@users.noreply.github.com>

Co-authored-by: polylane[bot] <277585245+polylane[bot]@users.noreply.github.com>
@polylane polylane Bot added polylane severity:high Polylane autofix severity: high labels Sep 15, 2026
@polylane
polylane Bot requested a review from NicolaiSchmid September 15, 2026 03:44
@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: fdfe1a47-e2d9-4dc0-9d8c-79a905ef72e6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@polylane

polylane Bot commented Sep 26, 2026

Copy link
Copy Markdown
Author

This pull request is waiting on a decision: merge it if the change is still wanted, or close it if the fix is no longer needed. Either one settles it.

It was opened on 2026-09-15 and has had no new activity for a week. If nothing happens on it within a week, Polylane closes it and keeps the branch, so reopening it brings the change back exactly as it stands.

@NicolaiSchmid, the change touches what you own, so the review is with you.

@Cosmo

Cosmo commented Sep 26, 2026

Copy link
Copy Markdown

please tell your ai bot to not @ me

@polylane

polylane Bot commented Sep 26, 2026

Copy link
Copy Markdown
Author

@Cosmo I can help with this once you have access to the Nicolai's Workspace workspace. Already have a Polylane account? Link your GitHub account and I'll pick this up.

Request access Connect GitHub

@Cosmo

Cosmo commented Sep 26, 2026

Copy link
Copy Markdown

@NicolaiSchmid your bot pinged me and now sent me an invite mail to my email address. Please get that in order. Thanks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

polylane severity:high Polylane autofix severity: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants