Skip to content
Draft
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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ benchmark/datasets
benchmark/tb_runs
dist
site
target
88 changes: 88 additions & 0 deletions .github/workflows/server-container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Publish switchyard-server container

on:
push:
branches: [main]
schedule:
- cron: "17 * * * *"

permissions:
contents: read
packages: write
Comment on lines +9 to +11

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 | 🟠 Major | ⚡ Quick win

Scope package-write permission to the individual jobs.

A workflow-level packages: write token is unnecessarily available to every current and future job. Remove the global permission block; grant contents: read and packages: write to publish, and only packages: write to cleanup.

Proposed permission scoping
-permissions:
-  contents: read
-  packages: write
-
 jobs:
   publish:
+    permissions:
+      contents: read
+      packages: write
     name: Publish container
...
   cleanup:
+    permissions:
+      packages: write
     name: Delete expired commit images
🧰 Tools
🪛 zizmor (1.26.1)

[error] 11-11: overly broad permissions (excessive-permissions): packages: write is overly broad at the workflow level

(excessive-permissions)

🤖 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/server-container.yml around lines 9 - 11, Remove the
workflow-level permissions block and define job-level permissions instead: grant
publish both contents: read and packages: write, while cleanup receives only
packages: write. Keep these permissions scoped to the respective jobs so other
current or future jobs do not inherit package-write access.

Source: Linters/SAST tools


concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}
cancel-in-progress: ${{ github.event_name == 'push' }}

env:
PACKAGE_NAME: switchyard-server

jobs:
publish:
name: Publish container
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not retain the registry-capable token in Git config.

actions/checkout persists its token by default; subsequent steps can read it from the workspace. This job already passes GITHUB_TOKEN directly to docker/login-action, so disable persistence.

Proposed fix
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
📝 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/checkout@v4
- uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 26-26: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 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/server-container.yml at line 26, Update the
actions/checkout@v4 step to disable credential persistence by setting
persist-credentials to false, while leaving the existing direct GITHUB_TOKEN
usage for docker/login-action unchanged.

Source: Linters/SAST tools


🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the workflow with line numbers, focusing on the cited region.
sed -n '1,140p' .github/workflows/server-container.yml | cat -n

# Also list all `uses:` references in the file for a quick inventory.
grep -nE '^\s*-\s*uses:|^\s*uses:' .github/workflows/server-container.yml || true

Repository: NVIDIA-NeMo/Switchyard

Length of output: 3903


🏁 Script executed:

#!/bin/bash
set -euo pipefail

nl -ba .github/workflows/server-container.yml | sed -n '1,140p' | grep -nE 'uses:|name:|on:|jobs:|runs-on:'

Repository: NVIDIA-NeMo/Switchyard

Length of output: 196


Pin these GitHub Actions to commit SHAs. actions/checkout@v4, docker/setup-buildx-action@v3, docker/login-action@v3, and both docker/build-push-action@v6 uses are still mutable tags; pin them to immutable SHAs and let Dependabot update the pins.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 26-26: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 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/server-container.yml at line 26, Update all referenced
GitHub Actions in the workflow—actions/checkout, docker/setup-buildx-action,
docker/login-action, and both docker/build-push-action entries—from mutable
version tags to immutable commit SHAs, preserving their current major-version
behavior. Add or retain Dependabot configuration so these SHA pins can be
automatically updated.

- name: Set image coordinates
id: image
shell: bash
run: |
echo "name=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/${PACKAGE_NAME}" >> "$GITHUB_OUTPUT"
echo "short_sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish commit image
uses: docker/build-push-action@v6
with:
context: .
file: crates/switchyard-server/Dockerfile
push: true
tags: ${{ steps.image.outputs.name }}:${{ steps.image.outputs.short_sha }}
build-args: |
IMAGE_VERSION=${{ steps.image.outputs.short_sha }}
VCS_REF=${{ github.sha }}
cache-from: type=gha,scope=switchyard-server
cache-to: type=gha,mode=max,scope=switchyard-server
- name: Publish main image
uses: docker/build-push-action@v6
with:
context: .
file: crates/switchyard-server/Dockerfile
push: true
tags: ${{ steps.image.outputs.name }}:main
build-args: |
IMAGE_VERSION=main
VCS_REF=${{ github.sha }}
cache-from: type=gha,scope=switchyard-server

cleanup:
name: Delete expired commit images
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ github.token }}
steps:
- name: Delete commit images older than 23 hours
shell: bash
run: |
set -euo pipefail
package_path="/orgs/${GITHUB_REPOSITORY_OWNER}/packages/container/${PACKAGE_NAME}/versions"
versions_file="${RUNNER_TEMP}/switchyard-server-versions.tsv"
cutoff="$(date -u -d '23 hours ago' +%s)"

gh api --paginate "${package_path}?per_page=100" \
--jq '.[] | [.id, .created_at, (.metadata.container.tags | join(","))] | @tsv' \
> "${versions_file}"

while IFS=$'\t' read -r version_id created_at tags; do
if [[ "${tags}" =~ ^[0-9a-f]{7}$ ]] \
&& (( $(date -u -d "${created_at}" +%s) < cutoff )); then
echo "Deleting expired container tag ${tags}"
gh api --method DELETE "${package_path}/${version_id}"
fi
done < "${versions_file}"
30 changes: 30 additions & 0 deletions crates/switchyard-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

ARG RUST_VERSION=1.96.1
FROM rust:${RUST_VERSION}-bookworm AS builder

WORKDIR /src
COPY Cargo.toml Cargo.lock rust-toolchain.toml ./
COPY crates ./crates
RUN cargo build --locked --release -p switchyard-server

FROM debian:bookworm-slim

RUN apt-get update \
&& apt-get install --yes --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder /src/target/release/switchyard-server /usr/local/bin/switchyard-server

ARG IMAGE_VERSION=local
ARG VCS_REF=unknown
LABEL org.opencontainers.image.source="https://github.com/NVIDIA-NeMo/Switchyard" \
org.opencontainers.image.description="Switchyard's libsy-based HTTP server" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.version="${IMAGE_VERSION}" \
org.opencontainers.image.revision="${VCS_REF}"

USER 65532:65532
EXPOSE 4000
ENTRYPOINT ["/usr/local/bin/switchyard-server"]
10 changes: 10 additions & 0 deletions crates/switchyard-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ export API_KEY="..."
cargo run -p switchyard-server -- --config routes.toml
```

The same server can run from the repository's Docker image:

```bash
docker run --rm -p 4000:4000 \
--env API_KEY \
--mount type=bind,src="$PWD/routes.toml",dst=/etc/switchyard/routes.toml,readonly \
ghcr.io/nvidia-nemo/switchyard-server:main \
--config /etc/switchyard/routes.toml
```

Target and route table names are local references. A target's `id` is the exact model ID sent
upstream, and a route's `id` is the model clients send to select that algorithm.

Expand Down
Loading