ci: add JSR publish workflow for @systeminit/swamp-lib#894
Conversation
Publishes packages/client to JSR on merge to main when files in packages/client/ change. Uses OIDC token auth (no secrets needed). Version is auto-generated from date + commit count. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
CI Security Review
Critical / High
None.
Medium
.github/workflows/publish-client.yml:32—denoland/setup-deno@v2not pinned to SHA.
Third-party actions should be pinned to a full commit SHA to prevent supply-chain attacks (tag rewriting, compromised maintainer account). Per the pinning policy, onlyactions/*(GitHub-owned) actions are acceptable with tag-only pins. Replace with the SHA for the current v2 release, e.g.:uses: denoland/setup-deno@<full-sha> # v2
Low
None.
Notes
- Prompt injection: N/A — no LLM interaction in this workflow.
- Expression injection:
${{ steps.version.outputs.version }}on line 47 is interpolated into arun:block, but the value is derived entirely fromdateandgit rev-list --count, not from any attacker-controlled input. Safe. - Triggers:
pull_request(notpull_request_target) withtypes: [closed]and anif: merged == trueguard. Checkout is hardcoded toref: main, so no PR-head code is checked out.workflow_dispatchhas no inputs. Both safe. - Permissions:
contents: readandid-token: writeat workflow level. Since there is only one job, workflow-level scoping is equivalent to job-level.id-token: writeis required for JSR's OIDC-based publish authentication — legitimate use. - Secrets: No secrets are referenced. Publishing relies entirely on OIDC.
- Auto-merge: Not applicable — this workflow publishes a package after a PR is already merged.
Verdict
PASS — Clean workflow with one medium supply-chain hardening suggestion (SHA-pin denoland/setup-deno). No critical or high findings.
There was a problem hiding this comment.
Code Review
Blocking Issues
- Shallow clone breaks version generation (
.github/workflows/publish-client.yml:28-42): Theactions/checkout@v4step defaults tofetch-depth: 1(shallow clone). The version generation commandgit rev-list --count HEAD -- packages/clientrequires full git history to produce a meaningful, monotonically increasing patch number. With a shallow clone, this will always return 0 or 1, making every publish stamp approximately0.YYYYMMDD.1regardless of actual commit history. Fix by addingfetch-depth: 0to the checkout step:- name: Checkout code uses: actions/checkout@v4 with: ref: main fetch-depth: 0
Suggestions
-
Consider job-level permissions (line 17-18): Permissions are set at the workflow level. With a single job this is functionally equivalent to job-level, but scoping to the job is the more conventional pattern in this repo (see
ci.yml) and makes it explicit which job needsid-token: write. -
Version monotonicity across days: The scheme
0.YYYYMMDD.Nresets the patch component semantically each day (since new-day commits touchingpackages/clientkeep incrementing the total count, not a per-day count, this actually works). Worth a brief comment in the workflow explaining how the version is derived so future maintainers don't misread it.
…A, job-level perms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
CI Security Review
Critical / High
None.
Medium
None.
Low
.github/workflows/publish-client.yml:50-53— The${{ steps.version.outputs.version }}expression is interpolated directly into adeno evalscript. While the value is currently safe (composed ofdateoutput andgit rev-list --count, both strictly numeric), using an environment variable would be more robust against future refactors that might change how the version is computed. Not exploitable as-is.
Verdict
PASS — Clean workflow. Permissions are minimal and job-scoped, id-token: write is justified for JSR OIDC auth, triggers are safe (pull_request with merged gate + workflow_dispatch), checkout is pinned to main (not PR head), third-party actions are SHA-pinned, and no secrets are exposed. No security concerns blocking merge.
There was a problem hiding this comment.
Code Review
Blocking Issues
None.
Suggestions
-
Defense-in-depth for version stamping (
.github/workflows/publish-client.yml:51-56): Thedeno evalstep interpolates${VERSION}via shell expansion into a JavaScript string literal. While the current version generation (date+git rev-list --count) guarantees VERSION contains only digits and dots, passing it through an environment variable would be more robust against future changes:- name: Stamp version working-directory: packages/client env: VERSION: ${{ steps.version.outputs.version }} run: | deno eval " const config = JSON.parse(await Deno.readTextFile('deno.json')); config.version = Deno.env.get('VERSION'); await Deno.writeTextFile('deno.json', JSON.stringify(config, null, 2) + '\n'); "
-
Inconsistent
setup-denopinning: This workflow correctly pinsdenoland/setup-denoto a commit SHA (667a34c...), but the existingci.ymlandrelease.ymluse tag-only (@v2). The new approach is better — consider aligning the other workflows to SHA pins as well (separate PR).
Overall: well-structured workflow with minimal permissions (job-level contents: read + id-token: write), proper SHA pinning, concurrency control, and the standard merged-PR guard. LGTM.
Summary
Adds a GitHub Actions workflow that publishes the
@systeminit/swamp-libclient package to JSR when files inpackages/client/change on merge to main.0.YYYYMMDD.Nworkflow_dispatchfor manual publishingTest plan
packages/client/changesdeno publishsucceeds with OIDC auth🤖 Generated with Claude Code