Developer platforms such as Heroku, Netlify and Render hold production secrets in environment variables and accept deploy and API tokens that can change or delete production, yet they are often run with personal accounts, broad roles and tokens that never expire. The 2022 theft of OAuth tokens issued to Heroku and Travis CI integrations, which were then used to clone private GitHub repositories, showed how far a PaaS credential can reach. This guide explains how access works on each platform, which admin roles and permissions matter, how API tokens, OAuth authorisations and Git integrations behave, what audit logging each offers, and a hardening baseline you can apply. It is for platform engineers and security teams responsible for applications on these services. For Vercel specifically see the Vercel Tokens Security Guide.
Key takeaways
- Tokens are the real perimeter. Heroku personal direct-authorisation tokens do not expire, Netlify personal access tokens act as the user, and Render API keys are created per account, so a leaked token is as good as a logged-in developer.
- Least-privilege roles exist, but often only on higher plans. Heroku separates view, deploy, operate and manage per app; Render's Contributor and Viewer roles need the Scale plan or higher; Netlify role options vary by plan.
- Deploy rights usually include secrets. On Heroku the deploy permission can view and edit config vars, so anyone who can ship code can read production credentials.
- Audit logs are limited and tier gated. Heroku's audit trail is Enterprise only and delivered as monthly archives; Netlify's team audit log is Pro and Enterprise; Render keeps at least 90 days for Pro and above.
- SSO enforcement changes token behaviour. Render invalidates API keys of members who have not signed in through SSO when enforcement is turned on, and Netlify denies personal tokens to SSO-required teams unless explicitly allowed.
How identity and access work in Heroku, Netlify and Render
| Area | Heroku | Netlify | Render |
|---|---|---|---|
| Human sign-in | Account with mandatory MFA; with SSO, MFA must be enforced at the identity provider | Account or Organization SSO (SAML) | Account; SAML SSO and SCIM on Scale and Enterprise |
| Organisation unit | Teams and Enterprise accounts | Teams (and organisations on Enterprise) | Workspaces, grouped into orgs on Scale or Enterprise |
| Access model | Team roles plus per-app permissions: view, deploy, operate, manage; collaborators on single apps | Team roles: Owner, Developer, Publisher, Internal Builder, Git Contributor, Reviewer, Billing Admin | Workspace roles: Admin, Developer, Contributor, Viewer, Billing; protected environments |
| Machine credentials | Platform API token, OAuth authorisations, team direct authorisations | Personal access tokens, OAuth applications, build and deploy hooks | API keys created in Account Settings |
| Secrets | Config vars per app | Environment variables with scopes and deploy contexts; Secrets Controller | Environment variables, environment groups, secret files |
The common pattern is that the developer's identity is the credential. Platform API tokens and keys inherit what the human can do, so access reviews must cover the tokens people created as well as their roles. See the OWASP NHI Top 10 NHI7 Long-Lived Secrets Guide.
Admin roles and privileged access
Heroku
Heroku documents four app permissions. View covers app info, releases and git pull. Deploy allows pushing code, viewing and editing config vars, rollbacks, one-off commands and scaling. Operate adds viewing logs, restarts, log drains and paid add-ons. Manage covers renaming, deleting or transferring apps, locking, and managing other users' permissions. Manage is a separate set of actions, not a superset. All team members get view on all team apps; the app creator gets all permissions; only users with manage or team admins can change access. Collaborators can be granted any combination on specific apps without seeing team settings or billing.
- Restrict deploy: because deploy exposes config vars, treat it as secret access and grant it only to people who need it.
- Use app locking on team apps so that only explicitly permissioned members have rights beyond view.
- Review collaborators: external collaborators added for a project are a common leftover.
Netlify
Owners have full team access, including members, roles, settings and project deletion. Developers manage deploys and project configuration but cannot remove members. Only Owners can delete or transfer projects. Git Contributors can trigger builds from private repositories without access to the Netlify app, and Netlify notes that deleting a Git Contributor from the team does not remove them from the repository, so they can still trigger builds until removed there. Shared environment variables can be read or edited only by Team Owners.
Render
Admins have full access, including members, workspace settings, audit log export and webhooks. Developers can create and modify projects, deploy and view logs, but are limited in protected environments, where creating or deleting services and SSH or Shell access are restricted. Contributors can deploy but cannot view sensitive fields, use SSH or Shell, or create and delete most resources. Viewers are read-only and cannot see service logs or sensitive fields. Only Admins can mark environments as protected.
- Protect production: mark production environments as protected and give most engineers Developer or Contributor.
- Keep Owners and Admins few: two or three named people per team or workspace, with SSO and phishing-resistant MFA at the identity provider. See the Phishing-Resistant MFA Guide.
Integrations, API tokens and service accounts
| Credential | Documented behaviour | Risk | Control |
|---|---|---|---|
| Heroku Platform API token | Used by the CLI for all API calls; stored in the OS keychain or ~/.netrc; HEROKU_API_KEY overrides; regenerated in the dashboard or on password change | Full user rights; plaintext in .netrc on machines without a keychain | Keep in the keychain; remove old .netrc entries; regenerate on offboarding |
| Heroku OAuth web flow token | Access tokens expire after 8 hours; refresh tokens do not expire; prefix HRKU- | Long-lived refresh tokens held by third-party integrations | Review and revoke authorisations; choose the narrowest scope |
| Heroku direct authorisation | Personal tokens do not expire; team tokens expire after one year, or 30, 60 or 90 days if chosen; team admins can revoke or regenerate | Non-expiring personal tokens in CI | Use team direct authorisations with 90 days or less for automation |
| Heroku OAuth scopes | global, identity, read, write, read-protected, write-protected; protected scopes include runtime secrets such as database URLs | Integrations granted global by default | Use read or write where secrets are not needed; note that log drain definitions with embedded credentials are visible even to read |
| Netlify personal access token | Acts on the user's account; expiry date chosen at creation; denied to SSO-required teams unless "Allow access to my SAML-based Netlify team" is selected; all tokens invalidated on password reset | Token in scripts with the owner's full rights | Short expiry; one token per purpose; avoid owner accounts for automation |
| Netlify OAuth applications | Required for public integrations; client key and secret registered in user settings | Secret leakage from the integration | Store the client secret in a vault; review authorised apps |
| Render API key | Created in Account Settings and shown once; revoke and recreate if compromised; invalidated when SCIM deprovisions the owner | Tied to a person, so automation breaks on leave or carries their rights | Dedicated automation member with a limited role; revoke on departure |
| Git provider integrations | Each platform connects to GitHub, GitLab or Bitbucket to build on push | Platform-held OAuth tokens to your source code | Install as a GitHub App limited to required repositories; review grants |
Where possible, keep platform tokens out of developer laptops and CI logs: store them in a secrets manager, inject them at run time, and scope them to one purpose. The PocketOS database deletion on another PaaS shows the failure mode: an account-scoped token created for managing domains was found by an AI coding agent and used to delete a production database and its backups. See the CI/CD Pipeline Identity Security Guide.
Secrets in environment variables
- Heroku config vars are visible to anyone with deploy or operate on the app, and to integrations holding a protected or global scope. Rotate config vars when someone with deploy access leaves.
- Netlify environment variables can be limited to scopes (Builds, Functions, Runtime, Post processing) and deploy contexts (Production, Deploy Previews, Branch deploys, Preview server, Local development) on Pro and Enterprise. Marking a variable "Contains secret values" puts it under the Secrets Controller: values become write-only, are masked outside the
devcontext, cannot use the post processing scope, and secret scanning fails a build when a secret appears in repository code or build output, including base64 and URI encoded forms. Do not setSECRETS_SCAN_ENABLED=falseto get a build through. - Render environment variables, environment groups and secret files (available at
/etc/secrets/<filename>) can be linked to many services. Scope an environment group to a single project environment so production secrets cannot be linked to preview services, and usesync: falsein Blueprints so secret values are never committed. - Preview deployments run code from branches and pull requests. Give them separate, low-privilege credentials, never production values.
For deeper guidance see the Secrets Management Guide.
Logging and detection
| Platform | Audit capability (as documented) | Access and export |
|---|---|---|
| Heroku | Enterprise accounts only; events include app.create, app.destroy, collaborator.create, config_change.set, config_change.remove, code_release.create, team_membership.create, app_transfer.create, trusted_ip.update | Monthly JSON archives from the dashboard or heroku enterprise:audits:export; no real-time logging and the current day is not included |
| Netlify | Team audit log on Pro and Enterprise: team settings, WAF rule updates, membership, project settings and integration changes; retention depends on plan | Team settings, Audit log; site audit log for single sites |
| Render | Workspace audit logs on Pro and higher, org-level on Scale and Enterprise; at least 90 days kept, starting from upgrade; covers members, services, datastores, environment variables, webhooks, shell sessions and SSO or SCIM changes | CSV export by Admins (90 days per export) or the List workspace audit logs and List organization audit logs API endpoints |
Detections worth building
- Config or environment variable changes to production apps outside a release pipeline (
config_change.seton Heroku; environment variable events on Render). - New collaborators or members with deploy or admin rights, especially external email domains.
- App transfers and deletions, which can move an app and its add-ons out of your control.
- Shell sessions into production services on Render, using the
source_ipanduser_agentmetadata. - SSO and SCIM configuration changes at the org level.
- Token use from unusual places: correlate source-control audit logs (for example GitHub) for clone activity by the platform's integration, as in the GitHub OAuth token breach, where stolen tokens issued to Heroku and Travis CI integrations were used to list and clone private repositories.
Because Heroku audit archives arrive monthly and lower plans may have no audit log, pull the logs that exist into the SIEM on a schedule and record the gaps in the Log Source Coverage Matrix Template.
Hardening baseline
- Centralise identity: move teams under SSO where the plan supports it (Heroku Enterprise, Netlify Organization SSO, Render Scale or Enterprise) and enforce MFA at the identity provider. On Render, review the enforcement report of members and API keys that will be invalidated before turning it on.
- Automate leavers: use SCIM where available. On Render, SCIM deprovisioning logs the member out and invalidates their API keys immediately.
- Apply least privilege: Heroku view by default and deploy only by need; Netlify Developers rather than Owners; Render Contributors in protected production environments.
- Replace personal automation tokens: Heroku team direct authorisations with 90-day expiry; Netlify tokens with short expiry; Render keys owned by a dedicated automation member.
- Mark secrets as secrets: Netlify Secrets Controller for every credential; Render environment groups scoped to one environment; Heroku config vars rotated after access changes.
- Separate preview from production credentials on every platform.
- Restrict Git integrations to the repositories each site or app needs.
- Collect audit logs on the highest available tier and keep them beyond the platform's own retention.
- Test and roll back: apply role changes to one team or workspace first, confirm deploys still work, and keep a record of previous role assignments so you can restore them quickly.
Common misconfigurations
- Non-expiring personal tokens in CI: a Heroku personal direct authorisation or a Netlify token with a distant expiry, owned by a developer who has left.
- Deploy access treated as harmless: on Heroku it reveals config vars, so a wide deploy group is a wide secrets group.
- Global OAuth scope for integrations: granting
globalorwrite-protectedto a monitoring or reporting tool that only needsread. - Disabling secret scanning: turning off Netlify scanning to unblock a build, instead of fixing the leaked value.
- Git Contributors and collaborators left behind: removed from the platform team but not from the repository, or added as Heroku collaborators and never removed.
- No audit trail on the current plan: discovering during an incident that the plan has no audit log, or that Heroku archives exclude the current day.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev. 5 | AC-2 Account Management; AC-6 Least Privilege; IA-5 Authenticator Management; AU-2 Event Logging | Roles, tokens and audit logs |
| CIS Controls v8.1 | Control 5 Account Management; Control 6 Access Control Management; Control 8 Audit Log Management | Accounts, roles and logging |
| ISO/IEC 27001:2022 | Annex A 5.15 Access control; 5.17 Authentication information; 8.15 Logging | Access and credentials |
| MITRE ATT&CK v20 | T1528 Steal Application Access Token; T1552.001 Credentials In Files; T1078.004 Cloud Accounts | Token theft and misuse |
Practitioner checklist
- Inventory every Heroku team, Netlify team and Render workspace with owners and plan level.
- Enforce SSO and MFA at the identity provider wherever the plan allows, with SCIM for leavers.
- Limit Owners and Admins to a few named people.
- Treat Heroku deploy and Render Developer in production as secret access and grant accordingly.
- Replace non-expiring personal tokens in automation with expiring team or dedicated credentials.
- Review Heroku OAuth authorisations and scopes, Netlify OAuth apps and Render API keys quarterly.
- Mark all Netlify credentials as secret values and keep secret scanning on.
- Keep production secrets out of preview deployments.
- Export available audit logs to the SIEM and alert on config changes, new members and transfers.
- Remove leavers from the Git provider as well as from the platform.
Standards and references
- Heroku Dev Center: App Permissions
- Heroku Dev Center: OAuth
- Heroku Dev Center: Audit Trail for Enterprise Accounts
- Netlify Docs: Roles and permissions
- Netlify Docs: Secrets Controller
- Netlify Docs: Get started with the Netlify API
- Render Docs: Workspace members and roles
- Render Docs: Audit logs
Related NHI Mgmt Group resources: Vercel Tokens Security Guide · SaaS API Token Security Guide · SaaS and OAuth App Governance Guide · SaaS Audit Log Retention and Licensing Guide