Skip to content
Back to blog
LaravelSeguridadBuenas prácticas

Laravel Vet reviews dependencies before Composer updates

Laravel Vet turns dependency review into a versioned trust record that Composer can enforce.

Ismael Catala5 min read

The problem is bigger than composer.lock

When I run composer update, I am not merely changing version numbers. I am bringing third-party code into an application, often through changes that nobody on the team has actually read. A composer.lock file makes installs reproducible, but it does not answer a basic question: who reviewed this package version and approved its contents?

Laravel Vet adds that missing check to the Composer workflow. It stores trust decisions in a vet.json file placed at the project root, alongside composer.json. That makes dependency approval part of the repository instead of a forgotten discussion in a pull request. (github.com)

A gate around changes written to vendor

Vet is installed as a development dependency and runs as a Composer plugin. Its documentation says it runs after composer install and before composer update writes updated packages into vendor. That distinction matters: updates can be stopped before new code is written, while installs are checked afterwards and fail when packages have not been recorded as trusted. (github.com)

Installation is straightforward, although the package requires PHP 8.4 or later. Composer will ask for permission to run the plugin the first time, since it participates in Composer operations. That permission should not be accepted automatically: the whole point is to add a tool that can inspect and enforce dependency changes.

composer require laravel/vet --dev
 
./vendor/bin/vet --init

The --init option creates the first vet.json from the packages already present in vendor. It is useful when adding Vet to an existing project. There is also a --fresh option, which removes the current trust file before creating a new baseline. (github.com)

Trust becomes part of version control

A vet.json entry contains more than a package name. Vet records the reviewed version and a hash of the package file tree. If the same version is later distributed with different bytes, that hash no longer matches and Vet asks for another review. (github.com)

That is what makes this more useful than an allowlist. The project is not trusting a Packagist name forever; it is trusting a specific version and the files that were reviewed. I would commit vet.json and treat its changes in pull requests with the same care as changes to composer.lock.

{
    "schema": 4,
    "require": {
        "carbonphp/carbon-doctrine-types": {
            "version": "3.2.1",
            "hash": "tree-v2:0f158f3b909fc01e691ed5f5121186056232b049031e7d3a914676d49881ece5"
        }
    }
}

That example is not a file to copy, especially not its hashes. Vet generates and updates the file using the packages installed in the actual project. The important part is that every change in it represents a new trust decision that needs an explanation and human review.

Manual review or an extra set of eyes

The main command is ./vendor/bin/vet. In an interactive terminal, it lists packages that are still untrusted and lets me select those I want to approve after inspecting the changes. If any package remains unapproved, the command exits with a non-zero status, which is exactly what I want from an automated policy. (github.com)

Vet can also send diffs to a coding agent installed on the machine, including Claude Code, Codex, Gemini, and opencode. The agent can return statuses such as PASS, FAIL, WARN, or SKIP, but it does not make the final decision. Vet writes nothing to vet.json until I explicitly select the packages I am willing to trust. (github.com)

This is a sensible use of AI in a security workflow: it reduces the initial reading load without turning a model into an invisible approver. A PASS is useful for triage, not as a reason to stop thinking. A FAIL or WARN needs context, and a sensitive dependency change can still deserve a manual review even when the agent finds nothing suspicious.

It fits CI, but it does not replace review

In non-interactive CI environments, Vet does not ask questions. It reports the issue and fails when it finds packages that are not trusted. That makes it suitable as an enforceable policy in self-hosted Laravel projects, provided both composer.lock and vet.json are committed to the repository. (github.com)

A minimal check can look like this:

composer install --no-interaction --prefer-dist
./vendor/bin/vet

The cleanest workflow is to review updates locally or in a pull request where an interactive terminal is available. Once the vet.json change is approved, CI only verifies that installed dependencies match the recorded trust state. It keeps dependency analysis separate from deployment.

The fine print

--init does not audit the dependencies already in the project. It trusts the bytes currently available in vendor, so it is not a retroactive security review and it does not prove that the initial baseline is safe. Teams that want a stricter rollout need to decide which packages should be reviewed before recording that baseline. (github.com)

Vet does not replace vulnerability alerts, licence checks, dependency maintenance, or static analysis in the application itself. Its scope is narrower: it records that somebody explicitly accepted a dependency version and notices when the package files change. It is a process control, not malware detection for PHP packages.

There is also a practical consideration when using agents: dependency diffs may leave the machine to be analysed by the agent provider. Vet displays the number and size of prompts before they are sent, but teams still need to follow their own data-handling and internal security rules. The project is also currently in beta, so I would test it in a branch or a non-critical repository before making it a deployment requirement. (github.com)

Laravel Vet does not remove supply-chain risk. What it does remove is a common excuse: a dependency update landed because Composer resolved it and nobody stopped to look. For teams maintaining Laravel applications over time, versioning that trust decision is a concrete and understandable improvement.


Source: Laravel News Official documentation: laravel/vet