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.