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
8–13 (illuminate/support +
illuminate/console)
Installation
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:
php artisan vendor:publish --tag=checkpoint-config
Usage
Run every check
php artisan checkpoint:scan
Limit or skip checks
--only and --skip take check display names
(case-insensitive), comma-separated.
php artisan checkpoint:scan --only="SQL Injection Risks,CSRF Protection"
php artisan checkpoint:scan --skip="NPM CVE Audit,Debug Functions in Production Code"
JSON for CI
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:
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
'checks' => [
Checks\ComposerAuditCheck::class => true,
Checks\NpmAuditCheck::class => false, // PHP-only apps
// …
],
Custom checks
'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
'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:
'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
'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:
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
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
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
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.
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:
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