Laravel Vet is a new package from the Laravel team that shows you the code that composer update is about to write into your vendor/ directory, and records the packages you trust in a vet.json file. It ships as a Composer plugin, so it runs after every install and before every update writes anything.
If you know cargo vet from the Rust world, this is the same idea for Composer. If you don't, here is the whole idea: every update brings new code into your project that nobody on your team has read. Vet shows you that code, one package at a time, before it lands. Once you trust a package, vet remembers it, so the next update only asks about what changed.
You don't have to read it all yourself. Vet hands each change to the coding agent already on your machine, such as Claude Code, Codex, Gemini or opencode, and the agent reads it for you and reports back: PASS, or FAIL with the file and the reason. You read the fails, press enter on the rest, and get on with your day.
Once you trust a package, vet remembers it, and the next update only asks about what changed. A package nobody has trusted exits with a non-zero status, which is what makes it useful in a build. It works with any project that has a
composer.json, so Laravel, Symfony, WordPress, and plain PHP are all fine.
As you may know, every Composer update writes thousands of lines of code that you either have to audit manually by reading diffs or blindly trust Packagist, and with security issues on the rise, auditing is becoming more and more important.
In fact we've had a run of supply chain news this year, from malware blocking and dependency policies in Composer 2.10 to the Axios npm package shipping a remote access trojan.
How Vet Works
Vet requires PHP 8.4 or later. Install it as a development dependency, and answer yes when Composer asks whether to allow the plugin:
composer require laravel/vet --dev
First, mark the packages currently installed in vendor/ as trusted:
./vendor/bin/vet --init
INFO Trusted [125] packages, and wrote [vet.json].
This creates vet.json using the package files already on your disk. It records your trust in those files without reviewing them. Vet then checks future updates against that record. If an update contains untrusted changes, Vet stops it before Composer writes the new files:
❯ composer update to review (1) carbonphp/carbon-doctrine-types 3.1.0 → 3.2.0 .............. 2 files changed │ │ ~ src/Carbon/Doctrine/DateTimeImmutableType.php │ @@ -17,7 +17,7 @@ │ /** │ * @SuppressWarnings(PHPMD.UnusedFormalParameter) │ */ │ - public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTimeImmutable │ + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?CarbonImmutable │ { │ return $this->doConvertToPHPValue($value); │ } │ │ ~ src/Carbon/Doctrine/DateTimeType.php │ @@ -17,7 +17,7 @@ │ /** │ * @SuppressWarnings(PHPMD.UnusedFormalParameter) │ */ │ - public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?DateTime │ + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Carbon │ { │ return $this->doConvertToPHPValue($value); │ } │ Packages: 1 to review, 124 trusted ERROR [1] package is not trusted. Read every change with [./vendor/bin/vet -v]. Run [./vendor/bin/vet] in a terminal to pick the ones that you trust. TIP Run [./vendor/bin/vet] in a terminal to hand every change to your coding agent.
Run ./vendor/bin/vet in a terminal to review the changes. You can read the code changes yourself, then press the space bar to select the packages you trust. Or you can ask a coding agent installed on your machine to review them first. Vet displays the agent's result next to each package:
INFO [claude] reviews [3] packages (58.1 KB). This takes a moment. to review (3) acme/logger 1.2.0 → 2.0.0 ................................. 12 files changed│ FAIL src/Ship.php reads .env and sends it to an unknown host│ src/Ship.php it posts the contents of [.env] to [telemetry.example.com] acme/tooling 4.1.0 → 4.2.0 ................................. 8 files changed│ WARN the changes add two commands│ The agent did not read [1] file, because it is too big. Read it yourself:│ resources/schema.php 612.4 KB carbonphp/carbon-doctrine-types 3.1.0 → 3.2.0 .............. 2 files changed│ PASS the changes narrow two return types
PASS means the agent completed its review and reported no attack. FAIL identifies a file and explains the problem the agent found.
WARN means the review is incomplete and needs your attention. A file may be too large, contain non-text data, or the agent may not have returned an answer. SKIP means Vet sent no files to the agent because nothing changed or it could not read the package's files.
Vet selects the PASS packages by default. Review the results and adjust the selection, then press Enter to save the packages you trust. The agent's results alone do not change vet.json.
Vet saves your choices in vet.json, next to composer.json. Commit this file to your repository. Each entry records a trusted package version and a hash, a value calculated from the package's files:
{ "schema": 4, "require": { "carbonphp/carbon-doctrine-types": { "version": "3.2.1", "hash": "tree-v2:0f158f3b909fc01e691ed5f5121186056232b049031e7d3a914676d49881ece5" } }}
The hash covers every file in the package. If the files change, even without a new version number, Vet requires another review. Vet exits with an error when a package is untrusted, so the check fails in your build until someone marks that package as trusted.
Vet is in beta right now, so the behavior can change before the first stable release. If you want a second layer on top of it, Heimdall sets a minimum age policy for your Composer dependencies so new releases have to sit for a few days before you install them. Visit the GitHub repo for the full documentation, and happy auditing!