genv.

PlatformAPI credentials: least privilege, storage and rotation

10 min readPublished Reviewed By the GENV team

In short

Issue a separate credential per integration with the narrowest privileges that work, keep the value outside your application database, never display it again after it is set, and rehearse rotation before you need it. A credential nobody can rotate calmly is one that will be rotated during an incident.

The credentials a billing platform holds are among the most powerful in a hosting business: a WHM token can terminate accounts, a registrar key can transfer domains away. They deserve more care than they usually get, and OWASP's secrets management guidance is a reasonable standard to hold yourself to.

One credential per integration, scoped

  • Per integration, not per person. When somebody leaves you want to rotate one named credential, not audit five.
  • Narrowest privileges that work. Start from nothing and add what fails. Starting from full access and removing is a subtraction nobody ever finishes.
  • Named so the provider's own audit log is readable. "genv-production" tells you something; "api" does not.
  • Restricted by source address where the provider supports it.

Test the scoping by attempting something the credential should not be able to do. Reading the permissions screen tells you what you configured; attempting the action tells you what is true.

Where the value lives

Not in your application database alongside the business data. A secrets store keeps the value, and the application keeps a reference plus a generation number. Two properties follow, and both matter:

  1. A database backup — or a database compromise — does not carry the credentials with it.
  2. "The store no longer has it" becomes a state you can represent. Without it, a rotated-away credential looks identical to a working one until something fails.

The second property is the one that changes day-to-day behaviour. A system that can say "the store answered and does not have this version" can tell a person to re-enter it. One that cannot will report a generic provider failure and send somebody looking at the wrong thing.

Never show it again

Once set, a credential is write-only from the application's point of view. No route returns it, no screen displays it, no log records it, no export includes it. An administrator may replace it; nobody may read it. If your system has a "reveal" button, the credential is effectively readable by anybody who compromises an administrator session.

Rotation without an outage

Rotation is the operation that proves the design. The sequence that works:

  1. Issue a new credential at the provider, alongside the old one. Both valid.
  2. Store the new one. The application records a new generation.
  3. Exercise it — run a connection check, not a real effecting operation.
  4. Watch for work still in flight under the old generation, and let it finish.
  5. Revoke the old credential at the provider.
  6. Confirm the check still passes.

The step people miss is the fourth. A queued operation that resolved its credential before the rotation may still be running; revoking immediately turns it into a failure of exactly the ambiguous kind that is expensive to resolve.

What to record

The audit a credential needs
EventRecordedNot recorded
Credential setWho, when, which connection, which generationThe value
Credential rotatedWho, when, previous generationEither value
Connection checkedOutcome, whether a real provider answeredThe credential, the provider's raw response
Provider refusedThat it refused, in your own wordsThe provider's message verbatim

The last row is worth a sentence. Provider error text is not yours: it can contain identifiers from other customers, internal hostnames, or wording you do not want to show. Map to your own closed vocabulary and record that.

Where GENV is

GENV holds provider credentials in a secrets store outside the application database, records a reference and a generation rather than a value, and returns credentials from no route at all. Each call resolves one under the narrowest scope the module declares — which bounds what GENV asks for and not what the token can do at the provider, since that is set where you issued it. A connection check records whether a real provider answered, which in the current build is always no, because every provider module runs against a deterministic stand-in. The distinction between "the store could not be reached" and "the store answered and no longer has this version" is represented explicitly, because they need different responses from a person.

Questions

How often should credentials be rotated?

On a schedule you can actually keep, plus immediately when somebody with access leaves. Quarterly is a reasonable default; the important part is having rehearsed it so it is uneventful.

Is encrypting credentials in the database enough?

It is better than plaintext and it is weaker than a separate store, because the key is usually available to the same application that was compromised. It also cannot represent "the store no longer has this".

What about credentials in environment variables?

Acceptable for the application's own infrastructure credentials, poor for per-customer provider credentials, which need to be added and rotated without a deployment.

Sources

  1. OWASP — Secrets Management Cheat Sheet

Official documentation, each requested on 21 September 2026. Provider behaviour changes; check the source before relying on a detail.