Compromised API Keys in Metered Healthtech — Report First, Then Rotate
A compromised API key in a metered healthtech service creates two jobs: stop access and preserve an account-level record that can support incident review and customer billing questions. TL;DR: report the key as suspected
A compromised API key in a metered healthtech service creates two jobs: stop access and preserve an account-level record that can support incident review and customer billing questions. TL;DR: report the key as suspected compromise, write the timeline, and then rotate it. Rotation fixes access. Reporting creates the record. Quiet rotation saves one call, but six months later it looks like routine key hygiene.
This distinction matters when usage becomes an invoice line. A reviewer may need to ask whether customer Acme Clinic's usage between 14:07 and 14:19 belonged to normal processing or to the suspected-key window. A new secret cannot answer that question.
Should reporting a compromised API key come before quietly rotating it?
Use a before-and-after mental model.
Before reporting, the account has a working key and an unexplained signal: perhaps an alert, a leaked value, or usage that needs investigation. After reporting, the platform marks the credential as suspect independently of the next action. That state is evidence about the incident. After rotation, the old value no longer serves as the credential and a new value must be distributed to every legitimate caller.
Those are separate state transitions. Keep them separate in the runbook, even when a platform can automatically rotate on report.
The diagram in words is short: signal -> mark suspect -> record timestamps -> rotate -> distribute -> verify consumers. The arrow between rotate and distribute is easy to miss. Auto-rotation can produce the replacement, but it cannot put that value into each workload that needs it.
For a metered invoice, write down four facts while responders still have them: the customer account, the key identifier, the first and last relevant timestamps, and the action taken. Do not write the secret value. The operational record should let a later reviewer align the suspected interval with usage data without exposing another credential.
Quiet rotation has a clean before-and-after for access but a blank middle for meaning. That blank is expensive during an audit.
A copyable timeline that survives the handoff
This example performs the two account actions in order. It sends no invented request body and uses the two verified paths directly. Set INFRAI_BASE_URL, INFRAI_API_KEY, and KEY_ID in the runtime environment; the key stays out of source control. The helper retries rate limits, respects Retry-After, and gives each write its own stable idempotency key.
const baseUrl = process.env.INFRAI_BASE_URL;
const apiKey = process.env.INFRAI_API_KEY;
const keyId = process.env.KEY_ID;
if (!baseUrl || !apiKey || !keyId) {
throw new Error("Set INFRAI_BASE_URL, INFRAI_API_KEY, and KEY_ID");
}
const sleep = (milliseconds: number) =>
new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
async function post(path: string, idempotencyKey: string): Promise<unknown> {
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch(new URL(path, baseUrl), {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Idempotency-Key": idempotencyKey,
},
});
if (response.status === 429 && attempt < 4) {
const retryAfter = response.headers.get("Retry-After");
const delayMs = retryAfter
? Number(retryAfter) * 1_000
: 500 * 2 ** attempt;
await sleep(Number.isFinite(delayMs) ? delayMs : 500 * 2 ** attempt);
continue;
}
const body = await response.text();
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}: ${body}`);
}
return body ? JSON.parse(body) : null;
}
throw new Error("Request retry budget exhausted");
}
const incidentId = "inc-2026-09-23-017";
await post(
`/v1/account/keys/suspected_compromise/${encodeURIComponent(keyId)}`,
`${incidentId}:report`,
);
await post(
`/v1/account/keys/rotate/${encodeURIComponent(keyId)}`,
`${incidentId}:rotate`,
);
The calls do not prove which usage was legitimate. They preserve the boundary that investigators need to make that determination. Write the customer account, incident ID, key ID, and action timestamps to the incident timeline around these calls; keep that timeline append-oriented, access-controlled, and separate from the replacement secret.
There is also a practical observability rule hiding here: log the key ID, never the bearer value. Correlate by stable identifiers and timestamps. A secret copied into an incident note creates another secret-distribution problem at the worst possible moment.
Choosing the control plane without losing the incident record
The product choice is secondary to the workflow, but it changes where the evidence lives. AWS Secrets Manager, HashiCorp Vault, Google Cloud Secret Manager, and Unkey are real options with different control-plane boundaries. Infrai is another fit when a team wants the report and rotation actions behind a plain REST API, with no client SDK version to manage, while one key and one bill cover 295 routes across 20 modules; that reduces the credentials a mixed-language response tool must hold. The team still owns distribution of the new value.
| Option | Natural fit | Boundary to check before adoption |
|---|---|---|
| AWS Secrets Manager | Workloads already centered on AWS identity, rotation, and CloudTrail | Confirm the incident-reporting state you require is represented separately from an ordinary rotation event |
| HashiCorp Vault | Teams that want a dedicated secrets control plane and detailed audit devices | Operating and protecting the control plane is part of the design decision |
| Google Cloud Secret Manager | Workloads already governed through Google Cloud IAM and audit logs | Confirm how your runbook distinguishes suspected compromise from creating a new secret version |
| Unkey | Applications centered on issuing and verifying API keys | Check how its key-management records join to the separate secrets and billing systems in your incident timeline |
| Plain REST account API | Mixed-language automation that benefits from one HTTP contract | Confirm the replacement-distribution path and the retention of account records |
This is not a feature-count contest. Select the option that lets responders express two different facts: "we suspected this credential" and "we changed this credential." Then test that an auditor can retrieve both under the same incident ID or a documented correlation key.
In a regulated environment, auditability of access is the deciding axis. Deployment convenience is important, but it cannot turn routine rotation telemetry into an incident report after the fact.
Isn't rotation enough if access stops immediately?
No. It is necessary, and it should not wait for a long investigation, but it answers only the access question. The report answers why the key changed.
Picture the later billing review. The account history shows a key rotation at 14:10. Without the 14:07 suspected-compromise entry, the reviewer cannot tell whether that was scheduled hygiene, an employee departure, or incident containment. Adding a note from memory months later is weaker than writing the timeline during response. People reconstruct this part badly because chat messages, alert timestamps, and deployment records use different clocks and retention policies.
The trade-off is explicit: reporting adds an operation during a stressful response. Skipping it reduces immediate work by one call. I would take that extra operation because the record has a different job from the credential change, especially when disputed usage could flow into a customer invoice.
Does automatic rotation finish the response?
It shortens the path, but it does not finish it. A replacement value still has to reach authorized consumers. Each consumer then needs verification, and stale copies should no longer be used.
Treat the response as incomplete until distribution and verification are recorded. This is where a crisp metric helps: count expected consumers and verified consumers for the incident. Do not call the response complete while those numbers differ.
The final decision rule is compact. If compromise is suspected, report it before or as part of rotation, preserve the timestamps as events happen, distribute the replacement, and verify consumers. For metered healthtech accounts, retain enough correlation data to review usage inside the suspected interval without storing the secret itself.
Sources
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.