Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add audit command to check for security issues #10798

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bb31ef4
Allow chaining methods after calling setTemporaryConstraints
GuySartorelli May 31, 2022
ad43746
Allow formatting console output as a table.
GuySartorelli May 31, 2022
ddc4580
Add audit command to check for security issues
GuySartorelli May 30, 2022
a2a7176
Add audit options to `RemoveCommand`.
GuySartorelli Jun 8, 2022
6be9e63
Add formats to audit format options for autocomplete.
GuySartorelli Jun 8, 2022
ac15082
Don't fail installer commands for auditor transport errors
GuySartorelli Jun 8, 2022
3465f33
Remove unnecessary colour from audit output
GuySartorelli Jun 8, 2022
f624169
Add new summary audit format to avoid spamming non-audit commands.
GuySartorelli Jun 8, 2022
f60001b
Update reference to packagist.org
GuySartorelli Jun 8, 2022
f27d2c9
Add missing return type
GuySartorelli Jun 8, 2022
c6743be
Use Loop to get HttpDownloader
GuySartorelli Jun 8, 2022
d944147
Be more explicit about types that should be const values
GuySartorelli Jun 8, 2022
c4d7f81
Correctly check for advisories (not packages) before filtering
GuySartorelli Jun 8, 2022
c7534f7
Allow more time for slow connections to fetch advisories
GuySartorelli Jun 8, 2022
f2a3627
Add `--locked` option to audit command.
GuySartorelli Jun 8, 2022
473a4d3
Fix error where a non-existent property is used.
GuySartorelli Jun 8, 2022
c3b1d54
Use canonical packages in installer audit.
GuySartorelli Jun 8, 2022
000c7f6
Remove phpstan errors that no longer apply from the baseline
GuySartorelli Jun 8, 2022
e4f13d0
Avoid passing null to parse_str() (deprecated in php8.1)
GuySartorelli Jun 8, 2022
b722fd1
Allow any PackageInterface to be used for auditing.
GuySartorelli Jun 8, 2022
cd97f8f
Remove unused `use` directives.
GuySartorelli Jun 8, 2022
bb87e3f
use `$format` as an argument in `audit()` instead of a class property.
GuySartorelli Jun 8, 2022
c75468d
Fix typo (remove double fullstop)
GuySartorelli Jun 12, 2022
20adbca
Pass in Composer object instead of creating a new one
GuySartorelli Jun 12, 2022
59c49bf
Don't forward values that are already there by default.
GuySartorelli Jun 12, 2022
52a3a07
Correctly set audit format as required and pass autocomplete values
GuySartorelli Jun 12, 2022
04a95e3
Adjust wording to be more consistent and grammatically correct.
GuySartorelli Jun 12, 2022
7cc5039
Mark Auditor class as internal and make the URL const private.
GuySartorelli Jun 12, 2022
9b5d59c
Fix test now that URL const is private.
GuySartorelli Jun 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 27 additions & 0 deletions doc/03-cli.md
Expand Up @@ -103,6 +103,8 @@ resolution.
* **--no-autoloader:** Skips autoloader generation.
* **--no-progress:** Removes the progress display that can mess with some
terminals or scripts which don't handle backspace characters.
* **--no-audit:** Does not run the audit step after installation is complete.
* **--audit-format:** Audit output format. Must be "table", "plain", or "summary" (default).
* **--optimize-autoloader (-o):** Convert PSR-0/4 autoloading to classmap to get a faster
autoloader. This is recommended especially for production, but can take
a bit of time to run so it is currently not done by default.
Expand Down Expand Up @@ -182,6 +184,8 @@ and this feature is only available for your root package dependencies.
* **--dev:** Install packages listed in `require-dev` (this is the default behavior).
* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules.
* **--no-install:** Does not run the install step after updating the composer.lock file.
* **--no-audit:** Does not run the audit steps after updating the composer.lock file.
* **--audit-format:** Audit output format. Must be "table", "plain", or "summary" (default).
* **--lock:** Only updates the lock file hash to suppress warning about the
lock file being out of date.
* **--with:** Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0
Expand Down Expand Up @@ -253,6 +257,8 @@ If you do not specify a package, Composer will prompt you to search for a packag
terminals or scripts which don't handle backspace characters.
* **--no-update:** Disables the automatic update of the dependencies (implies --no-install).
* **--no-install:** Does not run the install step after updating the composer.lock file.
* **--no-audit:** Does not run the audit steps after updating the composer.lock file.
* **--audit-format:** Audit output format. Must be "table", "plain", or "summary" (default).
* **--update-no-dev:** Run the dependency update with the `--no-dev` option.
* **--update-with-dependencies (-w):** Also update dependencies of the newly required packages, except those that are root requirements.
* **--update-with-all-dependencies (-W):** Also update dependencies of the newly required packages, including those that are root requirements.
Expand Down Expand Up @@ -295,6 +301,8 @@ uninstalled.
terminals or scripts which don't handle backspace characters.
* **--no-update:** Disables the automatic update of the dependencies (implies --no-install).
* **--no-install:** Does not run the install step after updating the composer.lock file.
* **--no-audit:** Does not run the audit steps after installation is complete.
* **--audit-format:** Audit output format. Must be "table", "plain", or "summary" (default).
* **--update-no-dev:** Run the dependency update with the --no-dev option.
* **--update-with-dependencies (-w):** Also update dependencies of the removed packages.
(Deprecated, is now default behavior)
Expand Down Expand Up @@ -850,6 +858,8 @@ By default the command checks for the packages on packagist.org.
mode.
* **--remove-vcs:** Force-remove the VCS metadata without prompting.
* **--no-install:** Disables installation of the vendors.
* **--no-audit:** Does not run the audit steps after installation is complete.
* **--audit-format:** Audit output format. Must be "table", "plain", or "summary" (default).
* **--ignore-platform-reqs:** ignore all platform requirements (`php`, `hhvm`,
`lib-*` and `ext-*`) and force the installation even if the local machine does
not fulfill these.
Expand Down Expand Up @@ -955,6 +965,23 @@ php composer.phar archive vendor/package 2.0.21 --format=zip
* **--dir:** Write the archive to this directory (default: ".")
* **--file:** Write the archive with the given file name.

## audit

This command is used to audit the packages you have installed
for possible security issues. Currently this only checks for and
lists security vulnerability advisories according to the
[Packagist.org api](https://packagist.org/apidoc#list-security-advisories).

```sh
php composer.phar audit
```

### Options

* **--no-dev:** Disables auditing of require-dev packages.
* **--format (-f):** Audit output format. Must be "table" (default), "plain", or "summary".
* **--locked:** Audit packages from the lock file, regardless of what is currently in vendor dir.

## help

To get more information about a certain command, you can use `help`.
Expand Down
5 changes: 5 additions & 0 deletions phpstan/baseline.neon
Expand Up @@ -4578,6 +4578,11 @@ parameters:
count: 1
path: ../src/Composer/SelfUpdate/Versions.php

-
message: "#^Short ternary operator is not allowed\\. Use null coalesce operator if applicable or consider using long ternary\\.$#"
count: 1
path: src/Composer/Util/Auditor.php

-
message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#"
count: 1
Expand Down
102 changes: 102 additions & 0 deletions src/Composer/Command/AuditCommand.php
@@ -0,0 +1,102 @@
<?php

namespace Composer\Command;

use Composer\Composer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Util\Auditor;
use Symfony\Component\Console\Input\InputOption;

class AuditCommand extends BaseCommand
{
protected function configure(): void
{
$this
->setName('audit')
->setDescription('Checks for security vulnerability advisories for installed packages.')
->setDefinition(array(
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables auditing of require-dev packages.'),
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format. Must be "table", "plain", or "summary".', Auditor::FORMAT_TABLE, Auditor::FORMATS),
new InputOption('locked', null, InputOption::VALUE_NONE, 'Audit based on the lock file instead of the installed packages.'),
))
->setHelp(
<<<EOT
The <info>audit</info> command checks for security vulnerability advisories for installed packages.

If you do not want to include dev dependencies in the audit you can omit them with --no-dev

Read more at https://getcomposer.org/doc/03-cli.md#audit
EOT
)
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$composer = $this->requireComposer();
$packages = $this->getPackages($composer, $input);
$httpDownloader = $composer->getLoop()->getHttpDownloader();

if (count($packages) === 0) {
$this->getIO()->writeError('No packages - skipping audit.');
return 0;
}

$auditor = new Auditor($httpDownloader);
return $auditor->audit($this->getIO(), $packages, $input->getOption('format'), false);
}

/**
* @param InputInterface $input
* @return PackageInterface[]
*/
private function getPackages(Composer $composer, InputInterface $input): array
{
if ($input->getOption('locked')) {
if (!$composer->getLocker()->isLocked()) {
throw new \UnexpectedValueException('Valid composer.json and composer.lock files are required to run this command with --locked');
}
$locker = $composer->getLocker();
return $locker->getLockedRepository(!$input->getOption('no-dev'))->getPackages();
}

$rootPkg = $composer->getPackage();
$installedRepo = new InstalledRepository(array($composer->getRepositoryManager()->getLocalRepository()));

if ($input->getOption('no-dev')) {
return $this->filterRequiredPackages($installedRepo, $rootPkg);
}

return $installedRepo->getPackages();
}

/**
* Find package requires and child requires.
* Effectively filters out dev dependencies.
*
* @param PackageInterface[] $bucket
* @return PackageInterface[]
*/
private function filterRequiredPackages(RepositoryInterface $repo, PackageInterface $package, array $bucket = array()): array
{
$requires = $package->getRequires();

foreach ($repo->getPackages() as $candidate) {
foreach ($candidate->getNames() as $name) {
if (isset($requires[$name])) {
if (!in_array($candidate, $bucket, true)) {
$bucket[] = $candidate;
$bucket = $this->filterRequiredPackages($repo, $candidate, $bucket);
}
break;
}
}
}

return $bucket;
}
}
7 changes: 6 additions & 1 deletion src/Composer/Command/CreateProjectCommand.php
Expand Up @@ -44,6 +44,7 @@
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Package\Version\VersionParser;
use Composer\Util\Auditor;

/**
* Install a package as new project into new directory.
Expand Down Expand Up @@ -88,6 +89,8 @@ protected function configure(): void
new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deleting the vcs folder.'),
new InputOption('remove-vcs', null, InputOption::VALUE_NONE, 'Whether to force deletion of the vcs folder without prompting.'),
new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'),
new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Whether to skip auditing of the installed package dependencies.'),
new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
new InputOption('ask', null, InputOption::VALUE_NONE, 'Whether to ask for project directory.'),
Expand Down Expand Up @@ -257,7 +260,9 @@ public function installProject(IOInterface $io, Config $config, InputInterface $
->setSuggestedPackagesReporter($this->suggestedPackagesReporter)
->setOptimizeAutoloader($config->get('optimize-autoloader'))
->setClassMapAuthoritative($config->get('classmap-authoritative'))
->setApcuAutoloader($config->get('apcu-autoloader'));
->setApcuAutoloader($config->get('apcu-autoloader'))
->setAudit(!$input->getOption('no-audit'))
->setAuditFormat($input->getOption('audit-format'));

if (!$composer->getLocker()->isLocked()) {
$installer->setUpdate(true);
Expand Down
5 changes: 5 additions & 0 deletions src/Composer/Command/InstallCommand.php
Expand Up @@ -15,6 +15,7 @@
use Composer\Installer;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Util\Auditor;
use Composer\Util\HttpDownloader;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
Expand Down Expand Up @@ -49,6 +50,8 @@ protected function configure()
new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('no-install', null, InputOption::VALUE_NONE, 'Do not use, only defined here to catch misuse of the install command.'),
new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after installation is complete.'),
new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
Expand Down Expand Up @@ -128,6 +131,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
->setClassMapAuthoritative($authoritative)
->setApcuAutoloader($apcu, $apcuPrefix)
->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
->setAudit(!$input->getOption('no-audit'))
->setAuditFormat($input->getOption('audit-format'))
;

if ($input->getOption('no-plugins')) {
Expand Down
5 changes: 5 additions & 0 deletions src/Composer/Command/RemoveCommand.php
Expand Up @@ -25,6 +25,7 @@
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Package\BasePackage;
use Composer\Util\Auditor;

/**
* @author Pierre du Plessis <pdples@gmail.com>
Expand All @@ -47,6 +48,8 @@ protected function configure()
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'),
new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'),
new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file.'),
new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies. (Deprecrated, is now default behavior)'),
new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements.'),
Expand Down Expand Up @@ -280,6 +283,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
->setDryRun($dryRun)
->setAudit(!$input->getOption('no-audit'))
->setAuditFormat($input->getOption('audit-format'))
;

// if no lock is present, we do not do a partial update as
Expand Down
5 changes: 5 additions & 0 deletions src/Composer/Command/RequireCommand.php
Expand Up @@ -31,6 +31,7 @@
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\IO\IOInterface;
use Composer\Util\Auditor;
use Composer\Util\Silencer;

/**
Expand Down Expand Up @@ -78,6 +79,8 @@ protected function configure()
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'),
new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'),
new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file.'),
new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements.'),
new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements.'),
Expand Down Expand Up @@ -414,6 +417,8 @@ private function doUpdate(InputInterface $input, OutputInterface $output, IOInte
->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
->setPreferStable($input->getOption('prefer-stable'))
->setPreferLowest($input->getOption('prefer-lowest'))
->setAudit(!$input->getOption('no-audit'))
->setAuditFormat($input->getOption('audit-format'))
;

// if no lock is present, or the file is brand new, we do not do a
Expand Down
5 changes: 5 additions & 0 deletions src/Composer/Command/UpdateCommand.php
Expand Up @@ -25,6 +25,7 @@
use Composer\Util\HttpDownloader;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Package\Link;
use Composer\Util\Auditor;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
Expand Down Expand Up @@ -58,6 +59,8 @@ protected function configure()
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
new InputOption('lock', null, InputOption::VALUE_NONE, 'Overwrites the lock file hash to suppress warning about the lock file being out of date without updating package versions. Package metadata like mirrors and URLs are updated if they changed.'),
new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'),
new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file.'),
new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
Expand Down Expand Up @@ -227,6 +230,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
->setPreferStable($input->getOption('prefer-stable'))
->setPreferLowest($input->getOption('prefer-lowest'))
->setTemporaryConstraints($temporaryConstraints)
->setAudit(!$input->getOption('no-audit'))
->setAuditFormat($input->getOption('audit-format'))
;

if ($input->getOption('no-plugins')) {
Expand Down
1 change: 1 addition & 0 deletions src/Composer/Console/Application.php
Expand Up @@ -529,6 +529,7 @@ protected function getDefaultCommands(): array
new Command\UpdateCommand(),
new Command\SearchCommand(),
new Command\ValidateCommand(),
new Command\AuditCommand(),
new Command\ShowCommand(),
new Command\SuggestsCommand(),
new Command\RequireCommand(),
Expand Down
9 changes: 9 additions & 0 deletions src/Composer/IO/ConsoleIO.php
Expand Up @@ -15,6 +15,7 @@
use Composer\Question\StrictConfirmationQuestion;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
Expand Down Expand Up @@ -342,6 +343,14 @@ public function select($question, $choices, $default, $attempts = false, $errorM
return $results;
}

/**
* @return Table
*/
public function getTable(): Table
{
return new Table($this->output);
}

/**
* @return OutputInterface
*/
Expand Down