PhishTriage — Privacy Policy

Effective date: 2026-08-18 Version: 1.5 Applies to: PhishTriage browser extension (Chrome, Edge, Firefox, Brave, Safari) version 1.0.0 and the default backend services at https://api.phishtriage.com and — only with file protection switched on — https://filescan.phishtriage.com.

This line used to stop at Brave, while the file-protection section below discussed Safari as a platform the feature does not reach. A build we ship was missing from the list of builds this policy covers, so here it is, with the two ways it differs: the Safari build has no file protection and no administrator policy. Safari implements neither the download APIs the first needs nor the managed storage the second is delivered through, so every sentence in this document about file protection, and every sentence about what an administrator can set for a managed fleet, describes the other four builds and not that one.

A second client reaches the same backend, and no version of this document has said so. api.phishtriage.com also answers the PhishTriage message filter on iOS, which sends text messages for a verdict. It is not the browser extension, and nothing in the extension can read your messages on any platform. But it is the same proxy, the same analysis path and the same operator, and an entire content-bearing surface was described nowhere. It is described here now, under Text messages. That section covers the message filter and does not attempt to be a full policy for the iOS app.

TL;DR

This paragraph used to offer "one number to remember: activeTab as the only required permission". The manifest inside the package a reviewer downloads says otherwise, so here is the real list.

Required at install: activeTab, storage, scripting — plus host access to four mail hosts (mail.google.com, outlook.office.com, outlook.office365.com, outlook.live.com), which the extension declares as static content-script matches and which your browser therefore grants when you install it, naming them in the install prompt.

Optional, requested only when you switch on the feature that needs them, and revocable from your browser's extension settings — chrome://extensions → PhishTriage → Details → Permissions on Chrome, Edge and Brave, about:addons → PhishTriage → Permissions on Firefox: webNavigation for threat monitoring, downloads for file protection, and <all_urls> for either — both features ask for it, and turning on just one of them is enough to be prompted for access to every site. With neither switched on, no host access beyond the four mail hosts above is ever requested.

Who we are

PhishTriage is operated by the PhishTriage team. For privacy questions, data-deletion requests, or anything else covered by this document, write to [email protected].

The data controller (for purposes of GDPR / UK GDPR) is the PhishTriage team. If your organization has deployed the extension with a self-hosted proxy (see "Self-hosting" below), the data controller is your organization — not us — and this document only describes the client-side behavior; ask your IT or DPO for their corresponding policy.

What the extension collects, and when

When you click "Analyze" (on-demand triage)

Pressing the Analyze button in the popup, in the sidebar, or in the toolbar of Gmail or Outlook Web is what causes the page or message in front of you to be read and sent for a verdict. When you press it, the extension reads the tab — through the activeTab permission on an ordinary page, and through the content script already loaded on the four mail hosts, which needs no activeTab — and sends a request to the configured proxy (default api.phishtriage.com) containing:

The payload also carries your pseudonymous ID (see below) and, if you've registered or linked an account, your extension ID. The proxy passes this content to a model and returns a structured verdict (risk score, IOCs, recommended actions). Which model, on whose hardware, is set out under Where analysis happens.

This paragraph used to read "the proxy forwards a sanitized version of this content to Anthropic's Claude API for analysis", and both halves need correcting. The first half is the routing: Anthropic is no longer the first thing asked and is often not asked at all. The second half is the word sanitized, which was carrying an implication it cannot support. It reads as though we strip something out for your sake. What it actually names is a pass that removes prompt-injection markers — [INST], <|…|>, Claude turn delimiters, XML-ish <system> tags, and lines that begin like an instruction ("IGNORE ALL PREVIOUS…") — before a model reads the content, plus the length caps stated above. It exists to stop a hostile email from giving orders to the analyst. It removes those patterns, and where a line begins like one it takes the rest of that line with it. Nothing is removed for being about you: no address, no URL and no name is stripped on privacy grounds. What reaches the model is what is listed above, minus injection markers.

If you do not press Analyze, no page text is read or sent for an Analyze verdict. URL or domain monitoring may still send the current URL or hostname for a threat verdict, and file protection may process downloads, as described below.

Analyze is not the only thing that sends, and this section used to say it was. Two other things do, and both have their own section below. With file protection on, every file you download is read on your device and a fingerprint of it — with its name, its size and the reasons behind our verdict — is sent, and with "Deep scan every file" on the file itself is sent; none of that waits for a press. And evidence capture, which is on unless you switch it off, sends a screenshot and the page's HTML when an Analyze of a web page comes back phishing or suspicious. The sentence above used to read "nothing on the page leaves your device", which was written before file protection shipped and stopped being true when it did — a blob: file a page builds is something on the page by any reading.

That sentence used to be followed by another, "the extension does not run on pages you haven't asked it to", which was never true of the mail hosts and is not true of file protection either. Code that runs is not the same as data that leaves, and the honest version of the running half is:

When you opt into URL or Domain Visit Tracking

These are off by default for you. In the popup they are the Background protection switch (domain tracking) and, under Advanced, Send full URLs (URL tracking). If you turn either on there or from the welcome page on first install, Chrome (or your browser) prompts you for permission to "read your browsing history" (webNavigation) and to "read and change all your data on websites you visit" (<all_urls>). You can decline the prompt, in which case the toggle reverts to off.

On a managed fleet these two are not yours to decide, and this section used to be silent about that. An administrator can force either one on — or off — with the trackVisits and trackDomains policy keys, and a forced value beats the popup: the setting you save is read back with the policy value on top of it, so turning it off changes nothing about what is sent. Forced URL tracking means the full URL of every main-frame navigation, query string included, goes to the proxy on every page you load. The popup does show you when this is the case — a control an administrator has set is drawn with a Managed badge and cannot be switched, and a notice at the top of ⚙ Settings says so. The document disclosed this override for file protection, for deep scan and for evidence capture, and named it nowhere for monitoring, which is the one place a person is most likely to assume the switch is theirs.

With Domain Visit Tracking enabled, every time you navigate to a new page, the hostname (e.g. example.com) is sent to the proxy's /visit endpoint with type domain. The full URL is not sent.

By default the hostname is sent on each navigation. An optional local cache — off by default, switched on under Advanced in the popup — limits this to at most once per hour per domain, however many pages you load on it.

With URL Visit Tracking enabled, the full URL (including path and query string) is sent on each main-frame navigation, with type url. This is the more invasive option; we ship it off by default for a reason. Enable it only if you want full-URL-level threat intelligence.

Both endpoints respond with a threat verdict. If the proxy flags a URL or domain as dangerous, the extension shows a full-page interstitial warning over the page; you can choose to go back, or to proceed anyway.

When you opt into File protection (early access)

File protection is off by default. It is available on Chrome, Edge, Brave (which runs the Chrome build) and Firefox, and not on Safari, which does not implement the download APIs it needs. Turning it on asks for two permissions together: downloads, and access to all sites. Both are needed — the part that catches a file a page builds inside the browser reads page content, so granting only downloads would cover less than this section describes.

By default the file itself stays on your device. This line used to read "no file and no part of any file leaves your device", and the second half of that was not true: one of the short reasons we send with the fingerprint names a file found inside an archive you downloaded, so a little of what is in the file can travel with it. What is sent is set out two paragraphs down. When you download a file the page created (a blob: or data: download — the shape used to smuggle files past email filters), the extension reads its bytes in your browser's memory, checks them there (is it an executable dressed as a document? an Office file carrying a macro? an executable inside an archive?), and warns you — or, in block mode, holds the download and asks first. Those bytes are discarded straight after the check and are never written anywhere by the extension.

What is sent by default: a SHA-256 fingerprint of the file (a 64-character hash), its filename and size, the extension's own verdict, and the short reasons behind that verdict. The fingerprint is checked against known-malware fingerprints. A hash is 32 bytes and cannot be turned back into your file — it lets us recognise a file we already know is malicious without ever seeing yours. The reasons are mostly fixed phrases the extension wrote ("runnable script (.js)", "Office document with an embedded macro"), and some quote the file's own name or extension, which are sent anyway. One of them quotes something that is not: the name of an executable found inside an archive you downloaded. Download invoice.zip with payload-for-acme.exe inside it and that inner name is sent with the fingerprint, without the file and without a second switch. The reasons were missing from this list until version 1.4, which is what made the "no part of any file" sentence above look true.

Sending an actual file is a separate, per-file choice. When a check is inconclusive, the warning may offer "Check it properly". A file is uploaded only if you click that button, for that one file. It is scanned in memory by our own scanner on our own hardware, the verdict is returned, and the file is not stored — we keep only the fingerprint and the verdict.

Unless you turn on "Deep scan every file". That setting is off by default, and it changes the rule above: with it on, every file you download is sent to us for a full malware scan as it arrives, rather than only the ones you ask about. Each file is still scanned in memory and still not stored — we keep the fingerprint and the verdict, as before — but the file itself leaves your device every time. It exists for people and organisations who want everything checked; if that is not you, leave it off and nothing is uploaded unless you ask. An administrator can require it for a managed fleet via policy.

Ordinary downloads (a normal link, a file a server sends) are covered too, but differently: the browser does not hand an extension the bytes of such a download, so the extension fetches the file's address a second time to read it, checks it in memory the same way, and discards it. Practical consequences, stated plainly: the file is transferred twice, and a one-time or single-use download link may fail that second fetch — in which case the check falls back to the file's name and type. The URL is checked for reputation on your device, against a threat list the extension downloads; the address of what you download is not sent anywhere to be looked up.

What this feature does not do: it does not read files already on your disk, does not watch your Downloads folder, and cannot stop an ordinary download before it saves — no browser extension can. For those it warns after the file lands and offers to delete it.

What the extension keeps on your own device

This subsection is new. The document described what we receive and never described what stays with you, and named exactly one storage key — pseudoId — in a way that read as the whole list. Keeping these copies locally does not by itself trigger a transmission; where a listed value is also sent, the relevant section below says so. This list is set out because it is data about you that exists because you installed us, and because the deletion routes further down act on our servers and cannot reach your browser profile.

Your settings themselves live in chrome.storage.sync, not local, which means your browser copies them to your Google or Mozilla account along with the rest of your profile if you have browser sync switched on. They are switch positions, not content.

Keeping evidence of phishing pages

This setting is on unless you turn it off. It is shown to you during setup, and it lives in the popup under ⚙ Settings, where you can switch it off at any time. With it off, nothing described in this section ever happens. An administrator can set it either way for a managed fleet with the evidenceCapture policy key.

It is the only setting in PhishTriage that is on before you touch anything, which is why it is described here in full rather than summarised. It used to be described as the only thing that sends without being switched on first, and that is a different and untrue claim: an administrator's policy can switch threat monitoring, file protection and "Deep scan every file" on for a managed fleet whatever your switches say, and registration — described under Pseudonymous ID — runs by itself at install, before there is a setting to have an opinion about.

A verdict says a page was phishing. It does not prove it to the registrar or host who can actually take that page down. This setting exists to produce something they can act on.

What is captured. Two things, and only these two: a screenshot of the visible part of the tab (a JPEG — what you saw, and what a victim would have seen), and the page's HTML as it stood in your browser (what the page actually is: the form that collects the password, the address it posts to, the code it loads).

When. At the moment you click Analyze on a web page — before the verdict exists — because a screenshot taken any later would be of whatever the tab had moved on to, and attaching the wrong page to a takedown request is worse than attaching nothing. Both are then held in the extension's memory only, for the few seconds the scan takes. Neither is written to disk, to browser storage, or anywhere else on your machine.

Whether it is sent. Only if that scan comes back phishing or suspicious. On any other verdict both are discarded and neither leaves your device. Nothing is captured on chrome:// pages, the Chrome Web Store, or other extensions' pages, because the browser refuses.

The mail suppression is four hostnames, not a rule about email. This is the most important correction in this version, so it is stated at length.

The extension does not capture anything on the mail path, and the mail path is exactly these four hosts:

On those four, no screenshot is taken and no HTML is collected, whatever the verdict. A picture there would be of your inbox, which is both the most sensitive thing this feature could take and useless for reporting a website.

Webmail anywhere else is treated as an ordinary web page. The check is a hostname comparison, not a judgement about whether you are reading mail. So if you use Yahoo Mail, Proton Mail, Fastmail, Zoho Mail, Roundcube, or your employer's own Outlook Web Access on a company domain — anything that is not one of the four hosts above — then pressing Analyze on a message there is pressing Analyze on a web page. If the verdict comes back phishing or suspicious, we receive:

This is the shipping configuration. Evidence capture is on unless you turn it off, and "suspicious" — the verdict class this document elsewhere calls the home of false positives — is enough to trigger it. Nothing about this requires you to have opted in.

To turn it off: open the PhishTriage popup, click ⚙ Settings, and switch off Keep evidence of phishing. With it off, nothing in this section happens anywhere, on any host. If you read mail on a provider that is not one of the four above, that switch is the one to look at.

This paragraph used to read "nothing is ever captured on the email path", full stop, and the sentence was written when Gmail and Outlook were the only mail the product had ever been pointed at. It read as a promise about email. It was only ever a promise about four hostnames, and the gap between those two things is somebody's inbox. The extension's own enterprise-policy schema has described the behaviour correctly — "webmail on any other host counts as a web page" — while this document denied it; we are fixing the document, not the schema.

Two honest caveats. Suspicious is where false positives live, so with this on you may upload a page that turns out to be perfectly legitimate — that is why suspicious pages are kept for a much shorter time, and why a reviewer who says it was not phishing deletes it on the spot — though inside an organisation that review is refused while the activity window is withholding, which is its default state; see the retention section. And the HTML usually does not contain what you typed, because typed values live in a property rather than in the page's markup, but a page can write them back into the markup, so usually is the honest word.

How long it is kept. Not by the 90-day rule below, which is about your own scan history. Evidence is about a third party and is kept on its own clock:

Who can look at it, and how. If you scan on your own, only you. If your account belongs to a team, then everyone on that team — the same people who can already see that the scan happened.

It appears in the portal in its own Phishing Evidence list on the dashboard, alongside the scan it came from while that scan still exists. The list is the part that matters, because evidence is kept longer than scan history is: once the 90-day sweep removes the scan record, the capture is still there, and the list is how you reach it to look at it, download it, or delete it. This paragraph used to say evidence was visible "on the scan it belongs to", and for the longer half of a 12-month capture's life that was not true of anything you could click.

Except in an organisation that reports in aggregate, where the list is withheld from everyone. Aggregate is the mode every organisation starts in. In it, a list of captures is per-person activity like any other, so the portal refuses the whole surface — not just the seeing. Nobody, the owner included and the person whose own device made the capture included, can open the Phishing Evidence list, download a screenshot or its page source, confirm a capture, reject one, or delete a single one. This document conceded only the seeing half and left "look at it, download it, or delete it" standing next to it.

Who can delete it. Inside a team, confirming or rejecting a capture is an owner's action — and only where the organisation reports attributed; in aggregate mode, per the paragraph above, nobody can. What works in every mode is deleting your own: the "Delete my scan history" button described under Retention removes every capture stored under your account, including ones whose scan record has already expired, and no organisation role can do that to you or undo it.

That button is scoped to your account, not to your hardware. This document used to say it removed "every capture made on your own devices", which is a different set: a capture your device made before you linked it to your account was written under that device's own prior identity, and the button does not reach it. That is the same edge the Retention section already states for scan and visit records, and it applies to captures too.

The page's own code is never run. Stored HTML from a hostile page is treated as hostile: it is never rendered in our portal, only downloaded as an inert file, and it is served as plain text so that opening the link cannot execute it. The screenshot is a picture and is checked to be one before it is stored — a file that is not really an image is refused.

What the extension never collects

Two of these bullets carry qualifications now, and the heading should be read with them rather than over them. Each names a specific thing the extension has no mechanism to obtain; none of them is a promise that nothing of the kind ever reaches us by another route, and where a route exists the bullet says so.

Text messages (PhishTriage for iOS)

This section is new. It covers a surface no previous version mentioned at all — searching version 1.4 for "SMS" or "text message" returns nothing, while the backend has had an endpoint for them the whole time. Nothing here is a change in behaviour. It is a gap in the document, and it is the largest one this version closes.

This is not the browser extension. What follows happens only if you installed the PhishTriage app on iOS and switched its message filter on. If you have not, none of this applies to you. The extension cannot read messages on any platform.

iOS decides which messages the filter is even offered. A message-filter extension does not get your message history and cannot go looking; the system hands it individual messages from senders it does not already trust, and that rule is the operating system's, not ours. We cannot widen it.

Only a message with a link in it is sent to us at all. Before any network request, the app checks the message for a link. A message with no link — and a message with no body — is allowed on the device, and nothing about it is sent. Link detection is the system's and errs toward finding one.

What is sent, when a message is sent:

All three arrive complete and are cut down on our side, on receipt — the sender to 100 characters, the body to 2,000, the version string to 40 — before anything else touches them. We state it that way round deliberately: the phone sends the whole message, so those limits bound what we keep and analyse, not what leaves your device. One of the two request shapes our own app can send also carries a small integer version number for the format itself. It describes the message, not you.

That is the entire request body. It carries none of the identifiers used everywhere else in this document: no pseudoId, no extension ID, no device id, no account. The message filter cannot attach one — Apple's deferral does not carry it — and we did not invent a substitute. The endpoint is unauthenticated for that reason, and rate-limited by IP address instead. Which is the honest qualification: your IP address is not in the body, but it arrives with the request as it does with every HTTP request, it is what the rate limit counts, and it is written to the log described below.

But the message can still name you. This is the same caveat the Analyze path carries, and it is just as true here. A text message often opens with your name; a delivery notice or a bank alert can carry an account reference, a booking number or an address. We do not go looking for any of it and none of it is stored — but it is in the body, and the body is sent.

Not every message we receive reaches a model. On our side, a keyword and blocklist pass answers first, and any URLs in the message are checked against our blocklist. Only a message that pass cannot decide is given to a model — and then through the same path as everything else, described under Where analysis happens, with the same order and the same conditions.

What is stored: no record of the message. This is the one place in this document where that is the answer. Unlike the triage and visit records described below, an SMS check writes none — not the sender, not the body, not the verdict, not the action returned. There is no message history in the portal, and "Delete my scan history" has nothing to delete here because nothing was written.

What is logged, which is the exception to the paragraph above. A request that reaches the endpoint handler produces one line in the same operational log described under The application log, with the same lifetime — which is to say nothing in the application deletes it. That line carries your raw IP address, the app version, a truncated hash of the sender (the first 12 hex characters of an unsalted SHA-256 — the caution stated for triage records applies here identically), whether the message contained a link, which of the two request shapes it arrived in, which pass decided it, the verdict and action, and how long it took. The message body is not logged, and the sender does not appear in the clear.

What we cannot offer you here, stated plainly. Every access and deletion route in this document works from a pseudoId or an extensionId, and an SMS request carries neither. So there is no record to return to you and none to delete — because none was written — and the log line described above is tied to nothing but the IP address it came from. We say "the log line" rather than "the only trace" deliberately: what a model provider does with a message we sent it for analysis is governed by their terms, not by this sentence. If you want the log line cleared, write to [email protected]; we clear it by hand, as with the rest of the log. We would rather say this than imply a remedy that does not exist.

Where analysis happens

This section is new in version 1.5. It exists because the claim it replaces was wrong in a direction that flatters nobody: the policy said your content goes to Anthropic as a matter of course, and that is no longer how the routing works.

The order. Anything that needs a model — a scan you pressed Analyze on, or a text message that got past both passes above — is offered to backends in a fixed order, and the first one to return a usable answer is the one that answers:

  1. A model we host ourselves. A machine we run, on our own network, rather than a model service we buy. This is asked first.
  2. A third-party model service. Not in the path in any deployment we run as of this version's effective date. It is named under Third-party processors, which says exactly what would put it there.
  3. Anthropic's Claude API. The last resort — and the only one of the three whose answer is used without being checked first, because there is nothing behind it to try.

The conditions. A tier is asked only if it is configured, has not been switched off, and the request still has time left in its budget; a tier that is not configured is simply not in the order. A tier that is asked and fails hands on to the next, and "fails" is deliberately broad: unreachable, too slow, an error, a rate limit, output we cannot parse, or a verdict that does not match the shape the rest of the system requires. Any of those, and the next tier gets the same content.

Why there is no sentence here saying your content stays on our hardware. Because it would be false on exactly the days it matters. The fallback exists because the first tier fails, and when it fails your content goes onward — that is the entire point of having one. Earlier this week a sampling parameter our self-hosted model rejects sent every scan to Anthropic until it was found and corrected. We are not putting a proportion or a duration here: any number we printed would be a measurement of one moment, and the routing is a rule, not a ratio. What you can rely on is the order and the conditions above.

Anthropic remains a processor. Less often is not never. A version of this policy that dropped Anthropic would be wrong the first hour the local machine misbehaved, and it misbehaved this week. Its entry under Third-party processors stands.

We are not telling you which country that machine is in, and that is deliberate. The Estonia statement under Third-party processors is about the PostgreSQL host and has only ever been about that machine. It is not a statement about the model server, and a draft of this section wrongly reused it before review caught it. We will name a location here when we can state it as precisely as we state the database's, and not before.

This page is dated, and the routing is not. Which tiers are configured is a property of the running service and can change without this document changing. So everything above is written as a rule plus a statement of the arrangement as at the effective date at the top — not as a claim about this instant. Where the two could diverge, the rule is the part you can rely on.

What does not change with the routing. Whichever tier answers, the content sent is the content listed above and the same verdict comes back. Analyze scans have the same record stored; SMS checks store no record, as stated under Text messages. A stored triage record does not note which backend produced the verdict, so we cannot tell you afterwards which one analysed any particular scan of yours — and neither can you.

What the backend stores, and for how long

The proxy at api.phishtriage.com (Node/Express; its source is not public today) logs every triage request and visit ping to a PostgreSQL database on hardware we own and operate — see Third-party processors below for the full storage picture, including where pre-migration records live. Stored fields per record:

Account and identity records

This subsection is new. The four record types above were presented as the whole storage picture, and they are not: they are the activity records. The control plane that sits under them was added when the product grew accounts, teams and plans, and it is where an email address actually lives. In schema order (migrations/ in the proxy repo):

An organisation can take you into it without asking, and this document never said so. It described two routes into a team — an invitation you accept, and an enrolment token an administrator pushes to a device — and there is a third. If an organisation has proved that it owns an email domain, then the next time someone signs in to the portal with a verified Google address on that domain, that account is made a member of that organisation. There is no invitation, no prompt, and no notice at sign-in; it happens during a sign-in that looks like every other sign-in. From that moment the organisation's owners can see that account's scans and its phishing evidence on the same terms as any other member's — subject to the reporting mode described under Keeping evidence of phishing pages.

Three limits are real and worth stating. An account that is already in an organisation is never moved into another one. Only activity from the instant the organisation switched to per-person reporting is ever attributed, so joining does not retroactively expose what you did before that. And the portal tells you which organisation you are in, when you joined, and which domain put you there, precisely because this is the one membership nobody is asked about. If you would rather it did not happen, do not sign in to the portal with an address on that domain — the extension scans perfectly well without an account.

A domain can be proved two ways, and only one of them is visible to you. The first is a DNS TXT record the organisation publishes. The second identifies the organisation by your identity provider's tenant — Google Workspace's hd claim today — and requires the organisation to publish nothing at all. So checking your domain's DNS records is not a way to rule this out.

Back to the record inventory:

The application log

Separately from the database, the proxy writes an operational log line as it handles a request. Three of those lines matter here — the third was missing from this list because the surface it belongs to was missing from this document:

This is the sense in which "we don't know your IP address", the sentence that stood at the top of this document until version 1.2, was false. The hashing is real, and it is what the database keeps; it is not what the log keeps.

The retention sweep described next deletes rows from the database. It does not touch this log — nothing in the application deletes it, so its lifetime is whatever the operator's log handling gives it. A deletion request that names your extensionId covers it; we clear it by hand. An SMS line has no extensionId in it to name — Text messages says what that means for a request.

Retention

Evidence records are the exception to everything in this section: they are kept on the separate, per-verdict clock set out above, and the 90-day sweep does not touch them. Because they outlive the scan they came from, they have their own list in the portal — see Who can look at it above — so a capture stays reachable, and individually deletable, for as long as it is kept. Unless your organisation reports in aggregate, which is the mode every organisation starts in: there the list is withheld from everyone and the whole-history button below is the only route.

Triage and visit records are deleted automatically. The hosted service runs with a retention window of 90 days: a sweep removes every triage and visit record older than that, several times a day. The window is configured server-side (RETENTION_DAYS); self-hosted proxies choose their own value, or set none — in which case records are kept until deleted by hand, and this policy does not apply to that deployment.

Independent of the sweep:

Pseudonymous ID

Your install is identified by a random string we call the "pseudoId". It is stored in chrome.storage.local under the key pseudoId and is sent with every triage and visit request. Depending on whether you have registered, it is one of two things:

Neither form is derived from your email, IP address, hardware ID, fingerprint, or any other identifier. Both are fresh random numbers. We treat the ID as a correlation key, not as proof of who you are: it exists so repeat use from the same install can be linked together server-side (for caching, or to show you a per-device history in the portal), not to identify you as a person.

When registration happens, corrected. This section used to say "registration is an explicit action; it does not happen on install". The opposite is the case, and has been since registration stopped being a button: it runs automatically from the browser's install event, and again before the first scan of a session if that attempt did not succeed. There is no register control in the popup. So on a working network the device-generated ID above exists only until the first /register call returns, and every install holds a server-minted identity before you have done anything with the extension.

Registration is not a sign-up and involves no account of yours. What it sends is the current pseudoId, which browser you are on, an empty label, and — only on a fleet where an administrator has pushed one by policy — an enrolment token that attaches the device to that organisation. No email address, and nothing you type. It fails silently and retries later: an unregistered device still scans.

We previously derived a registered user's identifier from a hash of their email address. We no longer do, and the derivation has been removed: a value computed from an email address is not genuinely pseudonymous, because anyone holding the address can recompute it.

The popup's account row can discard that identity. This document used to call the control Unregister, which is not a word in the interface: the button reads Leave team when your device belongs to an organisation and Sign out when you are signed in on your own, and it appears only in those two cases. Either one clears extensionId, pseudoId, the organisation and the stored tokens — and then immediately registers again, so what you end up with is a fresh server-minted identity rather than none. From the backend's point of view you are a different install; your old records stay where they are and can no longer be reached from this device.

When one ID covers several devices, corrected. This paragraph used to say that linking to a team "via an invite code" overwrote your pseudoId with the team owner's, and that this was the only way one ID reached more than one device. Every part of that is now wrong. There is no invite-code field in the extension any more — typing a code there enrolled a machine as a team device with no person attached, so it was removed, and a device now joins a team from the portal, where an administrator can see what is being attached to whom.

What actually happens: when you sign in, or when an administrator links your device from the portal, your local pseudoId is replaced by that account's id. It is never an organisation id — those are a separate namespace — and where an administrator links a device from the portal, the id it takes is the id of the account they linked it to, which may not be yours. The popup re-reads it from the server each time you open it. So every device you sign in on carries the same id and requests from all of them are linked together server-side; that is what makes one history out of several devices, and it is as true of a personal sign-in as of a team one. An install that has never been signed in keeps an id unique to itself.

Third-party processors

Anthropic — the Claude API, and the last tier of the order set out under Where analysis happens. Content reaches it when the tiers in front of it do not answer, which is a condition and not a frequency. We are deliberately not printing a proportion here: it would be a measurement of one moment presented as a property of the service, and the misconfiguration described under Where analysis happens is what that looks like when it goes the other way. This paragraph used to open "when you press Analyze, the extracted content is forwarded by the proxy to Anthropic's Claude API for analysis" — unconditional, correct about Anthropic receiving content, and wrong about the routing. Anthropic acts as a processor on our behalf under its API terms; per those terms, Anthropic does not use inputs to API requests to train models. See anthropic.com/legal/privacy for their data-handling commitments.

Ollama — a hosted third-party model service (Ollama Cloud), named here before it is used rather than after. It is the middle tier of that same order, and a tier is in the path only when it is configured, which is the rule stated under Where analysis happens. As of this version's effective date it is configured in no deployment we run. That is a statement as at the effective date above, not a permanent one — the rule that survives is the one in the previous sentence: a tier is in the path only when it is configured.

We are naming an unused processor for two reasons, and we think the alternative is worse. Turning it on takes no new code and no release — three configuration values and a restart — so the distance between "not in the path" and "in the path" is minutes, while the distance between editing this document and it reaching you runs through a store review. And this disclosure is the thing that enabling it is meant to wait for: the deploy configuration says in as many words that this policy must name Ollama before that tier carries real traffic. A policy that has to be rewritten the day someone sets three environment variables is a trap, so we would rather over-name a processor than under-name one. If it does take traffic, what it receives is what the tier ahead of it would have received — the content described under What the extension collects, and when and under Text messages — and nothing additional.

Records are stored in a PostgreSQL database on hardware we own and operate, located in Estonia. That sentence is about the database host and only about it; see Where analysis happens for why this document does not tell you where the model server is. Since August 2026 no third-party service stores your records — there is no managed-database provider in the path. Cloudflare, Inc. carries traffic between your browser and our server (TLS termination, tunneling, DDoS protection) and is a processor for data in transit only; it does not store your records.

One legacy exception: records created before the August 2026 migration to our own hardware remain in our previous datastore at Elastic Cloud (Elasticsearch as a managed service, europe-west3 GCP region, under Elastic's DPA). These are pre-release records from the beta period; the deployment that wrote them has been retired, no new records are added, and — unlike current records — they are outside the automatic 90-day deletion described in Retention. Write to [email protected] to have yours deleted immediately. This paragraph will be removed when that cluster is deleted.

This section used to end "we use no other third-party processors". Anthropic, Cloudflare and Elastic were the whole list when it did. Two more arrived with accounts and billing, and both are reachable from the popup's Log in button:

Beyond those: no analytics, no advertising, no telemetry SDKs, no error-reporting services, no fingerprinting libraries. That is a statement about what we have built into the product, not a claim that no third party is ever contacted — the portal's Google dependencies above are, on every page.

Two hosts receive your data, both ours:

Both are operated by us on the hardware described above; neither is a third party. An organization's admin policy can point either at a self-hosted service instead, in which case that service is your organization's to describe, not ours.

One further request, which the old wording denied. This list used to be introduced as "the extension talks to two hosts, both ours, and to nothing else". That has not been true since file protection shipped. With file protection on, an ordinary download has to be fetched a second time to be inspected at all — the browser does not hand an extension the bytes of one — and that second fetch goes to the address the download itself came from, which can be any host on the internet, carrying your cookies for that site so that a file behind a login can still be read. What the site sees is a second request for the file it just served you: the request carries no header, identifier or parameter of ours, and says nothing about your other browsing. The bytes go no further than your browser's memory unless the file-protection section above says otherwise. With file protection off, it does not happen.

Self-hosting

Organizations can run their own proxy and point their fleet at it by pushing an admin policy via enterprise managed storage (see the managed-storage.json schema in the source). This is deliberately the only way to change the backend: there is no user-facing setting, so nobody can be talked into pasting an attacker's URL and routing their scanned email to it. When your organization self-hosts, the rest of this document still describes the extension's behavior, but the backend's behavior is governed by your operator's policy, not ours. Your IT team is the data controller for those records. We have no access to a self-hosted proxy's data.

Your rights

You have, depending on jurisdiction, the right to:

We will respond to requests within 30 days. If you've used the extension across multiple browsers without linking them, each install has a separate pseudoId and you may need to request deletion for each.

Changes to this policy

When we make a material change — a new collection, a new processor, a shorter or longer retention period — we will bump the Version and Effective date at the top of this document and, where reasonable, display a notice on the next-launched welcome page. The full revision history of this document is kept in git; the repository is not public today, so write to [email protected] for a prior version and we will send it. This paragraph used to link the file directly, which returned 404 to everybody.

Contact

[email protected]

For security disclosures (vulnerabilities in the extension or proxy), write to [email protected].

← Back to phishtriage.com