Skip to content

chore(deps): drive python-sdk to zero known vulnerabilities in both ecosystems, pin Yarn 4, enforce release-age gate - #59

Merged
abbaseya merged 4 commits into
mainfrom
chore/dependabot-high-severity-release-tooling
Aug 12, 2026
Merged

chore(deps): drive python-sdk to zero known vulnerabilities in both ecosystems, pin Yarn 4, enforce release-age gate#59
abbaseya merged 4 commits into
mainfrom
chore/dependabot-high-severity-release-tooling

Conversation

@abbaseya

@abbaseya abbaseya commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Drives this repo to zero known vulnerabilities across both ecosystems, verified by auditing every resolved package version against OSV (which carries the same GHSA data Dependabot uses) rather than only the alerts Dependabot chose to surface.

Surface Before After
yarn.lock — 418 resolutions 6 packages / 15 advisories 0
Python, py3.13 newest compatible 1 package / 2 advisories 0
Python, py3.13 declared floors 1 package / 2 advisories 0
Python, py3.9 declared floors 1 package / 2 advisories 1 package / 2 advisories — irreducible, see §3
scripts/js_reference (murmurhash) 0 0

Counts are OSV records, not distinct vulnerabilities. OSV carries GHSA and PYSEC entries separately, so pytest's single CVE-2025-71176 appears as 2 records (GHSA-6w46-j5rx-g56g + PYSEC-2026-1845) where GHSA alone returns 1. Read the Python rows as one vulnerability if you are reconciling against Dependabot.

Nothing here reaches the published artifact. httpx is still the only runtime dependency; the wheel target is packages = ["src/convert_sdk"] and the sdist excludes tests, scripts, package.json, release.config.mjs, .yarnrc.yml and yarn.lock.

Four commits — the fourth applies the blocking finding from review.


1. chore(deps) — clear the npm alerts

All 8 open Dependabot alerts (4 high, 4 medium; no critical was open). Three of the four highs were reachable inside the existing semver ranges. The fourth — sigstore GHSA-52v5-jr5w-gjxr, where certificateOIDs verification constraints are silently dropped and never enforced — needs sigstore >= 4.1.1, and that only arrives through a major bump of the chain that pulls it in:

semantic-release 24.2.9 -> 25.0.9
  @semantic-release/npm 12.0.2 -> 13.1.5
    npm 10.9.8 -> 11.19.0
      libnpmpublish 10 -> 11.2.0  }-> sigstore ^3.0.0 -> ^4.0.0  (4.1.1)
      pacote        19 -> 21.5.1  }   @sigstore/core 2.0.0 -> 3.2.1
      tar 7.5.16 -> 7.5.22

@semantic-release/github moves to ^12 because that is what semantic-release 25 depends on. Leaving it at ^11 would hoist the older copy to the project root, where semantic-release resolves plugins from — so the root copy would shadow the one core installed.

# Severity Package Was Now Patched at
14 high ip-address 10.2.0 10.4.0 10.3.1
9 high brace-expansion 2.1.1 5.0.9 2.1.2
7 high js-yaml 4.2.0 4.3.1 4.3.0
2 high sigstore 3.1.0 4.1.1 4.1.1
13 medium ip-address 10.2.0 10.4.0 10.2.2
12 medium ip-address 10.2.0 10.4.0 10.2.1
4 medium tar 7.5.16 7.5.22 7.5.18
1 medium @sigstore/core 2.0.0 3.2.1 3.2.1

The OSV sweep also clears 7 advisories Dependabot had auto-dismissed and never showed as open — 4 more on tar (including a critical), 2 more on brace-expansion, 1 more on js-yaml. main carries 15 advisories in total; this branch carries none.

Node floor. semantic-release 25 and @semantic-release/github 12 require ^22.14.0 || >= 24.10.0. release.yml installs lts/* on both jobs — currently Node 24.19.0, satisfied today, and every future LTS line stays above the floor.


2. chore(ci) — pin Yarn 4, enforce a 3-day minimum release age

Same fix as convertcom/php-sdk#60, applied here.

The intent was to add the npmMinimalAgeGate that javascript-sdk and php-sdk carry. Checking whether it would actually fire turned up why it wouldn't. release.yml already runs corepack enablethat is not enough on its own. With no packageManager field in package.json, corepack has nothing to resolve, falls through to its own default (Yarn 1.22.22) and downloads it. From the last real release run (30637880488, 2026-07-31):

! Corepack is about to download .../yarn-1.22.22.tgz
yarn install v1.22.22
[1/4] Resolving packages...
warning Workspaces can only be enabled in private projects.
success Saved lockfile.

Three consequences, all live until this PR:

  1. The committed yarn.lock was never used. Classic Yarn cannot read a __metadata: version: 10 lockfile, so it re-resolved every release from the package.json semver ranges against whatever the registry served at that moment. success Saved lockfile. is Yarn 1 writing its own, which is then discarded.
  2. --immutable was a silent no-op. Classic spells it --frozen-lockfile, does not error on the unknown flag, and saved a lockfile regardless.
  3. .yarnrc.yml was never read. nodeLinker: node-modules did nothing, and an age gate added there would have done nothing either.

So the packageManager pin is what makes the gate exist, not a nicety alongside it. Written with corepack use yarn@4.18.0, never by hand. The version is not a free choice: 4.18.0 wrote the lockfile in commit 1, and Yarn 4.10.3 rejects that same lockfile as YN0028: The lockfile would have been modified by this install — the pin has to match the writer. Same pin and hash as php-sdk#60.

npmMinimalAgeGate: 4320 (3 days) is the same value javascript-sdk carries (php-sdk gets it when #60 merges). No npmPreapprovedPackages — unlike javascript-sdk, this project has zero @convertcom/* packages in package.json or yarn.lock (the SDK ships via PyPI; the only Node packages here are release tooling), so an exemption list would be dead config. Yarn 4.18 already defaults the gate to 1440 (1 day), so this raises it to the house value rather than introducing the concept.


3. chore(deps) — patch pytest CVE-2025-71176

Dependabot reports no pip alerts for this repo. The OSV sweep of the resolved dev tree finds one anyway: GHSA-6w46-j5rx-g56g / PYSEC-2026-1845 / CVE-2025-71176 — pytest's insecure temporary directory. /tmp/pytest-of-{user} is predictable, so a local user can pre-create or symlink it to cause a denial of service or possibly gain privileges. MODERATE, AV:L.

The advisory range is introduced: 0fixed: 9.0.3every pytest release before 9.0.3 is affected. pytest 9 requires Python >=3.10 (verified against the PyPI requires-python of every 8.4.x and 9.x release), NFR22 mandates the full CPython 3.9–3.13 matrix, and the SDK's own floor is 3.9. So no single specifier is both patched and installable across the matrix: the existing pytest>=8.4,<8.5 pin was right about the constraint but left all five cells on an affected release.

Split on an environment marker rather than narrowing the matrix:

"pytest>=9.0.3,<10; python_version >= '3.10'",
"pytest>=8.4,<8.5;  python_version < '3.10'",
Resolution corner pytest OSV
py3.13, newest compatible 9.1.1 clean
py3.13, declared floors (lowest-direct) 9.0.3 clean
py3.9, declared floors 8.4.0 the pytest advisory only (1 CVE, 2 OSV records)

The py3.9 cell is an irreducible remainder — no upstream release clears it while the SDK supports Python 3.9. It is bounded: pytest is dev-only and never ships, and the vector is local. The only route to a literal zero there is moving the SDK's requires-python floor to 3.10, which is a product decision about who can install the SDK, not a dependency fix — so it is flagged here, not taken. Collapse the two lines into pytest>=9.0.3 if that floor ever moves.


Verification

pytest 8 → 9 is a major bump, so the suite was run rather than assumed. Every command below is the one the workflow itself runs.

  • test matrix celluv sync --group dev --upgrade then pytest -p no:cacheprovider --cov=convert_sdk --cov-report=term-missing --cov-fail-under=85: 1134 passed, total coverage 96.76%, and the evaluation/ floor holds at 97% against its 95% gate.
  • lint + type-checkruff check src tests scripts demo, check_generated_pyi_header.py, mypy --strict (47 files), mypy --strict typecheck/serving_config_contract.py: all clean.
  • bounds-check (lower)--resolution lowest-direct plus the ci/lower-bounds-overrides.txt httpx pin, --ignore=tests/parity: 920 passed.
  • Cold-cache immutable installnode_modules removed and an empty YARN_GLOBAL_FOLDER, so all 418 packages download fresh with the age gate active: yarn install --immutable passes. The real CI shape, not a warm-cache stand-in.
  • yarn release:dry-run — loads all four configured plugins, passes verifyConditions for both exec and github (GitHub auth plus push permission), and analyzes commits to "no release". Correct: chore is a non-releasing type in release.config.mjs, so this PR will not cut a version.
  • No hardening opt-out was written. Yarn's lockfile migration is known to rewrite .yarnrc.yml with npmMinimalAgeGate: 0, enableScripts: true and approvedGitRepositories: ["**"] when it bumps a legacy lockfile. It did not fire here (no YN0087, the lockfile was already current), and yarn config --json confirms it after the change: gate = 4320 sourced from .yarnrc.yml, enableScripts = false, approvedGitRepositories = [].
  • Publish-age sweep — all 122 newly-added lockfile resolutions checked against their npm registry publish time. Zero under the 3-day floor; youngest is semantic-release@25.0.9 at 3.5 days.
  • Lockfile format unchanged (__metadata version 10, cacheKey 10c0).

Found while verifying — NOT fixed here

The 15-cell CPython matrix has never run anything but Python 3.13. Every test (py3.x / …) cell and bounds-check (lower) do uv sync --group dev --python <ver> and then a bare uv run pytest. Because .python-version pins 3.13, that bare uv run discards the venv the sync step just built and recreates it at 3.13. Reproduced locally:

$ uv sync --group dev --python 3.9
$ .venv/bin/python -V
Python 3.9.25
$ uv run pytest ...
Using CPython 3.13.13
Removed virtual environment at: .venv
Creating virtual environment at: .venv

and confirmed in the job log of the cell named test (py3.9 / ubuntu-latest): platform linux -- Python 3.13.12. That is why tests/test_packaging.py's bare import tomllib (stdlib only from 3.11) has never failed CI — it fails immediately under a real 3.9 interpreter.

This is left out deliberately. Repairing the matrix makes CI genuinely run 3.9–3.12 for the first time, which turns it red until test_packaging.py and anything else 3.9-incompatible is fixed — real work with its own blast radius that has no business riding inside a security fix. Filed separately.

It does not weaken anything above: since CI actually runs 3.13 everywhere, after commit 3 every cell installs a patched pytest. The 8.4.x fallback only materialises for someone deliberately on 3.9.

Also deliberately not done

  • No resolutions override forcing sigstore@4 under libnpmpublish@10 / pacote@19. Those were written against the sigstore 3 API; forcing a major across that boundary would be a guess, not a fix.
  • No bump of conventional-changelog-conventionalcommits off ^8. semantic-release 25's commit-analyzer and release-notes-generator still resolve conventional-changelog-writer@^8 and conventional-commits-parser@^6, the same pair as before, so ^8 stays correct and there is no advisory against it.
  • No .github/dependabot.yml. This repo has alerts but no update config, which is why these sat open. Worth adding as its own change.
  • No lockfile for scripts/js_reference (murmurhash@^2.0.1, currently advisory-free), and no fix for its install instruction, which names a package manager the house rules forbid. Not a vulnerability; separate cleanup.

Review outcome

Reviewed by @usmanabbas7 — verdict approve after one edit, with an independent re-derivation against GHSA (this PR audited OSV), landing on the same main = 6 packages / 15 advisories → branch = 0.

  • Finding 1, blocking — fixed in docs: align TESTING.md with the pytest marker split. TESTING.md still said pytest was pinned to >=8.4,<8.5 for Python 3.9, and pointed a future maintainer at collapsing the specifiers that way — which would have reintroduced CVE-2025-71176 on every cell. A repo-wide grep confirms TESTING.md and pyproject.toml were the only two homes for that rationale, so no third copy remains to drift.
  • Finding 4, accuracy — fixed above: the advisory counts are OSV records, and the Python figure is one CVE carried as two.
  • Finding 6, cosmetic — fixed in the same commit: .yarnrc.yml no longer credits php-sdk for a gate that lands only when build(deps): bump the npm_and_yarn group across 1 directory with 4 updates #60 merges.
  • Findings 2, 3, 5 — routed, not bundled. Filed as their own tasks: a release-tooling CI job so yarn.lock, the 4.18.0 pin and the age gate are gated at PR time instead of first exercised by release.yml on main (finding 2 — the strongest of the three, and exactly what would have caught classic Yarn servicing releases without a human reading job logs); converging javascript-sdk off yarn@4.10.3 (3); and the pytest-cov>=5,<6 cap now that the pytest floor moved two majors (5). None changes a resolved version, so none belongs in a security fix.

🤖 Generated with Claude Code

All eight open Dependabot alerts on this repo sit in `yarn.lock` — the
dev-only semantic-release tooling. None of them reach the published
wheel/sdist (httpx is still the only runtime dependency).

Three of the four highs were already reachable inside the existing semver
ranges; the fourth (sigstore GHSA-52v5-jr5w-gjxr, `certificateOIDs`
verification constraints silently dropped) needed sigstore >= 4.1.1, which
only arrives through a major bump:

  semantic-release 24.2.9 -> 25.0.9
    @semantic-release/npm 12.0.2 -> 13.1.5
      npm 10.9.8 -> 11.19.0
        libnpmpublish 10 -> 11.2.0  }-> sigstore ^3 -> ^4  (4.1.1)
        pacote        19 -> 21.5.1  }   @sigstore/core 2.0.0 -> 3.2.1
        tar 7.5.16 -> 7.5.22

`@semantic-release/github` moves to ^12 to match what semantic-release 25
depends on — leaving it at ^11 would hoist the older copy to the project
root and shadow the one core resolves.

Resolved (4 high, 4 medium — no criticals were open):

  #14 high    ip-address       10.2.0 -> 10.4.0   (needs >= 10.3.1)
  #9  high    brace-expansion  2.1.1  -> 5.0.9    (needs >= 2.1.2)
  #7  high    js-yaml          4.2.0  -> 4.3.1    (needs >= 4.3.0)
  #2  high    sigstore         3.1.0  -> 4.1.1    (needs >= 4.1.1)
  #13 medium  ip-address       (same bump as #14)
  #12 medium  ip-address       (same bump as #14)
  #4  medium  tar              7.5.16 -> 7.5.22   (needs >= 7.5.18)
  #1  medium  @sigstore/core   2.0.0  -> 3.2.1    (needs >= 3.2.1)

Node: semantic-release 25 requires ^22.14.0 || >= 24.10.0. release.yml
installs `lts/*`, currently Node 24.19.0 — satisfied, and every future LTS
line stays above the floor.

Verified: `yarn install --immutable` (what release.yml runs) passes against
the regenerated lockfile with the lockfile format unchanged (__metadata
version 10), and `yarn npm audit --all --recursive` reports no suggestions.
A `semantic-release --dry-run` against this branch loads all four configured
plugins, passes verifyConditions for both exec and github (GitHub
authentication + push permission), and analyzes commits to "no release" —
correct, since `chore` is a non-releasing type in release.config.mjs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@abbaseya abbaseya self-assigned this Aug 8, 2026
@abbaseya
abbaseya requested a review from usmanabbas7 August 8, 2026 16:27
@abbaseya abbaseya changed the title chore(deps): clear high-severity Dependabot alerts in release tooling chore(deps): clear high-severity Dependabot alerts, pin Yarn 4 and enforce release-age gate Aug 8, 2026
Same fix as convertcom/php-sdk#60, applied to this repo.

CI has never run Yarn 4. `release.yml` already ran `corepack enable` — which
is not enough on its own. With no `packageManager` field in `package.json`,
corepack has nothing to resolve and falls through to its own default, Yarn
1.22.22, which it then downloads. From the last real release run
(30637880488, 2026-07-31):

    ! Corepack is about to download .../yarn-1.22.22.tgz
    yarn install v1.22.22
    [1/4] Resolving packages...
    warning Workspaces can only be enabled in private projects.
    success Saved lockfile.

Three consequences, all live until now:

  1. The committed `yarn.lock` was never used. Classic Yarn cannot read a
     `__metadata: version: 10` lockfile, so it re-resolved every release from
     the `package.json` semver ranges against whatever the registry served at
     that moment. "success Saved lockfile." is Yarn 1 writing its own.
  2. `--immutable` was a silent no-op — classic spells it
     `--frozen-lockfile`, does not error on the unknown flag, and saved a
     lockfile anyway.
  3. `.yarnrc.yml` was never read. `nodeLinker: node-modules` did nothing,
     and an age gate added there would have done nothing either.

So the `packageManager` pin is what makes the gate exist, not a nicety
alongside it. Written with `corepack use yarn@4.18.0`, never by hand; 4.18.0
is the version that wrote the lockfile in the previous commit, and Yarn
4.10.3 rejects that lockfile as needing modification — the pin has to match.

`npmMinimalAgeGate: 4320` (3 days) matches javascript-sdk and php-sdk. No
`npmPreapprovedPackages`: unlike javascript-sdk, this project has zero
`@convertcom/*` npm packages in `package.json` or `yarn.lock` (the SDK ships
via PyPI; the only Node packages here are release tooling), so an exemption
list would be dead config. Yarn 4.18 already defaults the gate to 1440, so
this raises it to the house value rather than introducing it.

Verified after the change, per the known migration trap: `yarn config --json`
shows `npmMinimalAgeGate` = 4320 sourced from `.yarnrc.yml`, and
`enableScripts` / `approvedGitRepositories` still at their secure defaults —
no `YN0087` migration fired and no hardening opt-out was written. Cold-cache
`yarn install --immutable` (no node_modules, empty YARN_GLOBAL_FOLDER, all
418 packages fetched fresh with the gate active) passes, and
`yarn release:dry-run` loads every plugin and correctly declines to publish
off main. All 122 resolutions the previous commit adds were separately
checked against the 3-day floor; the youngest, semantic-release@25.0.9, is
3.5 days old.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the fix

Dependabot reports no pip alerts for this repo, but an OSV sweep of the
resolved dev tree finds one: GHSA-6w46-j5rx-g56g / PYSEC-2026-1845 /
CVE-2025-71176 — pytest's insecure temporary directory. `/tmp/pytest-of-{user}`
is predictable, so a local user can pre-create or symlink it to cause a denial
of service or possibly gain privileges. MODERATE, `AV:L`.

The advisory range is `introduced: 0` -> `fixed: 9.0.3`, i.e. EVERY pytest
release before 9.0.3 is affected, and pytest 9 requires Python >=3.10 (verified
against the PyPI `requires-python` of every 8.4.x and 9.x release). NFR22
mandates the full CPython 3.9-3.13 matrix and the SDK's own floor is 3.9, so no
single specifier is both patched and installable across the matrix — the
existing `pytest>=8.4,<8.5` pin was correct about the constraint but left all
five cells on an affected release.

Split on an environment marker instead of narrowing the matrix:

    "pytest>=9.0.3,<10; python_version >= '3.10'"
    "pytest>=8.4,<8.5;  python_version < '3.10'"

Resolution verified at all three corners the workflows exercise:

    py3.13 newest compatible   -> pytest 9.1.1   OSV: clean
    py3.13 declared floors     -> pytest 9.0.3   OSV: clean
    py3.9  declared floors     -> pytest 8.4.0   OSV: the pytest advisory only

The py3.9 cell is an irreducible remainder: no upstream release clears it while
the SDK supports 3.9. It is bounded — pytest is dev-only and never ships (wheel
target is packages = ["src/convert_sdk"], httpx is the only runtime dependency,
and the sdist excludes tests/), and the vector is local. Collapse the two lines
into `pytest>=9.0.3` if the SDK's requires-python floor ever moves to 3.10.

pytest 8 -> 9 is a major bump, so the suite was run rather than assumed:

  - `uv sync --group dev --python 3.13 --upgrade` + the workflow's own
    `pytest --cov=convert_sdk --cov-fail-under=85`: 1134 passed, total
    coverage 96.76%, and the evaluation/ floor holds at 97% (gate 95%).
  - `ruff check src tests scripts demo`, `check_generated_pyi_header.py`,
    `mypy --strict`, and `mypy --strict typecheck/serving_config_contract.py`
    all clean.
  - The `bounds-check (lower)` command (`--resolution lowest-direct` plus the
    ci/lower-bounds-overrides.txt httpx pin, `--ignore=tests/parity`):
    920 passed.
  - Forcing a genuine 3.9 interpreter runs green too, apart from one
    pre-existing break unrelated to this change: tests/test_packaging.py does a
    bare `import tomllib`, which is stdlib only from 3.11. CI never hits it —
    see the matrix note below.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@abbaseya abbaseya changed the title chore(deps): clear high-severity Dependabot alerts, pin Yarn 4 and enforce release-age gate chore(deps): drive python-sdk to zero known vulnerabilities in both ecosystems, pin Yarn 4, enforce release-age gate Aug 10, 2026
@usmanabbas7

Copy link
Copy Markdown
Collaborator

Review

Verdict: approve after one edit. One blocking defect (stale doc), four non-blocking follow-ups, one cosmetic. No defect found in the dependency changes themselves.

Verification method, and its limits

  • npm — parsed both lockfiles → 418 resolutions each (matches the body's "418"). Queried GitHub GHSA via GraphQL securityVulnerabilities(ecosystem: NPM) for the 377 unique package names across both, then evaluated each resolved version against every advisory's vulnerableVersionRange.
  • pip — same query with ecosystem: PIP over the 6 declared dev deps plus 22 plausible transitives.
  • Limit — this PR audited OSV; I audited GHSA. They overlap but are not identical, so this corroborates the result rather than reproducing the method. See finding 4. The semver range comparator was also hand-written, not node-semver.

Result: main = 6 packages / 15 advisories → this branch = 0 / 0. Matches the table in the description.

Claims checked — all confirmed

Claim Check
8 open Dependabot alerts, 4 high / 4 medium, no critical /dependabot/alerts?state=open#1, 2, 4, 7, 9, 12, 13, 14; severities, packages and patched-at all match the description's table cell-for-cell
Resolutions clear their advisories ip-address 10.4.0 ≥ 10.3.1 · brace-expansion 5.0.9 ≥ 2.1.4 · js-yaml 4.3.1 ≥ 4.3.1 · sigstore 4.1.1 ≥ 4.1.1 · @sigstore/core 3.2.1 ≥ 3.2.1 · tar 7.5.22 ≥ 7.5.21
No resolutions override needed main had 2 pacote copies (19.0.2, 20.0.1) + libnpmpublish 10.0.2; this branch has one each (21.5.1, 11.2.0) and a single sigstore 4.1.1 — no second vulnerable copy survives, so the decision to skip the override holds
pytest advisory shape GHSA-6w46-j5rx-g56g, range < 9.0.3, fixed 9.0.3, withdrawnAt: null
Marker split is unavoidable PyPI requires-python: 9.0.0–9.1.1 all >=3.10; 8.4.0 / 8.4.1 / 8.4.2 all >=3.9
Marker syntax actually works py3.9 sync installed pytest==8.4.2 + tomli + exceptiongroup; the 3.13 env ran pytest-9.1.1
yarn@4.18.0 pin + hash authentic isolated COREPACK_HOME → corepack fetched and hash-validated, printed 4.18.0. Byte-identical to the pin in php-sdk#60
Gate is live, not a dead key yarn config npmMinimalAgeGate4320, source .yarnrc.yml; same query without the file → 1440, the 4.18 default, exactly as the description states
No hardening opt-out written enableScripts=false, approvedGitRepositories=[], npmPreapprovedPackages=[] — all <default>
Node floor semantic-release@25.0.9 and @semantic-release/github@12.0.9 both engines.node: ^22.14.0 || >= 24.10.0; nodejs.org/dist newest LTS = v24.19.0
@semantic-release/exec correctly left at ^7 7.1.0 → engines.node >=20.8.1, peer semantic-release >=24.1.0
Nothing ships wheel target packages = ["src/convert_sdk"]; sdist exclude lists tests, scripts, package.json, release.config.mjs, .yarnrc.yml, yarn.lock
The matrix only ever ran 3.13 job 93439482535 (test (py3.9 / ubuntu-latest)): Using CPython 3.9.25Removed virtual environment at: .venvUsing CPython 3.13.12platform linux -- Python 3.13.12, pytest-9.1.1
test_packaging.py is 3.9-incompatible tests/test_packaging.py:8 — bare import tomllib, no tomli fallback, consumed at line 18
No .github/dependabot.yml .github/ contains only workflows/ci.yml and workflows/release.yml

Findings

1 — BLOCKING. TESTING.md:42-43 now contradicts pyproject.toml:176-177.

42  pytest is pinned to `>=8.4,<8.5` because pytest 9.x dropped Python 3.9 support,
43  which is the lower bound of the CI matrix.

Both clauses are false after this change: pytest is no longer pinned to that range, and 3.10–3.13 now resolve 9.x. pyproject.toml:174-175 tells the next maintainer to "collapse the two lines into pytest>=9.0.3" once requires-python moves to 3.10 — someone following TESTING.md instead would collapse them the other way and silently reintroduce CVE-2025-71176. Worth replacing 42-43 with the marker split, the CVE, and the 3.9 remainder, since the rationale is duplicated in two places and only one was updated.

2 — Non-blocking. The npm half of this PR has no CI coverage. .github/workflows/ci.yml (183 lines; jobs at :34 pr-title, :53 lint, :70 type-check, :92 test, :123 bounds-check, :163 build) has zero matches for node|yarn|corepack|npm. So yarn.lock, the 4.18.0 pin and the age gate are first exercised by release.yml on main — the failure mode is a blocked release rather than a red PR. The cold-cache --immutable install and dry-run in the description are the right compensating control, but they're manual and one-time. A release-tooling job (corepack enableyarn install --immutableyarn release:dry-run) turns that into a standing gate — and is what would have surfaced classic Yarn servicing releases without a human having to spot success Saved lockfile. in a job log.

3 — Non-blocking. Two Yarn pins across the house now. javascript-sdk/package.json:53yarn@4.10.3; this PR (package.json:16) and php-sdk#60 → yarn@4.18.0. The forcing reason here is sound (4.10.3 rejects the 4.18-written lockfile, YN0028), but nothing tracks converging javascript-sdk.

4 — Non-blocking, accuracy. "1 package / 2 advisories" for Python is an OSV count — OSV carries GHSA-6w46-j5rx-g56g and PYSEC-2026-1845 as separate records for the same CVE. GHSA returns exactly 1 advisory for pytest. One vulnerability, two records; the description is self-consistent, but the number won't reconcile for anyone comparing against Dependabot/GHSA.

5 — Non-blocking, forward risk. pyproject.toml:182 caps pytest-cov>=5,<6; latest is 7.1.0, and 5.0.0 declares only pytest>=4.6. pytest-cov 5.0.0 + pytest 9.1.1 is green here, so nothing is broken today, but the pair is roughly 18 months out of upstream co-testing and the pytest floor just moved two majors.

6 — Cosmetic. .yarnrc.yml:5-6 says the gate "Matches the gate in convertcom/javascript-sdk and convertcom/php-sdk". True for javascript-sdk (.yarnrc.yml:7 on main); php-sdk main still has a one-line .yarnrc.yml, so that half becomes true when #60 merges.


🤖 Review generated with Claude Code

TESTING.md still told the reader:

    pytest is pinned to `>=8.4,<8.5` because pytest 9.x dropped Python 3.9
    support, which is the lower bound of the CI matrix.

Both clauses are false after the marker split, and the second half is the
dangerous one: pyproject.toml says to collapse the two specifiers into
`pytest>=9.0.3` once requires-python moves to 3.10, while TESTING.md pointed
at `>=8.4,<8.5` — collapsing that way silently reintroduces CVE-2025-71176 on
every cell. The rationale was duplicated in two places and the earlier commit
updated only one.

TESTING.md now carries the specifiers, the CVE, why no single specifier works
across a 3.9-floored matrix, and an explicit "do not collapse to 8.4" with the
direction that IS correct. A repo-wide grep for the rationale
(`8.4,<8.5`, `pytest 9`, `drops Python 3.9`) finds only these two homes, so
there is no third copy left to drift.

Also softens the `.yarnrc.yml` comment (finding 6): it claimed the gate value
matches javascript-sdk AND php-sdk, but php-sdk's `main` still carries a
one-line `.yarnrc.yml` — its gate lands with php-sdk#60, which is still open.
Now credits javascript-sdk only, which is true today.

Both files are documentation/comment surfaces; no specifier, dependency or
resolution changes. Re-verified anyway: `yarn config npmMinimalAgeGate` still
reports 4320 sourced from `.yarnrc.yml`, and `yarn install --immutable` is
still clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@abbaseya

Copy link
Copy Markdown
Collaborator Author

Thanks — the GHSA re-derivation landing on the same main = 6/15 → branch = 0 is exactly the corroboration this needed, and finding 1 is a fair hit.

1 — blocking. Fixed in 1cfbd99. You're right that the second clause was the dangerous half: pyproject.toml says collapse up to pytest>=9.0.3, TESTING.md pointed down at >=8.4,<8.5, and following the wrong one silently reintroduces the CVE on every cell. TESTING.md now carries the specifiers, the CVE, why no single specifier works against a 3.9-floored matrix, and an explicit "do not collapse to 8.4" alongside the direction that is correct.

I also ran the sweep I should have run before pushing the pytest commit — 8.4,<8.5, pytest 9, drops Python 3.9 across *.md / *.toml / *.yml / *.py — and it finds only those two homes, so there is no third copy left to drift. That the rationale was duplicated and I updated one copy is the actual defect here; thanks for catching it.

4 — accuracy. Fixed in the description. You're right that the number doesn't reconcile: OSV carries GHSA-6w46-j5rx-g56g and PYSEC-2026-1845 as separate records for the same CVE, so "2 advisories" is an OSV record count where GHSA returns 1. The body now states that above the table and annotates the py3.9 row as "1 CVE, 2 OSV records".

6 — cosmetic. Fixed in the same commit. .yarnrc.yml and the PR body now credit javascript-sdk only, with php-sdk noted as landing when #60 merges.

2, 3, 5 — routed, not bundled. None of them changes a resolved version, so none belongs in a security fix, but all three are filed:

  • 2 is the strongest and I agree with your framing — the compensating control in the description is manual and one-time, and "a human spots success Saved lockfile. in a job log" is not a control at all. Filed with the job shape you suggested (corepack enable → assert Yarn is 4.x → yarn install --immutableyarn release:dry-run); every step already passes by hand including --immutable cold-cache, so it should be green on arrival. Say the word if you'd rather it ride in this PR than follow it.
  • 3 filed, including the question of whether the house pins one Yarn deliberately or accepts that the pin follows the lockfile writer — plus an audit for any other Berry-lockfile repo still carrying no packageManager field, which is the invisible version of this bug.
  • 5 filed as hygiene. Worth recording that pytest-cov 7.1.0 needs only requires-python >=3.9, so unlike pytest it can be a single specifier across the whole matrix — and that both coverage floors are release gates, so a bump needs them re-measured rather than just a green suite.

One note on your verification limits: the hand-written semver comparator is the part I'd trust least of the two methods, and it agreeing with node-semver-backed OSV on all 418 resolutions is a decent sign for both.

🤖 Reply generated with Claude Code

@usmanabbas7

Copy link
Copy Markdown
Collaborator

Re-review of 1cfbd99

1, 4, 6 — confirmed fixed. What I checked:

  • TESTING.md:42-59 now carries both specifiers, GHSA-6w46-j5rx-g56g / CVE-2025-71176, the 3.9-floor reasoning, and at :56-59 the explicit "Do not collapse these two lines into pytest>=8.4,<8.5" with the correct direction beside it. That closes the trap. The dangerous part was never the stale range — it was the arrow pointing at it.
  • .yarnrc.yml:5 → "Same value as the gate in convertcom/javascript-sdk"; body line 73 adds "(php-sdk gets it when build(deps): bump the npm_and_yarn group across 1 directory with 4 updates #60 merges)".
  • Body line 11 carries the OSV-record caveat above the table; line 94 annotates the py3.9 row 1 CVE, 2 OSV records.
  • 1cfbd99 is docs-only — .yarnrc.yml + TESTING.md, 23 insertions / 7 deletions, no specifier, dependency or resolution change. Re-ran the gate check anyway: npmMinimalAgeGate still 4320, source .yarnrc.yml.

Agreed that the duplicated rationale is the actual defect. A second copy of a rationale is a second thing that can rot, and the grep you ran afterwards is the one that belonged in the commit that split the specifier.

2 — let it follow, don't bundle it. A new CI job inside a zero-known-vulnerabilities PR muddies the revert story: if the job turns out flaky, you're either reverting the security fix with it or doing an untidy partial revert. Your assert Yarn is 4.x step is a good addition to the shape I suggested — that's the assertion that catches the original bug head-on rather than as a side effect of --immutable failing.

Ticket links, please. I can't locate the three follow-ups, or the CPython matrix task behind the description's "Filed separately". convertcom/python-sdk has zero GitHub issues (issues are enabled — all 60 /issues entries are pull requests), and nothing matching appears in the beads tracker either; newest item there is 2026-06-17. If they live in Asana or elsewhere, put the links in the description so they survive the merge. The matrix one is the one I'd least like to lose: tests/test_packaging.py:8 is a bare import tomllib, so whoever repairs the matrix inherits a red suite and needs to know that's expected rather than a regression they caused.

On 5 — keep the pytest-cov 7.1.0 requires-python >=3.9 detail in the ticket. It means the cap can collapse to a single specifier where pytest can't, which is the non-obvious asymmetry between the two. Agreed the coverage floors need re-measuring rather than a green suite, since both are release gates.

On the comparator — fair, and two independent implementations agreeing across 418 resolutions is stronger than either alone. One caveat on how independent they really were: both derived the package set from the same yarn.lock, and mine parsed it by hand, so a mis-parse would have made both methods miss the same package silently. My arriving at 418 resolutions independently is the only cross-check on that input, not on the range evaluation.


🤖 Review generated with Claude Code

@abbaseya
abbaseya merged commit 42c7150 into main Aug 12, 2026
27 checks passed
@abbaseya
abbaseya deleted the chore/dependabot-high-severity-release-tooling branch August 12, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants