Skip to content

Laravel Scalpel: Filesystem Intrusion Evidence Scanner for Laravel Apps

Laravel Scalpel is a post-compromise scanner that checks your deployed application's filesystem for rogue PHP files, obfuscated backdoors, tampered server directives, and changes from a trusted baseline.

Published · Updated · 4 min read

Laravel Scalpel: Filesystem Intrusion Evidence Scanner for Laravel Apps

What Is Laravel Scalpel?

Laravel Scalpel is an intrusion evidence scanner built by Harry Agustiana. Unlike tools such as Ward or Checkpoint—which audit source code and dependencies for known vulnerabilities—Scalpel focuses on a different question: have files in your deployed application already been added, changed, or removed?

It runs as a set of Artisan commands inside a Laravel application and requires PHP 8.2+ with Laravel 10 through 13.

The Six Built-in Scanners

Running php artisan scalpel:scan executes six scanners by default.

Structural Scanner

Looks for executable PHP files in directories where PHP should not appear—public/ and storage/ by default. It catches .php, .phtml, .pht, .phar, and double-extension files such as shell.php.jpg. Known-good paths like public/index.php and compiled views are allow-listed by default.

Obfuscated-Code Scanner

Checks PHP files for common backdoor patterns: eval(base64_decode(...)), compressed payload execution, dynamic function calls, direct evaluation of request input, and long encoded strings. Individual patterns can be disabled in config/scalpel.php if legitimate code triggers a false positive.

.htaccess Scanner

Flags handler and MIME-type mappings that allow the web server to execute Python, Perl, or CGI scripts, as well as Options +ExecCGI, external-URL rewrite rules, and directives like auto_prepend_file.

.user.ini Scanner

Reports per-directory PHP directives including auto_prepend_file, auto_append_file, include_path, and disable_functions—all of which an attacker can exploit to run hidden code on every request.

Environment Scanner

Checks for a missing, empty, or world-readable .env file, a .env placed under public/, an empty APP_KEY, key mismatches against .env.example, and APP_DEBUG=true in production.

Baseline Diff Scanner

Compares current files against a saved SHA-256 snapshot and reports added, modified, and deleted files.

Creating and Using a Baseline

# Record a trusted snapshot
php artisan scalpel:baseline

# Compare current files against it
php artisan scalpel:diff

Create the baseline only when the application is in a state you trust. After each deployment, the recommended workflow is:

php artisan optimize
php artisan scalpel:baseline --force

The vendor/ directory is excluded from content scans but included in baseline comparisons, so a file injected into an installed package still appears in the diff.

Fast Mode vs. Strict Mode

By default, Scalpel hashes every file on each run (strict mode). The --fast flag skips re-hashing when file size and modification time are unchanged—useful for large codebases, but it can miss a tampered file if an attacker preserves both attributes.

Signed Baselines

Set SCALPEL_SIGNING_ENABLED=true and provide a dedicated SCALPEL_SIGNING_KEY (not your APP_KEY) to HMAC-sign baselines and JSON reports. The diff command verifies the signature before use and raises a CRITICAL finding if it is invalid or missing.

Running Selective Scans and CI Integration

# Run only specific scanners
php artisan scalpel:scan --only=structural,obfuscated

# Output SARIF for CI and fail on MEDIUM or higher
php artisan scalpel:scan --format=sarif --fail-on=MEDIUM

Exit codes: 0 = clean, 1 = finding at or above --fail-on severity, 2 = findings below threshold or incomplete scan.

After each scan or diff, Scalpel dispatches a ScanFinished event containing findings, command name, and duration in milliseconds—making it straightforward to route alerts to Slack, email, or a webhook without parsing CLI output.

Note for CI pipelines: php artisan optimize compiles views under storage/framework/views. The obfuscated-code scanner reads those generated files and can produce many false positives. Add storage/framework/views to content_scan_excluded_paths or run optimize:clear before scanning.

Installation

composer require hryagstn/laravel-scalpel
php artisan vendor:publish --tag=scalpel-config

Key Takeaways

  • Scalpel detects post-compromise evidence; it is not a firewall or WAF.
  • Six scanners cover structural anomalies, obfuscated code, .htaccess/.user.ini tampering, environment issues, and baseline drift.
  • Signed baselines add tamper detection but cannot protect against an attacker who can read the signing key.
  • CI-friendly output formats (JSON, SARIF, GitHub Actions annotations) and configurable --fail-on severity make it easy to integrate into deployment pipelines.
  • The scanner runs with the same permissions as the application—use external scan triggers and read-only code directories for stronger guarantees.

Source: Laravel News — Laravel Scalpel Scans for Filesystem Intrusion Evidence

Frequently asked questions

How is Laravel Scalpel different from other Laravel security tools like Ward or Checkpoint?

Ward and Checkpoint inspect source code, configuration, and dependencies for known vulnerabilities before deployment. Scalpel is a post-compromise tool: it scans a deployed application's filesystem for evidence that files have already been added, changed, or removed by an attacker.

When should I create a Scalpel baseline, and how do I keep it current?

Create the baseline only when the application is in a state you trust. After every deployment, run `php artisan optimize` followed by `php artisan scalpel:baseline --force` to replace the old snapshot. Until a baseline exists, scans will report a MEDIUM finding prompting you to create one.

Can Scalpel produce false positives from Laravel's compiled views?

Yes. Running `php artisan optimize` compiles views under `storage/framework/views`, and the obfuscated-code scanner reads those files, potentially generating many MEDIUM and HIGH findings. To avoid this, add `storage/framework/views` to `content_scan_excluded_paths` in `config/scalpel.php`, or run `php artisan optimize:clear` before scanning.

Mohamed Said

About the author

Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

About GitHub LinkedIn WhatsApp Email Address My CV

Have a technical challenge?

Tell me what you’re building. I reply within two working days.

Discuss your project