What the agent does
The repository is digininja/DVWA, a PHP and MariaDB app. The Dockerfile is FROM php:8-apache, enables rewrite, installs gd, mysqli and pdo_mysql, and copies config/config.inc.php.dist to config/config.inc.php. compose.yml pulls ghcr.io/digininja/dvwa:latest with pull_policy always, publishes 127.0.0.1:4280 to port 80, and starts mariadb:10 with MYSQL_DATABASE dvwa. The published image is not the clone. To audit the files in the sandbox the agent builds the Dockerfile (pull_policy build) and keeps the bind on 127.0.0.1, then runs the setup page once so the users table exists. The README says not to put this app on a public host. The sandbox's localhost is the whole deployment.
The security level decides which source file runs. config/config.inc.php.dist sets default_security_level from DEFAULT_SECURITY_LEVEL or impossible. security.php accepts low, medium, high or impossible and stores it with dvwaSecurityLevelSet. The low sources are the ones the audit requests, so the agent sets the level to low after login and says so in the report. vulnerabilities/sqli/source/low.php sets $id from $_REQUEST['id'] and runs SELECT first_name, last_name FROM users WHERE user_id = '$id', and on failure it prints the mysqli error. vulnerabilities/exec/source/low.php passes $_REQUEST['ip'] to shell_exec of ping. vulnerabilities/upload/source/low.php moves the upload to hackable/uploads/ using the basename of the original filename, with no type check.
Authentication is login.php. It runs stripslashes on the username and password, then mysqli_real_escape_string, then md5 on the password, then interpolates both into SELECT * FROM users WHERE user='$user' AND password='$pass'. The order is the finding: stripslashes runs before the escape. The agent records login.php and that query, sends one login request against localhost that demonstrates the interpolation, and records the response. It also notes phpinfo.php, which calls phpinfo() after dvwaPageStartup with authenticated, so a logged-in session can dump the PHP build. That is sensitive data exposure with a file and a line, not a reason to paste the phpinfo body into the report.
The default database password is in two places the agent cites and does not spread further: config/config.inc.php.dist sets db_password from DB_PASSWORD or the dist default, and compose.yml sets MYSQL_PASSWORD to that same default, with MYSQL_ROOT_PASSWORD dvwa. disable_authentication in the dist config can turn the login off. Broken access control beyond the level switch includes the modules under vulnerabilities/bac/ and vulnerabilities/authbypass/, which the agent opens and, where a request shows another user's data, writes the file, the line and the response. No PHP file is edited. The fix for each finding is a sentence: a prepared statement in the low SQL source, escapeshellarg or no shell in the exec source, an allow-list on the upload, and removing phpinfo.php.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "The repository is already in the sandbox if resources cloned it. Build it from the Dockerfile (php:8-apache), do not rely on the prebuilt ghcr.io/digininja/dvwa image, and bind it on localhost only, with MariaDB as compose.yml describes. Set the security level to low after setup. Audit injection, broken access control, authentication flaws and sensitive data exposure. Start from vulnerabilities/sqli/source/low.php, vulnerabilities/exec/source/low.php, vulnerabilities/upload/source/low.php, login.php, phpinfo.php and config/config.inc.php.dist. For each finding write the file and line, one request against the running app, its severity and the fix, in security-audit.md. Change no code. Do not expose the app off localhost.",
"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
The result is security-audit.md as a session artifact: name, mime type, and a url on api.prix.dev that fetches the file with no key until url_expires_at. A later read of the session refreshes the url. GET /o/{org}/sessions/{id}/artifacts/security-audit.md with the key returns the file. The git tree is unchanged. The containers stay inside the sandbox and are gone when the sandbox is released.
Findings are grouped the way the prompt asked: injection (the low SQL source and the login query), command handling (the ping shell_exec), access control (the bac and authbypass modules the requests actually demonstrated), authentication (the stripslashes-then-escape order and the security-level switch), and sensitive data (phpinfo.php and the default database password in the dist config and compose file). Each one has the request that was sent to localhost and the response. A module that did not reproduce is not given a fake request.
Review is reading the cited line next to the response. DVWA ships documented vulnerabilities so this comparison is possible; the report is the agent's list, not a paste of the project's help pages. The transcript has the docker build, the setup request, the security-level change and each proof request. A reviewer who wants the impossible level checked sends a follow-up after the turn ends rather than treating the low-level findings as the whole app.
Run it on every before a class or a lab
A scheduled job POSTs this body to https://api.prix.dev/o/{org}/sessions the day a lab starts, with an Idempotency-Key of the date. The repository in resources must be one the Rush GitHub App is installed on, or the start answers 422 RESOURCE_NOT_ACCESSIBLE. One repository per run. A second lab image is a second session, not a second URL in resources.
An admin sets webhook_url on the key. Rush sends run.completed, run.failed or run.cancelled within about 30 seconds, signed with the whsec_ secret. Download security-audit.md from the session. The webhook result is capped at 16 KiB. Keep the app on localhost in the prompt so a lab run cannot become a public DVWA, which the project's README warns against.
The use case this job follows, with its automation and trigger: Produce a report or file, no repo.
Why run it on Rush?
DVWA wants PHP, Apache and MariaDB, and the compose file pulls a prebuilt image unless you build the clone. On a laptop that is a daemon and a port, and the README tells you not to face it at the network. The sandbox builds the Dockerfile, binds localhost, sends the proof requests, and keeps the output. The working tree you use for other work is not the one running Apache.
The report is a list you can mark. Each finding names a file and a line in this clone, plus the response from the app in that sandbox. Several vulnerable apps can be audited together, each session isolated, up to max_concurrent on the key (default 4, then 429). After the turn the sandbox parks, costs nothing while parked, and is kept for 7 idle days, so the next lab day can send a follow-up that sets the level to medium and appends those findings.
Frequently asked questions
Will it patch the PHP files?
No. The prompt says change no code. The fixes are sentences in security-audit.md. The session's changed files should be empty. If a setup script writes config/config.inc.php from the dist file, the report says that file was created by setup and is not a finding fix.
Why build the image instead of pulling ghcr.io/digininja/dvwa?
compose.yml sets pull_policy always and the image name ghcr.io/digininja/dvwa:latest, and the volume that would mount the clone is commented out. Pulling that image would audit a build that might not match the files in the sandbox. The Dockerfile in the clone is php:8-apache and copies this source, so the line numbers in the report match the tree.
The default level is impossible. Does the audit still see the low sources?
Only after the agent sets the level. config.inc.php.dist defaults default_security_level to impossible, and security.php is what switches it to low. The report has to say the level it set, because vulnerabilities/sqli/source/low.php is not the file the app runs at impossible.
Can I run this against our production PHP site?
This prompt sends proof requests at an app that is vulnerable on purpose, inside the sandbox, on localhost. That is the wrong prompt for a site with real users. For your own application, ask for a read of the code and a written finding, and name the host only if you control it and the requests are safe to send.