Where Developer Data Actually Leaks
Security budgets go to the perimeter. Production data mostly does not leave through the perimeter — it leaves through a developer debugging something at 4pm, using a tool that felt local and was not. None of the channels below require an attacker. All of them are someone being efficient.
1. Online formatters, validators and converters
The classic. An unreadable API response goes into the first search result for "json formatter", and a production payload lands in a stranger's server logs. The input is by definition data you could not read yourself, so you cannot sanity-check what you are sending.
Close it: learn one local command and make it reflexive — jq ., python3 -m json.tool, or your editor's format-document shortcut. For browser tools, verify the claim once with the network tab open; a genuinely client-side tool keeps working with the network off.
2. AI chat windows
Now the biggest single channel, and the least policed. "Why is this query slow" plus a schema and a few rows. "What does this stack trace mean" plus a trace containing a signed URL. The interaction feels like talking to a colleague, so it is filed as conversation rather than transmission.
Close it: use the enterprise tier where the retention and training terms are contractual rather than a setting. Redact before pasting — structure is almost always what you need help with, not values. A schema and three rows of foo/bar/baz debugs the query exactly as well as three real customers.
3. Pastebins and gists
"Unlisted" is not "private". An unlisted gist is world-readable to anyone with the URL, the URL appears in the referrer header of anything you click from it, and both GitHub gists and most pastebins are continuously scraped by people looking for exactly this.
Close it: for sharing inside a team, use a channel with an access model — a private repo, an internal paste service, a direct message. If a public paste has already happened, deleting it is necessary and not sufficient; rotate anything credential-shaped immediately.
4. Screenshots in tickets
A screenshot of a bug captures whatever else was on screen: the adjacent browser tabs, the notification that arrived mid-capture, the rest of the table, the email in the background. Tickets then get attachments that live forever in a system with far broader access than the data warranted.
Close it: capture a region, not a window, and never the full screen. Crop before attaching rather than after. Treat a screenshot of a table as a copy of that table, because that is what it is.
5. Error trackers
Sentry, Rollbar and friends are configured once and then trusted forever. By default many capture request bodies, query strings, local variables at the point of the exception and user context. That is enormously useful for debugging and it means your error tracker quietly becomes a secondary store of production PII, with its own access list and its own retention.
Close it: configure scrubbing deliberately rather than relying on defaults — most SDKs support a before-send hook. Audit what is actually in a recent event rather than what you assume is in it. The gap is usually surprising.
6. Logs that capture request bodies
Debug-level logging turned on during an incident and never turned off is how full request bodies end up in a log aggregator for eighteen months. Passwords in login payloads are the common case, because the login endpoint is the one people debug.
Close it: maintain a field denylist at the logging layer, not at each call site. Log identifiers, not payloads. Make debug logging expire — a flag that resets after 24 hours is worth more than a policy that says to turn it off.
7. Git history
Deleting a committed .env in a later commit removes it from the working tree and from nothing else. It is in the history, it is in every clone, and it is in every fork. If the repository was ever public, assume it is indexed.
Close it: rotate the credential first — history rewriting is cleanup, not remediation. Then git filter-repo or BFG, and force-push with everyone informed. Prevention is a pre-commit hook: gitleaks or detect-secrets costs one afternoon to wire up.
8. CI logs
Build logs are frequently readable by anyone who can read the repository, and often by more people than that. Secrets masked in one form appear unmasked in another — base64-encoded, JSON-escaped, or printed by a subprocess that the masker never sees. set -x in a shell step prints every expanded variable.
Close it: never set -x in a job handling secrets. Check who can read build logs on your platform, because the default is usually broader than the repository. Assume masking is best-effort.
9. Analytics and session replay
Session replay tools record the DOM. Unless configured otherwise they record form fields, account pages and anything else rendered on screen, then ship it to a third party. Teams that would never approve exporting a customer table do this by installing a script.
Close it: mask by default and allowlist what gets recorded, rather than blocking fields one at a time as you notice them. Check what a replay of your own account page contains.
Every channel here has the same shape: a moment where transmitting data felt like handling data.
The pattern worth teaching
These are not nine problems. They are one problem with nine surfaces — an action that moves data somewhere, presented in an interface that gives no indication anything moved. A textarea, a chat box, a screenshot key, a log line. Nothing prompts, nothing confirms, nothing shows a destination.
Which is why the effective intervention is awareness rather than tooling. Nobody pastes a customer record into a random website on purpose. Naming the pattern once — if it left your machine, it went somewhere — does more than a policy document, because it installs a half-second of hesitation at the exact moment it is needed.
If you do one thing after reading this: open your error tracker and look at what is actually inside a recent event. It is the channel with the widest gap between what people assume is captured and what is.