Short answer: generate the quarterly report from a point-in-time export of the live key inventory, join every key to an accountable owner and access policy, and preserve the evidence trail so an auditor can replay the result.
The page usually arrives late. A prepaid e-commerce balance is nearly empty, and the on-call sees a burst of rejected settlement calls. The alert is useful, but it is not the control an auditor wants to inspect. By then, nobody can answer a simpler question: which API credentials could still spend that balance, and who reviewed them this quarter?
That gap is an access-review problem, not a dashboard problem. The report needs to describe the inventory as it existed at the review cutoff, the decision made for each credential, and the person who approved or revoked it.
Stop.
What should a quarterly API credential access review contain?
Start with a snapshot, not a hand-maintained spreadsheet. Capture the key identifier (or a salted fingerprint), service, environment, owner, scopes, creation and last-used timestamps, expiration, status, and the balance or payment capability reachable through the credential. Do not store secret values in the report. A fingerprint lets the reviewer correlate records without creating another secret to protect.
The review cutoff must be explicit, including its timezone. A live inventory changes while a report is being assembled; an auditor should be able to distinguish “not present at 2026-09-30 23:59 UTC” from “created after the review.” Keep the raw export immutable, hash the file, and record who collected it. Retain the query or API request metadata alongside the export so the evidence can be reproduced.
The decision field is where the control becomes meaningful. Use a small vocabulary such as retain, rotate, reduce-scope, revoke, and exception. An exception needs an owner, reason, compensating control, and expiry date. “Looks active” is not an approval.
Here is a compact evidence shape. It is deliberately vendor-neutral and omits secret material:
type CredentialReview struct {
KeyFingerprint string `json:"key_fingerprint"`
Service string `json:"service"`
Environment string `json:"environment"`
Owner string `json:"owner"`
Scopes []string `json:"scopes"`
LastUsedAt time.Time `json:"last_used_at"`
ExpiresAt time.Time `json:"expires_at"`
Decision string `json:"decision"`
Reviewer string `json:"reviewer"`
ReviewedAt time.Time `json:"reviewed_at"`
ExceptionUntil *time.Time `json:"exception_until,omitempty"`
}
The report should also state the population and exclusions: production versus sandbox, disabled keys, service accounts, and any keys owned by a departed employee. Missing ownership is a finding. It should not silently disappear in a filter.
How can live key inventory become replayable audit evidence?
Treat collection and review as two separate events. A collector reads the provider inventory with read-only credentials, normalizes fields, and writes an append-only artifact. A reviewer then works from that artifact. This prevents a key being added or revoked halfway through review from changing the denominator without leaving a trace.
One practical pattern is a signed manifest next to the export. The manifest records the cutoff, source identity, row count, hash, and schema version. Store both in a write-once location with access logs. The storage system is less important than the properties: historical versions cannot be edited, reads are attributable, and retention covers the SOC 2 evidence window defined by your policy.
The join to identity is often the hard part. A service name is not an accountable person. Resolve ownership through your identity directory or on-call roster, then preserve the mapping used for that quarter. When ownership cannot be resolved, route the record to a queue with a due date; do not infer an owner from the last deploy author.
I initially expected the balance alert to be the leading signal. It was not. The useful signal was a weekly count of credentials with no owner or with scopes wider than the service contract. That earlier check creates time to rotate a key before a payment failure turns into a customer incident.
What failure modes make a SOC 2 report hard to defend?
The most common failure is proving that a review happened without proving what was reviewed. A signed approval on a current dashboard cannot establish the prior quarter's population. Another is counting keys rather than access paths: one credential may reach several payment operations, while five keys may all be disabled. Review the effective capability and scope, not just row totals.
Duplicate deliveries create a different trap. If the collector retries after a timeout and appends a second copy, the report can show inflated counts and conflicting decisions. Give each snapshot a deterministic identifier, make writes idempotent, and reject a duplicate manifest hash. Keep the retry log; it explains why a collector ran twice without changing the evidence.
The retry path deserves a test that looks like a small postmortem. Imagine the collector has read 4,812 records when the inventory endpoint closes the connection. The worker cannot know whether the last page was committed, so it starts again with the same cutoff and manifest ID. A naive append produces 9,624 rows, two owners may approve different copies, and the final CSV appears internally consistent until someone compares it with the source count. An idempotent writer instead keys the snapshot on (cutoff, source, schema_version), writes each record by fingerprint, and records the retry as metadata. The second pass can update a missing last_used_at, but it cannot create a second credential. Test this with a forced timeout and a partial write, then verify that the hash is stable when the source is unchanged and different when a scope changes. That is the sort of evidence an auditor can replay without trusting the collector's log message.
Thresholds need a human cost check. An alert for every unowned sandbox key creates noise and trains reviewers to approve blindly. A threshold that ignores production payment scopes creates false reassurance. Calibrate by environment and capability, then sample the “retain” decisions for stale use timestamps. Your mileage may vary because the right window depends on rotation policy and workload seasonality; document the rationale instead of claiming a universal number.
The report is not complete until remediation is closed. Link a revoke or rotation ticket, record its completion time, and run a second inventory query. If the follow-up still finds the key, the control should remain open with an explicit exception rather than being marked passed.
Choosing an implementation boundary
Teams usually combine three layers: a secrets manager for value storage and rotation, an inventory source that exposes metadata, and an evidence store with immutable retention. Cloud-native inventory services can reduce collection code, while a self-hosted registry can offer more control over schema and retention. The trade-off is operational ownership: every extra integration adds another identity mapping and another place where timestamps can disagree.
Do not choose on unit price alone. Choose the boundary that lets your reviewer answer, in one sitting, who had access, what they could do, when the snapshot was taken, and what changed afterward. A platform that lacks scope-level metadata is unsuitable for an audit focused on payment operations; a system that cannot export immutable history is unsuitable for quarterly evidence, even if its key issuance API is convenient.
Before production, test the control with seeded credentials: an expired key, an unowned key, a broad-scope key, a disabled key, and a key created after the cutoff. Verify that each lands in the expected decision state and that the generated hash changes when a material field changes. Then run the collector under its own identity and review its access logs as part of the same control.
The on-call page still matters. It is the last line for a balance that is about to run out. The quarterly access review is the earlier, quieter control that makes the page explainable to an auditor and actionable for the team.
References
- OWASP Secrets Management Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html
- AICPA SOC 2 overview: https://www.aicpa-cima.com/resources/landing/soc-2
- NIST SP 800-57 Part 1 Rev. 5, key management guidance: https://csrc.nist.gov/pubs/sp/800/57/pt1/r5/final
Top comments (0)