Upstream composer/composer state of the touched files before 062bfdce1b

This commit is contained in:
upstream committed 2026-10-17 03:31:37 +02:00
1 parent 1a98d2c35a
commit 12931dd03f
2 files changed
+325

No files matched your search

+136
View File
@@ -0,0 +1,136 @@
<?php declare(strict_types=1);
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Command;
use Composer\Composer;
use Composer\FilterList\FilterListProvider\FilterListProviderSet;
use Composer\Policy\ListPolicyConfig;
use Composer\Policy\PolicyConfig;
use Composer\Repository\RepositorySet;
use Composer\Repository\RepositoryUtils;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepository;
use Composer\Advisory\Auditor;
use Composer\Console\Input\InputOption;
class AuditCommand extends BaseCommand
{
protected function configure(): void
{
$this
->setName('audit')
->setDescription('Checks for security vulnerability advisories for installed packages')
->setDefinition([
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", "json", 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.'),
new InputOption('abandoned', null, InputOption::VALUE_REQUIRED, 'Behavior on abandoned packages. Must be "ignore", "report", or "fail".', null, ListPolicyConfig::AUDITS),
new InputOption('ignore-severity', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Ignore advisories of a certain severity level.', [], ['low', 'medium', 'high', 'critical']),
new InputOption('ignore-unreachable', null, InputOption::VALUE_NONE, 'Ignore repositories that are unreachable or return a non-200 status code.'),
])
->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
If you want to ignore repositories that are unreachable or return a non-200 status code, use --ignore-unreachable
Read more at https://getcomposer.org/doc/03-cli.md#audit
EOT
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$composer = $this->requireComposer();
$packages = $this->getPackages($composer, $input);
if (count($packages) === 0) {
if ($composer->getPackage()->getRequires() !== []
|| (!$input->getOption('no-dev') && $composer->getPackage()->getDevRequires() !== [])) {
$this->getIO()->writeError('No installed packages found. Please run "composer install" before running "audit" or pass "--locked" to audit the lock file.');
return Auditor::STATUS_FAILED;
}
$this->getIO()->writeError('No packages - skipping audit.');
return Auditor::STATUS_OK;
}
$auditor = new Auditor();
$repoSet = new RepositorySet();
foreach ($composer->getRepositoryManager()->getRepositories() as $repo) {
$repoSet->addRepository($repo);
}
$abandoned = $input->getOption('abandoned');
if ($abandoned !== null && !in_array($abandoned, ListPolicyConfig::AUDITS, true)) {
throw new \InvalidArgumentException('--abandoned must be one of '.implode(', ', ListPolicyConfig::AUDITS).'.');
}
$policyConfig = $this->createPolicyConfig($composer->getConfig(), $input);
if ($abandoned !== null) {
$policyConfig = $policyConfig->withAudit($abandoned);
}
$ignoreSeverities = $input->getOption('ignore-severity');
if (count($ignoreSeverities) > 0) {
$policyConfig = $policyConfig->withIgnoreSeverity(array_values($ignoreSeverities));
}
if ($input->getOption('ignore-unreachable')) {
$policyConfig = $policyConfig->withIgnoreUnreachable('audit');
}
$filterListProviderSet = $policyConfig->enabled ? FilterListProviderSet::create($policyConfig, $composer->getRepositoryManager()->getRepositories(), $composer->getLoop()->getHttpDownloader()) : null;
return min(255, $auditor->audit(
$this->getIO(),
$repoSet,
$policyConfig,
$packages,
$this->getAuditFormat($input, 'format'),
false,
$filterListProviderSet
));
}
/**
* @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([$composer->getRepositoryManager()->getLocalRepository()]);
if ($input->getOption('no-dev')) {
return RepositoryUtils::filterRequiredPackages($installedRepo->getPackages(), $rootPkg);
}
return $installedRepo->getPackages();
}
}
@@ -0,0 +1,189 @@
<?php declare(strict_types=1);
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Test\Command;
use Composer\Advisory\Auditor;
use Composer\Policy\ListPolicyConfig;
use Composer\Test\TestCase;
use UnexpectedValueException;
class AuditCommandTest extends TestCase
{
public function testSuccessfulResponseCodeWhenNoPackagesAreRequired(): void
{
$this->initTempComposer();
$appTester = $this->getApplicationTester();
$appTester->run(['command' => 'audit']);
$appTester->assertCommandIsSuccessful();
self::assertEquals('No packages - skipping audit.', trim($appTester->getDisplay(true)));
}
public function testErrorAuditingLockFileWhenItIsMissing(): void
{
$this->initTempComposer();
$this->createInstalledJson([self::getPackage()]);
$this->expectException(UnexpectedValueException::class);
$this->expectExceptionMessage(
"Valid composer.json and composer.lock files are required to run this command with --locked"
);
$appTester = $this->getApplicationTester();
$appTester->run(['command' => 'audit', '--locked' => true]);
}
public function testAuditPackageWithNoSecurityVulnerabilities(): void
{
$this->initTempComposer();
$packages = [self::getPackage()];
$this->createInstalledJson($packages);
$this->createComposerLock($packages);
$appTester = $this->getApplicationTester();
$appTester->run(['command' => 'audit', '--locked' => true]);
self::assertStringContainsString(
'No security vulnerability advisories found.',
trim($appTester->getDisplay(true))
);
}
public function testErrorAuditWithNoInstalledPackages(): void
{
$this->initTempComposer(['require' => ['dummy/pkg' => '1.0.0']]);
$appTester = $this->getApplicationTester();
$appTester->run(['command' => 'audit']);
self::assertSame(Auditor::STATUS_FAILED, $appTester->getStatusCode());
self::assertStringContainsString(
'No installed packages found. Please run "composer install" before running "audit"',
trim($appTester->getDisplay(true))
);
}
public function testAuditPackageWithNoDevOptionPassed(): void
{
$this->initTempComposer();
$devPackage = [self::getPackage()];
$this->createInstalledJson([], $devPackage);
$this->createComposerLock([], $devPackage);
$appTester = $this->getApplicationTester();
$appTester->run(['command' => 'audit', '--no-dev' => true]);
self::assertStringContainsString(
'No packages - skipping audit.',
trim($appTester->getDisplay(true))
);
}
public function testAuditWithMalwareAndCustomListBothFail(): void
{
$packages = [
['name' => 'safe/pkg', 'version' => '1.0.0'],
['name' => 'malicious/pkg', 'version' => '1.0.0'],
['name' => 'banned/pkg', 'version' => '1.0.0'],
];
$this->initTempComposerWithFilterLists($packages);
$this->createComposerLockWithPackages($packages);
$appTester = $this->getApplicationTester();
$exitCode = $appTester->run(['command' => 'audit', '--locked' => true]);
$display = $appTester->getDisplay(true);
self::assertStringContainsString('Found 2 packages matching filters', $display);
self::assertStringContainsString('malicious/pkg', $display);
self::assertStringContainsString('banned/pkg', $display);
self::assertSame(Auditor::STATUS_FAILED, $exitCode);
}
public function testAuditWithCustomListAuditReportDoesNotFail(): void
{
$this->initTempComposer([
'repositories' => [
'packages' => [
'type' => 'package',
'package' => $packages = [
['name' => 'safe/pkg', 'version' => '1.0.0'],
['name' => 'banned/pkg', 'version' => '1.0.0'],
],
'filter' => [
'company-banned' => [
['package' => 'banned/pkg', 'constraint' => '*', 'reason' => 'company policy'],
],
],
],
],
'config' => [
'policy' => [
'company-banned' => ['audit' => ListPolicyConfig::AUDIT_REPORT],
],
],
]);
$this->createComposerLockWithPackages($packages);
$appTester = $this->getApplicationTester();
$exitCode = $appTester->run(['command' => 'audit', '--locked' => true]);
$display = $appTester->getDisplay(true);
self::assertStringContainsString('Found 1 package matching filters', $display);
self::assertStringContainsString('banned/pkg', $display);
self::assertSame(Auditor::STATUS_OK, $exitCode);
}
/**
* @param list<array{name: string, version: string}> $packages
*/
private function initTempComposerWithFilterLists(array $packages): void
{
$this->initTempComposer([
'repositories' => [
'packages' => [
'type' => 'package',
'package' => $packages,
'filter' => [
'malware' => [
['package' => 'malicious/pkg', 'constraint' => '*', 'reason' => 'malware sample'],
],
'company-banned' => [
['package' => 'banned/pkg', 'constraint' => '*', 'reason' => 'company policy'],
],
],
],
],
'config' => [
'policy' => [
'company-banned' => true,
],
],
]);
}
/**
* @param list<array{name: string, version: string}> $packages
*/
private function createComposerLockWithPackages(array $packages): void
{
$packages = array_map(function (array $package) {
return self::getPackage($package['name'], $package['version']);
}, $packages);
$this->createInstalledJson($packages);
$this->createComposerLock($packages);
}
}