Privacy
Monitor stores other people's crashes. This page is the whole of what that means: what arrives, what is thrown away before anything is written, what is kept and for how long, and what leaves this platform.
Last read against the code on 29 August 2026. Written by the person who wrote the software, from the software. No lawyer has reviewed it, and it contains no legal terms that one would have written.
Two different sets of people
They are affected by different things and it is worth separating them before anything else.
You, the account holder. Monitor stores your email address, an optional display name, a password hash, when you last signed in, which organizations you belong to and in what role, and an audit log of the changes you made. Nothing else. There is no profile, no phone number, no billing address, and no analytics on any screen of this product.
The people using your app. They never interact with Monitor and never agreed to anything with us. What reaches us about them is whatever your app's crash reports happen to contain, which is why most of this page is about narrowing that.
What an event carries when it arrives
The Sentry SDKs decide what to send. This is what Monitor reads out of it and writes down.
- The exception chain. For each error in the chain: the type, the message, whether anything caught it, what captured it, a thread id, and the stack frames. A frame carries a function name, file, module, package, line, column, whether it is your code or a framework's, an instruction address for native frames, and the source context lines when the SDK sent them.
- Breadcrumbs, up to a hundred: a timestamp, category, level, message and an arbitrary data object, exactly as your app recorded them.
- Device, OS, app and runtime. Manufacturer, model, family, architecture, whether it is a simulator, memory, orientation, battery level, screen size; OS name, version, build, kernel version and whether the device is rooted; app identifier, name, version, build, start time and whether it was in the foreground.
- Release, distribution, environment, platform and the transaction or route name.
- Tags and extra, up to a hundred keys each, whatever your app put in them.
- An actor key and label. Only user.id or user.username, whichever the SDK sent. This is what makes "forty crashes, eleven people" possible.
- Debug images, the loaded modules and their addresses, which is what makes a native address resolvable at all.
- Four fields of the request, for web events: url, method, query string, and headers filtered by an allowlist. See below, because this is the part where the interesting decisions are.
- The browser, derived here from the User-Agent header rather than sent. The JavaScript SDKs do not send a browser context at all.
- The SDK name and version, and a locale.
Release health, when it is on, additionally stores sessions: a start time, a status, a duration, an error count, the release and the environment. Server side SDKs report these as per minute counts with no identity in them at all, and Monitor stores them in that shape.
What is dropped before anything is written
These are refusals at the normalizer, not fields hidden in a dashboard. The data does not reach storage.
request.data is read and discarded. It is the single most likely place for a password, a card number or a customer record to be sitting when something throws, and Monitor has no feature that needs one.
Headers are kept by an allowlist rather than removed by a denylist. Eleven names are on it: user-agent, referer, accept, accept-language, content-type, host, origin, x-forwarded-host, x-vercel-id, x-matched-path and x-nextjs-cache. Everything else is dropped, including cookie and authorization, and the direction of that list is the point: a denylist is a promise to have thought of every header a framework might invent, and the cost of being wrong is a credential in a database.
Monitor never records the network address an event was sent from. Nothing in the ingest path reads the connection address or a forwarding header for it, and there is no geolocation anywhere in this product. If your SDK sends user.ip_address or user.email, which it does when sendDefaultPii is on, the normalizer keeps only user.id or user.username and drops the rest. The stored field is called an actor rather than a user on purpose: the thing a crash is attributed to is usually an install id, and calling it a user invites storing more of one.
A JavaScript source map contains sourcesContent, which is the entire original text of every module the bundle covers. Storing a map as uploaded would mean storing a copy of your application. The extractor drops it, along with ignoreList, keeps only the file names, the symbol names and the mappings, and the uploaded file itself is discarded once it has been read. The same is true of a dSYM: what is kept is an address to function, file and line table, and the 134 MB artifact it came out of is not stored anywhere.
What leaves this platform
Three destinations, two of which only exist because you configured them.
- Alert email. Sent through Resend, to the addresses you list on the project's alert settings. An alert carries the exception type, the message, the culprit, the project name, the release, how many events and how many people, and a link. The stack trace is deliberately not in it: it is the most useful thing about a crash and also somebody's data sitting in a mailbox and in a provider's logs forever, so the mail carries the shape of the problem and the link carries the detail. Reading the link needs an account. Nothing about the message body is written to our logs, not even the subject line, because a subject line here is an exception message.
- Slack. Only if you paste an incoming webhook url into the project's settings. The same facts as the email, to the channel that webhook belongs to. There is no Slack app, no OAuth and no token stored.
- An upstream Sentry, if you set one. This one deserves a paragraph of its own, below.
Nothing else is contacted from any code path. On monitor.unrealops.ai, which is this site and the dashboard behind it, there is no analytics service, no logging service, no session replay, no object storage and no tracking script of any kind, and no cookie other than the session cookie that signs you in. The one client side script that does exist is the Sentry SDK Monitor uses to watch its own application, pointed at its own ingest: it sends nothing unless this dashboard throws, and the configuration that governs it is described at the end of the next section. The separate marketing site at unrealops.ai does carry a cookieless page analytics beacon, and it holds none of your project data.
The only third parties are the ones that make the service exist: Vercel hosts it, Turso holds the database, Resend sends the mail, and Stripe would take a payment except that billing is not configured on this deployment and nothing can be bought.
What we cannot promise, and what helps
The honest limit of everything above.
Monitor cannot tell you that your app is not putting a secret in an error message. An exception message is a string your code produced, a breadcrumb is a string your code recorded, and a tag is a key and a value your code chose. All three are stored as sent. If a query builder throws with the failing statement in the message, that statement is now in a crash report. If a breadcrumb logs a request payload, so is the payload. Nothing at this end can distinguish those from an ordinary error, and any product claiming it can is guessing.
The only filter that can stop data leaving at all is the one that runs on the device, before anything is transmitted. That is your SDK's beforeSend, and it is worth ten minutes on the first day.
SentryFlutter.init((options) {
options.dsn = const String.fromEnvironment('SENTRY_DSN');
// The SDK default. Leave it off unless you have decided you want
// the IP address and the user identity in your crash reports.
options.sendDefaultPii = false;
options.beforeSend = (event, hint) {
// Whatever your app must never send. This runs on the device,
// before anything is transmitted, so it is the only filter that
// can stop data leaving at all.
return event;
};
});Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
sendDefaultPii: false,
beforeSend(event) {
return event;
},
});The settings on this end that narrow what is stored, all of them off until you turn them on:
- Symbolication is off per project. A new project stores nothing beyond its events. Native frames still appear, marked unsymbolicated.
- Server source maps are a second switch under it, off by default. With it off, nothing from .next/server is uploaded at all.
- Turning symbolication off deletes the symbols already stored, and says how many bytes that freed.
- Retention is shorter on smaller plans, and it is enforced rather than published. See retention and deletion.
Monitor also watches itself through the same public SDK you would use, pointed at its own ingest, and the configuration that does it scrubs the request body, cookies and query string before an event leaves the process, because every payload this service handles belongs to somebody else. That is in the repository at apps/web/src/monitor.ts if you want to see the shape of a strict one.
Where the data physically is
Two providers, and no copies anywhere else.
Events, issues, sessions, symbol indexes and account rows are all in one hosted libSQL database at Turso. The application runs on Vercel. The ingest endpoint and every dashboard screen are served over HTTPS. Whatever encryption at rest those providers apply is theirs; Monitor adds none of its own, and the pages here will not claim a control that is not in the code. The one value stored deliberately in the clear is your project's DSN public key and any upstream Sentry DSN you set, both of which ship inside your own application by construction.
The provider regions this deployment currently runs in are the United States. The database runs in AWS us-west-2 (Oregon) and the application runs on Vercel in pdx1, also Oregon . There is no data transfer arrangement, no subprocessor agreement and no data processing addendum to sign, because none has been written. If you need one before you can point an app at this, say so and it will be an honest conversation rather than a document.
Getting your data out, or removed
One of these exists today and one does not.
Removal exists. You can purge a project, which deletes its rows for real, and you can delete an organization, which deletes everything beneath it including the accounts whose only membership it was. Both are described step by step on retention and deletion.
Export does not. There is no export endpoint, no download button and no documented read API. If you want your events out before you delete them, there is nothing here that does it for you today. Ask, and it will be done by hand or not at all, rather than promised as a feature.
Questions about any of this go to tc@devtc.pro.