# Composer Audit: Find Vulnerable PHP Packages Before Attackers Do

> Your composer.lock probably has vulnerable packages right now. Run composer audit to find them, fix with targeted updates, and add automated vulnerability scanning to your CI/CD pipeline so nothing ships unpatched.

**Author:** Matt King | **Published:** March 7, 2026 | **Category:** Security

---

The average Laravel application pulls in 80 to 120 Composer packages. Each one is a potential entry point for attackers if a vulnerability is disclosed and you do not update in time. In 2025 alone, over 400 PHP package vulnerabilities were added to the Security Advisories Database.

`composer audit` is the built-in tool for catching these before they reach production. This guide covers everything you need to know: how it works, how to fix what it finds, how to automate it in CI/CD, and how it compares to other tools like Dependabot.

## What composer audit does

Composer audit was introduced in Composer 2.4. It reads your `composer.lock` file and checks every installed package against the [PHP Security Advisories Database](https://github.com/FriendsOfPHP/security-advisories), which aggregates CVEs from the National Vulnerability Database, GitHub Security Advisories, and package maintainer reports.

```bash
composer audit
```

Here is what typical output looks like:

```
Found 2 security vulnerability advisories affecting 2 packages:

+-------------------+------------------------------+
| Package           | symfony/http-foundation      |
| CVE               | CVE-2024-5678                |
| Title             | Request validation bypass    |
| URL               | https://...                  |
| Affected versions | >=5.0.0, <5.4.5             |
| Reported at       | 2024-03-15T00:00:00+00:00   |
+-------------------+------------------------------+

+-------------------+------------------------------+
| Package           | guzzlehttp/guzzle            |
| CVE               | CVE-2023-9101                |
| Title             | SSRF in HTTP request handler |
| URL               | https://...                  |
| Affected versions | >=6.0.0, <6.5.3             |
| Reported at       | 2023-11-02T00:00:00+00:00   |
+-------------------+------------------------------+
```

If no vulnerabilities are found, it returns exit code 0. If vulnerabilities exist, it returns a non-zero exit code, which makes it easy to use as a gate in automated pipelines.

## How to fix vulnerabilities

### Option 1: Update specific packages

The safest approach is to update only the affected packages:

```bash
composer update symfony/http-foundation guzzlehttp/guzzle
```

This updates just those packages (and their dependencies) to the latest versions allowed by your `composer.json` constraints. Run `composer audit` again to confirm the fix.

### Option 2: Update all packages

If you are comfortable updating everything:

```bash
composer update
```

This is faster but carries more risk of breaking changes. Always run your test suite after a bulk update.

### Option 3: When you cannot update

Sometimes a fix requires a major version bump that would break your application. In that case:

1. Check if the vulnerability actually affects your usage. Read the CVE description carefully. A vulnerability in a feature you do not use may be lower priority.
2. If you must stay on the current version, consider adding a workaround (input validation, WAF rule, or middleware) while you plan the upgrade.
3. Document the decision. A suppressed vulnerability should be a conscious, tracked choice, not something that gets forgotten.

### Handling version constraints

If `composer update` does not resolve a vulnerability, your `composer.json` constraints may be too tight. For example, if you require `"symfony/http-foundation": "^5.4"` and the fix is in 6.0, you need to update the constraint:

```json
{
    "require": {
        "symfony/http-foundation": "^6.0"
    }
}
```

Then run `composer update symfony/http-foundation` and test thoroughly, since major version bumps often include breaking changes.

## Output formats

Composer audit supports multiple output formats for different use cases:

```bash
# Human-readable table (default)
composer audit

# JSON for CI/CD parsing and dashboards
composer audit --format=json

# Plain text for simple scripts
composer audit --format=plain

# Only check production dependencies
composer audit --no-dev
```

The JSON format is particularly useful for integrating with security dashboards or Slack notifications:

```bash
composer audit --format=json | jq '.advisories | length'
# Returns: 2
```

## Automating composer audit in CI/CD

### GitHub Actions

Add this to your workflow to block PRs and deployments with known vulnerabilities:

```yaml
name: Security Audit

on:
  push:
    branches: [main, develop]
  pull_request:
  schedule:
    - cron: '0 8 * * 1'  # Every Monday at 8am

jobs:
  security-audit:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'

      - name: Install dependencies
        run: composer install --no-interaction --prefer-dist

      - name: Run security audit
        run: composer audit --format=json
```

The `schedule` trigger is important. New CVEs are disclosed constantly, and a Monday morning audit catches anything published over the weekend, even if no code changed.

### GitLab CI

```yaml
security-audit:
  stage: test
  script:
    - composer install --no-interaction --prefer-dist
    - composer audit
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
```

### Bitbucket Pipelines

```yaml
pipelines:
  default:
    - step:
        name: Security Audit
        caches:
          - composer
        script:
          - composer install --no-interaction --prefer-dist
          - composer audit
```

## Composer audit vs Dependabot vs Snyk

| Feature | composer audit | Dependabot | Snyk |
|---------|---------------|------------|------|
| Cost | Free (built-in) | Free (GitHub) | Free tier / paid |
| When it runs | On demand / CI | Continuously | Continuously |
| Auto-fix PRs | No | Yes | Yes |
| Database | PHP Security Advisories | GitHub Advisory Database | Snyk Vulnerability DB |
| PHP-specific | Yes | Yes | Yes |
| License scanning | No | No | Yes |
| Container scanning | No | No | Yes |

**Our recommendation:** Use them together. Composer audit in CI catches vulnerabilities at deploy time. Dependabot catches them between deployments and opens fix PRs automatically. They complement each other.

### Setting up Dependabot for PHP

Create `.github/dependabot.yml` in your repository:

```yaml
version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
```

## Real-world example: Responding to a critical CVE

Here is a typical workflow when a critical vulnerability is disclosed:

1. Your Monday morning CI schedule runs `composer audit` and fails
2. You check the output and find a critical CVE in a package you use
3. You run `composer update package/name` locally
4. You run your test suite to verify nothing breaks
5. You push the fix and the audit passes
6. Production is updated within hours of the disclosure

For a recent real-world example, see our post on the [Livewire RCE vulnerability (CVE-2025-54068)](/blog/livewire-rce-cve-2025-54068), which was a critical remote code execution flaw that `composer audit` would have flagged immediately after the advisory was published.

## Composer audit config options

You can customize composer audit behavior through command-line flags and `composer.json` configuration:

```bash
# Ignore specific advisories (when you have assessed the risk)
composer audit --ignore=CVE-2024-5678

# Set a severity threshold (only fail on high and critical)
composer audit --abandoned=ignore

# Combine flags for CI
composer audit --no-dev --format=json --locked
```

For persistent configuration, add an `audit` section to your `composer.json`:

```json
{
    "config": {
        "audit": {
            "abandoned": "report"
        }
    }
}
```

The `--locked` flag ensures audit reads from `composer.lock` without checking if it is up to date with `composer.json`. This is faster in CI environments where you have already run `composer install`.

## Composer audit command documentation

Here is the complete reference for the `composer audit` command:

| Flag | Purpose |
|------|---------|
| `--format=table\|json\|plain\|summary` | Output format (default: table) |
| `--no-dev` | Skip dev dependencies |
| `--locked` | Audit from lock file without checking freshness |
| `--abandoned=ignore\|report\|fail` | How to handle abandoned packages |

Exit codes:
- **0** = No vulnerabilities found
- **1** = Vulnerabilities found
- **2** = Audit failed (network error, missing lock file, etc.)

The command sources its data from the [PHP Security Advisories Database](https://github.com/FriendsOfPHP/security-advisories), which aggregates from the National Vulnerability Database, GitHub Security Advisories, and package maintainer reports. The database is checked on every run (no local cache to go stale).

## Common issues and solutions

### "No lock file found"

Composer audit requires a `composer.lock` file. If you do not commit your lock file (you should), generate one first:

```bash
composer install
composer audit
```

### Old Composer version

Composer audit requires version 2.4 or later. Check your version and upgrade if needed:

```bash
composer --version
composer self-update
```

### False sense of security

Composer audit only checks against **known, disclosed** vulnerabilities. It cannot find zero-day exploits or vulnerabilities that have not been reported to the database yet. It is one layer of defense, not the only one.

For a more complete picture of your application's security posture, combine composer audit with:

- [Security header checks](/tools/header-check) to catch missing HTTP security headers
- External attack surface scanning to find exposed files, debug endpoints, and misconfigured DNS
- The [Laravel Security Checklist](/blog/laravel-security-checklist) for a comprehensive 30-point audit

## Summary

| What | Command |
|------|---------|
| Run a basic audit | `composer audit` |
| JSON output for CI | `composer audit --format=json` |
| Skip dev dependencies | `composer audit --no-dev` |
| Fix specific packages | `composer update package/name` |
| Fix all packages | `composer update` |
| Check Composer version | `composer --version` |

Run it locally. Run it in CI. Run it on a schedule. The five seconds it takes to run `composer audit` is nothing compared to the cost of deploying a known vulnerability to production.

---

*Want continuous monitoring beyond just dependencies? [Run a free StackShield scan](/free-scan) to check your full external attack surface, including security headers, exposed files, DNS configuration, and more. StackShield is a purpose-built [Laravel vulnerability scanner](/tools/laravel-scanner), and a lightweight [Snyk alternative](/compare/snyk) for teams that just want their Laravel app watched from the outside.*

---

## Frequently Asked Questions

### What does composer audit do?

Composer audit scans your composer.lock file against the PHP Security Advisories Database and reports any packages with known vulnerabilities. It shows the CVE identifier, severity level, affected version, and the version that contains the fix.

### How do I fix vulnerabilities found by composer audit?

Run composer update followed by the package names listed in the audit output. For example: composer update symfony/http-foundation guzzlehttp/guzzle. If a major version upgrade is needed and could break your code, pin to the latest safe minor version in your composer.json first, then run composer update.

### Should I run composer audit in CI/CD?

Yes. Adding composer audit to your CI/CD pipeline ensures no deployment goes out with known-vulnerable dependencies. You can use the --format=json flag for machine-readable output and fail the build on any findings. Most teams add it alongside their test suite so it runs on every pull request.

### What is the difference between composer audit and Dependabot?

Composer audit is a local CLI command that checks your lock file on demand or in CI. Dependabot is a GitHub service that monitors your repository continuously and opens pull requests when new vulnerabilities are disclosed. They complement each other: composer audit catches issues at deploy time, while Dependabot catches them between deployments.

### Can composer audit check dev dependencies?

Yes, composer audit checks all dependencies in your lock file by default, including dev dependencies. You can exclude dev packages by running composer audit --no-dev if you only want to audit production dependencies.

