An anonymous reporting channel loses its anonymity in places the form never shows: the IP address recorded by the hosting platform, the location inside a photo, the author inside a PDF, third-party analytics and bot checks, and the combination of department, shift and date in a small team. Each of these has a technical fix that has to be in the system from the scope onward. In the reporting channel we are building for a client, the portal was moved off Vercel because of the IP, every attachment loses its metadata before it is stored, and follow-up with the reporter happens through a case code and password, with no email.
What breaks anonymity in a whistleblowing channel#
An anonymous reporting channel loses its anonymity in places the form never shows. The IP address is written to the hosting platform's log, the photo carries its location, the PDF carries its author's name, analytics tells another company about the visit, and fields like department, shift and date point to one person in a small team.
In the European Union, Directive 2019/1937 requires private companies with 50 or more workers to set up internal reporting channels (article 8), and that is how the subject reached many companies. This article is about the system: what, in the way a channel is built, gives the reporter away.
Our first-hand material comes from an anonymous reporting channel we are building for a client in Brazil, as a custom software project. The providers that show up in search talk about not logging IPs and removing metadata, without showing where each log lives. The table below is the map we used in the scope.
| What gives the reporter away | How it leaks | How it is closed |
|---|---|---|
| IP address | access logs of the hosting platform, the CDN, the load balancer and the error monitoring tool | portal on hosting where every access log can be turned off, and an error tool that keeps no IP |
| Company network | the company firewall and computers log the websites visited | tell people, on the page itself, to report from a personal phone off the company network |
| Location and device | metadata written by the camera into photos and videos | strip the metadata before storing, without keeping the original |
| Document author | properties of the PDF and the Office file | strip the author and document data |
| File name | the original name carries a date, a time or a person's name | rename to attachment-1, attachment-2 |
| Page visit | third-party analytics and tracking cookies | no analytics on the reporting page, and only technical cookies |
| Bot check | an external service receives the IP and the browser fingerprint | proof of work in the browser, with a library hosted by the system itself |
| a confirmation needs an address, and the alert to the company may carry the text | no email to the reporter, and alerts to the company with no report content | |
| Form fields | department, shift and date together point to a few people | no department or employee ID field, with "where" and "when" optional |
| Time of submission | the minute of submission matches someone's shift | investigators do not need to see the hour and minute |
| Follow-up conversation | replying requires a contact | case code and password generated by the system |
| Who reads the case | the person reported is on the team that receives reports | access only to assigned cases, and the company chooses who gets access |
Why the IP gets recorded even when the system does not record it#
Most of the time, whoever records the visit is the hosting platform, and not the system's code. Hosting, CDN, load balancer and error tool keep every request for debugging, and in an anonymous channel each of those logs has to be found, turned off and checked before going live.
In the channel we are building, this was the main finding of the scope review. The client's system runs on Vercel, which records the IP of every request: its documentation lists the client IP (proxy.clientIp) among the fields of each request, and the "logs from your browser" filter works by matching IP and browser. Logs are kept from one hour to 30 days, depending on the plan.
The decision was to move the worker portal, and the part of the API it calls, off Vercel. Both run on AWS, in São Paulo, behind a dedicated distribution, with the API under the same address as the portal and attachments delivered only through that distribution, never through a direct storage link. On CloudFront, the AWS distribution service, access logging is something you turn on, and one of the fields it can store is the requester's IP (c-ip). The part used by the company's team, who sign in, stays on Vercel.
The delivery checklist has five items:
- Access logs of the distribution, the load balancer and the storage turned off.
- Data event logging turned off on the attachment storage.
- API request logs with no IP and no origin header.
- Error monitoring tool configured the same way.
- Network flow logs turned off on the portal's subnets.
The GDPR, in recital 30, lists IP addresses and cookies among the online identifiers that, combined with other data, can be used to identify a person. And there is one log the channel cannot reach: the company firewall and computers may record the websites people visit. That is why the channel page tells people, before the form, to report from their personal phone and off the company network.
What a photo, a video and a PDF say about who sent them#
Photos and videos may carry the coordinates of where they were taken and the device model. PDFs and Office files may carry the author's name, and the file name may carry a date and time. All of it reaches the investigator if the channel stores the attachment exactly as it was sent.
According to Apple's Personal Safety guide, when the iPhone Camera has access to location, it writes the coordinates of where the photo or video was taken into the metadata, and whoever receives the file may see it. Microsoft support explains that the properties of an Office file include the author, and that exporting to PDF can carry those properties along, through the "Document properties" option.
In the channel we are building, every attachment loses its metadata before it is stored. Photos lose location, device and date. Videos lose location and device, without re-encoding the image. PDFs lose author and document data. The file is cleaned in a holding area before being saved, the original is not kept, and the attachment reaches the company renamed (attachment-1, attachment-2).
Cleaning does not reach the content. A voice in a video, a face in a reflection or someone's way of writing are still there. The portal says so at the moment someone attaches a video or a PDF.
Cookies, analytics and bot checks: who else learns about the visit#
Every script from another company that the reporting page loads receives a request from the reporter's browser, with the IP and device data. The reporting page should load only what comes from the system itself.
Analytics is usually installed across the whole website, and nobody remembers to take it off the channel page. Google states that Google Analytics 4 does not log or store IP addresses. Even so, the reporter's browser talked to a Google server, and the channel's promise now depends on another company's policy.
The channel needs a bot check, because a public form attracts automated submissions, and the most common services work by looking at who is on the other side. Cloudflare's Turnstile Privacy Addendum, for example, says the service collects the client IP address, the TLS fingerprint and the User-Agent header.
In the channel we are building, the check is proof of work: the browser solves a small computation before submitting, with an open-source library hosted by the system itself, with no visual challenge and no external service. It takes a moment on the reporter's phone and makes thousands of automated submissions expensive. The portal uses no device identifier and no tracking cookie, only two technical cookies: the follow-up session cookie, deleted on sign-out, and the bot check cookie.
Without the IP, blocking by address is also off the table, and a block by volume would let anyone with the QR code take the channel down. So the channel never refuses reports because of volume: above 20 reports per hour on one channel, reports keep coming in, the automatic reading of the extra ones waits, and our team gets an alert.
How form fields and timing can identify someone#
A form with no name field can still identify someone when it asks for department, shift and date. In a small team, the three together point to one or two people, and the time of submission does the same when it matches someone's schedule. That is why the channel asks only for what helps the investigation and leaves the rest optional.
Picture a report tagged "shipping, night shift, yesterday" at a site with six people on that shift. The report has no name, and the investigator already knows where to start suspecting. The EU Whistleblower Directive addresses this in article 16: the reporter's identity may not be disclosed, and the rule also covers any information from which that identity may be deduced, directly or indirectly.
In the channel we are building, the form has no field for name, email, phone, employee ID or department. The fields are what happened, where it happened (free text, optional) and when it happened (date only, also optional), with a note asking people not to identify themselves in the text. The title the automatic reading generates for the case list carries no personal names.
Department has its place when answers are only read in aggregate. In Sistema Cuidar, the psychosocial risk assessment asks for department, role and shift, without identifying the person, and results only appear aggregated. It collected 370 answers anonymously. There nobody opens an individual answer, while in a reporting channel the investigator reads each case in full, and that is why the channel form does not ask for department.
Timing needs the same care. A report sent at 3 a.m., in a company where only one person works overnight, says who sent it. The investigator does not need the hour and minute to handle the case.
How to talk to the reporter without knowing who they are#
The conversation runs on a case code and a password, which the system generates on submission and shows only once. With them, the person returns to the channel page, sees the status of the case, answers the investigator's questions and reads the final response, without giving an email, a phone number or a name.
In the EU, feedback has a deadline: the Whistleblower Directive requires acknowledging receipt within seven days and giving feedback within three months (article 9). In an anonymous channel, that only works if the answer stays stored in the system itself, waiting for the person to come back.
In the channel we are building, the case code has 10 characters, without the ones that are easy to confuse, like 0 and O, and the password has 8 characters generated by the system. There is no downloadable receipt, because it would be saved on a device someone else might use, and a lost code cannot be recovered: the person files a new report and mentions the old one. Five wrong passwords for the same code block new attempts for 15 minutes. The lockout applies to the code, since the system does not know which address the attempts came from. On the other side, the company's messages are signed "Company", without the name of whoever wrote them.
Email stays out of this path. The reporter gets no notifications, because they give no contact. The company's team gets an email that only says there is something new, with no excerpt of the report, because email gets forwarded and stored on servers the channel does not control. These alerts belong in a notification map drawn up before development.
Who on the company side sees what#
Anonymity also depends on who reads the report, because the person reported may be on the team that receives reports. The channel needs roles with access only to assigned cases, exports without the report text, and a channel operator that does not read content.
In the channel we are building, the company administrator sees every case and assigns someone responsible for each one, and an operator sees only the cases assigned to them. The company that provides the channel sees only aggregated numbers, and none of its roles can open a case, report, attachment or message. Report and message text is encrypted in the database, and the case list export carries code, title, category and dates, without the content.
Part of this protection depends on the client. Automatic recusal for conflicts of interest, as in a report involving an administrator, is out of this version, and the case is reassigned by hand. That is why the scope lists among the client's obligations telling each company who should get access.
What does not guarantee anonymity#
A line in the privacy policy does not guarantee anonymity, and neither does a form without a name field, because both coexist with the IP in the hosting log and the location inside the photo. And total anonymity, with no way back to the reporter, keeps the company from asking questions and from answering.
That is why, in the channel we are building, anonymity is a product rule, and no client company can switch on a name field: there are no identified reports in this version. It is the same principle of keeping rules fixed and only values configurable that we explain in when to turn a business rule into a parameter.
A channel that takes the report and offers no way back keeps the investigator from asking for a missing detail, and the reporter never learns what was done. The design that works is the case code, which lets the person stay unnamed and come back whenever they want.
It also helps to tell anonymity apart from confidentiality. In a job board we are designing, the administrators see who reported a job post, and the company reported is told only about the consequence, never about the report. That is confidentiality: someone knows the author, and a rule keeps that information from reaching the wrong people. The EU Directive leaves it to each country to decide whether companies must accept anonymous reports (article 6) and requires confidentiality of identity in every case (article 16). The channel has to tell its users which of the two it offers.
Frequently asked questions#
Can a whistleblowing channel store the reporter's IP address?#
Technically it can, and on most websites the IP is stored without anyone deciding it, in the logs of the hosting platform, the CDN or the error monitoring tool. If the channel promises anonymity, none of those logs can keep the IP of whoever uses the reporting page, and every one of them has to be checked before going live.
Can someone find out who sent an anonymous photo?#
Often, yes. A photo taken with location turned on carries the coordinates of where it was taken and the device model, and the file name can include the date and time. That is why the channel strips metadata from photos, videos and PDFs before storing them, renames the attachment and warns that the content itself, such as a face or a voice, can also identify someone.
Does using the company phone or Wi-Fi compromise anonymity?#
It can. The company network and computers may log the websites people visit, and that log is out of the channel's reach. That is why the channel page we are building tells people, before the form, to report from their personal phone and off the company network.
Does an outsourced whistleblowing provider guarantee anonymity?#
It depends on how the system is built, because an external provider also runs on a hosting platform and also receives attachments. Before signing, ask where the portal is hosted, which access logs are turned off, whether attachment metadata is removed, which bot check the page uses and how the reporter gets feedback without giving a contact.
Sources#
- Vercel, Runtime Logs (retention by plan and the IP and browser filter): https://vercel.com/docs/logs/runtime
- Vercel, Log Drains Reference (
proxy.clientIpfield): https://vercel.com/docs/drains/reference/logs - AWS, CloudFront standard logging (optional access logs,
c-ipfield): https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/standard-logging.html - Apple, Personal Safety User Guide, location metadata in photos and videos: https://support.apple.com/guide/personal-safety/manage-location-metadata-in-photos-ips0d7a5df82/web
- Microsoft, remove hidden data and personal information by inspecting documents: https://support.microsoft.com/en-us/office/remove-hidden-data-and-personal-information-by-inspecting-documents-presentations-or-workbooks-356b7b5d-77af-44fe-a07f-9aa4d085966f
- Microsoft, save or convert to PDF (document properties option): https://support.microsoft.com/en-us/office/save-or-convert-to-pdf-or-xps-in-office-desktop-apps-d85416c5-7d77-4fd6-a216-6f4bf7c7c110
- Google, Analytics Help, IP masking in GA4: https://support.google.com/analytics/answer/2763052
- Cloudflare, Turnstile Privacy Addendum: https://www.cloudflare.com/turnstile-privacy-policy/
- Directive (EU) 2019/1937 on the protection of persons who report breaches (articles 6, 8, 9 and 16), Official Journal L 305 of 26 November 2019: https://www.boe.es/doue/2019/305/L00017-00056.pdf
- Regulation (EU) 2016/679 (GDPR), recital 30: https://www.boe.es/doue/2016/119/L00001-00088.pdf
Next step#
If you are about to launch a reporting channel, or want to sell one to other companies, the conversation starts with the table at the top of this article, row by row, before the first screen is drawn. It is the kind of project we build as custom software and, when the channel is the product you sell, as a SaaS product, with the same care for sensitive data we applied in Sistema Cuidar.

