andreapollastri/checkpoint

GitHub: andreapollastri/checkpoint

Stars: 56 | Forks: 1

# Checkpoint php artisan checkpoint:scan ## What it checks | # | Check | Severity | | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | | 1 | **Composer CVE Audit** — runs `composer audit` and reports known advisories | `FAIL` | | 2 | **NPM CVE Audit** — runs `npm audit` and flags critical/high vulnerabilities | `FAIL` / `WARN` | | 3 | **Environment Configuration** — `APP_DEBUG`, `APP_KEY`, `APP_URL`, `SESSION_SECURE_COOKIE` | `WARN` | | 4 | **`.gitignore` Sensitive Files** — ensures `.env`, `*.key`, `*.pem` are excluded; detects if `.env` is tracked by git | `FAIL` | | 5 | **File Permissions** — flags world-readable `.env` or world-writable `storage/` | `WARN` | | 6 | **Hardcoded Secrets** — scans PHP/JS files for API keys, Stripe tokens, AWS keys, GitHub PATs, PEM headers | `FAIL` | | 7 | **SQL Injection Risks** — detects raw queries with variable interpolation (`DB::select("… $var")`, `->whereRaw(…)`) | `FAIL` | | 8 | **Mass Assignment Vulnerabilities** — finds `$guarded = []`, `Model::unguard()`, or models with no fillable/guarded definition | `WARN` | | 9 | **XSS (Cross-Site Scripting) Risks** — flags unescaped `{!! $var !!}` in Blade views and raw `echo` of request input | `WARN` | | 10 | **CSRF Protection** — detects forms with `POST`/`PUT`/`PATCH`/`DELETE` missing `@csrf`, and checks middleware is present | `FAIL` | | 11 | **Open Redirect Risks** — spots `redirect($request->…)` or `header('Location: ' . $var)` with unvalidated input | `WARN` | | 12 | **Command Injection Risks** — finds `exec`, `shell_exec`, `system`, `passthru`, `proc_open` called with unescaped variables | `FAIL` | | 13 | **Insecure Deserialization** — detects `unserialize($userInput)` and the classic `unserialize(base64_decode(…))` exploit chain | `FAIL` | | 14 | **Debug Functions in Production Code** — finds `var_dump`, `dd`, `dump`, `ray` left outside of test files | `WARN` | | 15 | **Sensitive Data Exposure** — flags `display_errors = 1`, logging of passwords/tokens, and Telescope always-on config | `WARN` | | 16 | **SSRF Risks** — detects `Http::get($request->…)`, Guzzle/cURL/`file_get_contents` called with user-controlled URLs | `FAIL` | | 17 | **TLS Certificate Verification** — flags `withoutVerifying()`, `'verify' => false`, `CURLOPT_SSL_VERIFYPEER => false` | `FAIL` | | 18 | **CORS Configuration** — flags `allowed_origins => ['*']` combined with `supports_credentials => true` and other loose configs | `FAIL` / `WARN` | | 19 | **Package Freshness (Supply Chain)** — fails the scan if any Composer package was released within the last N days (default 3); whitelist via config | `FAIL` | | 20 | **Supply Chain Tooling** — when `package.json` is present, warns if no npm install-time guard (Safe-Chain, Socket CLI) is on PATH | `WARN` | | 21 | **Path Traversal Risks** — detects `Storage::get($request->…)`, `file_get_contents`/`include`/`require` with user-controlled paths | `FAIL` | | 22 | **Weak Cryptography** — flags `mcrypt_*`, ECB mode, DES/3DES/RC4, and `md5`/`sha1` used near password/token/HMAC keywords | `FAIL` / `WARN` | | 23 | **Insecure RNG** — detects `rand`/`mt_rand`/`uniqid` used in security contexts (tokens, CSRF, password reset, OTP) | `FAIL` | | 24 | **Session & Cookie Security** — audits `config/session.php` for `http_only=false`, `same_site=null/none`, `secure=false`, `encrypt=false` | `WARN` | | 25 | **EOL Versions** — flags Composer-locked Laravel and the running PHP when they are past or approaching upstream security cutoff | `FAIL` / `WARN` | | 26 | **Suspicious Vendor Autoload** — flags packages under `vendor/` that register PHP via `autoload.files` outside a baked-in whitelist (the mechanism abused by the May 2026 Laravel-Lang supply-chain attack) | `WARN` | ## Requirements - **PHP** `^8.1` is the package floor. Newer Laravel versions may require a higher PHP, so your effective minimum is whatever your Laravel major demands. - **Laravel** `8`–`13` (same major as `illuminate/*` 8.x–13.x used by your app) ## Installation composer require --dev andreapollastri/checkpoint The package auto-discovers itself via Laravel's package discovery — no manual registration needed. ## Recommended companion tools Checkpoint is a static scanner — it inspects your codebase but doesn't intercept package installs. To harden the npm install path against the next event-stream/ua-parser-js/chalk-style supply-chain attack, pair Checkpoint with **defense in depth**: run installs inside containers and add a runtime guard on the host when you must install locally. ### Docker (recommended) Run `composer install`, `npm install`, and `php artisan checkpoint:scan` **inside a container**, not directly on your laptop or server shell. Supply-chain malware often executes during a package's post-install script — if that runs on your host, it has access to your SSH keys, browser cookies, and filesystem. A disposable dev container limits blast radius to an isolated filesystem that you can throw away. Typical workflow: docker compose up -d docker compose exec app composer install docker compose exec app npm install docker compose exec app php artisan checkpoint:scan Use whatever Docker setup fits your project — Docker Compose, a devcontainer, or a CI image such as `composer:2` with Node in the same job. The goal is the same: **never let untrusted install scripts run on bare metal**; treat the host as a control plane only. ### Safe-Chain (recommended) [Safe-Chain](https://www.npmjs.com/package/@aikidosec/safe-chain) by 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 When you **do** run npm on the host (CI runners, one-off scripts), Safe-Chain blocks known-malicious packages **before** their install scripts run. Checkpoint's **Supply Chain Tooling** check verifies whether Safe-Chain (or an equivalent like Socket CLI) is on your `PATH` and emits a `WARN` if no protection is present. ## Usage ### Run all checks php artisan checkpoint:scan ### Run only specific checks php artisan checkpoint:scan --only="SQL Injection Risks,CSRF Protection" ### Skip specific checks php artisan checkpoint:scan --skip="NPM CVE Audit,Debug Functions in Production Code" ### JSON output (for CI/CD pipelines) php artisan checkpoint:scan --json The command exits with code `1` if any check returns `FAIL`, making it suitable as a pipeline gate. ## Configuration Checkpoint works out of the box with sensible defaults. Publish the config file when you need to toggle individual checks or tune the freshness gate: php artisan vendor:publish --tag=checkpoint-config This creates `config/checkpoint.php` with two sections: ### Enabling / disabling checks Every default check is listed and enabled. Set any entry to `false` to exclude it from the scan: 'checks' => [ Checks\ComposerAuditCheck::class => true, Checks\NpmAuditCheck::class => false, // skip npm audit on a PHP-only project Checks\EnvironmentCheck::class => true, // … Checks\PackageFreshnessCheck::class => true, Checks\SupplyChainToolingCheck::class => true, ], ### Package Freshness tuning 'package_freshness' => [ 'minimum_age_days' => 3, 'whitelist' => [ 'andreapollastri/checkpoint', // bundled — see note below // 'laravel/framework', // 'symfony/console', ], ], - `minimum_age_days` — packages released more recently than this fail the scan. Default `3`. **Set to `0` to bypass the freshness gate entirely** without removing the check from the scanner. - `whitelist` — fully-qualified package names (`vendor/package`) exempt from the freshness check. Use sparingly and ideally with an inline comment explaining why each entry is allowed. ### Suppressing individual findings Every `WARN` or `FAIL` finding is shown with a stable 12-character hash: FAIL Hardcoded Secrets 3 potential hardcoded secret(s) found. ✗ app/Services/PaymentService.php:14 — 'api_key' => 'sk_live_…' [a1b2c3d4e5f6] ✗ config/services.php:8 — $secret = 'super…' [9f8e7d6c5b4a] To silence one — false positive, accepted legacy code, internal test fixture — copy the bracketed hash into `config/checkpoint.php`: 'suppressed' => [ 'a1b2c3d4e5f6', '9f8e7d6c5b4a', ], On the next run those findings are filtered out. If every finding of a given check ends up suppressed, the check is downgraded to `PASS` with an explicit `"All N finding(s) suppressed via config."` message — so the suppression is visible in the output, not silently ignored. The hash is content-stable: refactors that only shift line numbers within the same file will **not** invalidate the suppression. The hash _does_ change if you alter the file path or the finding content itself, which is the intended safety net. ## Example output ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗██████╗ ██████╗ ██╗███╗ ██╗████████╗ ██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝██╔══██╗██╔═══██╗██║████╗ ██║╚══██╔══╝ ██║ ███████║█████╗ ██║ █████╔╝ ██████╔╝██║ ██║██║██╔██╗██║ ██║ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ██╔═══╝ ██║ ██║██║██║╚████║ ██║ ╚█████╗██║ ██║███████╗ ╚█████╗██║ ██╗██║ ╚█████╔╝██║██║ ╚███║ ██║ ╚════╝╚═╝ ╚═╝╚══════╝ ╚════╝╚═╝ ╚═╝╚═╝ ╚════╝ ╚═╝╚═╝ ╚══╝ ╚═╝ Laravel Security Scanner — andreapollastri/checkpoint Scanning: /var/www/my-app PASS Composer CVE Audit No known CVEs in Composer dependencies. FAIL Hardcoded Secrets 3 potential hardcoded secret(s) found. ✗ app/Services/PaymentService.php:14 — 'api_key' => 'sk_live_abc123…' [a1b2c3d4e5f6] ✗ config/services.php:8 — $secret = 'supersecretvalue' [9f8e7d6c5b4a] ✗ app/Http/Controllers/WebhookController.php:31 — 'api_key' => 'ghp_…' [3e2d1c0b9a8f] FAIL Path Traversal Risks 1 potential path traversal risk(s) found. ✗ app/Http/Controllers/DownloadController.php:24 — Storage::get($request->path) [7b6a5f4e3d2c] WARN Environment Configuration 3 environment issue(s) found. ⚑ APP_DEBUG is true — full stack traces will be exposed to end users. [5c4b3a2d1e0f] ⚑ SESSION_SECURE_COOKIE is not enabled. [1a2b3c4d5e6f] ⚑ APP_URL is set to "http://localhost" — update it for production. [6e5d4c3b2a1f] ───────────────────────────────────────────────────────── Summary 19 passed 4 warning(s) 2 failed (25 checks total) Scan result: FAIL — fix the issues above before deploying. ## Extending with custom checks Create a class that extends `AbstractCheck` and return a `CheckResult`: use Checkpoint\Checks\AbstractCheck; use Checkpoint\Checks\CheckResult; class MyCustomCheck extends AbstractCheck { public function name(): string { return 'My Custom Check'; } public function run(): CheckResult { // your logic here return CheckResult::pass('Everything looks good.'); // or: CheckResult::warn('Something to review.', ['detail one', 'detail two']); // or: CheckResult::fail('Critical issue found.', ['detail']); } } Then register it by building a `Scanner` manually instead of using the default: use Checkpoint\Scanner; $scanner = Scanner::withDefaultChecks(base_path()) ->add(new MyCustomCheck()); ## CI/CD integration Checkpoint can scaffold a ready-to-use pipeline for either provider in one command. ### GitHub Actions php artisan checkpoint:github Creates `.github/workflows/checkpoint.yml` — triggers on push to `main`/`master` and on every pull request. Uses `actions/checkout@v4`, `shivammathur/setup-php@v2` (PHP 8.2), Composer cache, and runs `php artisan checkpoint:scan`. Pass `--force` to overwrite an existing file. ### GitLab CI php artisan checkpoint:gitlab Creates `.gitlab-ci.yml` — runs on merge requests and default-branch pushes using the `composer:2` image with a Composer cache. If you already have a `.gitlab-ci.yml`, the command refuses to overwrite and prints the snippet to stdout so you can paste it into your existing pipeline. Use `--force` to overwrite. ### Custom usage If you prefer to wire Checkpoint into a pipeline you already maintain, just call: - name: Security audit run: php artisan checkpoint:scan --json | tee checkpoint-report.json ### Composer hooks Run Checkpoint automatically after every `composer install` / `composer update`: php artisan checkpoint:install-hooks This appends `@php artisan checkpoint:scan` to `scripts.post-update-cmd` and `scripts.post-install-cmd` in your `composer.json`. The command: - Confirms interactively before touching `composer.json` (skip the prompt with `--no-interaction` for CI). - Is idempotent: re-running on an already-installed setup is a no-op. - Preserves any existing hooks (append-only); does **not** overwrite scripts owned by other tools. - Supports `--remove` to cleanly uninstall and `--force` to replace stale Checkpoint entries. ### Exit codes | Code | Meaning | | ---- | ------------------------------------ | | `0` | All checks passed (or warnings only) | | `1` | At least one check returned `FAIL` | ## Architecture src/ ├── CheckpointServiceProvider.php # auto-registers the command ├── Scanner.php # orchestrates all checks ├── Commands/ │ ├── ScanCommand.php # php artisan checkpoint:scan │ ├── GithubPipelineCommand.php # php artisan checkpoint:github │ ├── GitlabPipelineCommand.php # php artisan checkpoint:gitlab │ └── InstallHooksCommand.php # php artisan checkpoint:install-hooks └── Checks/ ├── AbstractCheck.php # base class ├── CheckResult.php # pass / warn / fail value object ├── ComposerAuditCheck.php ├── NpmAuditCheck.php ├── EnvironmentCheck.php ├── GitIgnoreCheck.php ├── FilePermissionsCheck.php ├── HardcodedSecretsCheck.php ├── SqlInjectionCheck.php ├── MassAssignmentCheck.php ├── XssCheck.php ├── CsrfCheck.php ├── OpenRedirectCheck.php ├── CommandInjectionCheck.php ├── InsecureDeserializationCheck.php ├── DebugFunctionsCheck.php ├── SensitiveExposureCheck.php ├── SsrfCheck.php ├── TlsVerificationCheck.php ├── CorsConfigCheck.php ├── PackageFreshnessCheck.php ├── SuspiciousVendorAutoloadCheck.php ├── SupplyChainToolingCheck.php ├── PathTraversalCheck.php ├── WeakCryptographyCheck.php ├── InsecureRngCheck.php ├── SessionSecurityCheck.php └── EolVersionCheck.php ## License MIT — [Andrea Pollastri](https://andreapollastri.net)
标签:ffuf