DEV Community

Cover image for Composer Security Practices for Laravel Projects
Sumeet Shroff
Sumeet Shroff

Posted on

Composer Security Practices for Laravel Projects

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.lock is committed and not in .gitignore
  • [ ] CI runs composer install, not composer update
  • [ ] composer audit runs as a required CI step and fails the build on findings
  • [ ] Composer version is 2.9+ (composer --version)
  • [ ] audit.block-insecure is true in composer.json config (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/framework is 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.info connections (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.

Read the full guide

Top comments (0)