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
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,26 @@ jobs:
with:
context: .
push: false
load: true
tags: local-llm-router:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Scan the image for known vulnerabilities
# A release image may not carry a critical or high vulnerability that
# has a fix. This job gates the merge, and only merges are released.
uses: aquasecurity/trivy-action@v0.36.0
with:
image-ref: local-llm-router:${{ github.sha }}
scanners: vuln
severity: CRITICAL,HIGH
ignore-unfixed: true
exit-code: "1"
- name: Start the image and wait for it to answer
run: |
docker run -d --name smoke -p 8000:8000 -e ROUTER_API_KEYS=smoke local-llm-router:${{ github.sha }}
for attempt in $(seq 1 20); do
if curl -fsS http://127.0.0.1:8000/healthz; then exit 0; fi
sleep 1
done
docker logs smoke
exit 1
13 changes: 11 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,18 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
ROUTER_ENVIRONMENT=production

RUN useradd --create-home --uid 10001 appuser
# Take the distribution's security fixes; the base image tag lags behind them.
RUN apt-get update \
&& apt-get upgrade -y --no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --create-home --uid 10001 appuser
COPY --from=builder /wheels /wheels
RUN python -m pip install --no-cache-dir /wheels/* && rm -rf /wheels
# The running service never installs anything, so the installer and the
# libraries it vendors are removed rather than patched.
RUN python -m pip install --no-cache-dir /wheels/* \
&& rm -rf /wheels \
&& python -m pip uninstall -y pip \
&& rm -rf /usr/local/lib/python3.13/ensurepip

WORKDIR /app
COPY config ./config
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,40 @@ step, which stays disabled until a destination is configured. The tests run agai
on SQLite; the server deployment in [`deploy/kubernetes/mlflow.yaml`](deploy/kubernetes/mlflow.yaml)
has not been run on a cluster.

## Artifact scanning

Nothing is released without being scanned.

**Container.** CI scans the release image with [Trivy](https://trivy.dev/) and fails on any
critical or high vulnerability that has a fix, then starts the image and waits for `/healthz`.
The image takes the distribution's security updates at build time and ships without `pip`, so
the installer and the libraries it vendors are not there to be vulnerable.

**Models and adapters.** Loading weights can run code: a pickle checkpoint executes what it
names, and a repository that ships Python asks the loader to import it. `llm_router.artifact_scan`
inspects an artifact directory without loading anything from it.

```bash
python -m llm_router.artifact_scan ./artifacts/general-local --subject general-local
python -m llm_router.artifact_scan ./artifacts/claims-lora --kind adapter --expect sha256:...
```

| Rule | Refused |
|---|---|
| `pickle-format` | Any pickle-capable file (`.bin`, `.pt`, `.ckpt`, `.pkl`, ...), or a pickle under any other name. The finding names what it would import. |
| `executable-code` | Python, shell, or native libraries inside the artifact. |
| `remote-code` | A configuration with `auto_map` or `trust_remote_code`, or one that cannot be read. |
| `invalid-safetensors` | A header that does not account for exactly the bytes in the file. |
| `no-weights` | An artifact with no safetensors weights. |
| `symlink` | A link pointing outside the artifact. |
| `adapter-config` | An adapter that is not LoRA, or whose rank the engine would not load. |
| `checksum-mismatch` | A digest other than the one the catalog records. |

The digest covers every file's path and content, so it is the value to record as a checksum in
a deployment revision. The command exits 1 on any finding. It checks formats and provenance; it
does not judge what a model has learned. The catalog's checksums are placeholders, so no real
artifact has been scanned against them yet.

## Failure behaviour

| Condition | What the gateway does |
Expand Down
Loading
Loading