Skip to content

feat: multi-ecosystem support (12 GHSA ecosystems) + src/ refactor - #6

Merged
maxgfr merged 21 commits into
mainfrom
worktree-multi-ecosystem
Jul 21, 2026
Merged

feat: multi-ecosystem support (12 GHSA ecosystems) + src/ refactor#6
maxgfr merged 21 commits into
mainfrom
worktree-multi-ecosystem

Conversation

@maxgfr

@maxgfr maxgfr commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Résumé

Deux chantiers en un, sur 19 commits :

  1. Refactor multi-fichiers. script.sh (4 761 lignes) est éclaté en modules src/ concaténés par build.sh en un script.sh unique, qui reste l'artefact distribué (curl|bash, Homebrew, Docker, workflow réutilisable). Le premier commit est identique octet pour octet à l'original (modulo un newline final) — vérifiable par cmp. Un job CI verify-build refuse toute dérive entre src/ et script.sh.

  2. Support des 12 écosystèmes GHSA : npm (existant), PyPI, Go, Maven, crates.io, RubyGems, Packagist, NuGet, Pub, Hex, Swift, GitHub Actions.

Ce qui change

  • Lookups namespacés par écosystème (eco:nom), avec un wildcard * pour les feeds custom sans écosystème → fin des collisions inter-langages (commons-io npm vs maven), comportement des feeds custom préservé.
  • Registre unique ECOSYSTEM_REGISTRY d'où dérivent découverte, dispatch, --lockfile-types et recherche GitHub. Flux detect-then-load : seuls les feeds des écosystèmes détectés sont chargés.
  • Comparateurs de versions fidèles par écosystème : PEP 440 (cross-validé contre packaging, 0 divergence sur ~2 000 paires), Maven ComparableVersion (port du code maven-3.9.x), RubyGems (vs Gem::Version), Go semver, NuGet 4-composants. Harnais de tests table-driven (156 cas).
  • Parseurs de lockfiles réels + une fixture réaliste par écosystème (lockfile au vrai format, 1 paquet vulnérable + 1 sain) branchée dans la CI comme les fixtures npm, plus une fixture polyglotte (npm + PyPI + Go en une passe).
  • Feeds séparés par écosystème : data/ghsa.purl/osv.purl restent npm (noms inchangés) ; nouveaux data/{ghsa,osv}-<eco>.purl. Jamais de fichier combiné — la séparation borne les téléchargements et évite les gros bundles. Régénérés par le cron 12 h de update-feeds.yml.
  • Docker configurable : build-arg FEED_ECOSYSTEMS (défaut npm → image identique à aujourd'hui ; all ou liste). Tags -all publiés en plus. Dockerfile.lite inchangé.
  • Remédiation par écosystème dans les issues GitHub (npm audit / pip / go get / cargo update / bundle / composer / gradle / dotnet / dart / mix / swift…).

Correctif inclus

Bug latent scoped-npm : le parseur PURL coupait au premier @, ignorant silencieusement toutes les lignes pkg:npm/@scope/name@…1 948 / 9 060 advisories GHSA (21,5 %) perdues. Corrigé (coupe au dernier @) + test de régression. Les CI utilisateurs peuvent voir apparaître de nouvelles vulnérabilités : c'est voulu.

Rétrocompatibilité

Projets npm-only : comportement et sortie console identiques (hors advisories scoped récupérées). CLI, .package-checker.config.json, URLs raw data/ghsa.purl/osv.purl, colonnes CSV existantes (colonne ecosystem ajoutée en fin), reusable-check.yml : inchangés.

Tests

  • test-all-bugs.sh 35/35, test-version-compare.sh 156/156, metadata-collision 11/11, test-feed-generation.sh 6/6
  • 23 fixtures d'écosystèmes (exit 1 sur le vulnérable, safe non signalé) + gel de la sortie npm
  • Chaque écosystème smoke-testé contre son vrai feed GHSA/OSV
  • Job CI verify-build (anti-dérive src/ ↔ script.sh)

Revue finale : 0 bloquant, prête à merger.

maxgfr and others added 21 commits July 21, 2026 11:33
Generalize the feed fetchers to cover every supported purl type:
- FEED_ECOSYSTEM_MAP maps purl type <-> OSV/GHSA ecosystem string <-> OSV
  zip dir (empirically verified against the OSV bucket + real advisory data).
- Single shared jq program with per-type name canonicalization matching
  canon_purl_name (pypi PEP503, maven colon->slash, swift URL strip,
  composer/nuget/githubactions lowercase, others identity).
- fetch_osv loops the OSV zips; fetch_ghsa does ONE sparse clone + ONE
  parallel pass and splits by pkg:<type>/ prefix (npm keeps legacy filenames).
- Deterministic output: LC_ALL=C sort, and each parallel worker writes to its
  own temp file to avoid shared-pipe line tearing.

update-feeds.yml counts every data/*.purl and uses a 60-minute timeout.

Docker: FEED_ECOSYSTEMS build-arg (default npm = unchanged image) prunes the
baked-in feeds; add an -all image variant to the docker workflows.

Includes generated data/{ghsa,osv}-<eco>.purl for all ecosystems; npm feeds
stay byte-compatible.
Add the first per-ecosystem extension on top of the registry seam from
Tasks 1-4: a Cargo.lock row in ECOSYSTEM_REGISTRY and a shared
TOML "[[package]]" lockfile parser (analyze_toml_pkg_lock) that will be
reused by poetry.lock/uv.lock/pdm.lock in later tasks. Adds a real
cargo-generated rust-project fixture (time 0.1.45 vulnerable, serde and
libc safe) plus the shared test-vulnerabilities-multi.purl feed, and
wires both the multi-eco feed test and a real-feed smoke test into CI.
Add the RubyGems version comparator (compare_versions_gem) verified
segment-by-segment against real Gem::Version ordering, wire it into
compare_versions_eco, and add a Gemfile.lock parser (analyze_gemfile_lock)
that captures only GEM-section specs, skips GIT/PATH-sourced specs and
6-space dependency constraints, and strips native-gem platform suffixes
(nokogiri (1.16.5-arm64-darwin) -> 1.16.5). Adds a real bundle-lock-derived
ruby-project fixture, 24 comparator harness assertions, the shared
multi-ecosystem test feed lines, and CI wiring mirroring the rust/go/python
steps.
Add a composer.lock parser (analyze_composer_lock) that walks JSON
brace/bracket depth precisely (quoted-string contents skipped) so
"name"/"version" are only captured as DIRECT fields of a package object,
one level below the "packages"/"packages-dev" arrays - any nested
subtable (source, dist, require, autoload, authors, support, funding,
...) sits deeper and is excluded, so a nested author's "name" can never
clobber the package name. Package names are lowercased to match the
composer feed canon (vendor/package); versions have a leading "v"
stripped (composer routes through the plain semver comparator via the
existing "*" branch of compare_versions_eco - no new comparator needed)
and "dev-*" branch aliases are skipped silently.

Adds a hand-crafted but real-shaped php-project fixture (composer
wasn't available to generate one) covering both packages and
packages-dev, one vulnerable package (guzzlehttp/guzzle 7.4.0, matched
against the real GHSA-cwmx-hcrq-mhc3 range), two safe packages
(monolog/monolog, and psr/log tagged "v3.0.0" to exercise the v-strip),
and a packages-dev entry (phpunit/phpunit) proving the dev section is
parsed. Wires the shared multi-ecosystem test feed and CI steps
mirroring the rust/go/ruby steps.
Add a faithful port of Apache Maven's ComparableVersion ordering
(compare_versions_maven) wired into the comparator dispatch, plus
gradle.lockfile and pom.xml parsers registered under the maven purl type.

- Comparator reproduces the ComparableVersion Item tree (int/string/nested
  list), qualifier ranking (alpha<beta<milestone<rc<snapshot<''<sp<unknown),
  single-letter aliases, trailing-null trimming and the list-padding compare.
- gradle.lockfile: group:artifact:version=configs lines (skips comments and
  the empty= sentinel). pom.xml: <dependency> blocks, literal versions only
  (property/parent-inherited versions skipped), exclusions guarded.
- Canonical name is groupId:artifactId, matching the feed's maven canon key.
- Combined fixture (gradle.lockfile + pom.xml in one dir), shared test feed
  rows, and CI wiring (inline assertion + guarded real-feed smoke).
Add a SemVer-2 + 4th-part-Revision wrapper (compare_versions_nuget) wired
into the comparator dispatch, plus a packages.lock.json parser registered
under the nuget purl type.

- Comparator wraps the frozen npm compare_versions: strips build metadata,
  compares the Major.Minor.Patch.Revision quad numerically (missing Revision
  defaults to 0), then falls back to the go comparator's semver-2 dot-split
  pre-release identifier loop, lower-cased first since NuGet compares
  pre-release labels case-insensitively (unlike strict SemVer 2.0.0).
- packages.lock.json: JSON depth-tracking state machine (modeled on
  composer.lock's parser) walking dependencies -> framework -> package ->
  fields; Direct/Transitive entries are captured, Project entries are
  skipped by construction (no resolved field), names are lower-cased, and
  identical name/version pairs collapse across multi-targeted frameworks.
  csproj (tier 2) is not parsed.
- Hand-written fixture (two target frameworks, proving cross-framework
  dedupe), shared test feed rows, and CI wiring (inline assertion + guarded
  real-feed smoke), verified against the real GHSA nuget feed.
Add a pubspec.lock parser (analyze_pubspec_lock) registered under the pub
purl type; no new version comparator needed (pub falls through to the
existing npm-semver `*` dispatch branch).

- pubspec.lock is YAML: a line-anchored state machine tracks the current
  2-space-indented package-name key and its 4-space "source:"/"version:"
  fields, keyed on exact indent-width regex anchors (the same technique
  Gemfile.lock's parser uses to tell a 4-space spec line from a 6-space
  dependency-constraint line). Only source: hosted packages resolve to a
  checkable name+version pair; source: git (VCS-pinned) and source: sdk
  (the flutter/dart pseudo-packages) are skipped by construction, since
  emission only fires once "hosted" was actually seen for that block.
  Stops at the top-level sdks: key.
- Hand-written fixture (dart unavailable in this environment): dio@4.0.6
  vulnerable (picked from the real GHSA pub feed, <5.0.0 range), http/
  async/collection safe, one git-sourced entry and one sdk-sourced
  (flutter) entry both proving the skip logic, plus shared test feed rows
  and CI wiring verified against the real GHSA pub feed.
@maxgfr
maxgfr merged commit 69e5ef7 into main Jul 21, 2026
12 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.11.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants