---
name: write-finding-poc
description: Write a web finding's final PoC, the one script a triager runs - a single Python file that reproduces the finding end to end from a clean start and prints only the decisive result - and decide what else is attached. Use whenever you write or update a finding's PoC, such as when a finding is valid, on a retest that still reproduces, or when asked for a PoC.
---

# Write the finding PoC

The triager runs one command and sees the result the target returned or caused. When a `validate-*` skill covers the finding's class, it sets the PoC's shape where it differs from this, so read it before writing: an authentication bypass into a web UI, for one, gets a browser-console snippet instead of a Python file.

## The file

- One final PoC in the finding's `work/scratch/<finding-id>/` directory, beside its report: a short kebab-case name for the finding, never `poc.py`.
- Python with PEP 723 metadata, `curl_cffi` for HTTP and `websockets` for WebSockets. Always set `verify=False` for HTTP requests, and add an optional `--proxy` when useful.

## Default run

- The default run reproduces the finding end to end from only documented inputs: it establishes every automatable prerequisite, triggers the issue, prints only the decisive result returned or caused by the target, and exits non-zero when that is absent or mismatched.
- The result is readable text, such as the command output, leaked value or changed record, not a JSON dump of the responses. Text the PoC generates itself, such as `success` or `proof`, is not a result. Progress and diagnostics go to stderr.
- For OOB evidence, print the executed command and the matching provider fields: the token or hostname, event type, observation time and source.
- It works from a clean start, not from leftovers of testing, and requests no more than it needs. Redact only when copying its output into the report.

## Prerequisites

- Automate account creation, login, verification, discovery of ids or tokens, and other setup where possible. If one gate is genuinely manual, document the exact minimal actions and inputs, and make the PoC diagnose an incomplete gate precisely.
- When account creation cannot be automated, hardcode the researcher-owned test account's credentials as the PoC's defaults, with optional arguments such as `--user` and `--pass` for another account.
- Callbacks and mailboxes use the service the validator instructions name for a triager-facing PoC.
- A `toadcli tunnel` lasts only while this worker runs, so a final PoC that needs a public TCP, TLS or UDP listener uses the `pinggy` Python SDK instead. Keep the tunnel object and process alive until the PoC finishes, then call `tunnel.stop()`.

## Attachments

- The PoC is the one attachment. A file it consumes and cannot reasonably embed, such as a vendor's client library or a crafted document, is hosted, not attached: `toadcli report host FILE` prints a link and the file's SHA-256, and the PoC downloads the file from that link and checks the hash.
- When the engagement requires a PoC video, record `<name>.mp4`, matching the PoC's stem, with the `record-poc-video` skill.
- Beyond a matching video, add a supporting file only when reproduction is otherwise impossible. Never attach alternative PoCs, captured output, summaries, request or response dumps, logs, scratch notes, copied source or dependency files: the PoC's output goes in the report, and PEP 723 supplies dependencies.
- `toadcli finding show` lists files the hunter attached and `toadcli finding attachment ID -o FILE` saves one. They are input.

## Before publishing

Run the PoC from a clean start and confirm it prints the result. Its first non-proxy `uv run` command in the report is the one run in isolation and, when a video is required, recorded.
