Security at Bounty Operator
Where your code and keys go during a review, what is stored, which headers are in force, and how to report a vulnerability.
Data flow: browser, Worker, provider
-
Selects, checks, shows
- You choose the files. The privacy check runs here, and the preview shows the request before it leaves the tab.
- GitHub imports go straight to
api.github.com. - Prompt export of a core profile ends here. Nothing is sent.
-
Verifies, adds the method, forgets
- Checks your session, your allowance and the inputs again.
- Adds the review method of the profile you chose, sends the request with your API key to one fixed endpoint of your provider and streams the answer back.
- Holds the files, the key and the answer in memory for that one request. Writes none of them to storage or logs.
-
Runs the model
- Receives your files, your request, our review instructions and your API key.
- Bills your key and keeps data under its own terms.
The review engine is one set of modules. The browser, the Worker and the MCP server import the same files, and they are in the public repository with the three core profiles. The method of every other profile is not in the repository: the Worker adds it when a review runs.
For a core profile the preview is the whole prompt your model receives. For a hosted profile it is the request: your focus, your context and the files, line by line. The Worker adds the method after that, and the provider you chose is the only other party that receives it. No page, download or API response returns it.
A review started from a coding agent takes the same path: run_review sends the files and your provider key to the Worker, and steps 2 and 3 are unchanged.
What is stored and what is not
Never stored
- File contents and file names
- Instructions and evidence notes
- Model API keys and GitHub tokens
- Review output
- Raw IP addresses
- Card details
- Passkey private keys
Stored
- A random account identifier
- Passkey public keys, counters and labels
- SHA-256 hashes of the recovery code, session tokens and connection tokens
- Per review: status, profile, channel and time, for seven days
- Stripe customer and subscription IDs, status, paid-until time and receipt email
- A keyed hash of your IP address for rate limits, for up to a day
- Daily page and funnel totals with no account identifier
The privacy policy has the full list with retention times.
Headers and CSP in force
The Worker sets these on every response, pages and API alike:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self' https://api.github.com https://openrouter.ai; font-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'
Strict-Transport-Security: max-age=31536000; includeSubDomains
Cross-Origin-Opener-Policy: same-origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Permissions-Policy: camera=(), microphone=(), geolocation=()
What the policy means in practice:
Scripts and styles load from this origin only. There is no inline script, no inline style and no
eval, so injected markup has nothing to run.The browser can connect to three hosts: this one,
api.github.comfor imports andopenrouter.aifor the OpenRouter connection.The site cannot be framed, and forms post to this origin only.
Model output and pasted text are treated as untrusted. The app writes them with
textContentand never parses them as markup. Downloaded packets have remote images and raw HTML neutralised.
Check it yourself:
curl -sI https://bountyoperator.com/ | grep -i -E "content-security|strict-transport|cross-origin|x-frame|referrer|x-content|permissions"
Passkey-only accounts
Passkeys only
No password exists to phish, reuse or leak. Sign-up asks for no email address. User verification is required on every sign-in.
Secrets stored as hashes
Session tokens, connection tokens and the recovery code are 256-bit random values. We keep their SHA-256.
Hardened cookies
The session cookie is __Host-prefixed, HttpOnly, Secure and SameSite=Lax. It lasts up to 30 days.
Origin and CSRF checks
A state-changing request from the browser has to come from this origin. Signed-in requests also carry a per-session CSRF token.
Fresh passkey for sensitive actions
Rotating the recovery code, adding or removing a passkey, creating a connection token and deleting the account need a passkey check from the last 10 minutes.
Recovery resets everything
Using the recovery code replaces it and signs out every session and connection token. Removing a passkey signs out the other sessions.
Tokens, provider calls and billing
Connection tokens
A connection token starts with bok_ and lets an AI client do two things: read your usage and run a review. It cannot reach billing, passkeys, recovery or deletion. A token lasts 90 days, an account holds three, and revoking one takes effect at once.
Provider calls
Each provider has one fixed endpoint. The Worker refuses redirects, stops when the provider has been silent for 180 seconds, and caps the answer at 2 MB. Your API key is used for that request and is redacted from any error message.
Hosted profiles
The Worker reads the answer to a hosted profile as it streams, in memory, before it reaches you. An answer that repeats the method of the profile is stopped and the call ends with the code output_withheld. A file or a request that asks the model for its instructions is treated as data.
Input checks
A review takes up to 50 text files and 240 KB in total. Before anything is sent, the engine scans every line for API keys, access tokens, wallet keys and seed phrases, and blocks the request when it finds one. Email addresses and IP addresses are warnings you confirm. A finding names the file, the line and the kind of match, and never the matched text.
Billing
Payment runs on Stripe Checkout, so card details never touch our application. Webhooks are signature-checked, and paid access is recomputed from Stripe’s current state on every event, including refunds and disputes.
Report a vulnerability
Email security@bountyoperator.com.
Or use GitHub private vulnerability reporting. Both reach the maintainers privately.
Include:
- the affected surface, and the version or the time of the request
- a minimal reproduction
- the impact: what an attacker reads, changes or spends
- a proposed fix, if you have one
Remove secrets and other people’s data from anything you attach.
In scope
- The hosted site
- The Worker and its API routes, passkey sign-in and recovery, sessions and CSRF, connection tokens, quota and concurrency limits, billing state, the MCP endpoint, the content security policy and the static pages.
- The review engine
- Input checks, request assembly, review parsing and packet rendering. A way to make model output or pasted text execute script, load a remote resource or leave the page is a vulnerability.
- The MCP server
- The
bounty-operator-mcppackage and the endpoint at/api/mcp. - The CLI
- The
bounty-operator-kitPython package: the sanitizer, theai-reviewrequest path, file reads and writes.
Testing rules
- Use accounts you created. Leave other accounts alone.
- Keep request volume low. No denial-of-service testing and no automated scanning of the production site.
- Do not test payments against the live checkout. The billing logic runs locally with billing disabled.
- Stop at the first proof of a problem and report it.
The full policy is in SECURITY.md. The machine-readable contact is at /.well-known/security.txt.
Source
The source is public under the MIT licence: the Worker, the review engine with its three core profiles, the free tools, the MCP server and the command-line kit. The gauntlet stages and the panel cross-examination run on the hosted service. The licences page lists every dependency. The changelog lists what shipped and when.
A Worker built from the repository runs the hosted profiles on short stand-in instructions, and its /api/health reads "profiles": "community". On this site it reads "profiles": "hosted".