Safeguard
Best Practices

Never Investigate a Bug by Calling the API as a Real Customer

That read is very likely a write. Progress state, audit rows, quotas, notifications and billing all record the identity you sent, permanently, under a real person's name, and it contaminates the thing you were investigating.

Daniel Osei
Security Analyst
6 min read

You need to check whether a production bug affects a specific customer. The fastest way to find out is to call the API as them and look at the response.

Do not. That read is very likely a write, and you are about to put a permanent, wrong record under a real person's name in your production database.

This post is why that happens, how to investigate properly instead, and the design changes that stop the temptation existing. Written for engineers with production access and a genuine reason to use it.

Reads are writes more often than you think

An endpoint labelled a read frequently has side effects, and they are usually the ones with the longest memory:

  • Progress and state. Fetching a lesson marks it started. Opening a record marks it seen. Requesting a document marks it delivered.
  • Audit and access logs. A record that this identity accessed this resource at this time, which is exactly the sort of thing that is later used as evidence.
  • Analytics and billing. Session counts, active user metrics, seat usage, per-request charges.
  • Notifications. A "someone viewed your document" email, sent to a customer, about an action they did not take.
  • Rate limits and quotas. Consumed from their allowance, not yours.
  • Recommendation and personalisation state, which quietly reshapes what that user sees afterwards.

None of these appear in the endpoint's name. Many do not appear in its documentation. You find out afterwards, and often you find out because the customer asks.

The identity usually rides in a header: X-User-Id, an impersonation token, a tenant header. Anything downstream that attributes an action takes that value at face value, because that is its job.

Why this is worse than an ordinary mistake

Three reasons, and the third is the one that should settle it.

The record is durable and looks genuine. There is no marker distinguishing it from real activity. A month later nobody can tell the difference, including you.

It corrupts the evidence you were gathering. You went looking for whether the user hit a bug. Now their state includes your request, so the investigation contaminated the thing it was investigating.

It may be unlawful, and it is certainly a policy breach at most companies. Accessing a customer's data without a ticket, consent, or a documented break-glass process is the kind of thing that appears in an audit finding. If you are in scope for SOC 2, ISO 27001, HIPAA, or GDPR, the control you just walked around is one an auditor will test. "I was debugging" is a true explanation and not a defence.

What to do instead

In rough order of preference.

Query the database read-only. A replica, a read-only credential, a reporting connection. You want to know their state, and their state is in a table. Reading a row causes nothing.

-- what you actually wanted to know
SELECT status, created_at, last_event_at
FROM user_progress
WHERE user_id = $1;

Use an admin or support API designed for it. Most mature systems have one, and the distinction is that it reads about a user rather than as one, and it attributes the access to you. If yours does not have this, it is worth building, because the absence is what pushes people toward impersonation.

Read the logs and traces. If you want to know what happened to their request, the trace of their actual request is better evidence than a new request of your own.

Reproduce with a test account in the same tenant, with the same configuration. Slower, and it is the only method that gives you a live system to poke at without touching a real person.

If you genuinely must act as them, make it a process rather than a decision: a support ticket giving a reason, time-bounded access, an impersonation flag that is recorded on every resulting row, and a notification to the customer if your terms require it. Some products need this and there is nothing wrong with it. What is wrong is doing it by hand, with a header, with no record.

Designing so the temptation does not arise

Mark impersonated traffic, at the boundary. If a request arrives with support impersonation, set a flag that every downstream writer records. Then impersonated activity is filterable, excludable from analytics, and visible in an audit.

Separate the read path from the side effect. An endpoint that returns a resource and also records progress is two operations sharing a name. Splitting them means a support engineer has something safe to call, and it usually improves the API for everyone else too.

Make the safe path faster than the unsafe one. This is the whole discipline. People reach for impersonation because it takes thirty seconds and the correct route takes twenty minutes of finding credentials. Invert that and the problem mostly disappears. A read-only console, a support view, a documented query, anything that is quicker to reach than a curl command with a user header.

Refuse identity headers from outside. X-User-Id accepted from an untrusted caller is an authentication bypass, not merely an internal convenience. It should be stripped at the edge and settable only by an authenticated internal path.

The concession

Sometimes the safe routes genuinely do not exist, production is broken, a customer is affected, and the pragmatic thing is to make the call as them and clean up afterwards.

If that is where you are, do the three things that make it recoverable: write down what you did and when, before you do it; prefer the narrowest read available; and tell whoever owns that customer relationship. The failure is not the access, it is the access nobody can reconstruct later. An investigation that leaves a record is defensible. One that leaves only anomalous rows in a customer's history is not.

The implication

The reason this happens is not carelessness. It is that the fast path and the safe path point in different directions, and people under time pressure take the fast one, reliably, at every company.

So treat a colleague reaching for impersonation as a signal about your tooling rather than about their judgement. Build the read-only view, and the behaviour changes without anyone needing a policy.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.