chore(deps): drive python-sdk to zero known vulnerabilities in both ecosystems, pin Yarn 4, enforce release-age gate - #59
Conversation
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>
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>
c1f17ec to
ea5f49e
Compare
… 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>
ReviewVerdict: 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
Result: Claims checked — all confirmed
Findings1 — BLOCKING. Both clauses are false after this change: pytest is no longer pinned to that range, and 3.10–3.13 now resolve 9.x. 2 — Non-blocking. The npm half of this PR has no CI coverage. 3 — Non-blocking. Two Yarn pins across the house now. 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. 6 — Cosmetic. 🤖 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>
|
Thanks — the GHSA re-derivation landing on the same 1 — blocking. Fixed in I also ran the sweep I should have run before pushing the pytest commit — 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. 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:
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 🤖 Reply generated with Claude Code |
Re-review of
|
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.
yarn.lock— 418 resolutionsscripts/js_reference(murmurhash)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.
httpxis still the only runtime dependency; the wheel target ispackages = ["src/convert_sdk"]and the sdist excludestests,scripts,package.json,release.config.mjs,.yarnrc.ymlandyarn.lock.Four commits — the fourth applies the blocking finding from review.
1.
chore(deps)— clear the npm alertsAll 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
certificateOIDsverification constraints are silently dropped and never enforced — needssigstore >= 4.1.1, and that only arrives through a major bump of the chain that pulls it in:@semantic-release/githubmoves to^12because that is what semantic-release 25 depends on. Leaving it at^11would hoist the older copy to the project root, where semantic-release resolves plugins from — so the root copy would shadow the one core installed.ip-addressbrace-expansionjs-yamlsigstoreip-addressip-addresstar@sigstore/coreThe OSV sweep also clears 7 advisories Dependabot had auto-dismissed and never showed as open — 4 more on
tar(including a critical), 2 more onbrace-expansion, 1 more onjs-yaml.maincarries 15 advisories in total; this branch carries none.Node floor. semantic-release 25 and
@semantic-release/github12 require^22.14.0 || >= 24.10.0.release.ymlinstallslts/*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 ageSame fix as convertcom/php-sdk#60, applied here.
The intent was to add the
npmMinimalAgeGatethatjavascript-sdkandphp-sdkcarry. Checking whether it would actually fire turned up why it wouldn't.release.ymlalready runscorepack enable— that is not enough on its own. With nopackageManagerfield inpackage.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):Three consequences, all live until this PR:
yarn.lockwas never used. Classic Yarn cannot read a__metadata: version: 10lockfile, so it re-resolved every release from thepackage.jsonsemver ranges against whatever the registry served at that moment.success Saved lockfile.is Yarn 1 writing its own, which is then discarded.--immutablewas a silent no-op. Classic spells it--frozen-lockfile, does not error on the unknown flag, and saved a lockfile regardless..yarnrc.ymlwas never read.nodeLinker: node-modulesdid nothing, and an age gate added there would have done nothing either.So the
packageManagerpin is what makes the gate exist, not a nicety alongside it. Written withcorepack 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 asYN0028: 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 valuejavascript-sdkcarries (php-sdk gets it when #60 merges). NonpmPreapprovedPackages— unlikejavascript-sdk, this project has zero@convertcom/*packages inpackage.jsonoryarn.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-71176Dependabot 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: 0→fixed: 9.0.3— every pytest release before 9.0.3 is affected. pytest 9 requires Python>=3.10(verified against the PyPIrequires-pythonof 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 existingpytest>=8.4,<8.5pin was right about the constraint but left all five cells on an affected release.Split on an environment marker rather than narrowing the matrix:
lowest-direct)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-pythonfloor 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 intopytest>=9.0.3if 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.
testmatrix cell —uv sync --group dev --upgradethenpytest -p no:cacheprovider --cov=convert_sdk --cov-report=term-missing --cov-fail-under=85: 1134 passed, total coverage 96.76%, and theevaluation/floor holds at 97% against its 95% gate.lint+type-check—ruff 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-directplus theci/lower-bounds-overrides.txthttpx pin,--ignore=tests/parity: 920 passed.node_modulesremoved and an emptyYARN_GLOBAL_FOLDER, so all 418 packages download fresh with the age gate active:yarn install --immutablepasses. The real CI shape, not a warm-cache stand-in.yarn release:dry-run— loads all four configured plugins, passesverifyConditionsfor bothexecandgithub(GitHub auth plus push permission), and analyzes commits to "no release". Correct:choreis a non-releasing type inrelease.config.mjs, so this PR will not cut a version..yarnrc.ymlwithnpmMinimalAgeGate: 0,enableScripts: trueandapprovedGitRepositories: ["**"]when it bumps a legacy lockfile. It did not fire here (noYN0087, the lockfile was already current), andyarn config --jsonconfirms it after the change: gate = 4320 sourced from.yarnrc.yml,enableScripts= false,approvedGitRepositories=[].semantic-release@25.0.9at 3.5 days.__metadataversion 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 andbounds-check (lower)douv sync --group dev --python <ver>and then a bareuv run pytest. Because.python-versionpins3.13, that bareuv rundiscards the venv the sync step just built and recreates it at 3.13. Reproduced locally:and confirmed in the job log of the cell named
test (py3.9 / ubuntu-latest):platform linux -- Python 3.13.12. That is whytests/test_packaging.py's bareimport 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.pyand 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
resolutionsoverride forcingsigstore@4underlibnpmpublish@10/pacote@19. Those were written against the sigstore 3 API; forcing a major across that boundary would be a guess, not a fix.conventional-changelog-conventionalcommitsoff^8. semantic-release 25's commit-analyzer and release-notes-generator still resolveconventional-changelog-writer@^8andconventional-commits-parser@^6, the same pair as before, so^8stays correct and there is no advisory against it..github/dependabot.yml. This repo has alerts but no update config, which is why these sat open. Worth adding as its own change.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.docs: align TESTING.md with the pytest marker split.TESTING.mdstill said pytest was pinned to>=8.4,<8.5for 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 confirmsTESTING.mdandpyproject.tomlwere the only two homes for that rationale, so no third copy remains to drift..yarnrc.ymlno 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.release-toolingCI job soyarn.lock, the 4.18.0 pin and the age gate are gated at PR time instead of first exercised byrelease.ymlonmain(finding 2 — the strongest of the three, and exactly what would have caught classic Yarn servicing releases without a human reading job logs); convergingjavascript-sdkoffyarn@4.10.3(3); and thepytest-cov>=5,<6cap 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