diff --git a/.claude/skills/patch-cves/SKILL.md b/.claude/skills/patch-cves/SKILL.md new file mode 100644 index 000000000..f1dd2e4cd --- /dev/null +++ b/.claude/skills/patch-cves/SKILL.md @@ -0,0 +1,183 @@ +# Patching CVEs in Robusta: Automated Workflow + +This skill automates the process of identifying and patching CVE vulnerabilities in the Robusta Docker image and Python dependencies, focusing on critical, high, and medium severity issues. + +## Overview + +The workflow follows this systematic process: + +1. **Vulnerability Scanning** - Identify all CVEs in dependencies and Docker image +2. **Severity Filtering** - Focus on critical, high, and medium severity issues +3. **Root Cause Analysis** - Determine which packages/dependencies introduce vulnerabilities +4. **Upstream Research** - Check if newer releases already include fixes +5. **Patch Implementation** - Apply fixes via dependency upgrades or Dockerfile changes +6. **Validation** - Verify CVE fixes and ensure application functionality + +## Step-by-Step Process + +### 1. Vulnerability Scanning + +Use multiple scanning tools to identify vulnerabilities: + +```bash +# Scan Docker image for vulnerabilities +docker build -t robusta:latest . +docker scout cves robusta:latest + +# Scan Python dependencies for vulnerabilities +pip-audit +safety check + +# Validate pyproject.toml metadata and lockfile consistency (does not perform vulnerability scanning) +poetry check +# For CVE scanning of Python dependencies, use pip-audit, safety, or poetry-audit-plugin +``` + +**What to extract:** +- Affected package name and version +- CVE ID and severity level +- Fixed version (if available) +- Affected version range + +### 2. Severity Filtering + +Process vulnerabilities in this order: +1. **Critical** - Must be fixed before release +2. **High** - Should be fixed before release +3. **Medium** - Fix when safe and non-breaking + +Create a prioritized list and document each CVE: + +``` +CVE-XXXX-XXXXX (Critical): Package X - affects >=1.0.0,<1.2.0 + Fixed in: 1.2.5 + Status: Needs patching + +CVE-YYYY-YYYYY (High): Package Y - affects >=2.0.0,<2.1.0 + Fixed in: 2.1.3 + Status: Needs patching +``` + +### 3. Python Dependency Patches + +Two main strategies: + +**Strategy A: Direct Upgrade (Preferred)** +- Check `poetry.lock` for affected packages +- Update `pyproject.toml` with patched version +- Run `poetry update package-name` +- Verify in `poetry.lock` that lock file has updated to fixed version + +**Strategy B: Transitive Dependency Fix** +- Identify the parent package bringing in vulnerable version +- Upgrade parent package to one with updated dependencies +- This automatically pulls in the fixed transitive dependency + + +### 4. Dockerfile Patches + +For system-level vulnerabilities (non-Python packages): + +**Strategy A: Upgrade Base Image** +- Check if newer Python 3.11-slim image includes fixes +- Update FROM statement: `FROM python:3.11-slim` → newer version + +**Strategy B: Explicit Package Installation** +- Add specific package upgrade in RUN commands +- Example: `apt-get install -y libssl3` for OpenSSL CVEs + +**Strategy C: Apply Patches** +- Use patching tools for targeted fixes in builder stage +- Document with comments explaining which CVEs are fixed + +### 5. Validation Checklist + +✓ **CVE Verification** +- Run `docker scout cves` again on patched image +- Confirm target CVE no longer appears +- Note any remaining high/critical issues for tracking + +✓ **Build Verification** +```bash +# Build the Docker image +docker build -t robusta:test . + +# Verify build succeeds with no errors +echo "Build successful" +``` + +✓ **Functional Testing** +```bash +# Run basic smoke tests +pytest tests/ -v +``` + +✓ **Dependency Check** +```bash +# Verify no new vulnerabilities introduced +docker scout cves robusta:test --no-cache + +# Validate pyproject.toml metadata and lockfile consistency +poetry check --lock +``` + +### 6. Documentation + +Update these files with CVE fix details: + +**Dockerfile Comments:** +```dockerfile +# Patching CVE-XXXX-XXXXX (Critical): Package X +RUN apt-get install -y package-name +``` + +## Key Considerations + +### Python Package CVEs +- Check if vulnerability is in the installed wheel vs source +- For indirect dependencies, finding the transitive source is critical +- Use `poetry why package-name` to understand dependency relationships +- Go version matters for Go-based Python bindings (e.g., Cryptography) + +### System Library CVEs +- libexpat1, libssl, libc vulnerabilities are common +- These often have fixes in newer base images +- When possible, upgrade the base Python image before manual fixes + +### Testing Strategy +- Always rebuild and scan after each patch +- One CVE at a time is safer; group similar fixes together +- Document any CVEs that can't be patched with reasoning + +### Breaking Changes +- Verify patched versions don't introduce breaking changes +- Check release notes and migration guides +- Run full test suite, not just smoke tests for major upgrades + +## Implementation Notes + +1. Work through CVEs in severity order (Critical → High → Medium) +2. For each CVE, follow the complete cycle: identify → research → patch → validate +3. Commit each logical group of fixes separately +4. Keep diagnostics available: `docker scout cves` output, dependency trees, test results +5. If a patch can't be safely applied, document why in the code comments + +## Common Issues and Solutions + +### Issue: Patch introduces breaking changes +**Solution:** +1. Check if breaking change is in major version bump +2. Review if dependency needs to be pinned differently +3. Consider if a workaround exists (e.g., compatibility shim) + +### Issue: Transitive dependency is vulnerable +**Solution:** +1. Find which package brings it in: `poetry why vulnerable-package` +2. Update the parent package instead +3. Re-lock dependencies and verify fix + +### Issue: CVE disappears after unrelated patch +**Solution:** +1. Good sign - often due to transitive dependency updates +2. Still verify with `docker scout cves` on final image +3. Update documentation to credit upstream fixes diff --git a/Dockerfile b/Dockerfile index 2ddfcac59..0f200415a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,6 +41,9 @@ RUN poetry install --without dev --extras "all" COPY playbooks/ /etc/robusta/playbooks/defaults RUN pip install --no-cache-dir /etc/robusta/playbooks/defaults +# Patching CVE-2026-24049 (High): wheel path traversal vulnerability +RUN pip install --no-cache-dir "wheel>=0.46.2" + # Fixes k8s library bug - see https://github.com/kubernetes-client/python/issues/1867#issuecomment-1353813412 RUN find /app/venv/lib/python*/site-packages/kubernetes/client/rest.py -type f -exec sed -i 's:^\(.*logger.*\)$:#\1:' {} \; @@ -66,11 +69,12 @@ WORKDIR /app # Install necessary packages for the runtime environment # We're installing here libexpat1, to upgrade the package to include a fix to 3 high CVEs. CVE-2024-45491,CVE-2024-45490,CVE-2024-45492 +# Patching glibc for CVE-2026-0861, CVE-2026-0915, CVE-2025-15281 RUN apt-get update \ && dpkg --add-architecture arm64 \ && pip3 install --no-cache-dir --upgrade pip \ && apt-get install -y --no-install-recommends git ssh curl libcairo2 apt-transport-https gnupg2 \ - && apt-get install -y --no-install-recommends libexpat1 \ + && apt-get install -y --no-install-recommends libexpat1 libc6 libc-bin \ && rm -rf /var/lib/apt/lists/* @@ -81,12 +85,19 @@ RUN git config --global core.symlinks false RUN rm -rf /usr/local/lib/python3.11/ensurepip/_bundled/setuptools-65.5.0-py3-none-any.whl RUN rm -rf /usr/local/lib/python3.11/site-packages/setuptools-65.5.1.dist-info +# Patching CVE-2026-24049 (High): wheel path traversal vulnerability +# Patching CVE-2026-23949 (High): jaraco.context path traversal vulnerability (vendored in setuptools) +RUN pip3 install --no-cache-dir "wheel>=0.46.2" "setuptools>=80.10.1" \ + && rm -rf /usr/local/lib/python3.11/site-packages/setuptools/_vendor/wheel-0.45.1.dist-info + COPY --from=builder /app/venv /venv COPY --from=builder /etc/robusta/playbooks/defaults /etc/robusta/playbooks/defaults # Copy virtual environment and application files from the build stage COPY --from=builder /app /app # remove duplicated /app/venv - already copied to /venv RUN rm -rf /app/venv +# Remove vendored wheel 0.45.1 from setuptools in venv (CVE-2026-24049) +RUN rm -rf /venv/lib/python3.11/site-packages/setuptools/_vendor/wheel* # Set up kubectl COPY --from=builder /app/Release.key /tmp/Release.key diff --git a/poetry.lock b/poetry.lock index 7c1ab6e22..c0c1b7f7d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -264,13 +264,13 @@ xcb = ["xcffib (>=1.4.0)"] [[package]] name = "cairosvg" -version = "2.8.2" +version = "2.9.0" description = "A Simple SVG Converter based on Cairo" optional = true -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "cairosvg-2.8.2-py3-none-any.whl", hash = "sha256:eab46dad4674f33267a671dce39b64be245911c901c70d65d2b7b0821e852bf5"}, - {file = "cairosvg-2.8.2.tar.gz", hash = "sha256:07cbf4e86317b27a92318a4cac2a4bb37a5e9c1b8a27355d06874b22f85bef9f"}, + {file = "cairosvg-2.9.0-py3-none-any.whl", hash = "sha256:4b82d07d145377dffdfc19d9791bd5fb65539bb4da0adecf0bdbd9cd4ffd7c68"}, + {file = "cairosvg-2.9.0.tar.gz", hash = "sha256:1debb00cd2da11350d8b6f5ceb739f1b539196d71d5cf5eb7363dbd1bfbc8dc5"}, ] [package.dependencies] @@ -2307,13 +2307,13 @@ files = [ [[package]] name = "pyasn1" -version = "0.6.2" +version = "0.6.3" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" files = [ - {file = "pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf"}, - {file = "pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b"}, + {file = "pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde"}, + {file = "pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf"}, ] [[package]] @@ -2448,20 +2448,23 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" -version = "2.10.1" +version = "2.12.1" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.9" files = [ - {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, - {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, + {file = "pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c"}, + {file = "pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b"}, ] +[package.dependencies] +typing_extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + [package.extras] crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +dev = ["coverage[toml] (==7.10.7)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=8.4.2,<9.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] +tests = ["coverage[toml] (==7.10.7)", "pytest (>=8.4.2,<9.0.0)"] [[package]] name = "pymsteams" @@ -3015,13 +3018,13 @@ unleash = ["UnleashClient (>=6.0.1)"] [[package]] name = "setuptools" -version = "80.9.0" +version = "80.10.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" files = [ - {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, - {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, + {file = "setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173"}, + {file = "setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70"}, ] [package.extras] @@ -3712,13 +3715,13 @@ devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3) [[package]] name = "urllib3" -version = "2.6.2" +version = "2.6.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" files = [ - {file = "urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd"}, - {file = "urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797"}, + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, ] [package.extras] @@ -3926,13 +3929,13 @@ files = [ [[package]] name = "werkzeug" -version = "3.1.4" +version = "3.1.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" files = [ - {file = "werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905"}, - {file = "werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e"}, + {file = "werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131"}, + {file = "werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25"}, ] [package.dependencies] @@ -3977,4 +3980,4 @@ all = ["CairoSVG", "Flask", "better-exceptions", "datadog-api-client", "grafana- [metadata] lock-version = "2.0" python-versions = ">=3.10, <3.12" -content-hash = "5c73754492a8c78865bb325490efac86c4c3268840cf1d192a338fe608aedfbb" +content-hash = "13696f4d7ee044ffbc98d792caeccea0bf34997613c8ac755cf124ba4caf1cad" diff --git a/pyproject.toml b/pyproject.toml index 2ca0f4cc8..d32660b03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ supabase = "2.5.1" tabulate = { version = "^0.8.10" } slack-sdk = { version = "^3.23.0" } Flask = { version = "^3.0.0", optional = true } -werkzeug = { version = ">=3.0.6", optional = true } +werkzeug = { version = ">=3.1.6", optional = true } jinja2 = { version = ">=3.1.5", optional = true } grafana-api = { version = "^1.0.3", optional = true } watchdog = { version = "^2.1.0", optional = true } @@ -71,12 +71,14 @@ prometheus-api-client = "0.5.4" requests = "^2.32.3" certifi = "^2023.7.22" regex = "2024.5.15" -pyjwt = "^2.4.0" -urllib3 = "^2.6.2" +pyjwt = "^2.12.0" +urllib3 = "^2.6.3" httpx = "0.27.2" postgrest = "0.16.8" # Pin to fix cve https://github.com/robusta-dev/robusta/security/dependabot/85 -h2 = "^4.3.0" +h2 = "^4.3.0" +# Pin to fix CVE-2026-30922 (transitive via google-auth) +pyasn1 = ">=0.6.3" [tool.poetry.dev-dependencies] pre-commit = "^2.13.0"