Composer Security Practices for Laravel Projects
If you are running composer update in CI and calling it a security practice, you are doing the opposite of what is safe. This article walks through concrete, version-specific Composer hardening steps for Laravel projects — from lockfile discipline to CI egress controls — using lessons from the May 2026 Laravel-Lang supply chain attack.
Prerequisites: Composer 2.4 or later, Laravel 10 / 11 / 12, a CI pipeline (GitHub Actions examples used throughout).
Why Composer Is a High-Value Attack Surface
The average Laravel application pulls in 80–120 Composer packages. Each package maintainer's GitHub account is a potential entry point for an attacker. The May 2026 Laravel-Lang incident proved this: a single GitHub organization compromise let an attacker rewrite every git tag across four packages — laravel-lang/lang, laravel-lang/attributes, laravel-lang/http-statuses, and laravel-lang/actions — within a 90-minute window. Over 5,500 downstream repositories received a backdoored helpers.php within six hours.
That file was wired into Composer's autoload.files directive, which means it executed on every PHP request once installed. It silently exfiltrated .env files, AWS keys, GitHub tokens, Stripe secrets, SSH keys, and more to the attacker-controlled domain flipboxstudio.info.
The mechanism that made this possible — and that most teams overlook — is that git tags are mutable. When you pin "laravel-lang/lang": "^2.1" in composer.json, Composer resolves the tag at install time. If that tag is silently rewritten to point at a malicious commit, composer update will pull the new payload. The only immutable anchor is the SHA-256 hash recorded in composer.lock.
Rule 1: Commit composer.lock and Never Run composer update in CI
This is the single highest-impact change you can make.
# Safe — uses exact SHAs from composer.lock
composer install --no-dev --optimize-autoloader
# Dangerous in CI or production — upgrades deps and rewrites composer.lock
composer update
composer install respects the lockfile exactly. composer update re-resolves every constraint, which can pull in any newly tagged (or re-tagged) version.
In your GitHub Actions workflow:
- name: Install PHP dependencies
run: composer install --no-dev --optimize-autoloader --no-interaction
Do not run composer update as part of any automated pipeline. Reserve it for a dedicated branch where a human reviews the diff before merging. If composer.lock is not committed to your repository, fix that before anything else:
git add composer.lock
git commit -m "chore: commit composer.lock for reproducible builds"
Rule 2: Run composer audit on Every Build
Composer 2.4 introduced the audit command, which reads composer.lock and checks every installed package against the PHP Security Advisories Database. It takes a few seconds and surfaces real CVEs.
# Human-readable output
composer audit
# Machine-parseable output for CI artifact storage
composer audit --format=json
Add it as a required step in CI, placed after composer install:
- name: Security audit
run: composer audit --format=json > composer-audit.json
- name: Upload audit results
uses: actions/upload-artifact@v4
with:
name: composer-audit
path: composer-audit.json
Do not suppress audit output with COMPOSER_NO_AUDIT=1. Every advisory that surfaces represents a real CVE that should be triaged, not silenced.
Rule 3: Understand Composer 2.9 Security Blocking
Composer 2.9 (released 7 November 2025) added automatic security blocking via two new config keys:
{
"config": {
"audit": {
"block-insecure": true,
"block-abandoned": false,
"ignore-abandoned": ["some/abandoned-package"]
}
}
}
block-insecure defaults to true, meaning composer update will fail if any installed package has a known security advisory. This broke CI builds for teams with unresolved advisories in existing dependencies — builds that previously succeeded now fail.
The correct response is to resolve the advisory, not to disable the feature. If you have a legitimate reason to suppress a specific advisory (for example, a vulnerability that does not affect your usage pattern), audit-ignore it explicitly rather than turning off the entire mechanism:
# Acknowledge a specific advisory without disabling the feature
composer audit --ignore-severity low
Note: A bug in early Composer 2.9 releases (GitHub issue #12607) caused COMPOSER_NO_AUDIT=1 and --no-audit to be ignored by the new blocking logic. Verify your Composer version with composer --version if you rely on those flags.
Rule 4: Add Enlightn as a Complementary Scanner
composer audit covers the PHP Security Advisories Database. The Enlightn Laravel Security Checker adds Laravel-specific checks and integrates with Artisan:
composer require --dev enlightn/laravel-security-checker
php artisan security:check
Enlightn can also be scheduled to email your team when new vulnerabilities appear in your installed packages — useful for production monitoring between deployments:
// app/Console/Kernel.php
$schedule->command('security:check')->weekly();
The tradeoff: composer audit is zero-dependency and ships with every Composer 2.4+ installation. Enlightn requires an additional dev dependency but gives you scheduler integration and a broader Laravel-specific rule set. Most teams benefit from running both.
Rule 5: Enforce CI Egress Controls
The Laravel-Lang attack exfiltrated secrets by making outbound HTTP connections from build machines. GitHub Actions, by default, allows any process running in a workflow to make arbitrary outbound connections — including a backdoored Composer package.
StepSecurity Harden-Runner adds network-policy enforcement to GitHub Actions:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: step-security/harden-runner@v2
with:
egress-policy: block
allowed-endpoints: |
packagist.org:443
repo.packagist.org:443
github.com:443
api.github.com:443
- uses: actions/checkout@v4
- name: Install dependencies
run: composer install --no-dev --optimize-autoloader --no-interaction
- name: Security audit
run: composer audit
With egress-policy: block, any process in the workflow that tries to connect to flipboxstudio.info (or any domain not in your allowlist) will be blocked and logged. This is the exact mechanism that would have contained the May 2026 Laravel-Lang attack in CI environments with this control in place.
Tradeoff: You need to allowlist every outbound endpoint your build touches — Packagist, GitHub, your CDN, any API called during tests. Misconfigurations break legitimate builds. The allowlist is typically 5–10 entries and stable once configured.
Rule 6: Check for the Laravel-Lang Compromise Indicator
If your project uses any of the four affected packages, verify your build history:
# Check whether affected packages are in your lockfile
grep -E '"laravel-lang/(lang|attributes|http-statuses|actions)"' composer.lock
Indicator of compromise: outbound connections to flipboxstudio.info in network logs from your build machines or containers between 22–23 May 2026 UTC. If found, rotate all secrets accessible from your CI environment immediately — GitHub tokens, AWS credentials, Stripe keys, .env values.
Packagist.org now enforces stable version immutability for published releases, preventing re-tagging. This applies only to packages hosted on Packagist. Private forks or packages installed via repositories entries in composer.json do not benefit from this protection.
Patch Status for Active CVEs
Run composer audit to surface these automatically, but know the target versions:
| Package | CVE | Fixed In | Risk |
|---|---|---|---|
laravel/framework |
CVE-2025-27515 (wildcard file validation bypass) | 10.48.29 / 11.44.1 / 12.1.1 | Moderate |
livewire/livewire |
CVE-2025-54068 (unauthenticated RCE, CISA KEV) | 3.6.4 | Critical |
laravel/passport |
CVE-2026-39976 (authentication bypass, Passport 13.x) | 13.7.1 | High |
plank/laravel-mediable |
CVE-2026-4809 (arbitrary file upload / RCE) | > 6.4.0 | Critical |
The Livewire CVE (CVE-2025-54068) deserves emphasis: it bypasses the APP_KEY-signed checksum mechanism used for component state hydration. Attackers do not need your application key to exploit it. CISA confirmed active exploitation. If you are on any Livewire v3 release below 3.6.4, update immediately:
composer update livewire/livewire
composer audit
After upgrading Livewire, test any components that pass complex objects through properties — the patch tightened how untrusted input is handled during hydration.
Verification Checklist
Before considering your Composer security posture production-ready:
- [ ]
composer.lockis committed and not in.gitignore - [ ] CI runs
composer install, notcomposer update - [ ]
composer auditruns as a required CI step and fails the build on findings - [ ] Composer version is 2.9+ (
composer --version) - [ ]
audit.block-insecureistrueincomposer.jsonconfig (or Composer 2.9 default) - [ ] Livewire is on 3.6.4 or later
- [ ] Laravel Passport is on 13.7.1 or later (if using Passport 13.x)
- [ ]
laravel/frameworkis on 10.48.29 / 11.44.1 / 12.1.1 or later - [ ] CI egress is restricted to known-good endpoints
- [ ] Network logs checked for
flipboxstudio.infoconnections (if using laravel-lang packages)
Common Mistakes to Avoid
Pinning to a version constraint instead of committing the lockfile. Writing "laravel-lang/lang": "^2.1" in composer.json is not a pin — it is a range that resolves at install time. The lockfile SHA is the real pin.
Running composer update on a schedule in CI. Automated upgrades are appealing but introduce the risk of pulling in newly compromised package versions without human review of the diff.
Setting COMPOSER_NO_AUDIT=1 globally. This silences real CVE warnings. Each advisory should be triaged and either patched or explicitly acknowledged with a documented reason.
Assuming autoload.files packages are low-risk. Any package listed under autoload.files executes on every PHP request. The Laravel-Lang backdoor used exactly this mechanism.
For a broader look at supply chain attack anatomy, CVE timelines, and static analysis tooling for Laravel applications, see the Laravel Security Guide: Supply-Chain Risks, Composer, and Application Scanning.
If you need Laravel development in Mumbai, Mumbai Web Designer builds production-grade Laravel applications.
Top comments (0)