Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions .github/workflows/sdk-publish-crates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: sdk publish crates

on:
push:
tags:
- "mux-sdk-v*"
workflow_dispatch:
inputs:
version:
description: "SDK version to validate/publish, for example 0.1.0"
required: true
type: string

permissions: {}

concurrency:
group: sdk-publish-crates-${{ github.ref }}
cancel-in-progress: false

jobs:
version:
runs-on: ${{ vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }}
permissions:
contents: read
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Validate tag and package versions
id: version
env:
DISPATCH_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [[ "${GITHUB_REF_TYPE:-}" == "tag" ]]; then
[[ "$GITHUB_REF_NAME" =~ ^mux-sdk-v[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "tag must match mux-sdk-vX.Y.Z" >&2
exit 1
}
version="${GITHUB_REF_NAME#mux-sdk-v}"
else
version="$DISPATCH_VERSION"
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "workflow_dispatch version must match X.Y.Z" >&2
exit 1
}
fi
python3 - "$version" <<'PY'
import json
import pathlib
import sys
import tomllib

expected = sys.argv[1]
root = pathlib.Path.cwd()
versions = {
"typescript package.json": json.loads((root / "mux/bindings/typescript/package.json").read_text())["version"],
"python pyproject.toml": tomllib.loads((root / "mux/bindings/python/pyproject.toml").read_text())["project"]["version"],
"rust Cargo.toml": tomllib.loads((root / "mux/bindings/rust/Cargo.toml").read_text())["package"]["version"],
}
mismatches = {name: got for name, got in versions.items() if got != expected}
if mismatches:
for name, got in mismatches.items():
print(f"{name}: expected {expected}, got {got}", file=sys.stderr)
raise SystemExit(1)
print(f"All package versions match {expected}")
PY
echo "version=$version" >> "$GITHUB_OUTPUT"

bindings-e2e-rust:
needs: version
runs-on: ${{ vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }}
timeout-minutes: 40
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Init ghostty submodule
run: git submodule update --init --depth 1 ghostty

- name: Install Linux build dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang libclang-dev pkg-config

- name: Install zig
run: ./scripts/install-zig-ci.sh

- name: Rust version
run: |
rustc --version || true
if ! command -v cargo >/dev/null; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
fi

- name: Build mux server
working-directory: mux
run: cargo build -p mux-tui

- name: Python conformance fixtures
run: python3 mux/bindings/conformance/runner.py

- name: Rust binding e2e
run: bash mux/bindings/conformance/e2e.sh --require rust

publish:
needs: bindings-e2e-rust
runs-on: ${{ vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }}
permissions:
contents: read
id-token: write
environment:
name: crates-io
url: https://crates.io/crates/cmux-client
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Authenticate with crates.io trusted publishing
id: auth
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5

- name: Publish cmux-client
working-directory: mux
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish -p cmux-client --locked
Comment on lines +113 to +135

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Add a comment explaining the id-token: write permission.

The id-token: write permission is required for OIDC trusted publishing via rust-lang/crates-io-auth-action, but it has no explanatory comment. Adding one improves auditability for security reviewers.

📝 Suggested comment
     permissions:
       contents: read
-      id-token: write
+      id-token: write # Required for OIDC trusted publishing to crates.io
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
publish:
needs: bindings-e2e-rust
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
environment:
name: crates-io
url: https://crates.io/crates/cmux-client
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Authenticate with crates.io trusted publishing
id: auth
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
- name: Publish cmux-client
working-directory: mux
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish -p cmux-client --locked
publish:
needs: bindings-e2e-rust
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Required for OIDC trusted publishing to crates.io
environment:
name: crates-io
url: https://crates.io/crates/cmux-client
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Authenticate with crates.io trusted publishing
id: auth
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
- name: Publish cmux-client
working-directory: mux
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish -p cmux-client --locked
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 118-118: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)


[info] 113-113: workflow or action definition without a name (anonymous-definition): this job

(anonymous-definition)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/sdk-publish-crates.yml around lines 113 - 135, Add a brief
inline comment in the publish job explaining why the `id-token: write`
permission is set, noting that `publish` uses `rust-lang/crates-io-auth-action`
for OIDC trusted publishing to crates.io. Place the comment near the
`permissions` block so reviewers can easily see the security rationale when
reviewing `sdk-publish-crates.yml`.

Source: Linters/SAST tools

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Publish job lacks Rust toolchain

Medium Severity

The publish job runs cargo publish without the same Rust bootstrap used in bindings-e2e-rust. On runners where cargo is not preinstalled, trusted publishing succeeds but publishing fails even after e2e passed in a separate job.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2c3d626. Configure here.

137 changes: 137 additions & 0 deletions .github/workflows/sdk-publish-go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: sdk publish go

on:
push:
tags:
- "mux-sdk-v*"
Comment on lines +5 to +6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Nested Go Tag Is Missing

The Go binding is a nested module at mux/bindings/go, but this workflow only validates the shared mux-sdk-v* tag. A release can pass this workflow without creating the module-scoped semver tag Go tooling expects for github.com/manaflow-ai/cmux/mux/bindings/go, so consumers may not be able to install the advertised SDK version with normal go get version resolution.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

workflow_dispatch:
inputs:
version:
description: "SDK version to validate, for example 0.1.0"
required: true
type: string

permissions: {}

concurrency:
group: sdk-publish-go-${{ github.ref }}
cancel-in-progress: false

jobs:
version:
runs-on: ${{ vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }}
permissions:
contents: read
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Validate tag and package versions
id: version
env:
DISPATCH_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [[ "${GITHUB_REF_TYPE:-}" == "tag" ]]; then
[[ "$GITHUB_REF_NAME" =~ ^mux-sdk-v[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "tag must match mux-sdk-vX.Y.Z" >&2
exit 1
}
version="${GITHUB_REF_NAME#mux-sdk-v}"
else
version="$DISPATCH_VERSION"
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "workflow_dispatch version must match X.Y.Z" >&2
exit 1
}
fi
python3 - "$version" <<'PY'
import json
import pathlib
import sys
import tomllib

expected = sys.argv[1]
root = pathlib.Path.cwd()
versions = {
"typescript package.json": json.loads((root / "mux/bindings/typescript/package.json").read_text())["version"],
"python pyproject.toml": tomllib.loads((root / "mux/bindings/python/pyproject.toml").read_text())["project"]["version"],
"rust Cargo.toml": tomllib.loads((root / "mux/bindings/rust/Cargo.toml").read_text())["package"]["version"],
}
mismatches = {name: got for name, got in versions.items() if got != expected}
if mismatches:
for name, got in mismatches.items():
print(f"{name}: expected {expected}, got {got}", file=sys.stderr)
raise SystemExit(1)
print(f"All package versions match {expected}")
PY
echo "version=$version" >> "$GITHUB_OUTPUT"
Comment on lines +21 to +71

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider extracting the version validation job into a reusable workflow.

The version validation job (lines 21–71) is duplicated verbatim across all four SDK publish workflows. A bug or schema change in the validation logic would require coordinated edits in four files. A reusable workflow (workflow_call) would centralize this and reduce maintenance risk.

🧰 Tools
🪛 zizmor (1.26.1)

[info] 21-21: workflow or action definition without a name (anonymous-definition): this job

(anonymous-definition)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/sdk-publish-go.yml around lines 21 - 71, The version
validation logic in the version job is duplicated across multiple SDK publish
workflows, so extract it into a reusable workflow invoked with workflow_call and
have each publish workflow call that shared validator. Keep the existing
validation behavior in the reusable workflow by preserving the tag/dispatch
checks and package version comparison logic, and reference the version job plus
its validation step so it’s easy to replace without hunting through each
workflow file.


bindings-e2e-go:
needs: version
runs-on: ${{ vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }}
timeout-minutes: 40
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Init ghostty submodule
run: git submodule update --init --depth 1 ghostty

- name: Install Linux build dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang libclang-dev pkg-config

- name: Install zig
run: ./scripts/install-zig-ci.sh

- name: Rust version
run: |
rustc --version || true
if ! command -v cargo >/dev/null; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
fi

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Disable default caching on actions/setup-go in release workflows.

actions/setup-go enables module caching by default. In a release-adjacent workflow, a poisoned cache could compromise validation integrity. Add cache: false to both setup-go steps to ensure reproducible builds from a clean state.

🔒️ Proposed fix
       - name: Set up Go
         uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
         with:
           go-version: "1.22.x"
+          cache: false

Apply the same cache: false addition to the setup-go step in the validate-go-module job (line 129).

Also applies to: 129-129

🧰 Tools
🪛 zizmor (1.26.1)

[error] 104-104: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/sdk-publish-go.yml at line 104, Disable default module
caching in both actions/setup-go invocations within the sdk-publish-go workflow
by adding cache false to the setup-go steps used in the main release job and the
validate-go-module job. Update the setup-go entries identified by
actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c so the workflow always
uses a clean Go module state during release validation.

Source: Linters/SAST tools

with:
go-version: "1.22.x"

- name: Build mux server
working-directory: mux
run: cargo build -p mux-tui

- name: Python conformance fixtures
run: python3 mux/bindings/conformance/runner.py

- name: Go binding e2e
run: bash mux/bindings/conformance/e2e.sh --require go

validate-go-module:
needs: bindings-e2e-go
runs-on: ${{ vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }}
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: "1.22.x"

- name: Validate Go module
working-directory: mux/bindings/go
run: |
go build ./...
go vet ./...
Loading
Loading