-
Notifications
You must be signed in to change notification settings - Fork 16
fix(deps): resolve 19 dependabot security vulnerabilities #153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
aa7564d
d478d12
989b956
7fd6449
1c3682c
9c45ebf
6f4e25d
ade474f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,42 @@ | |
| All notable behavioural changes to `strands-robots` are logged here. Follows | ||
| [Keep a Changelog](https://keepachangelog.com/) conventions. | ||
|
|
||
| ## Unreleased - #153 (Security dependency bumps) | ||
|
|
||
| ### Changed | ||
|
|
||
| - **Pillow** floor raised from `>=8.0.0` to `>=10.3.0` (carries CVE-2024-28219 | ||
| buffer-overflow fix for downstream consumers who do not use this repo's lockfile). | ||
| - **Pillow** ceiling raised from `<12.0.0` to `<13.0.0` (allows 12.2.0+). | ||
| - **pytest** ceiling raised from `<9.0.0` to `<10.0.0` (allows 9.0.3). Note: | ||
| pytest 9.x removed nose-style `setup`/`teardown` and changed `--strict` | ||
| aliasing; no impact on this repo's test suite. | ||
| - **pytest-cov** ceiling raised from `<6.0.0` to `<8.0.0` (allows 7.1.0). | ||
| - **Transitive lockfile** (`uv.lock`) regenerated with full extras matrix, | ||
| resolving cryptography 48.0.0 (crosses 2 major versions from prior lock at | ||
| 46.0.6: 46->47->48; no deprecated API usage found in this repo), | ||
| python-multipart 0.0.28, gitpython 3.1.50, urllib3 2.7.0, and 42 | ||
| previously-unresolved transitive packages (mujoco, transformers 5.8.1, | ||
| libero, robomimic, robosuite, numba, hydra-core, etc.). | ||
| - transformers 5.x: explicit compat code at | ||
| `strands_robots/policies/lerobot_local/policy.py:308-321`. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line-number reference ( Non-blocking nit. |
||
| - The previous lockfile was generated against a partial extras set. | ||
| - The Pillow `>=10.3.0` floor is the minimum security guarantee; | ||
| the lockfile-pinned 12.2.0 is the recommended install path. | ||
|
|
||
| ### Security | ||
|
|
||
| - Closes 19 of 20 open Dependabot alerts. Remaining: diffusers | ||
| GHSA-98h9-4798-4q5v (tracked in #210, blocked by lerobot 0.5.1 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Security block calls out diffusers exposure for lockfile users but does not call out the inverse exposure for wheel-install users on the transitive CVEs. The PR description states this clearly ("only lockfile ( |
||
| cap `diffusers<0.36`). Lockfile users remain exposed until #210 lands; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Diffusers GHSA exposure is documented but has no automated tripwire. The entry says "Lockfile users remain exposed until #210 lands" — good disclosure. But there's no test or CI gate that fails (or even surfaces a notice) once lerobot relaxes its |
||
| mitigation requires either floating diffusers above 0.38.0 or upgrading | ||
| lerobot once an upstream release relaxes the cap. | ||
| - **Wheel-install exposure note**: `pip install strands-robots` consumers | ||
| (without `uv.lock`) are protected only for the Pillow CVE (floor raised | ||
| to `>=10.3.0`). Transitive CVE fixes (cryptography, urllib3, gitpython, | ||
| python-multipart) have no direct-dep floor and are mitigated only by | ||
| lockfile regeneration. | ||
|
|
||
| ## Unreleased - #178 (LiberoOffScreenRenderEngine retired) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per AGENTS.md > Project Dashboard rule ("never track work only in local markdown — the board is the source of truth"), this remaining alert should be filed as a tracked issue on Strands Labs - Robots and referenced here by issue number, e.g. - Closes 19 of 20 open Dependabot alerts. Remaining: diffusers
GHSA-98h9-4798-4q5v (tracked in #XXX, blocked by lerobot 0.5.1
cap `diffusers<0.36`).The PR description acknowledges this as "Known follow-up" but the issue itself isn't filed yet. Filing it before merge prevents the alert from going stale. |
||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing blank line between this |
||
| ### Removed: ``LiberoOffScreenRenderEngine`` simulation backend (BREAKING) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,7 +37,7 @@ dependencies = [ | |
| "strands-agents>=1.0.0,<2.0.0", | ||
| "numpy>=1.21.0,<3.0.0", | ||
| "opencv-python-headless>=4.5.0,<5.0.0", | ||
| "Pillow>=8.0.0,<12.0.0", | ||
| "Pillow>=10.3.0,<13.0.0", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Floor bump to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per AGENTS.md > Review Learnings (#85) > "Pin regression tests for reviewed fixes": even though this is a constraint-only change, the security-floor guarantee for downstream
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirming the lockstep with line 116 (hatch env). The new No change requested — noting for the human reviewer that the duplicate-pin-site issue is acknowledged in the PR description as a known follow-up. |
||
| ] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
|
||
| [project.optional-dependencies] | ||
|
|
@@ -84,8 +84,8 @@ all = [ | |
| "strands-robots[mesh-iot]", | ||
| ] | ||
| dev = [ | ||
| "pytest>=6.0,<9.0.0", | ||
| "pytest-cov>=4.0.0,<6.0.0", | ||
| "pytest>=6.0,<10.0.0", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| "pytest-cov>=4.0.0,<8.0.0", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pytest 9.0.x was released only days ago; bumping the cap to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pytest 9.x major bump deserves a The PR description correctly notes pytest 9 removed nose-style Not a blocker — the bump is right — but worth confirming |
||
| "ruff>=0.4.0,<1.0.0", | ||
| "mypy>=1.0.0,<2.0.0", | ||
| "pytest-timeout>=2.0.0,<3.0.0", | ||
|
|
@@ -108,11 +108,11 @@ packages = ["strands_robots"] | |
| installer = "uv" | ||
| features = ["all"] | ||
| dependencies = [ | ||
| "pytest>=6.0,<9.0.0", | ||
| "pytest-cov>=4.0.0,<6.0.0", | ||
| "pytest>=6.0,<10.0.0", | ||
| "pytest-cov>=4.0.0,<8.0.0", | ||
| "ruff>=0.4.0,<1.0.0", | ||
| "mypy>=1.0.0,<2.0.0", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pytest cap bumped to One thing worth confirming before merge: |
||
| "Pillow>=8.0.0,<12.0.0", | ||
| "Pillow>=10.3.0,<13.0.0", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the second of two
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The PR description and AGENTS.md both mention the duplicate Pillow/pytest pin sites as a known follow-up to consolidate, but no tracking issue is linked. With the new |
||
| "requests>=2.28.0,<3.0.0", | ||
| "msgpack>=1.0.0,<2.0.0", | ||
| "pyzmq>=27.0.0,<28.0.0", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,230 @@ | ||
| """Repo hygiene: pin minimum security-floor versions and cross-site consistency. | ||
|
|
||
| These tests guard the dependency constraints in ``pyproject.toml`` so a routine | ||
| ``uv lock --upgrade`` or constraint-cleanup PR cannot silently lower a floor | ||
| that was raised for a CVE, or let the two pin sites | ||
| (``[project.dependencies]`` / ``[project.optional-dependencies].dev`` / | ||
| ``[tool.hatch.envs.default]``) drift out of sync. | ||
|
|
||
| Pinned floors (raise, never lower without an explicit security review): | ||
|
|
||
| - Pillow >= 10.3.0 -- carries CVE-2024-28219 buffer-overflow fix. | ||
| Raised in PR #153 R1 from the previous >=8.0.0 floor. | ||
|
|
||
| Why only Pillow here? Floors are only added when a CVE recurs in a *direct* | ||
| dep that downstream `pip install` consumers can resolve below the fixed | ||
| version. Transitive bumps (cryptography, urllib3, gitpython, | ||
| python-multipart, etc.) are mitigated by `uv.lock` regeneration -- they | ||
| have no direct constraint to pin. | ||
|
|
||
| Cross-site consistency: every dep that appears in more than one pin block must | ||
| have identical floor + ceiling everywhere it appears, otherwise a future | ||
| Dependabot PR can update one site and forget the other -- the exact pattern | ||
| that motivated the floor sweep in PR #153. The check is property-style | ||
| (scans every dep that appears in >=2 sites), so adding a new dual-site dep | ||
| does not require updating an allowlist. | ||
|
|
||
| Add an entry to ``_SECURITY_FLOORS`` whenever a floor is raised for a CVE | ||
| fix. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import re | ||
| import tomllib | ||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
| from packaging.version import Version | ||
|
|
||
| REPO_ROOT = Path(__file__).resolve().parent.parent | ||
| PYPROJECT = REPO_ROOT / "pyproject.toml" | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- # | ||
| # Constraint parsing | ||
| # --------------------------------------------------------------------------- # | ||
|
|
||
| # Match e.g. ``Pillow>=10.3.0,<13.0.0``. Permissive on whitespace; case-insensitive | ||
| # on the package name (``Pillow`` vs ``pillow``). | ||
| _CONSTRAINT_RE = re.compile( | ||
| r"^\s*(?P<name>[A-Za-z0-9._-]+)\s*" | ||
| r"(?P<spec>(?:[<>=!~]=?\s*[A-Za-z0-9._*+-]+(?:\s*,\s*[<>=!~]=?\s*[A-Za-z0-9._*+-]+)*)?)\s*$" | ||
| ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No such dep exists today, so this isn't a current blocker. Cheap robustness fix: extend the name class to allow |
||
|
|
||
|
|
||
| def _parse_constraint(entry: str) -> tuple[str, str]: | ||
| """Return ``(canonical_name_lower, normalised_spec)``.""" | ||
| m = _CONSTRAINT_RE.match(entry.split(";", 1)[0]) # drop env markers | ||
| if m is None: | ||
| raise ValueError(f"Could not parse constraint: {entry!r}") | ||
| name = m.group("name").lower().replace("_", "-") | ||
| spec = re.sub(r"\s+", "", m.group("spec")) | ||
| return name, spec | ||
|
|
||
|
|
||
| def _floor_of(spec: str) -> str | None: | ||
| """Extract the ``>=`` lower bound from a spec like ``>=10.3.0,<13.0.0``. | ||
|
|
||
| Only ``>=`` is recognised. ``~=`` and ``==`` floors are intentionally not | ||
| accepted: ``_SECURITY_FLOORS`` entries must use ``>=`` so the pin clearly | ||
| states the minimum-acceptable version. If a future dep needs a different | ||
| operator, broaden this helper at the same time as the floor is added. | ||
| """ | ||
| m = re.search(r">=\s*([A-Za-z0-9._*+-]+)", spec) | ||
| return m.group(1) if m else None | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not urgent (no current dep uses
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
One extra clause in the helper or assertion message ( |
||
|
|
||
|
|
||
| def _parse_version(v: str) -> Version: | ||
| """Parse a version string using PEP 440 semantics. | ||
|
|
||
| Uses ``packaging.version.Version`` for correct handling of pre-releases, | ||
| post-releases, dev-releases, and local versions. This ensures that a | ||
| floor like ``>=10.3.0`` correctly rejects ``10.3.0rc1`` (which is | ||
| *before* the release and may not contain the CVE fix). | ||
| """ | ||
| return Version(v) | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- # | ||
| # Fixtures | ||
| # --------------------------------------------------------------------------- # | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module") | ||
| def pyproject() -> dict: | ||
| with PYPROJECT.open("rb") as fh: | ||
| return tomllib.load(fh) | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module") | ||
| def constraint_index(pyproject: dict) -> dict[str, dict[str, str]]: | ||
| """Map ``dep_name -> {site: spec}`` across every pin site in pyproject. | ||
|
|
||
| Sites tracked: | ||
| - ``project.dependencies`` | ||
| - ``project.optional-dependencies.<extra>`` for every declared extra | ||
| - ``tool.hatch.envs.default.dependencies`` | ||
|
|
||
| Self-references with extras (``"strands-robots[mesh]"``) have no spec and | ||
| are intentionally skipped by ``_parse_constraint`` raising ``ValueError``; | ||
| a real parse failure on a typo'd spec re-raises so the test surfaces the | ||
| bug instead of silently dropping the entry. | ||
| """ | ||
| index: dict[str, dict[str, str]] = {} | ||
|
|
||
| def absorb(site: str, entries: list[str]) -> None: | ||
| for raw in entries: | ||
| try: | ||
| name, spec = _parse_constraint(raw) | ||
| except ValueError: | ||
| # Self-reference like "strands-robots[mesh]" has no spec; | ||
| # let any other parse failure propagate so a typo doesn't | ||
| # silently disappear. | ||
| if raw.lstrip().startswith("strands-robots["): | ||
| continue | ||
| raise | ||
| index.setdefault(name, {})[site] = spec | ||
|
|
||
| project = pyproject.get("project", {}) | ||
| absorb("project.dependencies", project.get("dependencies", []) or []) | ||
| for extra, entries in (project.get("optional-dependencies", {}) or {}).items(): | ||
| absorb(f"project.optional-dependencies.{extra}", entries or []) | ||
|
|
||
| hatch_default = ( | ||
| pyproject.get("tool", {}).get("hatch", {}).get("envs", {}).get("default", {}).get("dependencies", []) or [] | ||
| ) | ||
| absorb("tool.hatch.envs.default.dependencies", hatch_default) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scope of the multi-site scan is Minor today (only |
||
|
|
||
| return index | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- # | ||
| # Security floors -- raise, never silently lower | ||
| # --------------------------------------------------------------------------- # | ||
|
|
||
| # (canonical_name, minimum_floor, rationale) | ||
| _SECURITY_FLOORS: list[tuple[str, str, str]] = [ | ||
| ( | ||
| "pillow", | ||
| "10.3.0", | ||
| "CVE-2024-28219 buffer-overflow fix; raised in PR #153 R1.", | ||
| ), | ||
| ] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only Pillow is enumerated here, but the PR's stated security wins also include transitive bumps for cryptography, python-multipart, gitpython, urllib3 — none of which have a direct constraint to pin. That's correct behaviour for this PR, but worth a short comment in this list explaining the policy: floors only get added when a CVE recurs in a direct dep, otherwise lockfile regeneration is the mitigation. Otherwise the next reviewer will reasonably ask "why isn't cryptography here?" |
||
|
|
||
|
|
||
| @pytest.mark.parametrize(("name", "min_floor", "rationale"), _SECURITY_FLOORS) | ||
| def test_security_floor_not_lowered( | ||
| constraint_index: dict[str, dict[str, str]], | ||
| name: str, | ||
| min_floor: str, | ||
| rationale: str, | ||
| ) -> None: | ||
| """Every pin site that declares ``name`` must enforce ``>=min_floor``. | ||
|
|
||
| Lowering a security floor requires deleting the entry from | ||
| ``_SECURITY_FLOORS`` with a reviewer-visible explanation, so the change | ||
| surfaces in PR review rather than slipping through a silent ``uv lock``. | ||
| """ | ||
| sites = constraint_index.get(name) | ||
| assert sites, ( | ||
| f"{name} not found in any pyproject pin site -- floor pin is dead. " | ||
| f"Either add it back or remove from _SECURITY_FLOORS. ({rationale})" | ||
| ) | ||
|
|
||
| for site, spec in sites.items(): | ||
| floor = _floor_of(spec) | ||
| assert floor is not None, ( | ||
| f"{name} in {site} has no >= floor (spec={spec!r}); cannot enforce CVE floor. {rationale}" | ||
| ) | ||
| assert _parse_version(floor) >= _parse_version(min_floor), ( | ||
| f"{name} floor regression in {site}: spec={spec!r} " | ||
| f"declares >={floor} but security minimum is >={min_floor}. " | ||
| f"{rationale}" | ||
| ) | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- # | ||
| # Multi-site consistency -- pinned in multiple blocks must match exactly | ||
| # --------------------------------------------------------------------------- # | ||
|
|
||
|
|
||
| def test_multi_site_pins_consistent(constraint_index: dict[str, dict[str, str]]) -> None: | ||
| """Every dep declared in 2+ pin sites must carry an identical spec. | ||
|
|
||
| Catches the foot-gun where a Dependabot PR updates | ||
| ``[project.optional-dependencies].dev`` but forgets | ||
| ``[tool.hatch.envs.default]`` (or vice versa), letting the hatch env | ||
| silently resolve a different version than the wheel install. | ||
|
|
||
| Property-style: scans every dep automatically. Adding a new dual-site | ||
| dep does not require updating an allowlist; the test catches drift on | ||
| every dep that lives in 2+ sites today and any added tomorrow. | ||
| """ | ||
| drifted = { | ||
| name: sites for name, sites in constraint_index.items() if len(sites) >= 2 and len(set(sites.values())) > 1 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drift detection compares specs as strings (after whitespace strip), so semantically-equivalent specs that differ only in operator order will false-positive. Example future Dependabot PR landing: would flag as drift even though pip/uv treat them identically. Today every entry uses floor-then-ceiling order so this is latent, but the test's stated property is "identical spec everywhere it appears". A more robust check parses each spec to a Not blocking; flagging so the next reader doesn't waste an afternoon debugging a false-drift failure. |
||
| } | ||
| assert not drifted, ( | ||
| f"Multi-site pins disagree -- update every site to the same constraint when bumping. Drift: {drifted}" | ||
| ) | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- # | ||
| # PEP 440 pre-release correctness (regression pin for R5 fix) | ||
| # --------------------------------------------------------------------------- # | ||
|
|
||
|
|
||
| def test_parse_version_rejects_prerelease_below_release() -> None: | ||
| """Verify that _parse_version correctly identifies pre-releases as below their release. | ||
|
|
||
| Regression pin: before R5, ``_version_tuple("10.3.0rc1")`` returned ``(10, 3, 0)`` | ||
| which compared equal to ``_version_tuple("10.3.0")``, silently accepting a | ||
| pre-release floor that might not carry the CVE fix. With ``packaging.version.Version``, | ||
| ``10.3.0rc1 < 10.3.0`` is correctly enforced. | ||
| """ | ||
| assert _parse_version("10.3.0rc1") < _parse_version("10.3.0") | ||
| assert _parse_version("10.3.0") >= _parse_version("10.3.0") | ||
| assert _parse_version("10.3") == _parse_version("10.3.0") | ||
| assert _parse_version("10.4.0") > _parse_version("10.3.0") | ||
| assert _parse_version("10.3.0.post1") > _parse_version("10.3.0") | ||
| assert _parse_version("10.3.0.dev1") < _parse_version("10.3.0") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
### Securityblock below explicitly flags diffusers lockfile exposure, but the same kind of caveat applies inversely to cryptography / python-multipart / gitpython / urllib3 listed here: these are transitive-only and no[project.dependencies]or extras floor was added, so apip install strands-robotsconsumer who doesn't useuv.lockcan still resolve vulnerable versions.Consider adding a sentence in the
### Securityblock clarifying that only Pillow's fix is constraint-level (and therefore wheel-install consumers are protected by the floor); for the transitive ones, onlyuv.lockusers get the fix. As-is, the "19 of 20 alerts closed" framing is accurate for lockfile users but overstated for wheel-install users.