← All case files

Case 004 · Multi-tenant B2B SaaS platform · Privacy and consent

The analytics knew your customers’ phone numbers.

Analytics collected email, phone and address from forms before any consent.

A dark laptop screen: a form filled with consent-test@example.com and a phone number, the cookie question still unanswered, and a network row in red, “em=consent-test%40example.com”, “sent before consent”.
Before
Contact details sent to analytics before consent.
After
Recommended: tracking waits for consent; a no-consent test.

The bug

On a multi-tenant B2B SaaS platform that holds invoices and customer records, the analytics tag collected email, phone and postal address from forms automatically, with redaction off. It ran before anyone accepted cookies, and visitors had no way to reject tracking at all. Inside the app, an advertising pixel received hashed copies of the operator’s email and phone and kept them in a cookie for two years.

How it shows up

On screen, it didn’t. The forms worked and the pages loaded. The data left in background requests nobody watched. Many teams also assume that hashed means anonymous. Ad platforms hash email addresses and phone numbers precisely so they can match them to people.

How we found it

During the same security assessment, we opened the platform in a clean browser, accepted nothing, filled in a form with our own test details and read the network traffic. The typed contact details went to the analytics provider. We recorded what was sent and stopped there, without changing anything or touching anyone else’s data.

The fix

The remediation we recommended: turn off automatic collection of personal data in the analytics settings and turn redaction on. Load analytics and the ad pixel only after the visitor consents, and add a button that rejects tracking.

The test that catches it

A Playwright test starts with a fresh browser profile and no consent. It fills in a form, then asserts that no request to an analytics or advertising domain carries the typed email or phone, as plain text or as a SHA-256 hash, and that no tracking cookie is set before consent. It runs in CI on every release.

no-consent.spec.tsIllustrative
import { test, expect } from "@playwright/test";
import { createHash } from "node:crypto";
import { TRACKER_DOMAINS, TRACKING_COOKIES } from "./trackers";

const email = "consent-test@example.com";
const phone = "+40700000000";
const sha256 = (value: string) =>
  createHash("sha256").update(value).digest("hex");
const secrets = [email, phone, sha256(email), sha256(phone)];

test("no contact details leave the page before consent", async ({ page }) => {
  const leaks: string[] = [];
  page.on("request", (request) => {
    const url = request.url();
    if (!TRACKER_DOMAINS.some((domain) => url.includes(domain))) return;
    const sent = url + (request.postData() ?? "");
    if (secrets.some((secret) => sent.includes(secret))) leaks.push(url);
  });

  await page.goto("/contact"); // fresh profile: nothing accepted
  await page.getByLabel("Email").fill(email);
  await page.getByLabel("Phone").fill(phone);
  await page.getByRole("button", { name: "Send" }).click();

  expect(leaks).toEqual([]);
  const cookies = await page.context().cookies();
  const tracking = cookies.filter((c) => TRACKING_COOKIES.includes(c.name));
  expect(tracking).toEqual([]);
});

What it puts at risk

Personal data sent to third parties without consent, which is GDPR and ePrivacy exposure for the platform. Hashing the data doesn’t change that.

Tell us what you’re shipping.

An app, a rewrite, an AI agent, an automation, or a release that needs testing. A free 30-minute call, then a price in writing. If you don’t need us yet, we’ll say so.

Book a free call