Security audit.
One Artisan command.

Catch CVEs, leaked secrets, and injection risks before they ship.

Coverage

Twenty-six checks.
One pass.

From lockfile advisories to Laravel config traps and the static patterns that keep biting real apps. Full catalogue in Docs → All checks.

01–02 CVE audits Composer and npm advisory scans against your lockfiles. Fail
03–06 Secrets & env Hardcoded keys, .gitignore gaps, loose permissions, unsafe APP_* / session flags. Fail / Warn
07–17 App risks SQLi, XSS, CSRF, SSRF, open redirects, command injection, weak crypto, TLS off. Fail / Warn
18–26 Supply chain Package freshness, suspicious vendor autoload.files, EOL PHP/Laravel, CORS, sessions. Fail / Warn
Install

Running in seconds.

Add it as a dev dependency. Laravel discovers the package for you — no provider to register.

bash — install
composer require --dev andreapollastri/checkpoint
bash — scan
php artisan checkpoint:scan
Docs

Everything you need to block bad ships.

Everything that stands between your Laravel app and a bad deploy.

Overview

Checkpoint is a security scanner for Laravel. One Artisan command runs a suite of built-in checks (plus any custom ones you register) and prints a clear PASS / WARN / FAIL report. Run it on your machine, after Composer installs, or as a hard gate in CI.

It audits code, config, and processes — not traffic at runtime. It is not a WAF and not a full SAST engine: a focused gate you can run in seconds, not a substitute for deeper analysis.

Requirements

  • PHP ^8.1 (your Laravel major may require higher)
  • Laravel 813 (illuminate/support + illuminate/console)

Installation

bash
composer require --dev andreapollastri/checkpoint

Package discovery wires the service provider automatically. Publish the config only when you need to toggle checks, suppress findings, or register custom ones:

bash
php artisan vendor:publish --tag=checkpoint-config

Usage

Run every check

bash
php artisan checkpoint:scan

Limit or skip checks

--only and --skip take check display names (case-insensitive), comma-separated.

bash
php artisan checkpoint:scan --only="SQL Injection Risks,CSRF Protection"
bash
php artisan checkpoint:scan --skip="NPM CVE Audit,Debug Functions in Production Code"

JSON for CI

bash
php artisan checkpoint:scan --json | tee checkpoint-report.json

Every finding carries a stable 12-character hash you can suppress in config. If you pipe through tee in CI, turn on pipefail so the Artisan exit code is not lost.

Fail on warnings

By default, warnings keep exit code 0. Tighten the gate when you are ready:

bash
php artisan checkpoint:scan --fail-on-warn

Configuration

Publish to config/checkpoint.php. Any check missing from the map stays enabled, so upgrades pick up new protections without a forced re-publish.

Toggle checks

php — config/checkpoint.php
'checks' => [
    Checks\ComposerAuditCheck::class => true,
    Checks\NpmAuditCheck::class      => false, // PHP-only apps
    // …
],

Custom checks

php — config/checkpoint.php
'extra_checks' => [
    \App\Security\MyCustomCheck::class,
],

Constructors may take no arguments, or a single $basePath string. Disable an extra check with the same checks map.

Package freshness

php — config/checkpoint.php
'package_freshness' => [
    'minimum_age_days' => 3,
    'whitelist' => [
        'andreapollastri/checkpoint',
        // 'vendor/package',
    ],
],

Set minimum_age_days to 0 to keep the check in the suite while skipping the age gate.

Suppress findings

Each WARN/FAIL line shows a hash like [a1b2c3d4e5f6]. Paste it here when you have reviewed and accepted the finding:

php — config/checkpoint.php
'suppressed' => [
    'a1b2c3d4e5f6',
],
Hashes ignore line-number shifts. Change the path or the finding text and the hash invalidates on purpose. Suppress every finding for a check and that check reports PASS with an explicit suppression note.

Exclude paths

php — config/checkpoint.php
'exclude_paths' => [
    'storage/app/mounted-data',
    'data/external',
],

Paths are relative to the project root and stack on top of built-in skips (vendor/, node_modules/, storage/, …).

Suspicious autoload whitelist

Under suspicious_autoload.whitelist, list package names or vendor/* wildcards for trusted autoload.files entries beyond the built-in allowlist.

Custom checks

Extend AbstractCheck, return a CheckResult, and Checkpoint will run it with the rest of the suite:

php
use Checkpoint\Checks\AbstractCheck;
use Checkpoint\Checks\CheckResult;

class MyCustomCheck extends AbstractCheck
{
    public function name(): string
    {
        return 'My Custom Check';
    }

    public function run(): CheckResult
    {
        return CheckResult::pass('Everything looks good.');
        // CheckResult::warn('Review this.', ['detail']);
        // CheckResult::fail('Critical.', ['detail']);
    }
}

Register the class in extra_checks so php artisan checkpoint:scan picks it up. Prefer that path over building a Scanner by hand — Scanner::withDefaultChecks($path)->add(...) remains available when you need it.

CI / CD

GitHub Actions

bash
php artisan checkpoint:github

Writes .github/workflows/checkpoint.yml: runs on push to main/master and on every PR, PHP 8.2 with Composer cache, optional Node + npm ci when package-lock.json is present, and checkpoint:scan --json with the report uploaded as an artifact. Pass --force to overwrite an existing workflow.

GitLab CI

bash
php artisan checkpoint:gitlab

Creates .gitlab-ci.yml, or prints a ready-to-paste snippet if one already exists. Same pattern: JSON report artifact and optional Node on Alpine when a lockfile is present. --force overwrites.

Composer hooks

bash
php artisan checkpoint:install-hooks

Appends @php artisan checkpoint:scan to post-install-cmd and post-update-cmd. Safe to re-run; --remove uninstalls; --force replaces stale entries.

Only post-* hooks: pre-update-cmd fires before resolution (too early to catch a package about to land), and pre-install-cmd on a fresh clone has no vendor/ yet so Artisan cannot run. Blocking malicious installs in real time is a job for Docker and Safe-Chain — see Companion tools.

Exit codes

Code Meaning
0 All checks passed, or only warnings (unless --fail-on-warn)
1 At least one FAIL, or a WARN when --fail-on-warn is set

Companion tools

Checkpoint finds what is already in your tree. It does not stop a malicious package the moment you install it. Supply-chain attacks often run in postinstall scripts — by then SSH keys, browser cookies, and the whole host filesystem are in reach. Close that gap with defense in depth: isolate installs, then add a guard when you must install on the host.

Docker (recommended)

Treat the laptop as a control plane. Run composer install, npm install, and php artisan checkpoint:scan inside a disposable container. If a package turns hostile during install, the blast radius is an isolated filesystem you can throw away — not your machine. Docker Compose, a devcontainer, or a CI image with Composer and Node all work; the habit matters more than the tool. Checkpoint’s own CI scaffolds already scan in ephemeral containers — mirror that locally.

bash
docker compose exec app php artisan checkpoint:scan

Safe-Chain

When you do run npm on the host — CI runners, one-off scripts, machines without Docker — add a second layer. Safe-Chain (Aikido) is a free shell shim that blocks known-malicious npm packages before their install scripts run. Install it once, globally:

bash
npm install -g @aikidosec/safe-chain
safe-chain setup

Checkpoint’s Supply Chain Tooling check looks for Safe-Chain or Socket CLI on PATH whenever a package.json is present, and warns if neither is there. Checkpoint will not install Safe-Chain for you: it is a global shell shim, not a project dependency, and a Composer package should not reach into another ecosystem’s package manager. Prefer Docker for day-to-day installs; keep Safe-Chain as the safety net where containers are not practical.

All checks

The built-in suite shipped with the package. Use the class keys below in config/checkpoint.php to toggle each check. Severity is the typical gate level for that check.

# Check What it looks for Severity
1 Composer CVE AuditChecks\ComposerAuditCheck::class composer audit advisories in the lockfile Fail
2 NPM CVE AuditChecks\NpmAuditCheck::class npm audit critical / high findings Fail / Warn
3 Environment ConfigurationChecks\EnvironmentCheck::class APP_DEBUG, APP_KEY, APP_URL, session cookie flags Warn
4 .gitignore Sensitive FilesChecks\GitIgnoreCheck::class Required ignore patterns; fails if .env is tracked Fail
5 File PermissionsChecks\FilePermissionsCheck::class World-readable .env or world-writable storage/ Warn
6 Hardcoded SecretsChecks\HardcodedSecretsCheck::class API keys, Stripe, AWS, GitHub PATs, PEM headers in PHP/JS Fail
7 SQL Injection RisksChecks\SqlInjectionCheck::class Interpolated variables in raw / *Raw queries Fail
8 Mass AssignmentChecks\MassAssignmentCheck::class $guarded = [] or Model::unguard() Warn
9 XSSChecks\XssCheck::class Unescaped Blade {!! !!} and raw echo of request input Warn
10 CSRF ProtectionChecks\CsrfCheck::class State-changing forms missing @csrf; middleware presence Fail
11 Open RedirectChecks\OpenRedirectCheck::class redirect($request…) / Location headers from user input Warn
12 Command InjectionChecks\CommandInjectionCheck::class exec, shell_exec, system, … with variables Fail
13 Insecure DeserializationChecks\InsecureDeserializationCheck::class unserialize on user-controlled / base64 chains Fail
14 Debug FunctionsChecks\DebugFunctionsCheck::class dd, dump, var_dump, ray outside tests Warn
15 Sensitive Data ExposureChecks\SensitiveExposureCheck::class display_errors, logging secrets, Telescope always-on Warn
16 SSRF RisksChecks\SsrfCheck::class HTTP / cURL / file_get_contents with request-controlled URLs Fail
17 TLS Certificate VerificationChecks\TlsVerificationCheck::class withoutVerifying(), verify => false, CURLOPT SSL off Fail
18 CORS ConfigurationChecks\CorsConfigCheck::class Wildcard origins with credentials and other loose cors.php values Fail / Warn
19 Package FreshnessChecks\PackageFreshnessCheck::class Composer packages newer than N days (default 3) Fail
20 Supply Chain ToolingChecks\SupplyChainToolingCheck::class No Safe-Chain / Socket CLI on PATH when package.json exists Warn
21 Path TraversalChecks\PathTraversalCheck::class Storage / file ops with user-controlled paths Fail
22 Weak CryptographyChecks\WeakCryptographyCheck::class mcrypt, ECB, weak ciphers, md5/sha1 near auth keywords Fail / Warn
23 Insecure RNGChecks\InsecureRngCheck::class rand / mt_rand / uniqid in security contexts Fail
24 Session & Cookie SecurityChecks\SessionSecurityCheck::class Weak http_only, same_site, secure, encrypt Warn
25 EOL VersionsChecks\EolVersionCheck::class PHP / Laravel past or near upstream security cutoff Fail / Warn
26 Suspicious Vendor AutoloadChecks\SuspiciousVendorAutoloadCheck::class Vendor autoload.files outside whitelist Warn

Limitations

  • Most app-level checks are regex heuristics. Expect false positives and false negatives around indirection, query bindings, and multi-line constructs.
  • Environment checks read the runtime config of the process running Artisan — not necessarily your production .env.
  • NPM audit needs Node and a lockfile on the machine that runs the scan.
  • Package freshness can fail legitimate urgent security patches until you whitelist the package or lower the age window.

Source and issues: github.com/andreapollastri/checkpoint · Packagist