From c8b8c26746b9ee7727b160181b45c602205da0ec Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 10 Nov 2026 05:48:34 +0000 Subject: [PATCH 1/3] ci: add SBOM generation workflow for releases Generate Software Bill of Materials (SBOM) in SPDX and CycloneDX formats using Syft v1.19.0 for each Python SDK release. The SBOM covers Python packages (gateway and SDK) and npm lockfiles. Features: - Generates SBOM on python-v* tag pushes (releases) - Dry-run on pull requests that modify dependency manifests - Publishes SBOM files as GitHub release assets - Uses Anchore Syft 1.19.0 for generation - Third-party actions pinned by commit SHA per repo convention The SBOM provides transparency for security audits and supply chain verification as documented in SECURITY.md. Addresses the SBOM gap identified in security evidence Nov 9, 2026. Signed-off-by: Cursor Agent Co-authored-by: Christopher --- .github/workflows/sbom.yml | 115 +++++++++++++++++++++++++++++++++++++ SECURITY.md | 4 ++ 2 files changed, 119 insertions(+) create mode 100644 .github/workflows/sbom.yml diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml new file mode 100644 index 00000000..dd70c7f3 --- /dev/null +++ b/.github/workflows/sbom.yml @@ -0,0 +1,115 @@ +name: SBOM Generation + +on: + push: + tags: + - 'python-v*' + pull_request: + paths: + - 'gateway/requirements.txt' + - 'sdk/python/pyproject.toml' + - 'sdk/typescript/package-lock.json' + - 'site/package-lock.json' + - '.github/workflows/sbom.yml' + workflow_dispatch: + +permissions: {} + +jobs: + generate-sbom: + name: Generate SBOM + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Install Syft + run: | + SYFT_VERSION="1.19.0" + curl -sSfL "https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/syft_${SYFT_VERSION}_linux_amd64.tar.gz" \ + -o syft.tar.gz + tar -xzf syft.tar.gz syft + sudo mv syft /usr/local/bin/ + syft version + + - name: Generate SBOM + run: | + # Generate SBOM for the entire repository, covering: + # - Python packages (gateway/requirements.txt, sdk/python/pyproject.toml) + # - npm packages (sdk/typescript/package-lock.json, site/package-lock.json) + # Syft scans pyproject.toml, requirements.txt, and package-lock.json automatically. + syft scan dir:. \ + --output spdx-json=sbom-spdx.json \ + --output cyclonedx-json=sbom-cyclonedx.json \ + --exclude './.*' \ + --exclude '**/node_modules' \ + --exclude '**/__pycache__' + + - name: Verify SBOM files + run: | + if [ ! -s sbom-spdx.json ]; then + echo "Error: SPDX SBOM file is empty or missing" + exit 1 + fi + if [ ! -s sbom-cyclonedx.json ]; then + echo "Error: CycloneDX SBOM file is empty or missing" + exit 1 + fi + + # Check package count + package_count=$(jq '.packages | length' sbom-spdx.json) + echo "SPDX SBOM contains $package_count packages" + echo "SPDX SBOM size: $(wc -c < sbom-spdx.json) bytes" + echo "CycloneDX SBOM size: $(wc -c < sbom-cyclonedx.json) bytes" + + if [ "$package_count" -lt 20 ]; then + echo "Warning: Expected at least 20 packages, found only $package_count" + fi + + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: sbom-artifacts + path: | + sbom-spdx.json + sbom-cyclonedx.json + retention-days: 90 + + publish-sbom: + name: Publish SBOM to Release + needs: generate-sbom + if: startsWith(github.ref, 'refs/tags/python-v') + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + steps: + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: sbom-artifacts + + - name: Create GitHub Release with SBOM + env: + GH_TOKEN: ${{ github.token }} + TAG_NAME: ${{ github.ref_name }} + run: | + # Check if release already exists + if gh release view "$TAG_NAME" --repo "${{ github.repository }}" >/dev/null 2>&1; then + echo "Release $TAG_NAME already exists, uploading SBOM assets" + gh release upload "$TAG_NAME" \ + sbom-spdx.json \ + sbom-cyclonedx.json \ + --repo "${{ github.repository }}" \ + --clobber + else + echo "Creating release $TAG_NAME with SBOM assets" + gh release create "$TAG_NAME" \ + sbom-spdx.json \ + sbom-cyclonedx.json \ + --repo "${{ github.repository }}" \ + --title "Python SDK $TAG_NAME" \ + --notes "SBOM files for $TAG_NAME release" + fi diff --git a/SECURITY.md b/SECURITY.md index d4e3f895..faf67419 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -19,6 +19,10 @@ AMW is maintained by a solo founder and is pre-revenue, so response times may va These are targets, not guarantees. Reports that require substantial investigation or coordination may take longer. +## SBOM (Software Bill of Materials) + +Each Python SDK release includes an SBOM in SPDX and CycloneDX formats, generated by Syft and published as release assets. The SBOM covers Python packages (gateway and SDK) and npm lockfiles. See `.github/workflows/sbom.yml` for generation details. + ## Scope This policy covers security issues in the public repository, including: From ec81d55ec2aee25173b913800972cb93b56d7187 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 10 Nov 2026 05:59:45 +0000 Subject: [PATCH 2/3] ci: align SBOM workflow to python-sdk-v* tags and scan installed environments - Change tag trigger from python-v* to python-sdk-v* to match release-python.yml convention (see PR #339) - Install Python dependencies with uv in virtual environments before scanning to capture resolved transitive dependencies - Scan installed environments (gateway and SDK Python packages) and node_modules (TypeScript SDK and site) separately, then merge into comprehensive SBOM - Update SECURITY.md to document SBOM scope: resolved Python dependencies and npm lockfiles - Note workflow is unvalidated on release tag; will verify on next python-sdk-v* release Before: 7 packages (only a few with exact pins in requirements.txt) After: Expected 50+ packages covering full dependency trees Addresses user feedback on PR #340 about thin coverage and tag pattern mismatch. Signed-off-by: Cursor Agent Co-authored-by: Christopher --- .github/workflows/sbom.yml | 125 +++++++++++++++++++++++++++++++------ SECURITY.md | 9 ++- 2 files changed, 113 insertions(+), 21 deletions(-) diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index dd70c7f3..aa3048da 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -3,7 +3,7 @@ name: SBOM Generation on: push: tags: - - 'python-v*' + - 'python-sdk-v*' pull_request: paths: - 'gateway/requirements.txt' @@ -27,6 +27,16 @@ jobs: with: persist-credentials: false + - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 + with: + python-version: "3.12" + enable-cache: false + + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: "22" + package-manager-cache: false + - name: Install Syft run: | SYFT_VERSION="1.19.0" @@ -36,20 +46,84 @@ jobs: sudo mv syft /usr/local/bin/ syft version + - name: Install Python dependencies + run: | + # Install gateway dependencies + cd gateway + uv venv .venv-sbom + uv pip install --python .venv-sbom -r requirements.txt -e "../sdk/python[verify]" + cd .. + + # Install SDK dependencies + cd sdk/python + uv venv .venv-sbom + uv pip install --python .venv-sbom -e ".[verify,mcp]" + cd ../.. + + - name: Install npm dependencies + run: | + # Install TypeScript SDK dependencies + cd sdk/typescript + npm ci + cd ../.. + + # Install site dependencies + cd site + npm ci + cd .. + - name: Generate SBOM run: | - # Generate SBOM for the entire repository, covering: - # - Python packages (gateway/requirements.txt, sdk/python/pyproject.toml) - # - npm packages (sdk/typescript/package-lock.json, site/package-lock.json) - # Syft scans pyproject.toml, requirements.txt, and package-lock.json automatically. - syft scan dir:. \ - --output spdx-json=sbom-spdx.json \ - --output cyclonedx-json=sbom-cyclonedx.json \ - --exclude './.*' \ - --exclude '**/node_modules' \ - --exclude '**/__pycache__' + # Generate comprehensive SBOM covering installed Python packages and npm packages. + # Scan installed virtual environments to capture resolved transitive dependencies. + + # Scan gateway Python environment + syft scan dir:gateway/.venv-sbom \ + --output spdx-json=sbom-gateway-spdx.json \ + --output cyclonedx-json=sbom-gateway-cyclonedx.json + + # Scan SDK Python environment + syft scan dir:sdk/python/.venv-sbom \ + --output spdx-json=sbom-sdk-python-spdx.json \ + --output cyclonedx-json=sbom-sdk-python-cyclonedx.json + + # Scan TypeScript SDK node_modules + syft scan dir:sdk/typescript/node_modules \ + --output spdx-json=sbom-sdk-typescript-spdx.json \ + --output cyclonedx-json=sbom-sdk-typescript-cyclonedx.json + + # Scan site node_modules + syft scan dir:site/node_modules \ + --output spdx-json=sbom-site-spdx.json \ + --output cyclonedx-json=sbom-site-cyclonedx.json + + # Merge all SPDX SBOMs into one comprehensive SBOM + jq -s ' + { + name: "agent-middleware", + spdxVersion: .[0].spdxVersion, + dataLicense: .[0].dataLicense, + SPDXID: .[0].SPDXID, + creationInfo: .[0].creationInfo, + packages: ([.[].packages // []] | add), + relationships: ([.[].relationships // []] | add), + files: ([.[].files // []] | add) + } + ' sbom-*-spdx.json > sbom-spdx.json + + # Merge all CycloneDX SBOMs + jq -s ' + { + bomFormat: .[0].bomFormat, + specVersion: .[0].specVersion, + serialNumber: .[0].serialNumber, + version: 1, + metadata: .[0].metadata, + components: ([.[].components // []] | add | unique_by(.name + "@" + .version)) + } + ' sbom-*-cyclonedx.json > sbom-cyclonedx.json - - name: Verify SBOM files + - name: Verify and report SBOM run: | if [ ! -s sbom-spdx.json ]; then echo "Error: SPDX SBOM file is empty or missing" @@ -60,14 +134,25 @@ jobs: exit 1 fi - # Check package count - package_count=$(jq '.packages | length' sbom-spdx.json) - echo "SPDX SBOM contains $package_count packages" - echo "SPDX SBOM size: $(wc -c < sbom-spdx.json) bytes" - echo "CycloneDX SBOM size: $(wc -c < sbom-cyclonedx.json) bytes" + # Count packages per component + gateway_count=$(jq '.packages | length' sbom-gateway-spdx.json) + sdk_python_count=$(jq '.packages | length' sbom-sdk-python-spdx.json) + sdk_ts_count=$(jq '.packages | length' sbom-sdk-typescript-spdx.json) + site_count=$(jq '.packages | length' sbom-site-spdx.json) + total_count=$(jq '.packages | length' sbom-spdx.json) + + echo "=== SBOM Package Counts ===" + echo "Gateway (Python): $gateway_count packages" + echo "SDK (Python): $sdk_python_count packages" + echo "SDK (TypeScript): $sdk_ts_count packages" + echo "Site: $site_count packages" + echo "Total (merged): $total_count packages" + echo "" + echo "SPDX SBOM size: $(wc -c < sbom-spdx.json) bytes" + echo "CycloneDX SBOM size: $(wc -c < sbom-cyclonedx.json) bytes" - if [ "$package_count" -lt 20 ]; then - echo "Warning: Expected at least 20 packages, found only $package_count" + if [ "$total_count" -lt 20 ]; then + echo "::warning::Expected at least 20 packages, found only $total_count" fi - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 @@ -81,7 +166,7 @@ jobs: publish-sbom: name: Publish SBOM to Release needs: generate-sbom - if: startsWith(github.ref, 'refs/tags/python-v') + if: startsWith(github.ref, 'refs/tags/python-sdk-v') runs-on: ubuntu-latest timeout-minutes: 5 permissions: diff --git a/SECURITY.md b/SECURITY.md index faf67419..d37e4a8b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -21,7 +21,14 @@ These are targets, not guarantees. Reports that require substantial investigatio ## SBOM (Software Bill of Materials) -Each Python SDK release includes an SBOM in SPDX and CycloneDX formats, generated by Syft and published as release assets. The SBOM covers Python packages (gateway and SDK) and npm lockfiles. See `.github/workflows/sbom.yml` for generation details. +Each Python SDK release includes an SBOM in SPDX and CycloneDX formats, generated by Syft and published as release assets. The SBOM catalogs: + +- **Python packages**: Resolved transitive dependencies from the gateway (`gateway/requirements.txt`) and SDK (`sdk/python/pyproject.toml`) as installed in their respective virtual environments. This captures the actual packages that ship. +- **npm packages**: Resolved dependencies from TypeScript SDK (`sdk/typescript/package-lock.json`) and documentation site (`site/package-lock.json`) lockfiles. + +The workflow installs dependencies with `uv` and scans the resulting virtual environments to capture resolved versions. See `.github/workflows/sbom.yml` for generation details. + +**Current limitations**: The SBOM workflow has not been validated on an actual release tag. Package counts and completeness will be verified after the next `python-sdk-v*` release. ## Scope From d2507f38579ca41329b60b0a8487dc2431799d81 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 10 Nov 2026 06:02:01 +0000 Subject: [PATCH 3/3] fix: remove site node_modules scan (no npm dependencies) The site has no npm dependencies (empty package.json), only a Python build script. Remove the site node_modules scan and npm ci step that caused the SBOM workflow to fail. Also remove site/package-lock.json from PR trigger paths since it's not relevant to actual dependencies. Signed-off-by: Cursor Agent Co-authored-by: Christopher --- .github/workflows/sbom.yml | 14 ++------------ SECURITY.md | 4 ++-- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index aa3048da..6ad8f7a9 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -9,7 +9,6 @@ on: - 'gateway/requirements.txt' - 'sdk/python/pyproject.toml' - 'sdk/typescript/package-lock.json' - - 'site/package-lock.json' - '.github/workflows/sbom.yml' workflow_dispatch: @@ -67,10 +66,8 @@ jobs: npm ci cd ../.. - # Install site dependencies - cd site - npm ci - cd .. + # Site has no npm dependencies (static build via Python) + # Skip npm install for site - name: Generate SBOM run: | @@ -92,11 +89,6 @@ jobs: --output spdx-json=sbom-sdk-typescript-spdx.json \ --output cyclonedx-json=sbom-sdk-typescript-cyclonedx.json - # Scan site node_modules - syft scan dir:site/node_modules \ - --output spdx-json=sbom-site-spdx.json \ - --output cyclonedx-json=sbom-site-cyclonedx.json - # Merge all SPDX SBOMs into one comprehensive SBOM jq -s ' { @@ -138,14 +130,12 @@ jobs: gateway_count=$(jq '.packages | length' sbom-gateway-spdx.json) sdk_python_count=$(jq '.packages | length' sbom-sdk-python-spdx.json) sdk_ts_count=$(jq '.packages | length' sbom-sdk-typescript-spdx.json) - site_count=$(jq '.packages | length' sbom-site-spdx.json) total_count=$(jq '.packages | length' sbom-spdx.json) echo "=== SBOM Package Counts ===" echo "Gateway (Python): $gateway_count packages" echo "SDK (Python): $sdk_python_count packages" echo "SDK (TypeScript): $sdk_ts_count packages" - echo "Site: $site_count packages" echo "Total (merged): $total_count packages" echo "" echo "SPDX SBOM size: $(wc -c < sbom-spdx.json) bytes" diff --git a/SECURITY.md b/SECURITY.md index d37e4a8b..e8599a77 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -24,9 +24,9 @@ These are targets, not guarantees. Reports that require substantial investigatio Each Python SDK release includes an SBOM in SPDX and CycloneDX formats, generated by Syft and published as release assets. The SBOM catalogs: - **Python packages**: Resolved transitive dependencies from the gateway (`gateway/requirements.txt`) and SDK (`sdk/python/pyproject.toml`) as installed in their respective virtual environments. This captures the actual packages that ship. -- **npm packages**: Resolved dependencies from TypeScript SDK (`sdk/typescript/package-lock.json`) and documentation site (`site/package-lock.json`) lockfiles. +- **npm packages**: Resolved dependencies from TypeScript SDK (`sdk/typescript/package-lock.json`) lockfile. -The workflow installs dependencies with `uv` and scans the resulting virtual environments to capture resolved versions. See `.github/workflows/sbom.yml` for generation details. +The workflow installs dependencies with `uv` (for Python) and `npm ci` (for TypeScript) and scans the resulting environments to capture resolved versions. See `.github/workflows/sbom.yml` for generation details. **Current limitations**: The SBOM workflow has not been validated on an actual release tag. Package counts and completeness will be verified after the next `python-sdk-v*` release.