Skip to content

Commit

Permalink
Merge pull request #88 from ans-group/fix-package-security-check
Browse files Browse the repository at this point in the history
Fix package security check
  • Loading branch information
phily245 authored Jul 19, 2024
2 parents 2e45d58 + 66d9567 commit 1a11d48
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 46 deletions.
1 change: 1 addition & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
rm -f composer.lock
sudo composer self-update
composer require laravel/framework:^${{ matrix.environment.laravel_version }}.0 -W
composer require enlightn/security-checker
- name: Execute tests
run: vendor/bin/phpunit
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dropped support for < Laravel 10 **(breaking change)**
- The `context` property for `UKFast\HealthCheck\Checks\CrossServiceHealthCheck` is now an array, not a string, matching other checks **(breaking change)**

### Fixed

- Fix PackageSecurityHealthCheck by using the `enlightn/security-checker` package instead of the archived `sensiolabs/security-checker` package **(breaking change)**

## [1.15.0] - 2024-07-17

### Added
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,6 @@
"illuminate/redis": "Allows the redis health check to function.",
"league/flysystem": "Allows the ftp health check to function.",
"guzzlehttp/guzzle": "Allows the http health check to function.",
"sensiolabs/security-checker": "Allows the package security health check to function."
"enlightn/security-checker": "Allows the package security health check to function."
}
}
9 changes: 5 additions & 4 deletions config/healthcheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
* Will be used in the HealthCheckController's response.
*/
'default-problem-http-code' => 500,

/*
* Default timeout for cURL requests for HTTP health check.
*/
Expand Down Expand Up @@ -114,10 +114,11 @@
],

/*
* A list of packages to be ignored by the Package Security health check
* Settings for the Package Security health check
*/
'package-security' => [
'ignore' => [],
'exclude-dev' => false, // Exclude dev dependencies from the security check
'ignore' => [], // Packages to ignore
],

'scheduler' => [
Expand All @@ -127,7 +128,7 @@

/*
* Default value for env checks.
* For each key, the check will call `env(KEY, config('healthcheck.env-default-key'))`
* For each key, the check will call `env(KEY, config('healthcheck.env-default-key'))`
* to avoid false positives when `env(KEY)` is defined but is null.
*/
'env-check-key' => 'HEALTH_CHECK_ENV_DEFAULT_VALUE',
Expand Down
26 changes: 18 additions & 8 deletions src/Checks/PackageSecurityHealthCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Exception;
use Illuminate\Support\Collection;
use SensioLabs\Security\SecurityChecker;
use Enlightn\SecurityChecker\SecurityChecker;
use UKFast\HealthCheck\HealthCheck;
use UKFast\HealthCheck\Status;

Expand All @@ -22,23 +22,33 @@ public function __construct()
$this->vulnerablePackages = collect();
}

public static function checkDependency(): bool
/**
* @param class-string $class
*/
public static function checkDependency(string $class): bool
{
return class_exists(SecurityChecker::class);
return class_exists($class);
}

public function status(): Status
{
try {
if (! static::checkDependency()) {
throw new Exception('You need to install the sensiolabs/security-checker package to use this check.');
if (! static::checkDependency(SecurityChecker::class)) {
if (static::checkDependency('SensioLabs\Security\SecurityChecker')) {
throw new Exception('The sensiolabs/security-checker package has been archived. Install enlightn/security-checker instead.');
}
throw new Exception('You need to install the enlightn/security-checker package to use this check.');
}

$checker = new SecurityChecker();
$result = $checker->check(base_path('composer.lock'), 'json');
$result = $checker->check(
base_path('composer.lock'),
config('healthcheck.package-security.exclude-dev', false),
);

$vulnerabilities = collect($result);

if ($result->count() > 0) {
$vulnerabilities = collect(json_decode((string) $result, true));
if ($vulnerabilities->isNotEmpty()) {
$this->vulnerablePackages = $vulnerabilities->reject(function ($vulnerability, $package) {
return in_array($package, config('healthcheck.package-security.ignore'));
});
Expand Down
72 changes: 41 additions & 31 deletions tests/Checks/PackageSecurityHealthCheckTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,43 @@ protected function partialMock($abstract, Closure $mock = null): MockInterface

public function testShowsProblemIfRequiredPackageNotLoaded()
{
$status = (new PackageSecurityHealthCheck($this->app))->status();
$status = (new MockedPackageSecurityHealthCheck)->status();

$this->assertTrue($status->isProblem());
$this->assertSame('You need to install the enlightn/security-checker package to use this check.', $status->context()['exception']['error']);
}

public function shows_problem_if_cannot_check_packages(): void
public function testShowsProblemIfIncorrectPackageLoaded(): void
{
$this->partialMock('overload:SensioLabs\Security\SecurityChecker', function ($mock) {
$mock->shouldReceive('check')->andThrow(new Exception('Lock file does not exist.'));
});
MockedPackageSecurityHealthCheck::$classResults = [
'Enlightn\SecurityChecker\SecurityChecker' => false,
'SensioLabs\Security\SecurityChecker' => true,
];
$status = (new MockedPackageSecurityHealthCheck)->status();

$status = (new PackageSecurityHealthCheck($this->app))->status();
$this->assertTrue($status->isProblem());
$this->assertSame('The sensiolabs/security-checker package has been archived. Install enlightn/security-checker instead.', $status->context()['exception']['error']);
}

public function testShowsProblemIfCannotCheckPackages(): void
{
$this->partialMock('overload:Enlightn\SecurityChecker\SecurityChecker', fn (MockInterface $mock) =>
$mock->shouldReceive('check')->andThrow(new Exception('File not found at [/tmp/composer.lock]'))
);

$status = (new PackageSecurityHealthCheck)->status();

$this->assertTrue($status->isProblem());
}

public function testShowsProblemIfPackageHasVulnerability(): void
{
$this->partialMock('overload:SensioLabs\Security\SecurityChecker', function ($mock) {
$this->partialMock('overload:Enlightn\SecurityChecker\SecurityChecker', fn (MockInterface $mock) =>
$mock->shouldReceive('check')
->andReturn(new MockResult(1, file_get_contents('tests/json/sensiolabsPackageHasVulnerability.json')));
});
->andReturn(json_decode(file_get_contents('tests/json/securityCheckerPackageHasVulnerability.json'), true))
);

$status = (new PackageSecurityHealthCheck($this->app))->status();
$status = (new PackageSecurityHealthCheck)->status();

$this->assertTrue($status->isProblem());
}
Expand All @@ -72,44 +85,41 @@ public function testIgnoresPackageIfInConfig(): void
],
]);

$this->partialMock('overload:SensioLabs\Security\SecurityChecker', function ($mock) {
$this->partialMock('overload:Enlightn\SecurityChecker\SecurityChecker', fn (MockInterface $mock) =>
$mock->shouldReceive('check')
->andReturn(new MockResult(1, file_get_contents('tests/json/sensiolabsPackageHasVulnerability.json')));
});
->andReturn(json_decode(file_get_contents('tests/json/securityCheckerPackageHasVulnerability.json'), true))
);

$status = (new PackageSecurityHealthCheck($this->app))->status();
$status = (new PackageSecurityHealthCheck)->status();

$this->assertTrue($status->isOkay());
}

public function testShowsOkayIfNoPackagesHaveVulnerabilities(): void
{
$this->partialMock('overload:SensioLabs\Security\SecurityChecker', function ($mock) {
$this->partialMock('overload:Enlightn\SecurityChecker\SecurityChecker', fn(MockInterface $mock) =>
$mock->shouldReceive('check')
->andReturn(new MockResult(0, '{}'));
});
->andReturn([])
);

$status = (new PackageSecurityHealthCheck($this->app))->status();
$status = (new PackageSecurityHealthCheck)->status();

$this->assertTrue($status->isOkay());
}
}

class MockResult
class MockedPackageSecurityHealthCheck extends PackageSecurityHealthCheck
{
public function __construct(
public readonly int $count,
private readonly string $vulnerabilities,
) {
}

public function __toString(): string
{
return $this->vulnerabilities;
}
/**
* @var array<string, bool>
*/
public static array $classResults = [
'Enlightn\SecurityChecker\SecurityChecker' => false,
'SensioLabs\Security\SecurityChecker' => false,
];

public function count(): int
public static function checkDependency(string $class): bool
{
return $this->count;
return static::$classResults[$class];
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"example/package": {
"version": "v1.2.3",
"version": "1.2.3",
"time": "2021-10-01T00:00:00+00:00",
"advisories": [
{
"title": "Example Vulnerability",
Expand All @@ -9,4 +10,4 @@
}
]
}
}
}

0 comments on commit 1a11d48

Please sign in to comment.