├── .github
├── dependabot.yml
└── workflows
│ └── tests.yaml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── config
└── laravel-security-checker.php
├── phpunit.xml.dist
├── phpunit10.xml.dist
├── resources
├── lang
│ ├── de
│ │ └── messages.php
│ ├── en
│ │ └── messages.php
│ ├── es
│ │ └── messages.php
│ └── nl
│ │ └── messages.php
└── views
│ └── security-mail.blade.php
├── src
├── Console
│ ├── SecurityCommand.php
│ ├── SecurityMailCommand.php
│ └── SecuritySlackCommand.php
├── Formatter
│ ├── FormatterInterface.php
│ └── SimpleFormatter.php
├── Mailables
│ └── SecurityMail.php
├── Notifications
│ └── SecuritySlackNotification.php
└── ServiceProvider.php
└── tests
├── SecurityCommandTest.php
├── SecurityMailCommandTest.php
├── SecurityMailTest.php
├── SecuritySlackCommandTest.php
├── SecuritySlackTest.php
└── TestCase.php
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "composer"
4 | directory: "/" # Location of package manifests
5 | schedule:
6 | interval: "daily"
7 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yaml:
--------------------------------------------------------------------------------
1 | name: tests
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | tests:
9 | runs-on: ubuntu-latest
10 |
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | php: [7.4, 8.0, 8.1, 8.2, 8.3, 8.4]
15 | laravel: ['6.*', '7.*', '8.*', '9.*', '10.*', '11.*', '12.*']
16 | exclude:
17 | - php: 7.4
18 | laravel: 12.*
19 | - php: 8.0
20 | laravel: 12.*
21 | - php: 8.1
22 | laravel: 12.*
23 | - php: 7.4
24 | laravel: 11.*
25 | - php: 8.0
26 | laravel: 11.*
27 | - php: 8.1
28 | laravel: 11.*
29 | - php: 7.4
30 | laravel: 10.*
31 | - php: 8.0
32 | laravel: 10.*
33 | - php: 8.4
34 | laravel: 10.*
35 | - php: 7.4
36 | laravel: 9.*
37 | - php: 8.3
38 | laravel: 9.*
39 | - php: 8.4
40 | laravel: 9.*
41 | - php: 8.2
42 | laravel: 8.*
43 | - php: 8.3
44 | laravel: 8.*
45 | - php: 8.4
46 | laravel: 8.*
47 | - php: 8.1
48 | laravel: 7.*
49 | - php: 8.2
50 | laravel: 7.*
51 | - php: 8.3
52 | laravel: 7.*
53 | - php: 8.4
54 | laravel: 7.*
55 | - php: 8.1
56 | laravel: 6.*
57 | - php: 8.2
58 | laravel: 6.*
59 | - php: 8.3
60 | laravel: 6.*
61 | - php: 8.4
62 | laravel: 6.*
63 |
64 | name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }}
65 |
66 | steps:
67 | - name: Checkout code
68 | uses: actions/checkout@v4
69 |
70 | - name: Setup PHP
71 | uses: shivammathur/setup-php@v2
72 | with:
73 | php-version: ${{ matrix.php }}
74 | extensions: dom, curl, libxml, mbstring, zip, json
75 | coverage: pcov
76 |
77 | - name: Install dependencies
78 | run: |
79 | composer require "illuminate/contracts=${{ matrix.laravel }}" --prefer-stable --no-update
80 | composer update --prefer-dist --no-interaction --no-progress
81 |
82 | - name: Grab PHPUnit major version
83 | id: phpunit_version
84 | run: echo "MAJOR_VERSION=$(vendor/bin/phpunit --version | grep --only-matching --max-count=1 --extended-regexp '\b[0-9]+\.[0-9]+' | cut -d '.' -f 1)" >> $GITHUB_OUTPUT
85 |
86 | - name: Execute tests (PHPUnit >= 10)
87 | if: "${{ fromJSON(steps.phpunit_version.outputs.MAJOR_VERSION) >= 10 }}"
88 | run: vendor/bin/phpunit --configuration=phpunit10.xml.dist
89 |
90 | - name: Execute tests (PHPUnit < 10)
91 | if: "${{ fromJSON(steps.phpunit_version.outputs.MAJOR_VERSION) < 10 }}"
92 | run: vendor/bin/phpunit --configuration=phpunit.xml.dist
93 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | composer.lock
3 | .idea
4 | build
5 | .phpunit.result.cache
6 | .phpunit.cache
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog for Laravel Security Checker
2 |
3 | ## v2.5.0 (2024-06-02)
4 | * add support for Laravel 11, PHP 8.3
5 |
6 | ## v2.4.0 (2023-04-28)
7 | * add support for Laravel 10, PHP 8.2
8 |
9 | ## v2.3.0 (2022-03-02)
10 | * add support for Laravel 9, PHP 8.1
11 | * dropped support for PHP 7.3, minimum version is now 7.4
12 |
13 | ## v2.2.1 (2021-03-10)
14 | FIXED
15 | * a permission failure when using this package with multiple users on the same server by adding `temp-dir` to available config options (thanks @thomasderoo4!)
16 |
17 | ## v2.2.0 (2021-02-03)
18 | A big thank you to @paras-malhotra and Enlightn for helping out this release.
19 |
20 | * switched out travis with github actions, tests will now run in a matrix from Laravel 6 to 8 in combination with PHP 7.3 to 8
21 | * `sensiolabs/security-checker` is abandoned, replaced with successor `enlightn/security-checker`
22 | * allowed PHP 8 in the version constraints
23 | * removed support for Laravel < 6.0, supported versions are now: 6.x to 8.x
24 | * added Spanish language files (thanks @gfmr806)
25 |
26 | ## v2.1.0 (2020-09-28)
27 | * dropped support for PHP 7.1, minimum version is now PHP 7.2
28 | * upgraded `guzzlehttp/guzzle` to a new major version (^v7.0.0)
29 | * added support for Laravel 8 (thanks @romanstingler, @nessimabadi!)
30 |
31 | ## v2.0.0 (2020-03-04)
32 | * dropped support for PHP 7.0, minimum version is now PHP 7.1.3
33 | * upgraded `sensiolabs/security-checker` to a new major version (^v6.0.0)
34 | * added support for Laravel 7 (thanks @cino!)
35 |
36 | ## v1.2.0 (2020-03-03)
37 | * improved the `security:now` command to return exit code 1 when vulnerabilities were found, this enables integration into CI flows
38 |
39 | ## v1.1.1 (2019-09-23)
40 | * added support for Laravel 6.0 (thanks @davejamesmiller!)
41 |
42 | ## v1.1.0 (2019-03-09)
43 | * added support for Laravel 5.8 (thanks @DevDavido!)
44 | * added logging for email and slack commands
45 |
46 | ## v1.0.0 (2019-01-12)
47 | * @DevDavido notified me about the SensioLabs Security Checker upgrade, I implemented their changes
48 | * bumped the package to a stable tag, I think it has matured enough now. :-)
49 |
50 | ## v0.3.0 (2018-08-03)
51 | * updated to work on Laravel 5.5.x, 5.6.x and 5.7.x, thanks @jorgenb
52 | * dropped support for PHP 5.x
53 | * added Slack notifications on vulnerabilities, thanks @jorgenb
54 | * renamed LCS_EMAIL_WITHOUT_VULNERABILITIES TO LCS_NOTIFY_WITHOUT_VULNERABILITIES to reflect the Slack notification
55 |
56 | ## v0.2.2 (2017-07-23)
57 | * wrote tests to cover 100% of the package functionality
58 |
59 | ## v0.2.1 (2017-07-23)
60 | * fixed a bug in the email where the CVE wasn't displayed correctly
61 | * added DE and NL languages. thanks @mijndert
62 |
63 | ## v0.2.0 (2017-07-22)
64 | * added configuration option that won't email you when there are no vulnerabilities and it is enabled by default.
65 | * code improvements
66 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Jorijn Schrijvershof
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Laravel Security Checker
2 | [](https://packagist.org/packages/jorijn/laravel-security-checker)
3 | [](https://packagist.org/packages/jorijn/laravel-security-checker)
4 | [](https://packagist.org/packages/jorijn/laravel-security-checker)
5 | 
6 |
7 | This package provides an effortless way for you to check your local `composer.lock` against the [Security Advisories Database](https://github.com/FriendsOfPHP/security-advisories).
8 | It can either display the results in your console or email them to you on a scheduled basis. It uses Laravel's markdown system, so it should fit nicely in your styling.
9 |
10 | #### Screenshot
11 |
12 |
13 | ## Installation
14 | Require this package with composer using the following command:
15 |
16 | ```bash
17 | composer require jorijn/laravel-security-checker
18 | ```
19 |
20 | ### Configuration
21 |
22 | #### Email
23 | If you want the package to send reports by email, you'll need to specify a recipient.
24 |
25 | ##### Option 1
26 | Add it to your `.env` file.
27 |
28 | ```
29 | LCS_MAIL_TO="someone@example.net"
30 | ```
31 |
32 | ##### Option 2
33 | Publish the configuration file and change it there.
34 |
35 | ```bash
36 | php artisan vendor:publish --provider="Jorijn\LaravelSecurityChecker\ServiceProvider" --tag="config"
37 | ```
38 |
39 | If you want to control on how the email is formatted you can have Laravel export the view for you using:
40 |
41 | ```bash
42 | php artisan vendor:publish --provider="Jorijn\LaravelSecurityChecker\ServiceProvider" --tag="views"
43 | ```
44 |
45 | By default, the package won't email you when there are no vulnerabilities found. You can change this setting by adding the following entry to your `.env` file.
46 |
47 | ```
48 | LCS_NOTIFY_WITHOUT_VULNERABILITIES=true
49 | ```
50 |
51 | #### Slack
52 | If you want the package to send the report to a Slack channel, you will need to specify a Slack Webhook
53 | in your `.env` file.
54 |
55 | E.g.:
56 |
57 | ```
58 | LCS_SLACK_WEBHOOK=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
59 | ```
60 |
61 | ### Scheduling
62 | The package exposes a new command for you:
63 |
64 | ```bash
65 | php artisan security-check:email
66 | ```
67 |
68 | You can hook it up into a regular crontab or add it into the Laravel Scheduler (`app/Console/Kernel.php`) like this:
69 |
70 | ```php
71 | protected function schedule(Schedule $schedule)
72 | {
73 | $schedule->command(\Jorijn\LaravelSecurityChecker\Console\SecurityMailCommand::class)
74 | ->weekly();
75 | }
76 | ```
77 |
78 | ## Running as a command
79 | This package provides a wrapper around the [Enlightn Security Checker](https://github.com/enlightn/security-checker) command. You can call it using `php artisan security-check:now`.
80 |
81 | 
82 |
83 | ## Translations
84 | If you need to translate this package into your own language you can do so by publishing the translation files:
85 |
86 | ```bash
87 | php artisan vendor:publish --provider="Jorijn\LaravelSecurityChecker\ServiceProvider" --tag="translations"
88 | ```
89 |
90 | Please consider helping out by creating a pull request with your language to help out others.
91 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jorijn/laravel-security-checker",
3 | "description": "Added Laravel functionality to the Enlightn Security Checker. Adds a command to check for, and optionally emails you, vulnerabilities when they affect you.",
4 | "license": "MIT",
5 | "keywords": [
6 | "laravel",
7 | "security",
8 | "composer",
9 | "dependencies"
10 | ],
11 | "authors": [
12 | {
13 | "name": "Jorijn Schrijvershof",
14 | "email": "jorijn@jorijn.com"
15 | }
16 | ],
17 | "require": {
18 | "php": ">=7.4|^8.0",
19 | "guzzlehttp/guzzle": "^7.0",
20 | "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
21 | "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
22 | "illuminate/bus": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
23 | "illuminate/mail": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
24 | "illuminate/queue": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
25 | "illuminate/notifications": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
26 | "ext-json": "*",
27 | "enlightn/security-checker": "^1.3|^2.0"
28 | },
29 | "require-dev": {
30 | "laravel/slack-notification-channel": "^1.0|^2.0|^3.2",
31 | "phpunit/phpunit": "^6.0|^7.0|^8.0|^9.0|^10.5|^11.5.3",
32 | "squizlabs/php_codesniffer": "~2.3|^3.6",
33 | "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0|^10.0",
34 | "mockery/mockery": "^1.2"
35 | },
36 | "suggest": {
37 | "laravel/slack-notification-channel": "If you need slack notifications, you must install this package."
38 | },
39 | "autoload": {
40 | "psr-4": {
41 | "Jorijn\\LaravelSecurityChecker\\": "src/"
42 | }
43 | },
44 | "autoload-dev": {
45 | "psr-4": {
46 | "Jorijn\\LaravelSecurityChecker\\Tests\\": "tests"
47 | }
48 | },
49 | "scripts": {
50 | "test": "phpunit",
51 | "cs": "phpcs --standard=psr2 src/"
52 | },
53 | "extra": {
54 | "laravel": {
55 | "providers": [
56 | "Jorijn\\LaravelSecurityChecker\\ServiceProvider"
57 | ]
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/config/laravel-security-checker.php:
--------------------------------------------------------------------------------
1 | [
14 | env('LCS_MAIL_TO', null)
15 | ],
16 |
17 | /*
18 | |--------------------------------------------------------------------------
19 | | Laravel Security Checker — Email settings
20 | |--------------------------------------------------------------------------
21 | |
22 | | Decides whether the package should send an email even if there aren't
23 | | any vulnerabilities found.
24 | |
25 | */
26 |
27 | 'notify_even_without_vulnerabilities' => env('LCS_NOTIFY_WITHOUT_VULNERABILITIES', false),
28 |
29 | /*
30 | |--------------------------------------------------------------------------
31 | | Laravel Security Checker — Slack Webhook URL
32 | |--------------------------------------------------------------------------
33 | |
34 | | Which Slack Webhook URL should we post to when using Slack notifications?
35 | |
36 | */
37 |
38 | 'slack_webhook_url' => env('LCS_SLACK_WEBHOOK', null),
39 | /*
40 | |--------------------------------------------------------------------------
41 | | Laravel Security Checker — Temp dir
42 | |--------------------------------------------------------------------------
43 | |
44 | | Decides where enlightn/security-checker will place its temp files.
45 | | Useful when using this package with multiple users/permissions on a single server.
46 | | See: https://github.com/enlightn/security-checker/issues/17
47 | | https://github.com/Jorijn/laravel-security-checker/issues/35
48 | | Value:
49 | | An absolute path to a directory to place the temp files in.
50 | | null = default /tmp directory
51 | |
52 | */
53 | 'temp_dir' => null
54 | ];
55 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | tests
15 |
16 |
17 |
18 |
19 | ./src
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/phpunit10.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | tests
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ./src
25 |
26 |
27 |
--------------------------------------------------------------------------------
/resources/lang/de/messages.php:
--------------------------------------------------------------------------------
1 | '{0} Keine Pakete haben Schwachstellen bekannt|{1} 1 Paket hat Schwachstellen bekannt|[2,*] :count Pakete haben Schwachstellen bekannt',
15 | 'title' => 'Titel',
16 | 'view' => 'Ansehen',
17 | 'cve' => 'CVE',
18 | 'information' => 'Information',
19 | 'no_recipients_configured' => 'Es wurden noch keine Empfänger konfiguriert!',
20 | 'body_no_vulnerabilities' => 'Es scheint, dass keine Pakete irgendwelche bekannten Schwachstellen haben. [Hier](https://github.com/jorijn/laravel-security-checker#configuration) können Sie mehr über die Deaktivierung dieser Nachricht lesen.'
21 | ];
22 |
--------------------------------------------------------------------------------
/resources/lang/en/messages.php:
--------------------------------------------------------------------------------
1 | '{0} No packages have known vulnerabilities|{1} 1 package has known vulnerabilities|[2,*] :count packages have known vulnerabilities',
15 | 'title' => 'Title',
16 | 'view' => 'View',
17 | 'cve' => 'CVE',
18 | 'information' => 'Information',
19 | 'no_recipients_configured' => 'No recipients has been configured yet!',
20 | 'body_no_vulnerabilities' => 'It seems that no packages have any known vulnerabilities. You can read more about disabling this message [here](https://github.com/jorijn/laravel-security-checker#configuration).'
21 | ];
--------------------------------------------------------------------------------
/resources/lang/es/messages.php:
--------------------------------------------------------------------------------
1 | '{0} Ningún paquete tiene vulnerabilidades conocidas | {1} 1 paquete tiene vulnerabilidades conocidas | [2, *]: recuento de paquetes tienen vulnerabilidades conocidas',
15 | 'title' => 'Titulo',
16 | 'view' => 'Vista',
17 | 'cve' => 'CVE',
18 | 'information' => 'Información',
19 | 'no_recipients_configured' => 'Aún no se ha configurado ningún destinatario.!',
20 | 'body_no_vulnerabilities' => 'Parece que ningún paquete tiene vulnerabilidades conocidas. Puede leer más sobre cómo deshabilitar este mensaje [aquí](https://github.com/jorijn/laravel-security-checker#configuration).'
21 | ];
22 |
--------------------------------------------------------------------------------
/resources/lang/nl/messages.php:
--------------------------------------------------------------------------------
1 | '{0} Er zijn geen pakketten met bekende kwetsbaarheden|{1} 1 pakket heeft bekende kwetsbaarheden|[2,*] :count pakketten hebben bekende kwetsbaarheden',
15 | 'title' => 'Titel',
16 | 'view' => 'Bekijken',
17 | 'cve' => 'CVE',
18 | 'information' => 'Informatie',
19 | 'no_recipients_configured' => 'Er zijn nog geen ontvangers geconfigureerd!',
20 | 'body_no_vulnerabilities' => 'Het lijkt erop dat er geen pakketten zijn met bekende kwetsbaarheden. Lees [hier](https://github.com/jorijn/laravel-security-checker#configuration) hoe je dit bericht uit kunt schakelen.'
21 | ];
22 |
--------------------------------------------------------------------------------
/resources/views/security-mail.blade.php:
--------------------------------------------------------------------------------
1 | @component('mail::message')
2 | # {{ $title }}
3 |
4 | @foreach ($packages as $package => $report)
5 | ## {{ $package }} — {{ $report['version'] }}
6 |
7 | @component('mail::table')
8 | | @lang('laravel-security-checker::messages.title') | @lang('laravel-security-checker::messages.cve') | @lang('laravel-security-checker::messages.information') |
9 | | :---- | :-- | :---------- |
10 | @foreach($report['advisories'] as $key => $information)
11 | | {{ $information['title'] }} | {{ $information['cve'] or '' }} | [@lang('laravel-security-checker::messages.view')]({{ $information['link'] }})
12 | @endforeach
13 | @endcomponent
14 | @endforeach
15 |
16 | @if (count($packages) === 0)
17 | {{ __('laravel-security-checker::messages.body_no_vulnerabilities') }}
18 | @endif
19 |
20 | @endcomponent
--------------------------------------------------------------------------------
/src/Console/SecurityCommand.php:
--------------------------------------------------------------------------------
1 | checker = $checker;
36 | }
37 |
38 | /**
39 | * Execute the command
40 | */
41 | public function handle()
42 | {
43 | // get the path to composer.lock
44 | $composerLock = base_path('composer.lock');
45 |
46 | // and feed it into the SecurityChecker
47 | $checkResult = $this->checker->check($composerLock);
48 |
49 | // then display it using the formatter provided
50 | app(SimpleFormatter::class)->displayResults($this->getOutput(), $composerLock, $checkResult);
51 |
52 | return (int) (count($checkResult) > 0);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Console/SecurityMailCommand.php:
--------------------------------------------------------------------------------
1 | checker = $checker;
38 | }
39 |
40 | /**
41 | * Execute the command
42 | */
43 | public function handle()
44 | {
45 | // get the path to composer.lock
46 | $composerLock = base_path('composer.lock');
47 |
48 | // and feed it into the SecurityChecker
49 | Log::debug('about to check for vulnerabilities');
50 | $checkResult = $this->checker->check($composerLock);
51 |
52 | // if the user didn't want any email if there are no results,
53 | // cancel execution here.
54 | $proceed = config('laravel-security-checker.notify_even_without_vulnerabilities', false);
55 | if ($proceed !== true && \count($checkResult) === 0) {
56 | Log::info('no vulnerabilities were found, not sending any email');
57 | return 0;
58 | }
59 |
60 | // get the recipients and filter out any configuration mistakes
61 | $recipients = collect(config('laravel-security-checker.recipients', [ ]))->filter(function ($recipient) {
62 | return $recipient !== null && !empty($recipient);
63 | });
64 |
65 | if ($recipients->count() === 0) {
66 | Log::error('vulnerabilities were found, but there are no recipients configured');
67 | $this->error(
68 | /** @scrutinizer ignore-type */__('laravel-security-checker::messages.no_recipients_configured')
69 | );
70 | return 1;
71 | }
72 |
73 | Log::warning('vulnerabilities were found, emailed to configured recipients');
74 | Mail::to($recipients->toArray())->send(new SecurityMail($checkResult));
75 |
76 | return 0;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Console/SecuritySlackCommand.php:
--------------------------------------------------------------------------------
1 | checker = $checker;
38 | }
39 |
40 | /**
41 | * Execute the command
42 | */
43 | public function handle()
44 | {
45 | // require that the user specifies a slack channel in the .env file
46 | if (!config('laravel-security-checker.slack_webhook_url')) {
47 | Log::error('checking for vulnerabilities using slack was requested but no hook is configured');
48 | throw new \Exception('No Slack Webhook has been specified.');
49 | }
50 |
51 | // get the path to composer.lock
52 | $composerLock = base_path('composer.lock');
53 |
54 | // and feed it into the SecurityChecker
55 | Log::debug('about to check for vulnerabilities');
56 | $vulnerabilities = $this->checker->check($composerLock);
57 |
58 | // cancel execution here if user does not want to be notified when there are 0 vulns.
59 | $proceed = config('laravel-security-checker.notify_even_without_vulnerabilities', false);
60 | if (count($vulnerabilities) === 0 && $proceed !== true) {
61 | Log::info('no vulnerabilities were found, not sending a slack notification');
62 |
63 | return 0;
64 | }
65 |
66 | Log::warning('vulnerabilities were found, sending slack notification to configured hook');
67 | Notification::route('slack', config('laravel-security-checker.slack_webhook_url', null))
68 | ->notify(new SecuritySlackNotification($vulnerabilities, realpath($composerLock)));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Formatter/FormatterInterface.php:
--------------------------------------------------------------------------------
1 | writeln(sprintf('Security Check Report: %s>', realpath($lockFilePath)));
19 |
20 | if ($count = count($vulnerabilities)) {
21 | $status = 'CRITICAL';
22 | $style = 'error';
23 | } else {
24 | $status = 'OK';
25 | $style = 'info';
26 | }
27 |
28 | $output->writeln(sprintf(
29 | '<%s>[%s] %d %s known vulnerabilities>',
30 | $style,
31 | $status,
32 | $count,
33 | 1 === $count ? 'package has' : 'packages have'
34 | ));
35 |
36 | if (0 !== $count) {
37 | $output->write("\n");
38 |
39 | foreach ($vulnerabilities as $dependency => $issues) {
40 | $dependencyFullName = $dependency.' ('.$issues['version'].')';
41 | $output->writeln(''.$dependencyFullName."\n".str_repeat(
42 | '-',
43 | strlen($dependencyFullName)
44 | ).">\n");
45 |
46 | foreach ($issues['advisories'] as $issue => $details) {
47 | $output->write(' * ');
48 | if ($details['cve']) {
49 | $output->write(''.$details['cve'].': ');
50 | }
51 | $output->writeln($details['title']);
52 |
53 | if ('' !== $details['link']) {
54 | $output->writeln(' '.$details['link']);
55 | }
56 |
57 | $output->writeln('');
58 | }
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Mailables/SecurityMail.php:
--------------------------------------------------------------------------------
1 | checkResult = $checkResult;
24 | }
25 |
26 | /**
27 | * Build the message.
28 | *
29 | * @return $this
30 | */
31 | public function build()
32 | {
33 | $packageCount = count($this->checkResult);
34 | $title = trans_choice('laravel-security-checker::messages.subject_new_vulnerabilities', $packageCount, [
35 | 'count' => $packageCount
36 | ]);
37 | $subject = '['.config('app.name').'] '.$title;
38 |
39 | return $this->subject($subject)
40 | ->markdown('laravel-security-checker::security-mail', [
41 | 'title' => $title,
42 | 'packages' => $this->checkResult
43 | ]);
44 | }
45 |
46 | /**
47 | * @return array
48 | */
49 | public function getCheckResult()
50 | {
51 | return $this->checkResult;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Notifications/SecuritySlackNotification.php:
--------------------------------------------------------------------------------
1 | vulnerabilities = $vulnerabilities;
35 | $this->composerLockPath = $composerLockPath;
36 | }
37 |
38 | /**
39 | * Get the notification's delivery channels.
40 | *
41 | * @return array
42 | */
43 | public function via()
44 | {
45 | return [ 'slack' ];
46 | }
47 |
48 | /**
49 | * Get the slack representation of the notification.
50 | *
51 | * @return SlackMessage
52 | */
53 | public function toSlack()
54 | {
55 | return (new SlackMessage)
56 | ->from(config('app.url'))
57 | ->content("*Security Check Report:* `{$this->composerLockPath}`")
58 | ->attachment(function ($attachment) {
59 | $attachment->content($this->textFormatter())->markdown([ 'text' ]);
60 | });
61 | }
62 |
63 | /**
64 | * Get the array representation of the notification.
65 | *
66 | * @return array
67 | */
68 | public function toArray()
69 | {
70 | return $this->vulnerabilities;
71 | }
72 |
73 | /**
74 | * @return string
75 | */
76 | protected function textFormatter()
77 | {
78 | $packageCount = \count($this->vulnerabilities);
79 | $content = trans_choice('laravel-security-checker::messages.subject_new_vulnerabilities', $packageCount, [
80 | 'count' => $packageCount,
81 | ]);
82 |
83 | if ($packageCount > 0) {
84 | foreach ($this->vulnerabilities as $dependency => $issues) {
85 | $dependencyFullName = sprintf('%s (%s)', $dependency, $issues[ 'version' ]);
86 |
87 | $content .= PHP_EOL;
88 | $content .= sprintf('*%s*', $dependencyFullName);
89 | $content .= PHP_EOL;
90 | $content .= str_repeat('-', \strlen($dependencyFullName));
91 | $content .= PHP_EOL;
92 |
93 | foreach ($issues[ 'advisories' ] as $issue => $details) {
94 | $content .= ' * ';
95 |
96 | if ($details[ 'cve' ]) {
97 | $content .= $details[ 'cve' ].' ';
98 | }
99 |
100 | $content .= $details[ 'title' ].' ';
101 |
102 | if (!empty($details[ 'link' ])) {
103 | $content .= $details[ 'link' ];
104 | }
105 |
106 | $content .= PHP_EOL;
107 | }
108 | }
109 | }
110 |
111 | return $content;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | mergeConfigFrom($configPath, 'laravel-security-checker');
28 |
29 | $this->app->bind(SecurityChecker::class, function() {
30 | $temp_dir = config('laravel-security-checker.temp_dir', null);
31 | return new SecurityChecker($temp_dir);
32 | });
33 | }
34 |
35 | /**
36 | * Bootstrap the application events.
37 | *
38 | * @return void
39 | */
40 | public function boot()
41 | {
42 | // configuration
43 | $configPath = __DIR__.'/../config/laravel-security-checker.php';
44 | $this->publishes([ $configPath => $this->getConfigPath() ], 'config');
45 |
46 | // views
47 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'laravel-security-checker');
48 | $this->publishes([
49 | __DIR__.'/../resources/views' => resource_path('views/vendor/laravel-security-checker'),
50 | ], 'views');
51 |
52 | // translations
53 | $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'laravel-security-checker');
54 | $this->publishes([
55 | __DIR__.'/../resources/lang' => resource_path('lang/vendor/laravel-security-checker'),
56 | ], 'translations');
57 |
58 | if ($this->app->runningInConsole()) {
59 | $this->commands([
60 | SecurityCommand::class,
61 | SecurityMailCommand::class,
62 | SecuritySlackCommand::class,
63 | ]);
64 | }
65 | }
66 |
67 | /**
68 | * Get the config path
69 | *
70 | * @return string
71 | */
72 | protected function getConfigPath()
73 | {
74 | return config_path('laravel-security-checker.php');
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/tests/SecurityCommandTest.php:
--------------------------------------------------------------------------------
1 | bindPassingSecurityChecker();
10 |
11 | $this->artisan(
12 | 'security-check:now'
13 | )->assertExitCode(0);
14 | }
15 |
16 | public function testFireMethodWithVulnerabilitiesFound()
17 | {
18 | $this->bindFailingSecurityChecker();
19 |
20 | $this->artisan(
21 | 'security-check:now'
22 | )->assertExitCode(1);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/SecurityMailCommandTest.php:
--------------------------------------------------------------------------------
1 | bindFailingSecurityChecker();
19 |
20 | // set the recipient for testing
21 | Config::set('laravel-security-checker.recipients', [ 'recipient@example.net' ]);
22 |
23 | // execute the command
24 | $this->artisan('security-check:email')->assertExitCode(0);
25 |
26 | Mail::assertSent(SecurityMail::class, function (SecurityMail $mail) {
27 | return $mail->hasTo('recipient@example.net');
28 | });
29 | }
30 |
31 | /**
32 | * Tests if the email will cancel if there are no recipients
33 | */
34 | public function testFireMethodWithoutRecipients(): void
35 | {
36 | Mail::fake();
37 |
38 | $this->bindFailingSecurityChecker();
39 |
40 | // set the recipient for testing
41 | Config::set('laravel-security-checker.recipients', [ ]);
42 |
43 | $this->artisan('security-check:email')->assertExitCode(1);
44 |
45 | // check that the mail wasn't sent
46 | Mail::assertNotSent(SecurityMail::class);
47 | }
48 |
49 | /**
50 | * Tests if the email will cancel if there are no vulnerabilities
51 | */
52 | public function testFireMethodWithoutVulnerabilities(): void
53 | {
54 | Mail::fake();
55 |
56 | $this->bindPassingSecurityChecker();
57 |
58 | // set the recipient for testing
59 | Config::set('laravel-security-checker.recipients', [ 'recipient@example.net' ]);
60 | Config::set('laravel-security-checker.notify_even_without_vulnerabilities', false);
61 |
62 | $this->artisan('security-check:email')->assertExitCode(0);
63 |
64 | // check that the mail wasn't sent
65 | Mail::assertNotSent(SecurityMail::class);
66 | }
67 |
68 | /**
69 | * Tests if the email will cancel if there are no vulnerabilities
70 | */
71 | public function testFireMethodWithoutVulnerabilitiesWithSending(): void
72 | {
73 | Mail::fake();
74 |
75 | $this->bindPassingSecurityChecker();
76 |
77 | // set the recipient for testing
78 | Config::set('laravel-security-checker.recipients', [ 'recipient@example.net' ]);
79 | Config::set('laravel-security-checker.notify_even_without_vulnerabilities', true);
80 |
81 | $this->artisan('security-check:email')->assertExitCode(0);
82 |
83 | // check that the mail was sent
84 | Mail::assertSent(SecurityMail::class, function (SecurityMail $mail) {
85 | return $mail->hasTo('recipient@example.net')
86 | && [ ] === $mail->getCheckResult();
87 | });
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/SecurityMailTest.php:
--------------------------------------------------------------------------------
1 | getFakeVulnerabilityReport();
17 | $mailable = new SecurityMail($vulnerabilities);
18 |
19 | $this->assertInstanceOf(SecurityMail::class, $mailable);
20 |
21 | // build the mailable with the build() method -- this fills all template
22 | // variables and generates the subject and title.
23 | $generatedMailable = $mailable->build();
24 | $vulnerabilityCount = count($vulnerabilities);
25 | $translatedSubject = trans_choice(
26 | 'laravel-security-checker::messages.subject_new_vulnerabilities',
27 | $vulnerabilityCount,
28 | [ 'count' => $vulnerabilityCount ]
29 | );
30 |
31 | // assert that this has been done correctly.
32 | $this->assertEquals($generatedMailable->viewData['title'], $translatedSubject);
33 | $this->assertEquals($generatedMailable->subject, '['.Config::get('app.name').'] '. $translatedSubject);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/SecuritySlackCommandTest.php:
--------------------------------------------------------------------------------
1 | bindFailingSecurityChecker();
25 |
26 | // set the recipient for testing
27 | Config::set(
28 | 'laravel-security-checker.slack_webhook_url',
29 | 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'
30 | );
31 |
32 | $this->artisan('security-check:slack')->assertExitCode(0);
33 |
34 | // https://github.com/laravel/framework/pull/21379
35 | Notification::assertSentTo(
36 | new AnonymousNotifiable,
37 | SecuritySlackNotification::class,
38 | function ($notification, $channels, $notifiable) {
39 | return $notifiable->routes['slack'] === config('laravel-security-checker.slack_webhook_url');
40 | }
41 | );
42 | }
43 |
44 | /**
45 | * Tests that no notification is sent if a Slack Webhook has not been configured.
46 | */
47 | public function testHandleMethodWithoutSlackWebHook(): void
48 | {
49 | Notification::fake();
50 |
51 | $this->bindFailingSecurityChecker();
52 |
53 | // set the recipient for testing
54 | Config::set('laravel-security-checker.slack_webhook_url', null);
55 |
56 | // Class should throw exception if no webhook is configured
57 | $this->expectException(\Exception::class);
58 |
59 | $this->artisan('security-check:slack')->assertExitCode(1);
60 |
61 | Notification::assertNotSentTo(
62 | new AnonymousNotifiable,
63 | SecuritySlackNotification::class
64 | );
65 | }
66 |
67 | /**
68 | * Test that no notification get's sent if there are no vulnerabilities
69 | */
70 | public function testHandleMethodWithoutVulnerabilities(): void
71 | {
72 | Notification::fake();
73 |
74 | $this->bindPassingSecurityChecker();
75 |
76 | // set the recipient for testing
77 | Config::set(
78 | 'laravel-security-checker.slack_webhook_url',
79 | 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'
80 | );
81 |
82 | $this->artisan('security-check:slack')->assertExitCode(0);
83 |
84 | // check that the notification wasn't sent
85 | Notification::assertNotSentTo(
86 | new AnonymousNotifiable,
87 | SecuritySlackNotification::class
88 | );
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/tests/SecuritySlackTest.php:
--------------------------------------------------------------------------------
1 | getFakeVulnerabilityReport();
15 | $composerLockPath = '/path/to/composer.lock';
16 | $notification = new SecuritySlackNotification($vulnerabilities, $composerLockPath);
17 |
18 | $this->assertInstanceOf(SecuritySlackNotification::class, $notification);
19 |
20 | $generatedNotification = $notification->toSlack();
21 |
22 | $this->assertEquals('slack', $notification->via()[0]);
23 | $this->assertEquals($notification->toArray(), $vulnerabilities);
24 | $this->assertEquals($generatedNotification->username, config('app.url'));
25 | $this->assertEquals("*Security Check Report:* `{$composerLockPath}`", $generatedNotification->content);
26 | $this->assertCount(count($generatedNotification->attachments), $vulnerabilities);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | allows('check')->andReturns([]);
23 |
24 | // bind Mockery instance to the app container
25 | $this->app->instance(SecurityChecker::class, $securityCheckerMock);
26 | }
27 |
28 | protected function bindFailingSecurityChecker(): void
29 | {
30 | $securityCheckerMock = \Mockery::mock(SecurityChecker::class);
31 | $securityCheckerMock->allows('check')->andReturns($this->getFakeVulnerabilityReport());
32 |
33 | // bind Mockery instance to the app container
34 | $this->app->instance(SecurityChecker::class, $securityCheckerMock);
35 | }
36 |
37 | /**
38 | * Returns a fake vulnerability report that is digestible by our package
39 | *
40 | * @return array
41 | */
42 | public function getFakeVulnerabilityReport(): array
43 | {
44 | return [
45 | 'bugsnag/bugsnag-laravel' => [
46 | 'version' => 'v2.0.1',
47 | 'advisories' => [
48 | 'bugsnag/bugsnag-laravel/CVE-2016-5385.yaml' => [
49 | 'title' => 'HTTP Proxy header vulnerability',
50 | 'link' => 'https://github.com/bugsnag/bugsnag-laravel/releases/tag/v2.0.2',
51 | 'cve' => 'CVE-2016-5385'
52 | ]
53 | ]
54 | ]
55 | ];
56 | }
57 | }
58 |
--------------------------------------------------------------------------------