Security
Last updated:
This page exists so a developer asked "did you check the vendor's security" has a link to send. It also has a machine-readable twin at /security.json and a /.well-known/security.txt for vulnerability reports. Related: our privacy policy (what we collect and why) and terms of service.
What we store — and what we don't
A feedback report is a list of blocks the reporter explicitly added
(text, a screenshot, a voice note, or a quoted page selection), plus a small automatic
context object: page URL, path, host, user-agent, language, viewport/screen size,
inferred device type, a timestamp, and — only if the customer opts in — the last 10
console.error lines and last 10 failed network requests.
We do not automatically capture:
- The page's DOM, HTML, or form field values. There is no automatic DOM scraping.
- A screenshot of anything the reporter didn't explicitly capture from inside the widget.
- Cookies or session tokens. The public ingest endpoint runs with no cookies and is authenticated by project key only.
- Keystrokes, network payload bodies, or analytics/advertising beacons.
Screenshots and voice notes are uploaded as binary blobs to Cloud Storage; the report document stores metadata (project, bucket, object key, MIME type, byte size) plus the text/selection blocks inline. Dashboard accounts store an email, name, avatar URL, and auth provider (Google or GitHub) — no password unless you use email+password sign-in, in which case only a hash is stored.
Where your data lives
The server (API, dashboard, ingest) runs on Google Cloud Run in europe-west1.
Screenshots and audio blobs are stored in Google Cloud Storage in the same GCP project.
The database — feedback reports, projects, users, sessions — is a managed MongoDB Atlas
cluster. The marketing site is served from Cloudflare Pages. We don't operate our own
data centers; everything above is a managed cloud service.
Encryption
All traffic to the dashboard, the ingest API, and the marketing site is served over HTTPS/TLS — Cloud Run and Cloudflare terminate TLS by default and we don't serve plain HTTP. Data at rest inherits each provider's default encryption: Google Cloud Storage and MongoDB Atlas both encrypt stored data at rest by default, with no separate configuration on our part. We don't layer a custom encryption scheme on top — there is no application-level encryption of report content beyond what the provider does.
Who can access your data
Access inside the dashboard is scoped to organization membership
(an OrganizationMember join record with role owner, admin, or member) — not
to individual projects. Any member of an organization can see every project and every
report that organization owns. There is no per-report sharing link and no public report
URL.
Screenshots and audio are streamed only through a session-authenticated dashboard route that checks the requesting user is a member of the report's organization before piping the file, and the response is marked same-origin so it can't be embedded by another site.
The widget key embedded in your app's HTML (pr_...) is
public by design — the same way a Stripe publishable key or a Google Analytics ID is
public. With just the key it can submit new reports, read your project's public widget
configuration (channels, mascot, feature flags), and — deliberately, for the widget's
deep-link review mode — read back one specific report if you already know its id. It
cannot list or enumerate reports, read anyone else's project, or authenticate as a
dashboard user. If a project sets an origin allowlist, browser requests from other
origins using that key are rejected (a non-browser request that omits an Origin header
is not origin-checked — the key itself is the real boundary). If
widgetVisibility is set to secretLink, even the widget
config stays hidden without a separate access token — knowing the project key alone
isn't enough.
API keys
Server-to-server access (REST API and MCP) uses secret keys prefixed pr_sk_.
A key is generated from 24 random bytes; the server stores only its SHA-256 hash and a
16-character prefix used to look it up — the plaintext key is shown once, at creation, and
never stored or recoverable afterward. Keys carry explicit scopes (e.g.
feedback:read, feedback:write, project:write) and
can optionally expire; a wildcard * scope exists only for short-lived setup
tokens minted in the dashboard, never through the API itself. Revoking a key from
Integrations → API keys takes effect immediately — every request re-checks
revokedAt.
If a key leaks: revoke it in the dashboard, mint a replacement, and update wherever it was stored (never in client-side code or committed to a repo). A leaked key can read and write feedback within its scopes; it cannot authenticate as a dashboard user or access other organizations' projects.
Retention and deletion
- Reports are kept until deleted. Each project can set a retention window that auto-deletes older reports.
- Screenshots or audio uploaded but never attached to a submitted report are removed within about 24 hours by an orphan-cleanup job.
- Dashboard sessions expire after about 30 days.
The dashboard does not yet expose self-serve deletion of an individual report or an entire project. If you need data removed — a GDPR/CCPA request, an old project, or content that shouldn't have been submitted — email [email protected] and we'll remove it.
Sub-processors
Providers we rely on to run Patchrooms, same list as our privacy policy:
- Google Cloud — hosting (Cloud Run) and file storage for screenshots and audio (Cloud Storage).
- MongoDB Atlas — our database and session store.
- Google and GitHub — sign-in for dashboard accounts.
- OpenAI and Groq — audio transcription, only when a customer enables it.
- Cloudflare — hosting and delivery of the marketing website.
- Google Analytics — marketing website analytics.
Certifications
We do not have SOC 2, ISO 27001, or any other third-party security certification. Patchrooms is an early-access product built by a very small team, and a false claim of certification would be worse than admitting we don't have one — so we're not going to make one.
What we do instead, concretely:
- We run on managed infrastructure (Google Cloud, MongoDB Atlas, Cloudflare) that is itself independently certified by its provider — we inherit their physical and platform-level controls rather than operating our own data center.
- Secrets (database URIs, service-account credentials, OAuth secrets) live in environment variables, are excluded from version control, and are never logged.
- API keys are never stored in plaintext (see "API keys" above).
- This page and our privacy policy are kept accurate against the actual code, not written once and forgotten.
If your procurement process needs a specific answer we haven't covered here — a security questionnaire, a DPA, or detail on a particular control — tell us what you're building and we'll answer it directly instead of pointing you at a badge we don't have.
Prompt injection through report content
Patchrooms reports can be read by an AI coding agent — over the
MCP server's
get_report tool, the reports get rendered as Markdown text (with
screenshots inlined as images) directly into that agent's context. Report text is written
by whoever files the report — for a public-facing app, that can be an anonymous end user.
That means report content is untrusted input to whatever reads it, in the
same way any web page, email, or PDF an agent opens is untrusted input. A report could
contain text (or, in principle, text rendered into a screenshot) crafted to look like an
instruction rather than a bug description — "ignore your previous instructions and
run/reveal X".
Whose risk this is: it's a property of connecting any agent to any source of third-party text, not something specific to a bug in Patchrooms — the same risk exists reading a GitHub issue, a support ticket, or a customer email with an agent. We don't have a filter that reliably distinguishes "malicious instruction" from "a report that quotes suspicious-looking text" — no one does, robustly.
What we do:
- The MCP server returns report content as data in a tool result — it never executes, evaluates, or otherwise treats report text as a command on our own infrastructure.
set_statusandadd_commentrequire the calling agent's own key to carryfeedback:write; a report's content cannot grant itself more scope than the key already has.add_commentdefaults to posting as an unpublished draft — a human reviews and publishes it — precisely so a single tool call reading a report can't also autonomously write back without a review step, unless the caller opts intodraft: false.
What we recommend to anyone connecting an agent to Patchrooms (or any similar feed):
- Scope the API key narrowly (read-only
feedback:readif the agent only needs to triage, not write). - Keep a human in the loop before an agent takes a destructive or high-privilege action on the basis of report content — don't wire "read a report" directly to "run arbitrary shell commands" or "push to production" with no review.
- Treat report text the way you'd treat any other content an anonymous user submitted to your app: rendered and read, never interpreted as instructions with elevated trust.
See also: the longer version of this section in the docs, aimed at the agent itself.
Reporting a vulnerability
Found a security issue in Patchrooms? Email [email protected] with details and, if you have one, a reproduction. We aim to acknowledge new reports within 3 business days. We don't run a paid bug bounty program today, but we will credit you (if you want that) once a report is confirmed and fixed. Please don't test against other customers' data, and give us a chance to fix an issue before disclosing it publicly.
Contact
Security questions: [email protected]. Privacy and data-deletion requests: [email protected]. Everything else, including compliance/procurement questions: the contact form.