Skip to content
Closed
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
42 changes: 40 additions & 2 deletions .github/workflows/build-c.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,23 @@
# Triggers:
# - release: When a GitHub release is published
# - workflow_dispatch: Manual trigger for testing
# - workflow_call: Reusable from .github/workflows/e2e-cloud.yml.
# Pass target_filter='linux-x64-gnu' to skip the
# macOS/arm64 matrix entries (only the Tokyo
# runner target is needed).
name: Build C SDK

on:
release:
types: [ published ]
workflow_dispatch:
workflow_call:
inputs:
target_filter:
description: 'Optional. Build only the given target (e.g. "linux-x64-gnu"). Empty = build all matrix platforms.'
type: string
default: ''
required: false
Comment on lines +28 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the workflows
ls -la .github/workflows || true

# Show the relevant sections of build-c.yml
echo "----- build-c.yml (workflow_call + permissions/upload-to-release) -----"
python3 - <<'PY'
import pathlib, re
p = pathlib.Path(".github/workflows/build-c.yml")
print("exists:", p.exists())
if not p.exists():
    raise SystemExit(1)

text = p.read_text()
# Print a small window around workflow_call, if present
m = re.search(r'^\s*workflow_call:\s*$', text, re.M)
if m:
    start = max(0, text.rfind('\n', 0, m.start()-1))
# Print the whole workflow_call input section (around lines 20-60-ish)
lines = text.splitlines()
for i, line in enumerate(lines, start=1):
    if "workflow_call:" in line:
        a=i-5; b=i+80
        for j in range(max(1,a), min(len(lines),b)+1):
            print(f"{j:4d}:{lines[j-1]}")
        break

print("\n--- upload-to-release job section ---")
# Print permissions and job definition for upload-to-release
for i, line in enumerate(lines, start=1):
    if re.search(r'^\s*upload-to-release\s*:\s*$', line):
        a=i-10; b=i+120
        for j in range(max(1,a), min(len(lines),b)+1):
            print(f"{j:4d}:{lines[j-1]}")
        break
else:
    # try search by job id substring
    for i, line in enumerate(lines, start=1):
        if "upload-to-release" in line:
            a=i-5; b=i+80
            for j in range(max(1,a), min(len(lines),b)+1):
                print(f"{j:4d}:{lines[j-1]}")
            break

print("\n--- top-level permissions section (if any) ---")
for i, line in enumerate(lines, start=1):
    if re.match(r'^\s*permissions:\s*$', line):
        a=i-3; b=i+10
        for j in range(max(1,a), min(len(lines),b)+1):
            print(f"{j:4d}:{lines[j-1]}")
        break
PY

# Show the relevant parts of e2e-cloud.yml where build-c.yml is called
echo "----- e2e-cloud.yml (workflow_call to build-c.yml) -----"
python3 - <<'PY'
import pathlib, re
p = pathlib.Path(".github/workflows/e2e-cloud.yml")
print("exists:", p.exists())
if not p.exists():
    raise SystemExit(1)
text = p.read_text()
lines=text.splitlines()
# find job(s) invoking build-c reusable workflow
for i,line in enumerate(lines, start=1):
    if "build-c.yml" in line or "build-c" in line and "uses:" in line:
        pass

# print jobs with uses: .github/workflows/build-c.yml or similar
for i,line in enumerate(lines, start=1):
    if re.search(r'uses:\s*.*\.github/workflows/build-c\.yml', line):
        a=i-15; b=i+80
        for j in range(max(1,a), min(len(lines),b)+1):
            print(f"{j:4d}:{lines[j-1]}")
        break
else:
    # fallback: show any job that looks like workflow call
    for i,line in enumerate(lines, start=1):
        if re.search(r'uses:\s*.*\.github/workflows/build-c', line):
            a=i-15; b=i+80
            for j in range(max(1,a), min(len(lines),b)+1):
                print(f"{j:4d}:{lines[j-1]}")
            break

print("\n--- any top-level permissions in e2e-cloud.yml ---")
for i,line in enumerate(lines, start=1):
    if re.match(r'^\s*permissions:\s*', line):
        a=i; b=i+15
        for j in range(a, min(len(lines),b)+1):
            print(f"{j:4d}:{lines[j-1]}")
        break
PY

# Show any other workflows that call build-c.yml (to check PR behavior)
echo "----- Find other callers of build-c.yml -----"
rg -n "uses:.*\.github/workflows/build-c\.yml" .github/workflows || true

Repository: boxlite-ai/boxlite

Length of output: 12289


Split the GitHub Release upload out of the reusable C build workflow to keep PR permissions read-only.

.github/workflows/build-c.yml is invoked via workflow_call, but it also contains upload-to-release with permissions: contents: write (guarded by the release-only if:). This still forces the caller in .github/workflows/e2e-cloud.yml (build_c_sdk) to request contents: write, expanding the PR token even though the release-upload job won’t run for PR/workflow_call builds. Move the asset upload to a release-only workflow (or a separate reusable workflow) and keep the reusable build path read-only.

🤖 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/build-c.yml around lines 28 - 34, The reusable C build
workflow (.github/workflows/build-c.yml) currently includes the
upload-to-release job which requires permissions: contents: write and forces
callers (e.g., the build_c_sdk invocation in .github/workflows/e2e-cloud.yml) to
request write perms; to fix, extract the upload-to-release job into a separate
release-only workflow (or reusable workflow) that is triggered only for release
events and retains permissions: contents: write, then remove the
upload-to-release job and any contents: write permission from build-c.yml so the
reusable build path remains read-only; update callers like the build_c_sdk
workflow to call the new release workflow when performing releases (or call the
upload workflow directly from release workflows) so PR workflow_call invocations
no longer require expanded permissions.


env:
CARGO_TERM_COLOR: always
Expand All @@ -30,30 +41,57 @@
config:
uses: ./.github/workflows/config.yml

# Compute the matrix the `build` job iterates over. When called via
# workflow_call with target_filter set, narrow to that single entry.
# `matrix` context is not available in job-level if:, so we filter
# the JSON here and have `build` consume `setup_matrix.outputs.platforms`.
setup_matrix:
name: Setup matrix
needs: config
runs-on: ubuntu-latest
outputs:
platforms: ${{ steps.filter.outputs.platforms }}
steps:
- id: filter
env:
ALL_PLATFORMS: ${{ needs.config.outputs.platforms }}
TARGET_FILTER: ${{ inputs.target_filter }}
run: |
if [ -n "${TARGET_FILTER:-}" ]; then
FILTERED=$(printf '%s' "$ALL_PLATFORMS" | jq -c --arg t "$TARGET_FILTER" '[.[] | select(.target == $t)]')
COUNT=$(printf '%s' "$FILTERED" | jq 'length')
[ "$COUNT" -ge 1 ] || { echo "::error::No matrix entry matches target_filter=$TARGET_FILTER"; exit 1; }
echo "Filtered to $COUNT entry(ies): $FILTERED"
else
FILTERED="$ALL_PLATFORMS"
echo "No filter — full matrix"
fi
echo "platforms=$FILTERED" >> "$GITHUB_OUTPUT"

# ============================================================================
# BUILD
# ============================================================================
# Builds libboxlite for each platform (static + shared) and packages with
# C headers.
#
# Platform-specific considerations:
# - Linux: Uses manylinux_2_28 container for glibc compatibility
# - Guest binary built on host first (Ubuntu has musl-tools, manylinux doesn't)
# - Runtime/libkrun built in container (needs glibc 2.28)
# - macOS: Builds directly on runner (ARM64 only)
#
# After building, fix-go-symbols.sh localizes Go runtime symbols from
# embedded libgvproxy in the static library, preventing conflicts when
# linked into a Go binary.
# ============================================================================
build:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
name: Build (${{ matrix.target }})
needs: config
needs: [config, setup_matrix]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.config.outputs.platforms) }}
include: ${{ fromJson(needs.setup_matrix.outputs.platforms) }}

steps:
- uses: actions/checkout@v4
Expand Down
52 changes: 52 additions & 0 deletions .github/workflows/build-runner-binary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,30 @@
# Triggers:
# - workflow_run: After "Build C SDK" completes (ensures libboxlite.a exists)
# - workflow_dispatch: Manual trigger for testing
# - workflow_call: Reusable callers can set libboxlite_source=build to use a
# C SDK artifact built from the same checkout.
name: Build Runner Binary

on:
workflow_run:
workflows: ["Build C SDK"]
types: [completed]
workflow_dispatch:
inputs:
libboxlite_source:
description: 'libboxlite.a source for manual runs'
type: choice
default: source
options:
- source
- release
Comment on lines +23 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add missing 'build' option and align default with workflow_call.

The workflow_dispatch input is missing the build option that is documented in the workflow_call description (line 34) and implemented in the workflow logic (lines 94-113). This prevents manual testing of the artifact-based build path. Additionally, the default differs between triggers: workflow_dispatch defaults to source while workflow_call defaults to release, creating inconsistent behavior.

✅ Proposed fix to add 'build' option and align defaults
   workflow_dispatch:
     inputs:
       libboxlite_source:
         description: 'libboxlite.a source for manual runs'
         type: choice
-        default: source
+        default: release
         options:
           - source
           - release
+          - build
📝 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
inputs:
libboxlite_source:
description: 'libboxlite.a source for manual runs'
type: choice
default: source
options:
- source
- release
inputs:
libboxlite_source:
description: 'libboxlite.a source for manual runs'
type: choice
default: release
options:
- source
- release
- build
🤖 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/build-runner-binary.yml around lines 23 - 30, Update the
workflow_dispatch inputs for libboxlite_source to match workflow_call: add the
missing 'build' option to the options list for libboxlite_source and change its
default from 'source' to 'release' so both triggers use the same default
behavior; modify the inputs block that defines libboxlite_source (the inputs:
libboxlite_source, description, type, default, options entries) to include
'build' and set default='release' to align with workflow_call and the runtime
logic that checks libboxlite_source.

workflow_call:
inputs:
libboxlite_source:
description: 'How to obtain libboxlite.a: release, build, or source'
type: string
default: release
required: false
Comment on lines +31 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate libboxlite_source input to prevent silent failures.

The workflow_call input uses type: string without validation, allowing invalid values like 'foo' or 'typo'. When an invalid value is passed, all conditional steps (lines 77, 83, 95, 102, 115) will be skipped, causing sdks/go/libboxlite.a to be missing and the runner build to fail with an unclear error message at link time.

🛡️ Proposed fix to add early validation

Add a validation step after checkout:

     steps:
       - uses: actions/checkout@v4
 
+      - name: Validate libboxlite_source input
+        run: |
+          VALID_VALUES="release build source"
+          VALUE="${{ inputs.libboxlite_source }}"
+          if [ -n "$VALUE" ] && ! echo "$VALID_VALUES" | grep -qw "$VALUE"; then
+            echo "::error::Invalid libboxlite_source='$VALUE'. Must be one of: $VALID_VALUES"
+            exit 1
+          fi
+
       - name: Set up Go
         uses: actions/setup-go@v5
🧰 Tools
🪛 zizmor (1.25.2)

[error] 18-37: use of fundamentally insecure workflow trigger (dangerous-triggers): workflow_run is almost always used insecurely

(dangerous-triggers)

🤖 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/build-runner-binary.yml around lines 31 - 37, The workflow
accepts workflow_call.inputs.libboxlite_source as a free-form string which can
silently skip all conditionals and leave sdks/go/libboxlite.a missing; add an
explicit validation step (after the checkout step) that reads the
libboxlite_source input and fails fast unless it equals one of the allowed
values ("release", "build", "source"), emitting a clear error message and
exiting non‑zero so the job fails early; place this validation as a separate
step named e.g. "Validate libboxlite_source" and reference
workflow_call.inputs.libboxlite_source in the step (and still preserve the
existing conditional steps that check that variable to produce
sdks/go/libboxlite.a).


permissions:
contents: read
Expand Down Expand Up @@ -56,7 +73,14 @@ jobs:
VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Set up Rust
if: inputs.libboxlite_source == 'source'
uses: actions-rust-lang/setup-rust-toolchain@v1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin action to commit SHA for supply chain security.

The action reference actions-rust-lang/setup-rust-toolchain@v1 is not pinned to a commit hash, creating a supply chain security risk if the tag is moved or the action is compromised.

🔒 Proposed fix to pin the action
       - name: Set up Rust
         if: inputs.libboxlite_source == 'source'
-        uses: actions-rust-lang/setup-rust-toolchain@v1
+        uses: actions-rust-lang/setup-rust-toolchain@b113a30d27a8e59c969077c0a0168cc13dab5ffc  # v1
         with:
           toolchain: ${{ needs.config.outputs.rust-toolchain }}

Note: Replace the SHA with the actual commit hash for the v1 tag from the upstream repository.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 78-78: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 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/build-runner-binary.yml at line 78, Replace the unpinned
action reference uses: actions-rust-lang/setup-rust-toolchain@v1 with a pinned
commit SHA for supply-chain security; locate the line containing uses:
actions-rust-lang/setup-rust-toolchain@v1 and update it to use the specific
commit hash for the v1 tag (i.e., uses:
actions-rust-lang/setup-rust-toolchain@<COMMIT_SHA>), ensuring you fetch and
insert the correct SHA from the upstream repository for the v1 tag.

Source: Linters/SAST tools

with:
toolchain: ${{ needs.config.outputs.rust-toolchain }}

- name: Download prebuilt libboxlite.a
if: inputs.libboxlite_source == '' || inputs.libboxlite_source == 'release'
env:
VERSION: ${{ steps.version.outputs.version }}
GH_REPO: ${{ github.repository }}
Expand All @@ -67,6 +91,34 @@ jobs:
curl -fsSL "${URL}" | tar xz -C /tmp/
cp "/tmp/boxlite-c-v${VERSION}-linux-x64-gnu/lib/libboxlite.a" sdks/go/libboxlite.a

- name: Download libboxlite.a from sibling Build C SDK job
if: inputs.libboxlite_source == 'build'
uses: actions/download-artifact@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin action to commit SHA for supply chain security.

The action reference actions/download-artifact@v4 is not pinned to a commit hash, creating a supply chain security risk.

🔒 Proposed fix to pin the action
       - name: Download libboxlite.a from sibling Build C SDK job
         if: inputs.libboxlite_source == 'build'
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16  # v4
         with:
           name: c-sdk-linux-x64-gnu
           path: /tmp/c-sdk/

Note: Replace the SHA with the actual commit hash for the v4 tag from the actions/download-artifact repository.

📝 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
uses: actions/download-artifact@v4
- name: Download libboxlite.a from sibling Build C SDK job
if: inputs.libboxlite_source == 'build'
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: c-sdk-linux-x64-gnu
path: /tmp/c-sdk/
🧰 Tools
🪛 zizmor (1.25.2)

[error] 96-96: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 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/build-runner-binary.yml at line 96, Replace the loose
action reference actions/download-artifact@v4 with a pinned commit SHA to
mitigate supply-chain risk: update the uses line to
actions/download-artifact@<commit-sha> (fetch the exact v4 tag commit hash from
the actions/download-artifact repo) so the workflow uses the specific immutable
commit instead of the floating tag.

Source: Linters/SAST tools

with:
name: c-sdk-linux-x64-gnu
path: /tmp/c-sdk/

- name: Stage libboxlite.a from sibling artifact
if: inputs.libboxlite_source == 'build'
run: |
set -euo pipefail
ARCHIVE=$(ls /tmp/c-sdk/boxlite-c-v*-linux-x64-gnu.tar.gz | head -1)
[ -n "$ARCHIVE" ] || { echo "::error::No c-sdk archive in artifact"; exit 1; }
tar xzf "$ARCHIVE" -C /tmp/c-sdk/
A=$(find /tmp/c-sdk -name libboxlite.a | head -1)
[ -n "$A" ] || { echo "::error::libboxlite.a not found in archive"; exit 1; }
mkdir -p sdks/go
cp "$A" sdks/go/libboxlite.a
ls -lh sdks/go/libboxlite.a

- name: Build libboxlite.a from current source
if: inputs.libboxlite_source == 'source'
run: |
make setup:build runtime dist:go
mkdir -p sdks/go
cp target/release/libboxlite.a sdks/go/libboxlite.a
ls -lh sdks/go/libboxlite.a

- name: Rewrite go.work for minimal modules
run: |
printf 'go 1.25.4\n\nuse (\n\t./runner\n\t./daemon\n\t./common-go\n\t./api-client-go\n\t./libs/computer-use\n\t../sdks/go\n)\n' > apps/go.work
Expand Down
Loading
Loading