A leaked Heroku token is usually a two-stage incident: the token itself can deploy, scale or delete apps, and with the right scope it can read every config var, which is where Heroku apps keep their database URLs and third-party API keys. Heroku has several credential types with very different lifetimes, from 8-hour OAuth access tokens to direct authorisations and refresh tokens that never expire, plus Heroku Postgres connection URLs that are themselves credentials. This guide shows how to tell those credentials apart, what each one reaches, which signals Heroku's audit trail gives you, and the exact revoke, regenerate and rotate steps from Heroku's Dev Center. It is written for platform and security teams running production on Heroku. For roles, secrets handling and audit logging across Heroku, Netlify and Render, read the Heroku, Netlify and Render PaaS Security Guide first; this page goes deeper on leak response.
Key takeaways
- Identify by prefix and context. Current OAuth tokens are 65 characters and begin
HRKU-; older unprefixed tokens are UUID-like and only become prefixed when regenerated; apostgres://URL in a Heroku app is a database credential. - Scope tells you if secrets are gone.
global,read-protectedandwrite-protectedinclude config vars; plainreadandwritedo not, though embedded credentials such as log drain URLs may still be visible. - Some tokens never expire. Personal direct authorisations and OAuth refresh tokens have no expiry, so a leak from years ago can still be live.
- Rotate config vars, not just tokens. If the leaked token could read config vars, every secret in them is exposed and must be rotated at its issuer, starting with Heroku Postgres credentials.
- Audit trail arrives late. Heroku's audit trail is Enterprise only and exported as monthly JSON archives up to midnight UTC of the current day, so plan to pull it during an incident rather than rely on alerts.
How identity and access work in Heroku
People sign in to Heroku with mandatory multi-factor authentication or through SSO. The CLI and all automation call the Platform API with a bearer token. Heroku's documentation describes these credential types:
| Credential | How to recognise it | Acts as | Lifetime | Where it is found |
|---|---|---|---|---|
| Platform API token (CLI login) | Printed by heroku auth:token; stored in the heroku-cli keychain entry or in ~/.netrc for api.heroku.com and git.heroku.com | The user | Expires after a designated period; regenerated on demand or on password change | Laptops, .netrc files, CI variables named HEROKU_API_KEY |
| OAuth access token (web flow) | 65 characters, HRKU- prefix | The user, limited to the granted scope | 8 hours | Third-party integrations |
| OAuth refresh token | Held by the integration with its client secret | Mints new access tokens | Does not expire | Integration back ends |
| Personal direct authorisation | An OAuth token created from an API token, HRKU- prefix | The user, with the chosen scope | Does not expire | Scripts and CI |
| Team direct authorisation | Created by heroku authorizations:create --team=... or the dashboard | The team; cannot perform user-only actions | 30, 60 or 90 days or 1 year | Team automation |
| Heroku Postgres credential | postgres:// URL in DATABASE_URL or similar config var | A database role; the default owner credential owns the database | Until rotated | Config vars, application logs, developer tools |
What each scope exposes
| Scope | Apps and resources | Config vars and runtime secrets | Account information |
|---|---|---|---|
global | Read and write | Yes | Yes |
write-protected | Read and write | Yes | No |
read-protected | Read | Yes | No |
write | Read and write | No (embedded credentials in some resources, such as log drains, may be visible) | No |
read | Read | No (same caveat) | No |
identity | No | No | Read only |
The CLI's default authorisation is equivalent to global, so a leaked CLI token or HEROKU_API_KEY should always be treated as exposing config vars for every app its owner can reach.
Admin roles and privileged access
Heroku permissions are set per app and per user. The ones that matter for a leak:
- Deploy: push code, view and edit config vars, scale, roll back and run one-off commands. A token whose owner holds Deploy can read the app's secrets and run code inside it.
- Operate: everything in Deploy plus logs, log drains, restarts and paid add-ons. Log drains are a quiet exfiltration route because they stream application output elsewhere.
- Manage: rename, delete, lock and transfer apps and manage other users' permissions. Combined with a leaked token this is the destructive tier.
- Team admins: can add and remove users, change permissions and revoke or regenerate team authorisations. Responders need a team admin available during an incident.
All team members have View on all team apps by default, which reveals releases and code but not config var values. Keep Deploy and Operate on production apps to the people and pipelines that need them, as the Privileged Access Management Guide recommends for any standing access.
Integrations, API tokens and service accounts
Choosing a credential for automation
| Use case | Prefer | Avoid |
|---|---|---|
| CI deploying one or two apps | Team direct authorisation with 90 days or less and the narrowest scope that works | A developer's HEROKU_API_KEY |
| Reporting or inventory | read scope authorisation | global |
| Third-party integration | OAuth with read or write unless secrets are genuinely needed | Granting global by default |
| Read-only database access for analysts | A separate Postgres credential (production plans) with limited grants | Sharing the default owner URL |
Team authorisations cannot perform user-only actions even with global, and they survive staff changes, which makes them the right home for automation. Heroku also shows the token only once when it is created or regenerated, so place it straight into the CI secret store.
Third-party tokens held by Heroku
Heroku itself holds tokens for your other systems, most obviously the GitHub integration. In the GitHub OAuth token breach of April 2022, OAuth user tokens issued to the Heroku Dashboard and Travis CI integrations were stolen and used to list and clone private repositories, and an AWS key found in a cloned npm repository was then used against npm's infrastructure. If Heroku or any connected integration announces a compromise, revoke the GitHub authorisation for the integration, review repository clone activity, and rotate any secrets stored in the repositories it could read. The SaaS and OAuth App Governance Guide covers the governance side.
Logging and detection
Detection coverage
| Detector | Rule or type | Notes |
|---|---|---|
| GitHub secret scanning | heroku_platform_api_oauth2_token | Private repositories with GitHub Advanced Security; push protection and validity check; not a public partner pattern |
| GitHub secret scanning | heroku_postgres_connection_url | Private repositories with GitHub Advanced Security; push protection; no validity check |
| Gitleaks default rules | heroku-api-key-v2 (prefix HRKU-AA) and heroku-api-key (UUID near the word "heroku") | The older rule is context based, so a legacy token under a neutral name is missed |
Because Heroku is not listed as a GitHub partner for public repositories, a token pushed publicly is not reported to Heroku by GitHub and will not be revoked for you. Also scan for .netrc files in backups, dotfile repositories and container images, since the CLI falls back to .netrc when no keychain is available and leaves old entries there. See the Git Secret Scanning Guide.
Audit trail signals
Enterprise accounts can export a JSON audit archive per calendar month from Settings (Export as JSON in the Audit Logs row) or with heroku enterprise:audits:export from the enterprise CLI plugin. Entries are not always in time order, so sort by created_at. Events worth hunting after a leak:
config_change.setandconfig_change.removeoutside your release process.collaborator.createandteam_membership.createadding unfamiliar accounts.code_release.createnot matched to a pipeline run, andapp_transfer.createorapp.destroy.trusted_ip.update, which could loosen network restrictions.
Heroku's list has no generic OAuth or API token events and no log drain events, so list authorisations and log drains directly during an investigation. Accounts without Enterprise have no audit trail at all, which makes application and database logs, plus GitHub's own audit log, the main evidence.
Metrics
- Personal direct authorisations in use by automation, trending to zero.
- Team authorisations with expiry of 90 days or less, as a share of the total.
- Integrations holding
globalor protected scopes. - Days since the last Postgres credential rotation for each production database.
- Time from a secret alert to revocation and config var rotation.
Leak response: revoke and rotate
- Classify the credential:
HRKU-token, legacy UUID token, CLI token from.netrc, or a Postgres URL. Find the owner and scope withheroku authorizations(add--team=<team>for team authorisations). - Revoke the authorisation: call
DELETE /oauth/authorizations/{authorization-id}, or for team authorisations use Settings, Team Authorizations, Revoke, which any team admin can do and which is immediate and permanent. Where a pipeline must keep running, use Regenerate instead: the old value stops working at once and you deploy the new one. - Regenerate the user's API token: if a CLI token or
HEROKU_API_KEYleaked, the user regenerates it in the Heroku web interface (a password change also regenerates it), then removes stale.netrcentries withHEROKU_NETRC_WRITE=true heroku logout. - Decide whether config vars are exposed: if the scope was
global,read-protectedorwrite-protected, or the token was a CLI token, list the apps the owner could reach and treat every secret in their config vars as exposed. - Rotate Heroku Postgres credentials: run
heroku pg:credentials:rotate(Classic) orheroku data:pg:credentials:rotate(Advanced), with--allif several credentials were exposed. Heroku updates the config vars on attached apps and restarts them. Without--forceit waits up to 30 minutes for open transactions; with--forceit kills connections at once, which is right when an attacker may hold a live session. - Rotate other config var secrets at their issuers: cloud keys, payment and email provider keys, OAuth client secrets. Update the config vars and let the release restart dynos.
- Hunt for changes: export the current month's audit archive and look for the events listed above, then review log drains, collaborators and add-ons by hand.
- Record the cause and fix the source: remove the token from history and images, and document the evidence with the Secrets Rotation Runbook Template.
Hardening baseline
- Inventory authorisations: list personal and team authorisations, integrations and their scopes; record an owner for each.
- Move CI to team authorisations: create a team authorisation with the shortest workable expiry, switch the pipeline, verify a deploy, then revoke the personal token it replaces.
- Reduce scopes: re-authorise integrations with
readorwritewhere they do not need secrets. - Tighten app permissions: remove Deploy and Operate from people who only need View on production.
- Separate database credentials: on production plans, give analytics and tools their own Postgres credentials so one leak does not expose the owner role.
- Rehearse rotation: run a Postgres credential rotation in staging to learn its restart behaviour before you need it in production.
- Roll back: if a revoked token breaks a job, issue a new narrow team authorisation; never restore the leaked one.
Small teams can stop at team authorisations for CI, scanning for HRKU- and .netrc, and a rotation runbook for Postgres and the main config vars. Larger organisations should use Enterprise audit archives in the SIEM on a monthly schedule, enforce SSO, and review integrations quarterly. Whatever the size, write down in advance which secrets live in each production app's config vars, so that the rotation list after a leak is a lookup rather than an investigation.
Common misconfigurations
- Personal direct authorisations in CI: they never expire and act with the user's full rights.
- Revoking the token but not rotating config vars: anything the token read is still exposed.
- Integrations granted
global: a breach at the integrator exposes every secret. - Old
.netrcentries left behind: the CLI does not remove them when it moves to the keychain. - Everyone uses the Postgres owner credential: rotation then affects every consumer at once.
- Relying on an audit trail you do not have: outside Enterprise there is none, and inside it there are no token events.
Framework mapping
| Control area | NIST SP 800-53 Rev 5 | NIST CSF 2.0 | ISO/IEC 27001:2022 Annex A | MITRE ATT&CK |
|---|---|---|---|---|
| Token inventory, expiry and revocation | IA-5, AC-2 | PR.AA-01 | A.5.17 Authentication information | T1078.004 Cloud Accounts |
| No tokens or database URLs in code and dotfiles | IA-5(7) | PR.AA-01 | A.8.4 Access to source code | T1552.001 Credentials In Files |
| App permissions and OAuth scopes | AC-6 | PR.AA-05 | A.8.2 Privileged access rights | T1528 Steal Application Access Token |
| Third-party integrations holding tokens | AC-6, IA-5 | GV.SC-05 | A.5.19 Information security in supplier relationships | T1199 Trusted Relationship |
| Audit review | AU-6, SI-4 | DE.CM-09 | A.8.16 Monitoring activities | T1098 Account Manipulation |
Practitioner checklist
- List every personal and team authorisation with owner, scope and expiry.
- Replace personal tokens in CI with team authorisations of 90 days or less.
- Re-authorise integrations with the narrowest scope.
- Scan repositories, images and backups for
HRKU-, legacy tokens,.netrcandpostgres://URLs. - Keep a runbook for Postgres credential rotation, including when to use
--force. - After any token leak with protected or global scope, rotate config var secrets at their issuers.
- Export audit archives monthly (Enterprise) and check config, collaborator and release events.
- Know who your team admins are and how to reach them out of hours.
Standards and references
- Heroku Dev Center: Heroku OAuth
- Heroku Dev Center: CLI authentication
- Heroku Dev Center: Heroku Postgres credentials
- Heroku Dev Center: App permissions
- Heroku Dev Center: Audit trails for Enterprise accounts
- GitHub Docs: Supported secret scanning patterns
- NIST: SP 800-53 Rev 5, Security and Privacy Controls
Related NHI Mgmt Group resources: Heroku, Netlify and Render PaaS Security Guide · Leaked Credential Response Playbook · GitHub OAuth Token Breach · Secrets Management Guide