What the agent does
The agent checks out the pull request's branch in its own sandbox and reads how the existing routes protect themselves. In an Express app like gothinkster/node-express-realworld-example-app, package.json pins express ~4.18.1, express-jwt ^8.4.1, jsonwebtoken ^9.0.2 and @prisma/client ^4.16.1, and the build runs through Nx 17.2.6 (nx serve, nx build, nx test). Routes live in src/app/routes/ as article/, auth/, profile/ and tag/, each with a controller that declares the routes and a service that calls Prisma.
Authentication is one file. src/app/routes/auth/auth.ts exports auth.required and auth.optional, both expressjwt with algorithms: ['HS256'] and a token read from an Authorization header that starts with Token or Bearer. Ownership is checked in the services, not the middleware: in article.service.ts, updating or deleting an article loads it with prisma.article.findFirst, compares existingArticle.author.id with the caller's id and throws HttpException(403, ...) when they differ, and deleting a comment does the same with comment.author.id. So a new write route needs both: auth.required in the controller and an ownership comparison in its service, and the agent checks each new route for the pair.
Then it traces each new input. For every req.params, req.query and req.body field a new route reads, it follows the value through the controller and service. Prisma's query builder parameterizes values, so the injection risks are $queryRawUnsafe and $executeRawUnsafe, a $queryRaw built by concatenating strings instead of the tagged template, and a where object spread straight from req.body, which lets a caller add filters such as another author's id. Values reaching fs paths, child_process or a redirect URL get the same trace. The repository has no raw queries today, so any new one gets a close read.
It confirms authorization findings against a running app, because the existing tests cannot: src/tests/prisma-mock.ts replaces the Prisma client with mockDeep<PrismaClient>() from jest-mock-extended, so the suite never touches a database. The agent runs nx test for what it covers, then starts Postgres in the sandbox, applies src/prisma/migrations/ with prisma migrate deploy, starts the server with a test JWT_SECRET, registers two users and calls the new route with the second user's token against the first user's data, recording the status code. Last, it scans the diff for secrets. auth.ts and token.utils.ts fall back to 'superSecret' when JWT_SECRET is unset, which means anyone can sign a valid token on a server started without it; a new route or service that repeats that fallback, or any key or token committed in code or fixtures, is a finding.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Review the open pull request for security problems only. First read the auth middleware and how existing services check ownership before updates and deletes. Trace every new request parameter to where it reaches the database, the filesystem or a shell, check that each new route enforces the same authentication and ownership checks as its neighbours, and look for secrets in the diff, including hard-coded fallbacks for signing keys. Run the test suite, then start the app against a fresh Postgres with the Prisma migrations applied and confirm each authorization finding by calling the route as a second user. Post findings as inline review comments with the exploit in one sentence each and the status code you observed; post nothing if there are none. Do not push commits.",
"resources": [
{
"url": "https://github.com/your-org/your-express-app/pull/123"
}
]
}- 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.
- 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.
- 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
What comes back is either no review at all or inline comments, one per finding, on the line where the problem is: a route registered without auth.required, a service that updates by slug without comparing the author, a $queryRawUnsafe with a request value in it, a where built from the whole request body, or a new secret fallback. Each comment gives the exploit in one sentence, such as any signed-in user deleting another user's article by slug, and the fix in the codebase's own pattern.
For authorization findings the comment includes the status code the second user got from the running app, so a reviewer can tell an observed hole from a suspected one. The session's transcript keeps the nx test output, the migration and server start, and each request the agent sent with its response.
Before merging, a reviewer checks that each confirmed finding has a fix and a test that would catch it again. Because the existing suite mocks Prisma, an ownership test that runs against a real database is the most useful addition to ask the author for.
Run it on every pull request that changes a route
An automation with a GitHub trigger on pull_request.opened and pull_request.synchronize runs this review on every push. Since the prompt posts nothing when there are no findings, running it on every pull request costs reviewers nothing; to narrow it, have your server check whether files under src/app/routes/ changed before it sends the request with the pull request URL in resources.
Use the head SHA as the Idempotency-Key so a redelivered GitHub webhook does not start a second review, and set a webhook URL on the API key so run.completed tells you each review has finished, including the ones that found nothing.
The use case this job follows, with its automation and trigger: Review every pull request.
Why run it on Rush?
In an app whose tests mock the database, the only way to know whether a new route lets one user touch another user's data is to run it against a real database with two real users. That is a lot to set up for every pull request on a laptop, so most reviews read the code and guess. On Rush each review gets its own sandbox with the branch, a fresh Postgres, the migrations applied and the server running, and it is all thrown away when the review is done.
Each comment comes with the request and response that prove it, and the session keeps them, so a security finding can be checked by anyone on the team. Reviews run in parallel across pull requests, and none of them is given your real JWT_SECRET or database, because the sandbox uses its own.
Frequently asked questions
Prisma parameterizes queries. What injection is left?
$queryRawUnsafe and $executeRawUnsafe with request values in them, $queryRaw called with a concatenated string instead of the tagged template, and filter objects built from req.body, which let a caller add conditions you did not intend. The agent checks each of those on new code.
Our tests mock Prisma. How does it confirm a finding?
It starts Postgres in its sandbox, applies your Prisma migrations, runs the server with a test signing key and calls the route as two different users. The status code it gets goes in the comment, so the finding does not depend on the mocked tests.
Will it comment on the existing superSecret fallback?
The prompt limits inline comments to what the pull request changes, so an untouched fallback is mentioned in the final answer rather than as a comment. A new file or route that adds the same fallback gets an inline comment.
Does it work with NestJS or Fastify instead?
Yes. The trace is the same; only the patterns change, such as guards and decorators in NestJS or preHandler hooks in Fastify. The agent reads the existing routes first, so it compares new code with whatever the app already uses.