diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b8646aa..813c9b0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -92,6 +92,7 @@ jobs: - name: Generate SPDX SBOM uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 with: + syft-version: v1.51.1 path: target/release-package/model-port-${{ github.ref_name }}-linux-amd64 format: spdx-json output-file: dist/model-port-${{ github.ref_name }}-linux-amd64.spdx.json @@ -240,13 +241,35 @@ jobs: - name: Generate container SPDX SBOM uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 + env: + SYFT_SELECT_CATALOGERS: +rust-cargo-lock-cataloger,+javascript-lock-cataloger with: + syft-version: v1.51.1 image: ${{ steps.image.outputs.name }}@${{ steps.push.outputs.digest }} format: spdx-json output-file: dist/${{ matrix.image }}-${{ github.ref_name }}-linux-amd64.spdx.json upload-artifact: false upload-release-assets: false + - name: Verify SBOM includes locked application dependencies + env: + RELEASE_IMAGE: ${{ matrix.image }} + run: | + python3 - "dist/${RELEASE_IMAGE}-${GITHUB_REF_NAME}-linux-amd64.spdx.json" <<'PYCODE' + import json + import os + import sys + with open(sys.argv[1], encoding="utf-8") as handle: + sbom = json.load(handle) + ecosystem = "npm" if os.environ["RELEASE_IMAGE"] == "modelport-dashboard" else "cargo" + assert any( + ref.get("referenceType") == "purl" + and ref.get("referenceLocator", "").startswith(f"pkg:{ecosystem}/") + for package in sbom.get("packages", []) + for ref in package.get("externalRefs", []) + ), f"container SBOM is missing the locked {ecosystem} dependency inventory" + PYCODE + - name: Attest container SBOM uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 84b5060..ed5736a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable ModelPort changes are recorded here. The project follows ## [Unreleased] +## [0.1.3] - 2026-09-07 + +### Release correction + +- Include locked Rust and npm application dependency metadata in runtime + images, outside the dashboard's web root. Container SPDX inventories now + include application dependencies alongside operating-system packages. +- Fail release publication if a container SBOM lacks the expected Cargo/npm + entries. Earlier container SBOMs covered operating-system packages only; + their artifacts and tags remain unchanged. + ## [0.1.2] - 2026-09-07 ### Authentication and production acceptance @@ -143,7 +154,8 @@ Back up PostgreSQL and run a restore drill before upgrading. Compose still uses the PostgreSQL 18 volume `modelport_modelport-postgres-18`; export any older volume before removing it. -[Unreleased]: https://github.com/tiammomo/ModelPort/compare/v0.1.2...HEAD +[Unreleased]: https://github.com/tiammomo/ModelPort/compare/v0.1.3...HEAD +[0.1.3]: https://github.com/tiammomo/ModelPort/releases/tag/v0.1.3 [0.1.2]: https://github.com/tiammomo/ModelPort/releases/tag/v0.1.2 [0.1.1]: https://github.com/tiammomo/ModelPort/releases/tag/v0.1.1 [0.1.0]: https://github.com/tiammomo/ModelPort/tree/v0.1.0 diff --git a/Cargo.lock b/Cargo.lock index 81e009e..0d20708 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "model-port" -version = "0.1.2" +version = "0.1.3" dependencies = [ "argon2", "async-stream", @@ -1419,7 +1419,7 @@ dependencies = [ [[package]] name = "modelport-ops-agent" -version = "0.1.2" +version = "0.1.3" dependencies = [ "anyhow", "axum", @@ -1436,7 +1436,7 @@ dependencies = [ [[package]] name = "modelport-ops-protocol" -version = "0.1.2" +version = "0.1.3" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 5dec980..7789956 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "model-port" -version = "0.1.2" +version = "0.1.3" edition = "2024" rust-version = "1.96" description = "A self-hosted multi-protocol model gateway for Anthropic and OpenAI-compatible workflows." @@ -29,7 +29,7 @@ axum = { version = "0.8", features = ["macros"] } futures-util = "0.3" httpdate = "1" jsonschema = { version = "0.48", default-features = false } -modelport-ops-protocol = { version = "0.1.2", path = "crates/ops-protocol" } +modelport-ops-protocol = { version = "0.1.3", path = "crates/ops-protocol" } openidconnect = { version = "4.0.1", default-features = false, features = ["reqwest", "rustls-tls"] } rand_core = { version = "0.6", features = ["getrandom"] } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "stream"] } diff --git a/Dockerfile b/Dockerfile index 03af71d..58d8960 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 ARG RUST_VERSION=1.96.0 -ARG MODELPORT_VERSION=0.1.2 +ARG MODELPORT_VERSION=0.1.3 ARG MODELPORT_SOURCE_REVISION=unknown ARG MODELPORT_SOURCE_STATE=unknown ARG MODELPORT_BUILD_DATE=unknown @@ -34,6 +34,7 @@ RUN mkdir -p /data /config \ && chown -R modelport:modelport /data /config COPY --from=builder /app/target/release/model-port /usr/local/bin/model-port +COPY --from=builder /app/Cargo.lock /usr/share/modelport/sbom/Cargo.lock # Keep source metadata after dependency and binary layers so a new commit label # does not invalidate the slow apt or Rust build cache. diff --git a/crates/ops-agent/Cargo.toml b/crates/ops-agent/Cargo.toml index ce8b48b..d0dcce5 100644 --- a/crates/ops-agent/Cargo.toml +++ b/crates/ops-agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "modelport-ops-agent" -version = "0.1.2" +version = "0.1.3" edition = "2024" rust-version = "1.96" description = "Deterministic, read-only operations agent for ModelPort." @@ -10,7 +10,7 @@ publish = false [dependencies] anyhow = "1" axum = "0.8" -modelport-ops-protocol = { version = "0.1.2", path = "../ops-protocol" } +modelport-ops-protocol = { version = "0.1.3", path = "../ops-protocol" } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } serde_json = "1" sha2 = "0.10" diff --git a/crates/ops-agent/Dockerfile b/crates/ops-agent/Dockerfile index 9318785..1fcb5e0 100644 --- a/crates/ops-agent/Dockerfile +++ b/crates/ops-agent/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 ARG RUST_VERSION=1.96.0 -ARG MODELPORT_VERSION=0.1.2 +ARG MODELPORT_VERSION=0.1.3 ARG MODELPORT_SOURCE_REVISION=unknown ARG MODELPORT_SOURCE_STATE=unknown ARG MODELPORT_BUILD_DATE=unknown @@ -31,6 +31,7 @@ RUN useradd --system --home /nonexistent --shell /usr/sbin/nologin modelport-ops && chown -R modelport-ops:modelport-ops /var/lib/modelport-ops COPY --from=builder /app/target/release/modelport-ops-agent /usr/local/bin/modelport-ops-agent +COPY --from=builder /app/Cargo.lock /usr/share/modelport/sbom/Cargo.lock ARG MODELPORT_SOURCE_REVISION ARG MODELPORT_SOURCE_STATE diff --git a/crates/ops-protocol/Cargo.toml b/crates/ops-protocol/Cargo.toml index c143317..4a2af6b 100644 --- a/crates/ops-protocol/Cargo.toml +++ b/crates/ops-protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "modelport-ops-protocol" -version = "0.1.2" +version = "0.1.3" edition = "2024" rust-version = "1.96" license = "MIT" diff --git a/dashboard/Dockerfile b/dashboard/Dockerfile index 323bf75..e6ac89b 100644 --- a/dashboard/Dockerfile +++ b/dashboard/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG MODELPORT_VERSION=0.1.2 +ARG MODELPORT_VERSION=0.1.3 ARG MODELPORT_SOURCE_REVISION=unknown ARG MODELPORT_SOURCE_STATE=unknown ARG MODELPORT_BUILD_DATE=unknown @@ -33,6 +33,7 @@ LABEL org.opencontainers.image.title="ModelPort Dashboard" \ COPY deploy/docker/dashboard.nginx-main.conf /etc/nginx/nginx.conf COPY deploy/docker/dashboard.nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /app/dist /usr/share/nginx/html +COPY --from=builder /app/package.json /app/package-lock.json /usr/share/modelport/sbom/ USER nginx diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index f86b791..f6b2e69 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -1,12 +1,12 @@ { "name": "dashboard", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dashboard", - "version": "0.1.2", + "version": "0.1.3", "dependencies": { "@radix-ui/react-avatar": "^1.1.12", "@radix-ui/react-dialog": "^1.1.16", diff --git a/dashboard/package.json b/dashboard/package.json index 7b674d2..d258ea4 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -1,7 +1,7 @@ { "name": "dashboard", "private": true, - "version": "0.1.2", + "version": "0.1.3", "type": "module", "scripts": { "dev": "vite", diff --git a/deploy/release/compose.yml b/deploy/release/compose.yml index 605db91..667ec81 100644 --- a/deploy/release/compose.yml +++ b/deploy/release/compose.yml @@ -31,7 +31,7 @@ services: max-file: "${MODELPORT_LOG_MAX_FILES:-5}" modelport: - image: ${MODELPORT_IMAGE:-ghcr.io/tiammomo/modelport:0.1.2} + image: ${MODELPORT_IMAGE:-ghcr.io/tiammomo/modelport:0.1.3} pull_policy: ${MODELPORT_PULL_POLICY:-missing} init: true read_only: true @@ -86,7 +86,7 @@ services: max-file: "${MODELPORT_LOG_MAX_FILES:-5}" dashboard: - image: ${MODELPORT_DASHBOARD_IMAGE:-ghcr.io/tiammomo/modelport-dashboard:0.1.2} + image: ${MODELPORT_DASHBOARD_IMAGE:-ghcr.io/tiammomo/modelport-dashboard:0.1.3} pull_policy: ${MODELPORT_PULL_POLICY:-missing} init: true read_only: true @@ -108,7 +108,7 @@ services: ops-agent: profiles: ["ops-agent"] - image: ${MODELPORT_OPS_AGENT_IMAGE:-ghcr.io/tiammomo/modelport-ops-agent:0.1.2} + image: ${MODELPORT_OPS_AGENT_IMAGE:-ghcr.io/tiammomo/modelport-ops-agent:0.1.3} pull_policy: ${MODELPORT_PULL_POLICY:-missing} init: true read_only: true diff --git a/docker-compose.yml b/docker-compose.yml index 86886ad..456d161 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,7 +33,7 @@ services: context: . dockerfile: Dockerfile args: - MODELPORT_VERSION: ${MODELPORT_VERSION:-0.1.2} + MODELPORT_VERSION: ${MODELPORT_VERSION:-0.1.3} MODELPORT_SOURCE_REVISION: ${MODELPORT_SOURCE_REVISION:-unknown} MODELPORT_SOURCE_STATE: ${MODELPORT_SOURCE_STATE:-unknown} MODELPORT_BUILD_DATE: ${MODELPORT_BUILD_DATE:-unknown} @@ -99,7 +99,7 @@ services: context: . dockerfile: dashboard/Dockerfile args: - MODELPORT_VERSION: ${MODELPORT_VERSION:-0.1.2} + MODELPORT_VERSION: ${MODELPORT_VERSION:-0.1.3} MODELPORT_SOURCE_REVISION: ${MODELPORT_SOURCE_REVISION:-unknown} MODELPORT_SOURCE_STATE: ${MODELPORT_SOURCE_STATE:-unknown} MODELPORT_BUILD_DATE: ${MODELPORT_BUILD_DATE:-unknown} @@ -129,7 +129,7 @@ services: context: . dockerfile: crates/ops-agent/Dockerfile args: - MODELPORT_VERSION: ${MODELPORT_VERSION:-0.1.2} + MODELPORT_VERSION: ${MODELPORT_VERSION:-0.1.3} MODELPORT_SOURCE_REVISION: ${MODELPORT_SOURCE_REVISION:-unknown} MODELPORT_SOURCE_STATE: ${MODELPORT_SOURCE_STATE:-unknown} MODELPORT_BUILD_DATE: ${MODELPORT_BUILD_DATE:-unknown} diff --git a/docs/RELEASING.md b/docs/RELEASING.md index d560982..3548ec9 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -66,12 +66,22 @@ The release workflow: - builds the Linux amd64 backend archive; - emits SHA-256 checksums and an SPDX JSON SBOM; - creates GitHub build-provenance and SBOM attestations; -- publishes versioned backend and dashboard images to GHCR; +- publishes versioned gateway, dashboard and Operations Agent images to GHCR; - publishes Linux x86_64 container SBOMs, signs immutable image digests with keyless Cosign, and attaches GitHub provenance/SBOM attestations; - records all three immutable image references as Release assets; - creates the GitHub Release from the existing tag. +Runtime images retain their locked application dependency metadata under +`/usr/share/modelport/sbom/`, outside the dashboard's served directory. Container +SBOM verification requires Cargo or npm package entries as well as the scanner's +system package inventory. The pinned Syft scanner explicitly adds its Cargo/npm +[lockfile catalogers](https://oss.anchore.com/docs/guides/sbom/catalogers/), which +are not enabled by the default image scan. Lockfile entries describe build inputs, including +workspace/build dependencies; they do not prove each package is reachable in +the running service. The v0.1.2 and earlier container SBOMs lack this application +inventory; use v0.1.3 or newer for the corrected container evidence. + The tag must resolve to a commit on protected `main`. Publication first creates a draft and uploads all assets, checks the asset count, then publishes it under the repository's immutable-release policy. Do not delete or retag a failed