Rust cargo vet, go vet.. meet PHP vet Laravel has released Laravel Vet in beta, a Composer plugin that audits PHP dependencies by showing the code each `composer update` would write into `vendor/` and recording trusted packages in a `vet.json` file. Vet hands each change to a coding agent such as Claude Code, Codex, Gemini or opencode to report PASS or FAIL, and a package nobody has trusted fails the build until someone reads it. The tool works with any PHP project that has a `composer.json` and requires PHP 8.4 or later. Note Laravel Vet is in beta. The behaviour can change before the first stable release. Laravel Vet is a dependency audit for PHP. It 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. 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. Vet works with any PHP project. Laravel, Symfony, WordPress, or plain PHP: if you have a composer.json , you can use it. It ships as a Composer plugin, so it runs after every composer install and before every composer update writes anything. There is no step to add. ❯ 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. You read the changes, or you let your agent read them, and vet writes your decision down. Your build then holds you to it: a package that nobody has trusted fails the build until someone reads it. ./vendor/bin/vet to review 2 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 carbonphp/carbon-doctrine-types 3.1.0 → 3.2.0 .............. 2 files changed │ PASS the changes narrow two return types Requires PHP 8.4+ https://php.net/releases/ . You can install Laravel Vet via Composer: composer require laravel/vet --dev Composer asks whether to allow the plugin the first time. Answer yes, and vet runs on every install and update from then on. You can also run it yourself with the ./vendor/bin/vet script that is included with the package: ./vendor/bin/vet Vet has one command. It audits what vendor/ holds, and when you run it in a terminal, it asks which of the untrusted packages you trust. You can read the changes yourself, or ask your coding agent to read them first. Until vet.json exists, vet has no earlier version to compare an update against, so the first step is to record the packages you trust today. The --init option records every package that vendor/ holds today, and writes vet.json for the first time. The --fresh option deletes vet.json first, then does the same, so you start from an empty trust file: ./vendor/bin/vet --init to trust 125 brianium/paratest v7.24.0 dev .............................. never trusted brick/math 0.18.0 ............................................ never trusted carbonphp/carbon-doctrine-types 3.2.0 ........................ never trusted … INFO Trusted 125 packages, and wrote vet.json . The --init option trusts the bytes that are already on your disk, and nothing else. When composer.lock asks for a version that vendor/ does not hold yet, vet leaves that version alone and asks you to read it: to read first 2 acme/logger 1.2.0 → 2.0.0 .................................... never trusted acme/tooling 4.1.0 → 4.2.0 ................................... never trusted ERROR composer would write 2 packages that vendor/ does not hold. Run ./vendor/bin/vet in a terminal to read them, or run composer install first. Once the trust file exists, ./vendor/bin/vet tells you where you stand. It reads every installed package, compares it against your entries, and names the packages that have none: ./vendor/bin/vet INFO All 125 packages are trusted. Vet exits with a non-zero status when a package is not trusted, which is what makes it useful in a build. Without a terminal, in your CI or inside the Composer plugin, the report is all that vet writes. Until vet.json exists, vet audits nothing and asks no question. It names the command that starts the trust file, and exits with a non-zero status: WARN No trust file yet. Run ./vendor/bin/vet --init to record every package that vendor/ holds today in vet.json . In a terminal, vet follows the report with a question. Every package that you do not trust yet appears in the list. Press the space bar to pick a package, ctrl+a to pick every package, and enter to record the ones that you picked. The changes of each package sit in the report above the list, so you read first and pick second: ┌ How do you want to review these packages? ───────────────────┐ │ › Manually, and pick the packages that I trust │ │ Automatically, with my coding agent reading the changes first│ └──────────────────────────────────────────────────────────────┘ ┌ Which packages do you trust? ────────────────────────────────┐ │ ◼ acme/logger 1.2.0 → 2.0.0 │ │ ◻ acme/tooling 4.1.0 → 4.2.0 │ │ ◻ brick/math 0.18.0 │ └──────────────────────────────────────────────────────────────┘ INFO Recorded acme/logger 2.0.0 at 8002bb9cf6c9 . INFO Run composer install to write those bytes to vendor/. The run exits with a non-zero status until you trust every package. A package you skip fails the run, in the same way it fails your build. Above ten packages, the report lists each package with the count of its changed files and shows no change. A change that runs past forty lines stops there, and ./vendor/bin/vet