What the agent does
The repository is juice-shop/juice-shop, Juice Shop 20.3.0. package.json sets engines to Node 22 through 26, start to node build/app, and serve to tsx app.ts plus the Angular dev server. The frontend package depends on @angular/core ^22.0.1, Angular Material and the CLI at the same range, and TypeScript ~6.0.3. The API is Express ^4.22.1, Sequelize ^6.37.3 and sqlite3, with 61 route modules under routes/. config/default.yml sets server.port to 3000 and application.domain to juice-sh.op. The Dockerfile builds on node:24 and runs as a non-root user with CMD /juice-shop/build/app.js, exposing 3000. In the sandbox the agent runs npm install and npm run serve so the API and ng serve are the process it can send requests to on localhost, and it does not publish that port.
Injection is read off the route files before any request is sent. routes/search.ts builds the product search with a template string: models.sequelize.query of a SELECT whose LIKE clauses interpolate criteria, which comes from req.query.q, truncated at 200 characters. The file marks that line as the union SQL challenge. routes/login.ts does the same with email: SELECT * FROM Users WHERE email = '${req.body.email}' AND password = the hash of the body password. The agent records the file and the line, then sends one request to the running app on localhost that shows the query is not parameterized, and records the response in the finding. It does not need a second copy of the exploit in the report beyond that request and the response.
Authentication and data exposure are the same pass over the code, then a request. lib/insecurity.ts imports jsonwebtoken 0.4.0 (the version pinned in package.json; express-jwt is 0.1.3) and signs with an RSA private key that is a string constant in that file, algorithm RS256, expiresIn 6h. The report names the file and the line and does not paste the key. routes/updateProductReviews.ts updates a review by _id: req.body.id, which the file marks as the NoSQL review challenge. ftp/ is copied into the image and is the place the agent looks for files that should not be downloadable. config/default.yml also holds a Google OAuth client id and a chatbot config pointed at localhost:11434; those are configuration findings, not a reason to start Ollama.
Broken access control is the routes that trust an id from the body. routes/payment.ts loads cards with UserId: req.body.UserId. routes/order.ts decrements a wallet for req.body.UserId. The agent reads the check, or the lack of one, then sends a request as one user against another user's id and writes the status code. The prompt says change no code, so there is no fix commit. Each finding's fix is one sentence in security-audit.md: a bound parameter, a key loaded from the environment, or an owner check on the route.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Clone is not required if the repository is already in the sandbox. Read package.json (Juice Shop, Node 22-26, npm run serve), frontend/package.json (Angular 22), config/default.yml (port 3000) and the routes under routes/. Run the app on localhost only. Audit injection, broken access control, authentication flaws and sensitive data exposure. Start from routes/search.ts, routes/login.ts, lib/insecurity.ts, routes/payment.ts, routes/order.ts and routes/updateProductReviews.ts. For each finding write the file and line, one request that shows it against the running app, its severity and the fix, in security-audit.md. Do not paste private keys. Change no code.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- 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
security-audit.md is an artifact on the session, with a name, a mime type and a url on api.prix.dev. The url downloads that one file with no key until url_expires_at, about an hour. Read the session again for a new url, or GET /o/{org}/sessions/{id}/artifacts/security-audit.md with the key. The repository has no new commit. Git status in the transcript should be clean.
Each finding is file, line, the request that was sent to localhost, the response that shows the issue, a severity, and a fix that was not applied. A route that turned out to check the owner is omitted rather than padded in. The file's own list is the count.
A reviewer judges the audit by re-reading the cited line and the recorded response, not by trusting the severity word. The fix sentences are suggestions. The trajectory has the commands (npm install, npm run serve) and each request, with output, so a finding whose response does not match the line is visible. Juice Shop is insecure on purpose; the report is useful because the lines are real and the requests were sent in the sandbox, which is the only place this app should run.
Run it on every new release of the app
When a release tag is pushed, your CI job POSTs this body to https://api.prix.dev/o/{org}/sessions with resources set to the repository and the tag in the Idempotency-Key. One repository per run. The Rush GitHub App has to be installed on that repo or the resource is 422 RESOURCE_NOT_ACCESSIBLE. A retried notification with the same key and body returns the same run.
Point the API key at a webhook_url from an admin session. Rush POSTs run.completed when the turn finishes, within about 30 seconds. The audit file is an artifact; data.result stops at 16 KiB, so the handler downloads the artifact from the session. Verify webhook-signature with the whsec_ secret. Juice Shop must stay on localhost inside the sandbox. Do not add a port publish or a public URL to the prompt.
The use case this job follows, with its automation and trigger: Produce a report or file, no repo.
Why run it on Rush?
Running Juice Shop on a laptop means a Node process, an Angular dev server and a browser on your machine, and a working tree you might forget to reset. The sandbox runs npm run serve there, sends the proof requests to localhost, and keeps every command and response on the session. Your machine never has the app bound, which matches the project's own warning that it is not a template to copy into production.
The findings are checkable. Each one cites a file and a line you can open, plus the request the agent sent and the response it got. Several apps can be audited at once, each in its own sandbox, until the key's max_concurrent (default 4) answers 429. The sandbox parks afterward, costs nothing while parked, and stays for 7 idle days, so a follow-up can ask for one route to be re-checked without installing Juice Shop again.
Frequently asked questions
Will it open a pull request with the fixes?
No. The prompt says change no code, and the result is security-audit.md. The fixes are sentences next to the findings. A later run can apply one fix and open a pull request; this one does not, so the report can be scored against the known lines.
Does the Angular version matter?
The UI is Angular 22, from frontend/package.json (@angular/core ^22.0.1). Most of the issues this audit is aimed at are in the Express routes and lib/insecurity.ts, not in a component template. The agent still reads the frontend enough to see which API the screens call, and it runs ng serve only because npm run serve starts it beside the API.
Why not just read the official solutions file?
The repository contains a solutions document and vuln-line comments. The audit is still required to send a request to the running app and record the response. A comment that says a line is vulnerable is a hint. The finding is the response. The agent does not copy the solutions file into security-audit.md.
Is it safe to point this at our real Angular app?
The proof requests are written for an app that is insecure on purpose, and they stay on localhost in the sandbox. Pointing the same prompt at a production app you do not own is not this job. For your own app, change the prompt to read and report, and do not include a request that would change data.