# Audit a Laravel Repository per Customer Request

One API call per customer: an agent runs the Laravel app's PHPUnit suite, checks Composer for stale majors and returns a JSON audit to your webhook.

[Run this on Rush](https://cloud.getrush.ai/run/audit-customer-repos/laravel/), in a Project that has your repository.

## What the agent does

Your backend starts one session for each customer's repository and the agent works on its own clone. In a Laravel codebase like koel/koel the first files it opens are `composer.json`, `composer.lock`, `package.json` and, because the repository has one, `AGENTS.md`, whose Laravel Boost guidelines tell an agent to read the exact package versions before doing anything. It finds `php` `>=8.3`, `laravel/framework` `^13.0` locked at `v13.37.0`, required extensions such as `ext-exif`, `ext-gd` and `ext-fileinfo`, `roave/security-advisories` at `dev-latest` in `require-dev`, and a Vue front end managed by pnpm `10.32.1`. It installs the PHP extensions the lock asks for, runs `composer install`, and then `composer outdated --direct --major-only` to list only the direct packages a major version or more behind. A constraint like `predis/predis` `~1.0`, locked at `v1.1.10`, is the kind of row it checks against Packagist and reports with both versions.

Then it finds every test command the project defines, not just one. `composer.json` has `"test": "@php artisan test --parallel"`, which runs PHPUnit through ParaTest; `phpunit.xml.dist` defines `feature`, `unit` and `integration` suites and sets `DB_CONNECTION=sqlite` with `DB_DATABASE=:memory:`, plus a long list of service keys blanked or faked for tests. The GitHub workflows run the same suite three ways (`test-backend-sqlite.yml`, `test-backend-pgsql.yml`, `test-backend-mariadb.yml`, all on PHP 8.3), and the front end has `pnpm test` (`vp test`) and `pnpm typecheck` (`vue-tsc --noEmit`). The agent runs `php artisan key:generate` the way the workflows do, then `composer test`, `pnpm install --frozen-lockfile`, `pnpm test` and `pnpm typecheck`, and records each command with whether it passed.

For risky patterns it reads the places Laravel apps usually go wrong and judges each one. Raw SQL is the first: `whereRaw`, `selectRaw` and `DB::raw` appear in `app/Repositories/` (`SongRepository.php`, `AlbumRepository.php`, `PlayRepository.php` among them), and the agent checks each call for request input interpolated into the string instead of bound as a parameter. In a streaming app it follows the paths from `PlayController`, `DownloadSongController` and `app/Services/Streamer/` to the filesystem, looking for a path built from the request. It checks unescaped Blade output (`{!! !!}`), mass-assignment guards on models, and routes registered without auth middleware. A hit that turns out to be safe, such as the `{!! $slot !!}` in the vendored mail template, is left out; each one kept has its file, line and reason.

The prompt forbids changing any file. The agent writes nothing back to the repository, and the setup it needs, from the generated app key to the SQLite database, stays in its own sandbox. Its final answer is the JSON with the three arrays.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "codex",
  "input": "Audit this Laravel repository without changing any tracked file. Return only JSON with three arrays. outdated_dependencies: direct Composer and npm packages whose latest release is at least one major version ahead of what the lockfile installs, with name, ecosystem (composer or npm), locked and latest. tests: every test and type-check command the project defines in composer.json, package.json and its CI workflows, with the command, passed true or false, and the last 20 lines of output when it failed. risky_patterns: up to ten patterns such as raw SQL built from request input, unescaped Blade output of user data, file paths built from the request, or models with no mass-assignment guard, each with file, line, pattern and a one-sentence reason. Install whatever PHP extensions and services the tests need inside the sandbox.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

The answer is the JSON, returned as the session's `result` and in the `run.completed` webhook's `data.result` when it is 16 KiB or less; past that, your server reads `data.session_url` with the key to get it. Because a Laravel app has two package managers, every `outdated_dependencies` entry names its ecosystem, so your product can group Composer and npm findings. The `tests` array has one entry per command, so a customer sees that `composer test` passed while `pnpm typecheck` failed, with the tail of the failing output beside it.

Your product checks the JSON before using it: three arrays, the expected fields, at most ten risky patterns. Anyone spot-checking an audit should confirm that each raw-SQL finding really involves request input rather than a constant, and that a test failure is not a missing PHP extension the sandbox could have installed. The transcript holds the `composer outdated` output, each test run and each file the agent opened, so a disputed line can be traced to its source.

## Run it on every pull request merged to the customer's main branch

A dev tool can re-run the audit whenever a customer's default branch moves. Your GitHub webhook handler sees the push, starts a session with the repository in `resources` and an `Idempotency-Key` made from the repository and the commit SHA, so a redelivered GitHub event returns the same run instead of starting a second. Keep the returned `execution_id` with the customer and commit in your database.

When the turn ends, Rush signs and POSTs `run.completed` to the key's webhook URL. Your handler checks `webhook-signature`, drops any `webhook-id` it has already processed (Rush retries with the same id), finds the commit by `execution_id` and stores the JSON next to it, so the customer sees the audit change commit by commit.

## Why run it on Rush?

A Laravel test run needs PHP with the right extensions, Composer, Node, pnpm and often a database, and it executes the customer's code. Doing that for many customers on your own infrastructure means one shared machine running many strangers' code. Each Rush session is its own sandbox, so a customer's `composer install`, generated app key and test database exist only there.

Many customers can be audited at once, each in its own sandbox, up to the key's concurrent run limit, and each result is backed by a transcript of real commands. When a customer asks why a package is flagged or a test is marked failed, your support team opens the session and shows the command and its output instead of re-running anything.

## Frequently asked questions

### Does the audit cover the JavaScript side of a Laravel app?

Yes, when the repository has one. The prompt asks for npm packages as well as Composer ones and for every test and type-check command in `package.json`, so a Laravel app with a Vue or React front end gets both in one audit, each entry marked with its ecosystem.

### What if the PHPUnit suite needs MySQL or Postgres?

The agent reads `phpunit.xml.dist` and the CI workflows to see which database the suite expects. When it is SQLite in memory, as in many Laravel apps, nothing else is needed; when it is MySQL or Postgres, the agent starts that database in the sandbox and points the test environment at it.

### Why codex in the request instead of claude?

Either works; `agent` is the harness that does the job and both read and run a Laravel repository. Pick the one your org has an account for, or run the same prompt with each on a few repositories and keep the one whose audits your team prefers.

### Can it run on a private customer repository?

Yes, if your org's Rush GitHub App installation covers it. `GET /o/{org}/github` lists the repositories each installation reaches; a repository outside them is refused with `422 RESOURCE_NOT_ACCESSIBLE`, and no sandbox starts.
