mteu / advisory-matcher
PHP library for matching installed packages against security advisories from Packagist, OSV and NVD
Requires
- php: ~8.3.0 || ~8.4.0 || ~8.5.0
- ext-ctype: *
- ext-mbstring: *
- composer/semver: ^3.4
- psr/clock: ^1.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.1
- psr/http-message: ^2.0
- psr/log: ^2.0 || ^3.0
- psr/simple-cache: ^2.0 || ^3.0
Requires (Dev)
- armin/editorconfig-cli: ^2.1
- ergebnis/composer-normalize: ^2.28
- friendsofphp/php-cs-fixer: ^3.8
- nyholm/psr7: ^1.8
- phpunit/phpcov: ^10.0 || ^11.0
- phpunit/phpunit: ^11.5 || ^12.1
- shipmonk/composer-dependency-analyser: ^1.8.4
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Advisory Matcher
Framework-agnostic PHP library that matches installed packages against security advisories from Packagist, OSV, and NVD to answer on whether a package is affected by a security advisory.
You hand it packages. It gives back advisories, plus a record of which packages it actually checked and which sources failed.
Warning
This project is not yet intended for production use. The API may change without notice.
🚀 Features
⚡️ Installation
composer require mteu/advisory-matcher
You also need a PSR-18 HTTP client and PSR-17 factories. (A PSR-16 cache and a PSR-3 logger are optional.)
See framework setup for TYPO3, Symfony, and Drupal examples.
💡 Usage
use mteu\AdvisoryMatcher\Advisory\AdvisoryMatcher; use mteu\AdvisoryMatcher\Package\Ecosystem; use mteu\AdvisoryMatcher\Source\CompositeAdvisorySource; use mteu\AdvisoryMatcher\Source\Cache\CachingAdvisorySource; use mteu\AdvisoryMatcher\Source\PackageRef; use mteu\AdvisoryMatcher\Source\Packagist\PackagistAdvisorySource; use mteu\AdvisoryMatcher\Source\Osv\OsvAdvisorySource; // $httpClient: Psr\Http\Client\ClientInterface (set timeouts on it) // $factory: An object implementing the PSR-17 request and stream factories // $cache: Psr\SimpleCache\CacheInterface $source = new CompositeAdvisorySource([ new CachingAdvisorySource(new PackagistAdvisorySource($httpClient, $factory, $factory), $cache), new CachingAdvisorySource(new OsvAdvisorySource($httpClient, $factory, $factory, recordCache: $cache), $cache), ]); $matcher = new AdvisoryMatcher(); $packages = [ new PackageRef(Ecosystem::Composer, 'symfony/http-kernel', '5.4.19'), new PackageRef(Ecosystem::Npm, 'lodash', '4.17.20'), ]; $batch = $source->advisoriesFor($packages); foreach ($packages as $package) { if (!$batch->wasAnsweredFor($package)) { printf("%s: not checked\n", $package->name); // unknown continue; } $matches = $matcher->matchAll($batch->advisoriesFor($package), $package->version); foreach ($matches->affected as $advisory) { $fix = $matcher->fixedVersionFor($advisory, $package->version); printf("%s: %s %s, fixed in %s\n", $package->name, $advisory->severity->value, $advisory->cve, $fix ?: 'no release yet'); } foreach ($matches->undetermined as $advisory) { printf("%s: %s, could not tell\n", $package->name, $advisory->advisoryId); } }
To check the PHP runtime, add NvdAdvisorySource($httpClient, $factory, $nvdApiKey)
and a PackageRef(Ecosystem::Php, 'php', '8.3.6'). The API key is optional but
raises the rate limit.
📝 Things to know
- Check each package with
wasAnsweredFor().isComplete()only tells you that no source reported a failure. A package that a feed has never heard of goes unanswered without any failure, soisComplete()can be true while that package was never checked. - Matching returns affected, not affected or undetermined. Undetermined means the version couldn't be parsed or no usable range existed. It does not mean clean.
- Failures are yours to handle.
$batch->failureslists them, each with aFailureReason. The library doesn't retry. CallisTransient()to see whether asking again makes sense. - Advisory text comes from third parties. Escape every field before you show it. I mean it.
- Store findings by fingerprint, not by
advisoryId. Get it with$advisory->getFingerprint()and look it up withAdvisoryFingerprint::identitiesOf($advisory). - The library uses your HTTP client, so timeouts and proxy settings are yours to set. Each source also accepts
userAgent:andendpoint:.
📙 Documentation
See the public API reference for the supported types.
Everything else is @internal. 🤷♂️
🤝 Contributing
Contributions are very welcome! Please have a look at the Contribution Guide. It lays out the workflow of submitting new features or bugfixes.
🔒 Security
Please refer to the Security Policy if you discover a security vulnerability in this library.
⭐ License
This library is licensed under the GPL-2.0-or-later license.
💬 Support
For issues and feature requests, please use the GitHub issue tracker.