diff --git a/README.md b/README.md index 1e2a47f..9e53a06 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,54 @@ does not exist or it’s more than an hour old. For example: composer audit --update ``` +Configuration +------------- + +Composer Audit can be configured using the [`extra`][composer.json extra] property +in your `composer.json` file, all configuration should be supplied under the +`composer-audit` key. + +```json +{ + ... + "extra": { + ... + "composer-audit": { + "option1": "super" + }, + ... + }, + ... +} +``` + +### Ignoring an advisory + +Currently only filtering advisories by CVE is possible, further options are planned. + +#### Ignoring an advisory by CVE + +You are able to ignore warnings about an advisory by filtering based on its CVE +reference, this is useful if you decide the risk is acceptable or not applicable +and you cannot otherwise upgrade the package to resolve the problem. + +```json +{ + ... + "extra": { + ... + "composer-audit": { + "ignore": [ + {"type": "cve", "value": "CVE-2000-1234567"}, + {"type": "cve", "value": "CVE-2000-7654321"} + ] + }, + ... + }, + ... +} +``` + Example ------- @@ -88,5 +136,6 @@ composer://symfony/http-foundation (2.0.4) Hyperlinks will be rendered to the appropriate CVE and advisory where available. +[composer.json extra]: https://getcomposer.org/doc/04-schema.md#extra [FriendsOfPHP/security-advisories]: https://github.com/FriendsOfPHP/security-advisories [security.symfony.com]: https://security.symfony.com/ diff --git a/src/AuditCommand.php b/src/AuditCommand.php index 7fd172b..8f0449a 100644 --- a/src/AuditCommand.php +++ b/src/AuditCommand.php @@ -65,6 +65,24 @@ protected function execute(InputInterface $input, OutputInterface $output) // NULL if option is unknown. $lockOption = $this->getComposer()->getConfig()->get('lock'); + // Pull config from the extra array in composer.json. + $config = $this->getComposer()->getPackage()->getExtra()['composer-audit'] ?? []; + $ignoreCves = []; + + foreach ($config['ignore'] ?? [] as $rule) { + $type = (string) $rule['type'] ?? ''; + $value = (string) $rule['value'] ?? ''; + + if ($type === 'cve' && $value !== '') { + $ignoreCves[] = $value; + } else { + $output->writeln(sprintf( + 'Ignoring invalid ignore rule: `%s`', + json_encode($rule) + ), OutputInterface::VERBOSITY_VERBOSE); + } + } + if ($lockOption === null || $lockOption === true) { if (!$this->getComposer()->getLocker()->isLocked()) { $output->writeln('Lock file not found.'); @@ -123,6 +141,7 @@ protected function execute(InputInterface $input, OutputInterface $output) return \count($packageAdvisories); }, $advisories)); $packagesAffected = \count($advisories); + $ignoredAdvisories = 0; // @todo Pluralization? $output->writeln(sprintf( @@ -133,12 +152,13 @@ protected function execute(InputInterface $input, OutputInterface $output) $packagesAffected )); - $output->writeln(''); + $exitCode = 0; ksort($advisories, \SORT_NATURAL | \SORT_ASC); + $firstAdvisory = true; foreach ($advisories as $reference => $packageAdvisories) { - $output->writeln(sprintf('composer://%s (%s)', $reference, $packages[$reference])); + $firstAdvisoryForPackage = true; foreach ($packageAdvisories as $advisory) { $title = $advisory['title']; @@ -166,6 +186,22 @@ protected function execute(InputInterface $input, OutputInterface $output) $cve = $cveLink = null; } + if (\in_array($cve, $ignoreCves, true)) { + ++$ignoredAdvisories; + + continue; + } + + if ($firstAdvisory) { + $output->writeln(''); + $firstAdvisory = false; + } + + if ($firstAdvisoryForPackage) { + $output->writeln(sprintf('composer://%s (%s)', $reference, $packages[$reference])); + $firstAdvisoryForPackage = false; + } + if ($output->isDecorated()) { $output->writeln(sprintf( $cve !== null ? '* %s: %s' : '* %2$s', @@ -185,12 +221,29 @@ protected function execute(InputInterface $input, OutputInterface $output) } } } + + $exitCode = 1; } - $output->writeln(''); + // Only need a spacer if any adisories were written. + if (!$firstAdvisory) { + $output->writeln(''); + } + } + + if ($ignoredAdvisories) { + $output->writeln(sprintf( + '%u advisories were ignored.', + $ignoredAdvisories + )); + + // Change exit code to indicate some things were ignored. + if ($exitCode === 1) { + $exitCode = 2; + } } - return 1; + return $exitCode; } if ($output->isVerbose()) { diff --git a/tests/integration/ignore--broken-show-warnings.test b/tests/integration/ignore--broken-show-warnings.test new file mode 100644 index 0000000..c6a065c --- /dev/null +++ b/tests/integration/ignore--broken-show-warnings.test @@ -0,0 +1,57 @@ +--TEST-- +Test invalid ignore rules are handled. +--CONDITION-- +true +--COMPOSER-- +{ + "require-dev": { + "symfony/http-foundation": "=2.0.4" + }, + "extra": { + "composer-audit": { + "ignore": [ + {"type": "package", "value": "foo/bar"}, + {"type": "cve", "value": ""}, + {"type": "", "value": "test"} + ] + } + } +} +--ARGS-- +-vv +--EXPECT-EXIT-- +1 +--EXPECT-OUTPUT-- +Ignoring invalid ignore rule: `{"type":"package","value":"foo\/bar"}` +Ignoring invalid ignore rule: `{"type":"cve","value":""}` +Ignoring invalid ignore rule: `{"type":"","value":"test"}` +Found 9 advisories affecting 1 package(s). + +composer://symfony/http-foundation (2.0.4) +* Request::getClientIp() when the trust proxy mode is enabled + - +* CVE-2012-6431 Routes behind a firewall are accessible even when not logged in + - + - +* CVE-2013-4752 Request::getHost() poisoning + - + - +* CVE-2014-5244 Denial of service with a malicious HTTP Host header + - + - +* CVE-2014-6061 Security issue when parsing the Authorization header + - + - +* CVE-2015-2309 Unsafe methods in the Request class + - + - +* CVE-2018-11386 Denial of service when using PDOSessionHandler + - + - +* CVE-2018-14773 Remove support for legacy and risky HTTP headers + - + - +* CVE-2019-18888 Prevent argument injection in a MimeTypeGuesser + - + - + diff --git a/tests/integration/ignore--broken.test b/tests/integration/ignore--broken.test new file mode 100644 index 0000000..71e81b2 --- /dev/null +++ b/tests/integration/ignore--broken.test @@ -0,0 +1,53 @@ +--TEST-- +Test invalid ignore rules are handled. +--CONDITION-- +true +--COMPOSER-- +{ + "require-dev": { + "symfony/http-foundation": "=2.0.4" + }, + "extra": { + "composer-audit": { + "ignore": [ + {"type": "package", "value": "foo/bar"}, + {"type": "cve", "value": ""}, + {"type": "", "value": "test"} + ] + } + } +} +--ARGS-- +--EXPECT-EXIT-- +1 +--EXPECT-OUTPUT-- +Found 9 advisories affecting 1 package(s). + +composer://symfony/http-foundation (2.0.4) +* Request::getClientIp() when the trust proxy mode is enabled + - +* CVE-2012-6431 Routes behind a firewall are accessible even when not logged in + - + - +* CVE-2013-4752 Request::getHost() poisoning + - + - +* CVE-2014-5244 Denial of service with a malicious HTTP Host header + - + - +* CVE-2014-6061 Security issue when parsing the Authorization header + - + - +* CVE-2015-2309 Unsafe methods in the Request class + - + - +* CVE-2018-11386 Denial of service when using PDOSessionHandler + - + - +* CVE-2018-14773 Remove support for legacy and risky HTTP headers + - + - +* CVE-2019-18888 Prevent argument injection in a MimeTypeGuesser + - + - + diff --git a/tests/integration/ignore--cve-with-error.test b/tests/integration/ignore--cve-with-error.test new file mode 100644 index 0000000..f0e7eb4 --- /dev/null +++ b/tests/integration/ignore--cve-with-error.test @@ -0,0 +1,23 @@ +--TEST-- +Test that an ignored CVE does not result in an error. +--CONDITION-- +true +--COMPOSER-- +{ + "require-dev": { + "symfony/security-core": "=5.2.7" + }, + "extra": { + "composer-audit": { + "ignore": [ + {"type": "cve", "value": "CVE-2021-21424"} + ] + } + } +} +--ARGS-- +--EXPECT-EXIT-- +0 +--EXPECT-OUTPUT-- +Found 1 advisories affecting 1 package(s). +1 advisories were ignored. diff --git a/tests/integration/ignore--cve.test b/tests/integration/ignore--cve.test new file mode 100644 index 0000000..32f2d45 --- /dev/null +++ b/tests/integration/ignore--cve.test @@ -0,0 +1,45 @@ +--TEST-- +Test that an ignored CVE does not result in an error. +--CONDITION-- +true +--COMPOSER-- +{ + "require-dev": { + "symfony/http-foundation": "=2.0.4" + }, + "extra": { + "composer-audit": { + "ignore": [ + {"type": "cve", "value": "CVE-2012-6431"}, + {"type": "cve", "value": "CVE-2013-4752"}, + {"type": "cve", "value": "CVE-2014-5244"} + ] + } + } +} +--ARGS-- +--EXPECT-EXIT-- +2 +--EXPECT-OUTPUT-- +Found 9 advisories affecting 1 package(s). + +composer://symfony/http-foundation (2.0.4) +* Request::getClientIp() when the trust proxy mode is enabled + - +* CVE-2014-6061 Security issue when parsing the Authorization header + - + - +* CVE-2015-2309 Unsafe methods in the Request class + - + - +* CVE-2018-11386 Denial of service when using PDOSessionHandler + - + - +* CVE-2018-14773 Remove support for legacy and risky HTTP headers + - + - +* CVE-2019-18888 Prevent argument injection in a MimeTypeGuesser + - + - + +3 advisories were ignored.