fix(frontend): remediate frontend + extension audit findings #6084
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: SBOM | |
| on: | |
| push: | |
| branches: [ main ] | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| packages: read | |
| concurrency: | |
| group: sbom-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| build-sbom: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| env: | |
| # Populated in "Gather SBOM files" step | |
| SBOM_FILES: "" | |
| SBOM_COUNT: "0" | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | |
| - name: Setup Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Generate Python SBOM (CycloneDX) | |
| run: | | |
| set -euo pipefail | |
| repo_root="${GITHUB_WORKSPACE:-$PWD}" | |
| # Install CycloneDX Python tooling. Newer releases expose the `cyclonedx-py` CLI, | |
| # older ones expose `cyclonedx-bom`. We support both. | |
| python -m pip install -q cyclonedx-bom | |
| gen_from_requirements() { | |
| local req="$1" | |
| local req_dir req_file out_file | |
| local pyproject_args=() | |
| req_dir="$(dirname "$req")" | |
| req_file="$(basename "$req")" | |
| out_file="$repo_root/sbom-python.cdx.json" | |
| if [ -f "$repo_root/pyproject.toml" ]; then | |
| pyproject_args=(--pyproject "$repo_root/pyproject.toml") | |
| fi | |
| echo "Generating Python SBOM from ${req}" | |
| # Try modern CLI first: cyclonedx-py requirements <file> | |
| if command -v cyclonedx-py >/dev/null 2>&1; then | |
| echo "Using cyclonedx-py" | |
| if (cd "$req_dir" && cyclonedx-py requirements "$req_file" "${pyproject_args[@]}" -o "$out_file"); then | |
| return 0 | |
| fi | |
| echo "cyclonedx-py positional requirements failed; trying input flag form" | |
| if (cd "$req_dir" && cyclonedx-py requirements -i "$req_file" "${pyproject_args[@]}" -o "$out_file"); then | |
| return 0 | |
| fi | |
| echo "cyclonedx-py input flag requirements failed; trying stdin form" | |
| if (cd "$req_dir" && cyclonedx-py requirements - -o "$out_file" < "$req_file"); then | |
| return 0 | |
| fi | |
| echo "cyclonedx-py failed; will try fallbacks" | |
| fi | |
| # Module invocation for modern CLI (avoid depending on CLI binary layout) | |
| if python -c "import importlib.util, sys; sys.exit(0 if importlib.util.find_spec('cyclonedx_py') else 1)" >/dev/null 2>&1; then | |
| echo "Using python -m cyclonedx_py" | |
| if (cd "$req_dir" && python -m cyclonedx_py requirements "$req_file" -o "$out_file"); then | |
| return 0 | |
| fi | |
| echo "python -m cyclonedx_py positional requirements failed; trying stdin form" | |
| if (cd "$req_dir" && python -m cyclonedx_py requirements - -o "$out_file" < "$req_file"); then | |
| return 0 | |
| fi | |
| echo "python -m cyclonedx_py failed; trying legacy CLI" | |
| fi | |
| # Legacy CLI: cyclonedx-bom -r -i <file> | |
| if command -v cyclonedx-bom >/dev/null 2>&1; then | |
| echo "Using cyclonedx-bom (legacy)" | |
| if (cd "$req_dir" && cyclonedx-bom -r -i "$req_file" -o "$out_file"); then | |
| return 0 | |
| fi | |
| echo "cyclonedx-bom failed; trying legacy module" | |
| fi | |
| # Legacy module invocation | |
| if python -c "import importlib.util, sys; sys.exit(0 if importlib.util.find_spec('cyclonedx_bom') else 1)" >/dev/null 2>&1; then | |
| echo "Using python -m cyclonedx_bom (legacy)" | |
| if (cd "$req_dir" && python -m cyclonedx_bom -r -i "$req_file" -o "$out_file"); then | |
| return 0 | |
| fi | |
| fi | |
| echo "All Python SBOM generation strategies failed" >&2 | |
| return 1 | |
| } | |
| gen_requirements_from_pyproject() { | |
| local pyproject="$1" | |
| local generated_req | |
| generated_req="$(mktemp)" | |
| python - "$pyproject" "$generated_req" <<'PY' | |
| import sys | |
| import tomllib | |
| from pathlib import Path | |
| pyproject = Path(sys.argv[1]) | |
| generated_req = Path(sys.argv[2]) | |
| data = tomllib.loads(pyproject.read_text(encoding="utf-8")) | |
| dependencies = data.get("project", {}).get("dependencies") or [] | |
| dependencies = [str(dependency).strip() for dependency in dependencies if str(dependency).strip()] | |
| if not dependencies: | |
| raise SystemExit(f"No [project].dependencies entries found in {pyproject}") | |
| generated_req.write_text("\n".join(dependencies) + "\n", encoding="utf-8") | |
| print(f"Wrote {len(dependencies)} requirements from {pyproject} to {generated_req}") | |
| PY | |
| if gen_from_requirements "$generated_req"; then | |
| rm -f "$generated_req" | |
| return 0 | |
| fi | |
| rm -f "$generated_req" | |
| return 1 | |
| } | |
| if [ -f "$repo_root/tldw_Server_API/requirements.txt" ]; then | |
| gen_from_requirements "$repo_root/tldw_Server_API/requirements.txt" | |
| elif [ -f "$repo_root/requirements.txt" ]; then | |
| gen_from_requirements "$repo_root/requirements.txt" | |
| elif [ -f "$repo_root/pyproject.toml" ]; then | |
| gen_requirements_from_pyproject "$repo_root/pyproject.toml" | |
| else | |
| echo "No Python dependency source found; cannot generate SBOM" >&2 | |
| exit 1 | |
| fi | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '20' | |
| - name: Generate Node SBOM (CycloneDX NPM) | |
| run: | | |
| if [ -f apps/package-lock.json ]; then \ | |
| (cd apps && npx -y @cyclonedx/cyclonedx-npm --output-file ../sbom-node.cdx.json); \ | |
| elif [ -f apps/tldw-frontend/package-lock.json ]; then \ | |
| (cd apps/tldw-frontend && npx -y @cyclonedx/cyclonedx-npm --output-file ../../sbom-node.cdx.json); \ | |
| else \ | |
| echo "No package-lock.json found; skipping Node SBOM"; \ | |
| fi | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd | |
| - name: Authenticate to GHCR | |
| if: ${{ hashFiles('sbom-python.cdx.json') != '' && hashFiles('sbom-node.cdx.json') != '' }} | |
| run: | | |
| echo "${{ github.token }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin | |
| - name: Resolve CycloneDX CLI digest | |
| if: ${{ hashFiles('sbom-python.cdx.json') != '' && hashFiles('sbom-node.cdx.json') != '' }} | |
| run: | | |
| set -euo pipefail | |
| ref="ghcr.io/cyclonedx/cyclonedx-cli:0.30.0" | |
| echo "Resolving digest for ${ref}" | |
| # Prefer buildx imagetools; fallback to manifest inspect if needed | |
| if docker buildx imagetools inspect "$ref" >/dev/null 2>&1; then \ | |
| digest=$(docker buildx imagetools inspect "$ref" | awk '/^Digest:/ {print $2; exit}'); \ | |
| else \ | |
| digest=$(docker manifest inspect "$ref" | jq -r '.manifests[0].digest' || true); \ | |
| fi | |
| if [ -z "${digest:-}" ] || ! echo "$digest" | grep -Eq '^sha256:[0-9a-f]{64}$'; then \ | |
| echo "Failed to resolve digest for $ref"; \ | |
| exit 1; \ | |
| fi | |
| echo "CDX_CLI_DIGEST=$digest" >> "$GITHUB_ENV" | |
| echo "Resolved digest: $digest" | |
| - name: Merge SBOMs (CycloneDX CLI) | |
| run: | | |
| set -euo pipefail | |
| if [ -f sbom-python.cdx.json ] && [ -f sbom-node.cdx.json ]; then \ | |
| docker run --rm -v "$PWD":/work -w /work "ghcr.io/cyclonedx/cyclonedx-cli@${CDX_CLI_DIGEST}" \ | |
| merge --input-files sbom-python.cdx.json sbom-node.cdx.json --output-file sbom.cdx.json; \ | |
| elif [ -f sbom-python.cdx.json ]; then \ | |
| cp sbom-python.cdx.json sbom.cdx.json; \ | |
| elif [ -f sbom-node.cdx.json ]; then \ | |
| cp sbom-node.cdx.json sbom.cdx.json; \ | |
| else \ | |
| echo "No SBOMs generated"; \ | |
| exit 1; \ | |
| fi | |
| - name: Upload SBOM artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: sbom-cyclonedx | |
| path: sbom.cdx.json | |
| - name: Validate SBOM (CycloneDX CLI - pinned digest) | |
| if: ${{ hashFiles('sbom.cdx.json') != '' && hashFiles('sbom-python.cdx.json') != '' && hashFiles('sbom-node.cdx.json') != '' }} | |
| continue-on-error: true | |
| run: | | |
| docker run --rm -v "$PWD":/work -w /work "ghcr.io/cyclonedx/cyclonedx-cli@${CDX_CLI_DIGEST}" \ | |
| validate --input-file sbom.cdx.json |