BlogPrivacy & Compliance

How to Find Personal Data in Your Codebase

Most teams find out where personal data actually lives in their codebase during an incident — a breach investigation, a customer complaint, or a compliance audit — instead of before one. By then it's reactive, expensive, and stressful. This is a guide to doing it proactively, and cheaply, instead.

Why this is harder than it sounds

Personal data ends up in places nobody planned for it to be:

  • Seed/fixture files — a developer copies a real customer record into a test fixture "just to have realistic data," and it ships to every environment forever.
  • Log statementsconsole.log(user) or an error handler that dumps a full request body, both of which can include email addresses, phone numbers, or worse.
  • Config and .env files committed by accident — connection strings with embedded credentials, API keys tied to real accounts.
  • Comments and dead code — a commented-out debug line with a real user's data, left in for months.
  • Field names that leak intent — a database migration or API schema with a column literally named aadhaar_number or ssn, which tells you exactly what kind of data flows through that path even before you find an actual value.

None of these show up in a normal code review focused on logic and style. You need something that's actually looking for the shape of personal data, not just reading for correctness.

What to actually search for

A practical personal-data sweep looks for a small number of well-defined, high-signal patterns rather than trying to catch everything:

  • Email addresses — structurally unambiguous, easy to match reliably.
  • Phone numbers — meaningful but noisier; a phone-shaped digit sequence can also be an internal ID, so this needs to be treated as medium-confidence, not an automatic finding.
  • National ID formats — e.g. India's Aadhaar and PAN have specific structural shapes. A string matching the shape is a real signal, but matching the shape doesn't prove it's a real, valid ID — that distinction matters when you're deciding how urgently to act on it.
  • Credential-bearing connection strings — a database URL or API endpoint with a username/password embedded directly in it. This is both a privacy finding and a secrets-exposure finding at the same time.
  • Field names that reference personal data — a column or variable literally named email, phone, ssn, or similar. This is a weaker signal than an actual matched value (the field could be empty, or holding test data), but it's still useful: it tells you where to look even when no real value is currently present.

The honest way to think about these: some are near-certain (an email address matches a very specific structural pattern), and some are only suggestive (a 12-digit number that has Aadhaar's shape, or a column name that references personal data without any value to confirm it). A good scanning approach keeps that distinction visible instead of flattening everything into one "PII found" alert — a security team that gets 200 identical-looking alerts, most of which turn out to be field-name matches with no real data behind them, stops trusting the tool.

Two different places, two different jobs:

  1. Your own source code and configuration — the fastest, cheapest place to start, and the one most directly under your control. This catches the seed-file-with-real-data problem, the accidentally-committed .env, the debug log statement.
  2. A deployed application's actual behavior — what does a real HTTP response contain? What gets logged in production? This catches things static analysis of source code can't: a bug that leaks a field it shouldn't, or a third-party library that logs more than you expect.

Static source-code scanning is the higher-leverage starting point, because it's something you can run locally, in CI, or as a pre-commit check, entirely under your own control, with no dependency on a specific deployed environment.

A practical, local-first starting point

You don't need to set up infrastructure to start this. PatchVex's local scan command runs entirely on your own machine, against your own working directory, with zero network calls and zero account required — the lowest-friction way to get a first real answer to "do we have this problem, and where." It runs the same detection patterns described above directly against your files on disk — no server, no upload, nothing leaves your machine. The output tells you exactly which files and line numbers matched, at what confidence level, with the actual matched value always redacted (you get "this looks like an email address," never the email address itself printed back at you).

The same detection logic also runs as part of a CI-integrated scan, so a pull request that introduces a real credential or a fixture with real personal data can fail the check before it merges, not months later during an audit. PatchVex's CLI is not yet published for general install — if you'd like early access, get in touch or try the free web scanner for the surface-level checks (headers, TLS, cookies, CORS, exposed API keys) that are publicly available today.

What to do once you find something

Finding a match is the start, not the end:

  • Real personal data in a fixture or seed file — replace it with synthetic data. There is no reason a test fixture needs a real email address; a clearly-fake one (test-user-1@example.test) does the same job with zero risk.
  • A credential-bearing connection string — rotate the credential immediately (treat it as compromised the moment it was committed, regardless of whether the repo is public), then move it into environment variables that are never committed to source control.
  • A field-name-only match with no confirmed value — not urgent on its own, but worth knowing about: it tells you which parts of your system are the right place to focus a deeper review, or to apply stronger access controls proactively.
  • A genuine match in a log statement — fix the log statement to redact or omit the field, and separately consider whether the personal data that was already logged needs to be purged from log storage/retention.

Why this matters beyond any specific regulation

This isn't only a DPDP exercise, even though that's a concrete, current reason to do it now if you have users or customers in India. Knowing where personal data actually lives in your systems is foundational to basic security hygiene, independent of any specific law: you can't apply the right access controls, retention policy, or encryption to data you don't know exists. The regulatory deadline is a forcing function; the underlying work is worth doing regardless of the deadline.

For teams that want this as a policy gate rather than a one-off scan — a machine-readable rule that says "no personal data in logs, ever, and CI fails if we find one" — see Privacy-as-Code.