← Back to Blog
· 9 min read · API Stronghold Team

Your Ex-Employee's API Key Is Still Deploying to Production

Cover image for Your Ex-Employee's API Key Is Still Deploying to Production

You deprovisioned the account in Okta. The user can’t log in. You checked the box. But their GitHub PAT is still live, their AWS IAM key still works, and the CI/CD pipeline running under their Slack OAuth grant is still deploying to production.

This is not a corner case. It is the default behavior of every major enterprise SSO system in production today.

SSO revocation handles authentication. It does almost nothing about authorization artifacts that were issued before the revocation happened. Those tokens have their own lifecycle, and most teams have no idea where they live or how many are still active.

What SSO Actually Revokes (It’s Less Than You Think)

When you disable a user in Okta, Azure AD, or Google Workspace, here is what actually happens: the IdP session token is invalidated, and future SAML assertions or OIDC tokens from that IdP will be blocked. The user cannot authenticate through the SSO provider again.

That sounds complete. It is not.

What SSO revocation does not touch: any OAuth access token already granted to a third-party service. Any refresh token a connected app holds. Any API key, PAT, or service account credential that was issued outside the IdP’s direct control.

The gap is structural. SAML and OIDC are authentication protocols. They prove who you are at login time. Once a downstream service has issued its own token, that token’s lifecycle is managed by the service, not the IdP. Okta has no authority to reach into GitHub and revoke a personal access token. Azure AD cannot tell AWS to expire an IAM user key. The protocols were not designed for that.

Most enterprise SSO systems do not propagate revocation events to connected apps by default, even for apps in the SSO catalog. Some services support SCIM for account deactivation, which helps with disabling the account itself. But disabling an account and revoking issued tokens are different operations. Many services only do one when asked to do both.

The assumption that “disabling SSO = full access removal” is one of the most common offboarding gaps in enterprise security. The checkbox is real. The protection it implies often is not.

The Token Graveyard: What Survives After Offboarding

Here is what typically remains live after an Okta or Azure AD deprovision event.

GitHub PATs and fine-grained tokens. These are issued by GitHub directly. They do not expire unless the user sets an expiration or GitHub revokes them. A developer who created a PAT two years ago with repo and workflow scope still has full write access to every repository that token touches, even after their GitHub account is suspended, if the token was already issued and never rotated.

AWS IAM user keys. IAM users and their access keys are entirely separate from any SSO/SAML integration. A developer with a programmatic IAM user key they created six months ago will retain access indefinitely. The key is tied to the IAM user, not the federated identity, so deprovisioning the SAML federation does nothing to it.

OAuth refresh tokens. When a user authorizes an OAuth app, the resulting refresh token can outlive the session by months or years. The app uses it to silently re-authenticate. Until the user or an admin explicitly revokes the OAuth grant in the issuing service, the token remains valid.

Database credentials provisioned manually. Postgres passwords, MongoDB connection strings, Redis auth tokens. These live in .env files, Kubernetes secrets, and CI/CD variable stores. They were provisioned by a human, often the engineer who set up the service. No offboarding workflow touches them unless someone manually hunts them down.

Kubernetes service account tokens. If a developer created a service account and generated a long-lived token, that token is valid until deleted in the cluster. It does not expire on its own.

Slack and Notion OAuth app installations. Both platforms allow users to install OAuth apps that post messages, read channel history, or update pages under their identity. After offboarding, those app authorizations often remain. The app keeps posting. The name on the messages is gone, but the access is not.

Long-lived JWT secrets in CI/CD pipelines. A JWT signed with a shared secret and a 1-year expiry is just a text string in an environment variable. Revoking the user does not invalidate the JWT.

npm, PyPI, and Docker Hub publish tokens. Package registry tokens are issued by the registry and managed there. They have nothing to do with enterprise SSO. A developer’s npm token can publish to an organization’s scoped packages indefinitely after their company email is gone.

Why This Happens: The Architecture Problem

The core issue is that OAuth 2.0 token revocation, defined in RFC 7009, is optional. Services are not required to implement it. Many do not. Even services that do implement it only revoke tokens they issued; they cannot accept revocation requests from third parties like your IdP.

Service-issued credentials were designed to be decoupled from identity provider lifecycles. This was intentional. API keys are treated as infrastructure credentials, not identity artifacts. The AWS documentation describes IAM access keys as long-term credentials. GitHub PATs are designed to work in automated environments where interactive SSO is unavailable. That design assumption baked in a lifecycle mismatch that most teams never explicitly address.

SCIM (System for Cross-domain Identity Management) helps with the account layer. When your IdP supports SCIM provisioning to a service, deprovisioning in the IdP can disable the account in the connected service. But SCIM typically handles account state, not token revocation. Disabling a GitHub account via SCIM does not automatically revoke PATs issued under that account. The behavior varies by service and is often undocumented.

The result: most organizations have no authoritative inventory of what tokens exist, who issued them, what services they reach, or when they expire. Offboarding checklists address the visible layer. The invisible layer, credentials in config files and secrets stores, persists untouched.

A Real Offboarding Scenario (Step by Step)

An engineer on a six-person platform team resigns. Their last day is Friday. IT deprovisions their Okta account at 5:00 PM. They cannot log in to Okta-connected services. The offboarding ticket is marked complete.

Here is what nobody checks.

Eighteen months ago, the engineer created a GitHub PAT with repo and workflow scope to debug a CI/CD pipeline issue. They put it in a .env file in two microservice repositories under the variable name GITHUB_TOKEN. The token never expired. Both .env files are still checked into private branches that haven’t been rotated.

Eight months ago, they provisioned an AWS IAM user to test a Lambda deployment script. The access key was added to their personal AWS CLI config and also pasted into a Terraform variable file for a staging environment. That IAM user still has ec2:* and s3:* permissions. The key is active.

Six months ago, they installed a Slack bot under their OAuth credentials to post daily standup summaries to an internal channel. The bot is still running. It still authenticates with a refresh token that does not expire until the grant is explicitly revoked in Slack’s app management settings. The bot’s messages still appear with an integration name, but the underlying identity is their personal Slack OAuth grant.

Nothing triggered an alert. The SIEM saw a clean offboarding. The Okta logs showed the account disabled. The actual access surface remained intact.

This is not hypothetical. Variations of this scenario appear in post-mortems across every industry that relies on developer tooling.

What Proper Token Revocation Looks Like

The fix is not a better checklist. Checklists fail at scale because they depend on humans remembering what was provisioned months or years earlier.

The right approach starts at issuance. Every credential should be inventoried when it is created: who created it, what service it accesses, what scope it carries, and what the expiration is. Without an issuance record, you cannot have a reliable revocation process.

Bind service credentials to service identities, not human accounts. If a microservice needs to call GitHub’s API, it should authenticate as a service account or GitHub App, not as a developer’s PAT. Human credentials should never be the long-term auth mechanism for automated systems. When a human leaves, service credentials tied to their account become orphaned infrastructure.

Automate revocation as part of the offboarding workflow. The IT ticket that disables Okta should also trigger a scan of your secrets manager, CI/CD variable stores, and infrastructure-as-code for credentials associated with the departing engineer’s identity. This requires that identity be attached to every credential at creation time.

Set short TTLs and rotate on schedule. A token that expires in 30 days limits the blast radius of a missed revocation. Rotation policies on static credentials mean that even if a revocation step is missed, the credential eventually expires on its own.

Where the platform supports it, use workload identity instead of static keys. AWS OIDC lets GitHub Actions authenticate to AWS without an IAM user key. The GitHub Actions OIDC token is scoped to a specific workflow, expires after the run, and is issued by GitHub’s identity service rather than stored anywhere. There is nothing to rotate. Nothing to accidentally leave behind.

Offboarding in Okta doesn't revoke a GitHub PAT

API Stronghold replaces static credentials with session-scoped phantom tokens. When an engineer leaves, their references expire automatically. No credential hunt, no .env archaeology.

No credit card required

The Phantom Token Pattern: Revoke at the Proxy, Not the Source

Even with all of the above in place, some systems still require static credentials. Legacy APIs, third-party services with no OIDC support, vendors that only issue long-lived tokens. Workload identity does not cover everything.

The phantom token pattern addresses this by ensuring that services never hold the real credential directly. A proxy layer sits between the service and the upstream API. When a service needs to authenticate, it requests a scoped, session-bound reference token from the proxy. The proxy holds the actual credential. The service holds only a reference that is valid for the duration of the session.

Revoking access becomes a single operation: invalidate the reference at the proxy. The upstream credential does not change. The service’s reference simply stops working. No rotation required at the source.

This works equally well for human offboarding and machine identity rotation. When an engineer leaves, their session references expire. No credential hunt, no .env file archaeology, no CI/CD variable store audit. The proxy’s record is the single source of truth.

The pattern eliminates credential sprawl by design. There are no .env files containing real keys. There is no token graveyard to audit. References that are not renewed expire automatically, so even a missed manual revocation does not result in persistent access.


SSO revocation is table stakes. The harder problem is everything provisioned before the offboarding event fired. If credentials live in .env files and service configs, you are always one missed token away from a ghost-access incident.

Nothing to revoke because nothing persists

Services never hold real keys when you use phantom tokens. Session references expire automatically at offboarding. No rotation required, no token graveyard to audit.

No credit card required

Keep your API keys out of agent context

One vault for all your credentials. Scoped tokens, runtime injection, instant revocation. Free for 14 days, no credit card required.

Get posts like this in your inbox

AI agent security, secrets management, and credential leaks. One email per week, no fluff.

Your CI pipeline has permanent keys sitting in env vars right now. Scoped, expiring tokens fix that in an afternoon.

One vault for all your API keys

Zero-knowledge encryption. One-click sync to Vercel, GitHub, and AWS. Set up in 5 minutes — no credit card required.