diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 0dba8d5..f9cf4dc 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -133,6 +133,7 @@ jobs: exit-code: "1" format: "table" cache-dir: .trivy-cache + trivyignores: ".trivyignore" - name: Scan for MEDIUM/LOW vulnerabilities (warning only) uses: aquasecurity/trivy-action@0.33.1 @@ -142,6 +143,7 @@ jobs: exit-code: "0" format: "table" cache-dir: .trivy-cache + trivyignores: ".trivyignore" continue-on-error: true - name: Generate SARIF report for GitHub Security @@ -152,6 +154,7 @@ jobs: format: "sarif" output: "trivy-results.sarif" cache-dir: .trivy-cache + trivyignores: ".trivyignore" continue-on-error: true - name: Upload Trivy scan results diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index ea5ce31..2733473 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -47,9 +47,12 @@ jobs: # Skip hooks that don't work well in CI: # - no-commit-to-branch: Always fails in CI (we're on main/master) # - trufflehog: Has wasm/go-re2 panic bug in GitHub Actions environment + # - safety: Skipped if SAFETY_API_KEY secret is not configured # Note: shellcheck, hadolint, trivy use mise-managed binaries which ARE # installed by mise-action above, so they should work in CI. - SKIP: no-commit-to-branch,trufflehog + SKIP: no-commit-to-branch,trufflehog${{ secrets.SAFETY_API_KEY == '' && ',safety' || '' }} # Safety CLI API key for dependency vulnerability scanning + # Get your key at: https://safetycli.com/ (free account) + # Add as repository secret: Settings → Secrets → Actions → SAFETY_API_KEY SAFETY_API_KEY: ${{ secrets.SAFETY_API_KEY }} run: uv run pre-commit run --all-files --show-diff-on-failure diff --git a/.secrets.baseline b/.secrets.baseline index 5f9eaf0..40a169a 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -127,5 +127,5 @@ } ], "results": {}, - "generated_at": "2026-01-12T16:32:14Z" + "generated_at": "2026-01-12T20:07:54Z" } diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000..c5fa036 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,44 @@ +# Trivy Vulnerability Ignore File +# ================================ +# This file tells trivy to skip specific CVEs during image scanning. +# Use this for vulnerabilities that: +# - Have no fix available yet (check Alpine security tracker) +# - Are false positives for our use case +# - Are accepted risks with documented justification +# +# Format: One CVE ID per line, optionally with comment +# Docs: https://aquasecurity.github.io/trivy/latest/docs/configuration/filtering/ +# +# Before adding a CVE here: +# 1. Verify no fix is available: https://security.alpinelinux.org/ +# 2. Document the reason and expected fix date if known +# 3. Set a reminder to revisit when fixes become available +# +# To check if fixes are now available, run: +# just docker::scan +# +# ============================================================================= +# IGNORED VULNERABILITIES +# ============================================================================= + +# --- Alpine Base Image CVEs (No Fix Available) --- +# These are in the python:3.11-alpine base image's system packages. +# We run `apk upgrade --no-cache` in the Dockerfile to get the latest patches, +# but some CVEs may not have fixes yet. + +# CVE-2026-22184 - zlib vulnerability (alpine/zlib 1.3.1-r2) +# Status: No fix available as of 2025-01-12 +# Tracker: https://security.alpinelinux.org/ +# CVE-2026-22184 + +# CVE-2025-60876 - busybox vulnerability (alpine/busybox 1.37.0-r30) +# Status: No fix available as of 2025-01-12 +# Tracker: https://security.alpinelinux.org/ +# CVE-2025-60876 + +# ============================================================================= +# NOTES +# ============================================================================= +# - Uncomment CVE lines above ONLY if you've verified no fix is available +# - Review this file monthly to remove CVEs that now have fixes +# - The `apk upgrade` in Dockerfile should auto-fix most CVEs when rebuilding diff --git a/Dockerfile b/Dockerfile index d62ea5e..6625ae6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -68,6 +68,11 @@ LABEL org.opencontainers.image.title="FreeCAD Robust MCP Server" \ org.opencontainers.image.authors="Sean P. Kane " \ org.opencontainers.image.base.name="python:3.11-alpine" +# Upgrade all Alpine packages to fix CVEs in base image (zlib, busybox, etc.) +# This ensures we get security patches even if the base image is slightly stale +# hadolint ignore=DL3018 +RUN apk upgrade --no-cache + # Create non-root user for security (Alpine uses addgroup/adduser) RUN addgroup -g 1000 mcpuser && \ adduser -u 1000 -G mcpuser -s /bin/sh -D mcpuser diff --git a/addon/FreecadRobustMCPBridge/wiki-source.txt b/addon/FreecadRobustMCPBridge/wiki-source.txt index 7fd6f2c..d0098ea 100644 --- a/addon/FreecadRobustMCPBridge/wiki-source.txt +++ b/addon/FreecadRobustMCPBridge/wiki-source.txt @@ -10,7 +10,7 @@ |Icon=FreecadRobustMCPBridge.svg |Description=Bridge workbench designed to provide an interface between FreeCAD and the Robust MCP Server to enable AI assistants (like Claude) to control FreeCAD via the Model Context Protocol (MCP). It provides XML-RPC and JSON-RPC interfaces for external automation. |Author=Sean P. Kane -|Version=0.6.1 +|Version=99.99.99-test |Date=2026-01-12 |FCVersion=0.21+ |Download=[https://github.com/spkane/freecad-robust-mcp-and-more/releases Latest Release] diff --git a/just/docker.just b/just/docker.just index 98f6556..1d1ba2e 100644 --- a/just/docker.just +++ b/just/docker.just @@ -92,34 +92,66 @@ clean-all: @echo "Docker images and build cache cleaned." # Scan Docker image for vulnerabilities (warn on all severities) +# Uses .trivyignore file to skip CVEs without available fixes scan: #!/usr/bin/env bash set -euo pipefail + IGNOREFILE="{{project_root}}/.trivyignore" + IGNORE_FLAG="" + if [ -f "$IGNOREFILE" ]; then + IGNORE_FLAG="--ignorefile $IGNOREFILE" + echo "Using ignore file: $IGNOREFILE" + fi echo "Scanning {{image_name}} for vulnerabilities..." echo "" - trivy image --severity HIGH,CRITICAL {{image_name}} + trivy image --severity HIGH,CRITICAL $IGNORE_FLAG {{image_name}} echo "" echo "Scanning for MEDIUM/LOW (informational)..." - trivy image --severity MEDIUM,LOW {{image_name}} || true + trivy image --severity MEDIUM,LOW $IGNORE_FLAG {{image_name}} || true # Scan Docker image with strict settings (fail on HIGH or CRITICAL) +# Uses .trivyignore file to skip CVEs without available fixes scan-strict: #!/usr/bin/env bash set -euo pipefail + IGNOREFILE="{{project_root}}/.trivyignore" + IGNORE_FLAG="" + if [ -f "$IGNOREFILE" ]; then + IGNORE_FLAG="--ignorefile $IGNOREFILE" + echo "Using ignore file: $IGNOREFILE" + fi echo "Scanning {{image_name}} for HIGH/CRITICAL vulnerabilities..." echo "(Build will fail if any HIGH or CRITICAL CVEs are found)" echo "" - trivy image --severity HIGH,CRITICAL --exit-code 1 {{image_name}} + trivy image --severity HIGH,CRITICAL --exit-code 1 $IGNORE_FLAG {{image_name}} echo "" echo "✓ No HIGH or CRITICAL vulnerabilities found!" echo "" echo "Scanning for MEDIUM/LOW (informational only)..." - trivy image --severity MEDIUM,LOW --exit-code 0 {{image_name}} || true + trivy image --severity MEDIUM,LOW --exit-code 0 $IGNORE_FLAG {{image_name}} || true + +# Scan Docker image WITHOUT ignore file (shows all CVEs including unfixable) +scan-all: + #!/usr/bin/env bash + set -euo pipefail + echo "Scanning {{image_name}} for ALL vulnerabilities (ignoring .trivyignore)..." + echo "" + trivy image --severity HIGH,CRITICAL {{image_name}} + echo "" + echo "Scanning for MEDIUM/LOW..." + trivy image --severity MEDIUM,LOW {{image_name}} || true # Scan Docker image and output SARIF report scan-sarif output="trivy-results.sarif": - trivy image --format sarif --output {{project_root}}/{{output}} {{image_name}} - @echo "SARIF report written to {{project_root}}/{{output}}" + #!/usr/bin/env bash + set -euo pipefail + IGNOREFILE="{{project_root}}/.trivyignore" + IGNORE_FLAG="" + if [ -f "$IGNOREFILE" ]; then + IGNORE_FLAG="--ignorefile $IGNOREFILE" + fi + trivy image --format sarif --output {{project_root}}/{{output}} $IGNORE_FLAG {{image_name}} + echo "SARIF report written to {{project_root}}/{{output}}" # Create and configure buildx builder for multi-arch builds setup-buildx: diff --git a/just/release.just b/just/release.just index 31adfaf..5ee7486 100644 --- a/just/release.just +++ b/just/release.just @@ -782,67 +782,101 @@ rollback-release tag: echo "Git/GitHub Rollback complete!" echo "==========================================" - # MCP Server: Offer to yank from PyPI and provide Docker cleanup info + # MCP Server: Provide PyPI and Docker cleanup instructions if [ "$COMPONENT" = "mcp-server" ]; then echo "" echo "--- PyPI Cleanup ---" echo "" - echo "Would you like to yank version $VERSION from PyPI?" - echo " - Yanking hides the version from 'pip install freecad-robust-mcp'" - echo " - Users who pin to this version can still install it" - echo " - This can be undone later with 'twine yank --undo'" + echo "To yank version $VERSION from PyPI (hides from 'pip install'):" echo "" - read -p "Yank from PyPI? [y/N] " -n 1 -r + echo " 1. Go to: https://pypi.org/manage/project/freecad-robust-mcp/releases/" + echo " 2. Click 'Options' → 'Yank'" + echo " 3. Enter a reason (e.g., 'Released from wrong branch')" + echo " 4. Confirm" + echo "" + echo "Note: Yanking hides the version from default pip install, but users" + echo " who pin to this exact version can still install it." + echo " Yanking can be undone from the same page." + echo "" + read -p "Open PyPI release page in browser? [y/N] " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then - echo "" - echo "Attempting to yank from PyPI..." - echo "(This requires PyPI authentication - you may be prompted to login)" - echo "" - if command -v twine &> /dev/null || uv run twine --version &>/dev/null; then - if uv run twine yank freecad-robust-mcp "$VERSION" 2>&1; then - echo " ✓ Version $VERSION yanked from PyPI." - else - echo "" - echo " Yank failed. You can try manually:" - echo " uv run twine yank freecad-robust-mcp $VERSION" - echo "" - echo " Or use the PyPI web interface:" - echo " https://pypi.org/manage/project/freecad-robust-mcp/release/$VERSION/" - fi + PYPI_URL="https://pypi.org/manage/project/freecad-robust-mcp/releases/" + if command -v open &> /dev/null; then + open "$PYPI_URL" + elif command -v xdg-open &> /dev/null; then + xdg-open "$PYPI_URL" else - echo " twine not available. Install with: uv add twine" - echo "" - echo " Or use the PyPI web interface:" - echo " https://pypi.org/manage/project/freecad-robust-mcp/release/$VERSION/" + echo "Could not open browser. Visit: $PYPI_URL" fi - else - echo "" - echo "Skipped PyPI yank. You can do this later with:" - echo " uv run twine yank freecad-robust-mcp $VERSION" - echo "" - echo "Or via web interface:" - echo " https://pypi.org/manage/project/freecad-robust-mcp/release/$VERSION/" fi echo "" echo "--- Docker Hub Cleanup ---" echo "" - echo "Docker Hub tags must be deleted manually." - echo "Tags to delete: $VERSION, latest (if this was the latest release)" + echo "To delete Docker Hub tags:" echo "" - echo "Web interface:" - echo " https://hub.docker.com/r/spkane/freecad-robust-mcp/tags" + echo " 1. Go to: https://hub.docker.com/repository/docker/spkane/freecad-robust-mcp/tags" + echo " 2. Find tag: $VERSION" + echo " 3. Click the checkbox and 'Delete'" + echo " 4. Also delete 'latest' tag if this was the latest release" echo "" - echo "Or via API (requires DOCKER_HUB_TOKEN):" - echo " curl -X DELETE -H \"Authorization: Bearer \$DOCKER_HUB_TOKEN\" \\" - echo " https://hub.docker.com/v2/repositories/spkane/freecad-robust-mcp/tags/$VERSION/" + read -p "Open Docker Hub tags page in browser? [y/N] " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + DOCKER_URL="https://hub.docker.com/repository/docker/spkane/freecad-robust-mcp/tags" + if command -v open &> /dev/null; then + open "$DOCKER_URL" + elif command -v xdg-open &> /dev/null; then + xdg-open "$DOCKER_URL" + else + echo "Could not open browser. Visit: $DOCKER_URL" + fi + fi fi echo "" echo "==========================================" - echo "Rollback process complete!" + echo "ROLLBACK SUMMARY" echo "==========================================" + echo "" + + # Show what was done and what remains + case "$COMPONENT" in + mcp-server) + echo "COMPLETED (automated):" + echo " ✓ GitHub Release deleted" + echo " ✓ Git tag deleted (local and remote)" + echo "" + echo "MANUAL STEPS REQUIRED:" + echo " □ PyPI: Yank version $VERSION" + echo " URL: https://pypi.org/manage/project/freecad-robust-mcp/releases/" + echo "" + echo " □ Docker Hub: Delete tag $VERSION" + echo " URL: https://hub.docker.com/repository/docker/spkane/freecad-robust-mcp/tags" + echo "" + echo " □ Docker Hub: Delete 'latest' tag (if this was the latest release)" + echo " URL: https://hub.docker.com/repository/docker/spkane/freecad-robust-mcp/tags" + ;; + workbench|macro-magnets|macro-export) + echo "COMPLETED (automated):" + echo " ✓ GitHub Release deleted" + echo " ✓ Git tag deleted (local and remote)" + echo "" + echo "MANUAL STEPS REQUIRED:" + echo " (none - rollback is complete!)" + ;; + *) + echo "COMPLETED (automated):" + echo " ✓ GitHub Release deleted (if existed)" + echo " ✓ Git tag deleted (local and remote)" + echo "" + echo "MANUAL STEPS REQUIRED:" + echo " (unknown component - verify no additional cleanup needed)" + ;; + esac + + echo "" # Delete only the GitHub Release (keep tag) delete-github-release tag: