Security

Contractor Scopes That Expire on Their Own

Antonio Evans11 min read

A calendar page with a red circle around a date, and a key attached to it by a string that ends at the date.
Key points

Key points

  1. 1Every agent credential should carry a fixed expiry timestamp and a named human owner at creation.
  2. 2Store that expiry in a scheduler that actually revokes the credential, so a forgotten offboarding becomes a no-op.
  3. 3Time-bound tokens and calendar-bound service accounts move the default from access persisting to access ending on its own.
  4. 4The review exists to catch credentials renewed without a stated reason, not to replace expiry.

Why the cheapest offboarding is the one you never do

Most access programs are built around an event: a person leaves, someone runs a checklist, accounts get disabled. That works when there is a defined last day, a returned laptop and an HR record feeding an identity provider. The model has a gap that widens as agents start holding credentials of their own.

An employee who connects a coding agent to internal systems often creates credentials outside central single sign-on: personal access tokens, bot tokens from a personally created app, API keys copied from a shared pool. Disabling the identity provider account does not touch those. As one practitioner writeup puts it, when you disable the Okta account, those API keys do not get revoked, and they are still stored on the machine heading for the exit interview (Scalekit). An agent on a schedule does not need to decide to use a leftover key; it just runs.

CISA's Cyber Essentials frames access as something that needs limits, "just as those set in the physical work environment do," and says setting approved access privileges requires knowing who operates on your systems and with what level of authorization and accountability (CISA Cyber Essentials). For a machine credential, "who operates" is two facts: which agent holds it, and which human is answerable for it. If you cannot answer both from the credential record alone, you have an orphan waiting to be found during an incident.

The decision rule: treat offboarding as a fallback for exceptions, and make expiry the default. A credential issued for a three week integration should be dead in three weeks whether or not anybody remembered the contractor. Offboarding then only has to handle the credentials that were deliberately renewed, which is a reviewable list rather than an unbounded inventory.

What a self-expiring contractor scope actually contains

A scope that expires on its own is not one control. It is four fields plus one mechanism, and all five have to exist together or the expiry is decorative.

A fixed expiry timestamp, meaning a UTC value recorded at creation, not "90 days from last use" and not "until the project ends." Time-bound credentials are an established pattern: OpenShift bound service account tokens are audience and time-bound (Red Hat docs), and Google Cloud lets you request short-lived credentials for a service account with a specified lifetime instead of downloading a long-lived key (Google Cloud IAM docs).

A named human owner, meaning a person, not a team alias and not a ticket queue. The owner is who gets pinged before expiry and who must justify a renewal. Access review templates consistently treat a named approver plus a date as what makes a review count as evidence rather than opinion (SecureSlate).

A stated purpose and scope ceiling written at creation. A revocation path that does not depend on the owner logging in, because if deletion requires the owner, the expiry is the real control and the dashboard is convenience. And a scheduler with authority to revoke: a calendar reminder to a human does not count; a scheduled job or workflow that calls the revoke endpoint does.

PropertyWeak versionStrong versionWhat breaks without it
Expiry"Review in a quarter"UTC timestamp at creationAccess persists indefinitely
OwnerTeam alias or noneOne named personNobody notices the lapse
ScopeInherited from a templateWritten for this engagementOver-broad access survives
RevocationOwner clicks deleteAutomated call, no humanExpiry becomes advisory
EnforcementCalendar reminderScheduler that revokesForgotten offboarding is a cleanup

The weak column's failure mode is documented. Access accretes over an engagement: a contractor who needed one app ends up with root on a shared server, secrets handed over as world-readable files, and production deploying from repos under their personal account. You cannot revoke what you cannot see (MavenHandle).

Choosing TTLs: a worked example with numbers

TTL choice trades operational noise against exposure window. Too short and the agent breaks mid-task; too long and you have rebuilt the problem. Start from the engagement length, not from a house default.

Take a contractor engaged for 8 weeks to build an integration. The agent needs read access to a staging database, write access to a feature branch, and read access to a ticketing system. The naive issue is a personal access token with no expiry, or one defaulted to a year. authentik, for instance, defaults service account tokens to 360 days but lets you set a specific expiration date at creation (authentik docs). A year is not a contractor scope; it is a permanent grant with a longer review cycle.

A better calculation:

  1. Base TTL equals contract length plus a short grace period. Eight weeks plus five business days is 61 days, covering final handover when the agent may still reproduce an issue.
  2. Split by sensitivity. The staging read token gets the base TTL; the write token on the feature branch gets 30 days, renewed when the branch moves. Expensive or irreversible permissions get the shortest windows.
  3. Warn at 20 percent before expiry. For a 61 day TTL, warn around day 49: enough lead time to renew deliberately, too early to renew by reflex.
  4. Cap renewals. Two renewals without a written reason escalate to the owner's manager. That is what turns expiry from a formality into a decision.

Run the numbers on the savings. Suppose you issue 40 agent credentials a quarter for contractors and agency staff, and historically about a quarter outlive the engagement. That is 10 live credentials per quarter beyond their useful life, or 40 per year, each a search target during any access review. A self-expiring design compresses that to renewals with written reasons, which for the same population should be a handful.

The same logic applies to human tokens. A coding agent using a scoped key per repository benefits from a 7 or 14 day TTL that rotates silently, because rotation is automated and the developer never sees it. The point of short TTLs is not that humans click more buttons; it is that the clicks disappear while the exposure window shrinks.

Making expiry real: the scheduler, not the calendar reminder

The gap between "we set a TTL" and "the credential dies" is where most programs fail. Three patterns close it, in increasing order of reliability.

Native expiry on the credential is strongest, because there is nothing to forget. Kubernetes service account tokens and cloud short-lived credentials work this way, and the identity provider can be the enforcement point rather than a downstream script. authentik's service accounts document expiry, custom expiration dates and revocation, and note that when a token expires the system rotates API tokens automatically (authentik docs). Prefer this whenever the platform offers it.

A scheduled job that revokes is the fallback for credential types with no native expiry, such as many API keys, bot tokens and personal access tokens. The job reads the registry and calls the provider's revoke endpoint at the recorded timestamp. That is the calendar-bound service account in practice: the calendar is a cron job or workflow engine, and the credential record is the source of truth.

Reconciliation compares what the registry says should exist against what the provider says does exist, and flags or revokes the difference. This catches credentials created outside the process, which is exactly the population behind the original problem. The Scalekit walkthrough describes those out-of-band connections: personal access tokens generated months earlier, bot tokens from personal Slack apps, API keys copied from a shared pool, none linked to SSO, none in a registry (Scalekit).

# One credential record. The scheduler reads expires_at and acts.
credential:
  id: cred_8f21c
  kind: agent_api_key
  agent: invoice-reconciler
  owner: [email protected]
  purpose: "reconcile staging invoices, contractor engagement 2 of 2"
  scope:
    - staging-db:read
    - feature-branch:write
  issued_at: "2026-03-02T09:00:00Z"
  expires_at: "2026-05-02T09:00:00Z"
  warn_at: "2026-04-12T09:00:00Z"
  renewals:
    - date: "2026-05-02T09:00:00Z"
      reason: "handover reproduction window"
      approved_by: [email protected]
  state: active

Two fields carry more weight than the rest. purpose names the engagement in the owner's words, so a reviewer can tell at a glance whether it still applies. approved_by on each renewal makes a renewal a decision with a name attached, not an absence of a decision.

The review that catches credentials renewed without a reason

Expiry handles the majority. The review handles exceptions, and its job is narrow: find credentials renewed without a reason, and credentials whose owner no longer exists. A review that tries to do more becomes quarterly archaeology.

Access reviews are detective controls, not preventive ones. If your reviews keep finding departed people with live accounts, the review is working and your offboarding is broken; fix offboarding rather than reviewing more often (SecureSlate). Same here: if the review keeps finding expired-but-alive agent credentials, the scheduler is broken, so fix the scheduler.

Scope in tiers, working outward from the most critical systems to sensitive business systems and then supporting tools (SecureSlate). For agent credentials, add the category the tiers miss: machine identities. As that guidance notes, API keys, service principals and CI tokens do not leave the company, so nobody offboards them, and they frequently hold broader access than any human.

A privileged access review template is a useful spine because it already asks for the right fields: a complete inventory of privileged and service accounts, current permission sets, assessment of necessity against role requirements, identification of inactive or orphaned accounts, separation-of-duties checks, technical and business validation, and documented remediation with evidence (Mitti). Adapt it rather than starting blank.

For each credential in scope, ask three questions and record answers:

  1. Does the owner still exist and still work here? If not, revoke today regardless of TTL.
  2. Was the last renewal justified in writing? An empty renewal record is a finding even if expiry is future.
  3. Is the scope still what it was issued for? Broadening without a renewal is the classic finding.

Offer a decision per row: retain, revoke or downgrade, with a named approver and a date. Retain should be rare for a healthy fleet, because a healthy fleet is mostly short-lived credentials that renew quietly and leave few rows to review.

Keep a removals log with a ticket reference per revocation, since auditors often sample that log directly (SecureSlate). When a credential expires on schedule, log it as a scheduled removal with the scheduler's run ID. That turns the boring majority of removals into evidence the control works.

Applying this to non-human and third-party identities

Agent credentials are not always issued by you. Third-party agents request access through OAuth, customer agents connect to your MCP server, and in-product assistants act on user data. The same two properties apply, with a twist: the owner may be a product user rather than an employee.

The checklist pattern for SaaS teams is a good template. Give every agent its own identity separate from the user it serves, never pass the user's session or access token to the agent, exchange it for a new narrower token, limit a delegated agent to the intersection of what the agent may do and what the user may do, require out-of-band human approval for irreversible or external actions, log both identities and the decision, and make revocation instant and cascading and tested before you need it (WorkOS). That last item is the self-expiring discipline expressed as a product requirement.

If you hand an agent keys to a CMS, a concrete version exists: use the lowest role that works, create a dedicated user named for AI work and delete it when the project ends, create one application password per tool and per site named with the tool and date, keep it only in the client config, and know how to revoke access before you grant it (UiChemy). Naming a credential with tool and date is the manual ancestor of an expiry timestamp: it makes the credential's age visible to whoever finds it later.

Where you must rotate rather than expire, plan for the cost. Rotation gets skipped for fear of downtime and manual process, and the fix is automation plus a lifecycle view of each secret from creation through revocation (Doppler). Expiry-driven design has an advantage: because credentials are expected to die, consumers are built to fetch a new one. The team that never rotates has agents holding keys that cannot be replaced without a scramble.

Adopt one habit from developer offboarding: assume compromise rather than assuming the credential was safe because it was never shared. Rotation-after-exposure as a default treats every credential that outlived its justification as a small incident, with a revocation and a log entry (MavenHandle). Under an expiry regime that costs nearly nothing, because the revocation was going to happen anyway.

If your team standardizes this across many vendors, fewer long-lived keys help: Quanta Inference puts text, image, video, audio and embedding models behind one OpenAI-compatible key, which shrinks the number of credentials you track (Quanta Inference). And if picking the first two TTLs keeps getting deferred, a free 30-minute discovery call shortens the debate (Quanta Consulting).

A rollout sequence that survives week one

Do not retrofit expiry onto every credential at once. This sequence gets a working control in a few weeks and leaves the messy remainder visible.

Week 1, inventory and classify: export every credential an agent can use, from every system in scope, and classify by type (native expiry, revocable by API, revocable only by hand). Note the owner for each. Expect a significant fraction with no owner; that is the finding, not a failure.

Week 2, write the record schema and backfill the top tier: production, source control, secrets manager and the identity provider first. For each credential fill owner, purpose and a new expires_at set to today plus a TTL derived from engagement length. Backfilling with a real expiry is the difference between a policy and a change.

Week 3, build the scheduler for the cheapest wins: start with credential types that have a clean revoke endpoint, run one cycle in dry-run mode, and compare what it would have revoked against what people expected to keep. Every surprise is either a missing owner or a renewal without a reason, and both are worth fixing before the job goes live.

Week 4, run the first narrow review on only renewals and ownerless credentials, deciding retain, revoke or downgrade with a named approver, and log the removals. After that, keep reconciliation running to catch out-of-band creations and do a quarterly read for empty renewal reasons and scope drift. If the review grows, the scheduler is not doing its job.

Two metrics tell you whether it works. First, the count of credentials past expires_at that are still valid: it should be zero, and any non-zero reading is a scheduler bug. Second, the share of credentials revoked by the scheduler versus by a human: the scheduler share should climb toward all of it, because that is what an offboarding you never have to do looks like when measured.

FAQ

FAQ

How do I pick a TTL for an agent credential without guessing?

Start from the engagement or workflow length and add a short grace period for handover, then shorten it further for write or irreversible permissions. Set a warning a fifth of the way before expiry so renewal is deliberate, and cap renewals without a written reason. The TTL should be defensible in one sentence if a reviewer asks why.

What is a calendar-bound service account, exactly?

It is a service account whose lifetime is recorded in a scheduler alongside the data needed to revoke it, so revocation happens at a known time without a human remembering. The calendar is a cron job, workflow engine or the platform's own expiry mechanism. What matters is that the credential record is the source of truth and something with authority reads it.

Is a quarterly access review enough on its own?

No. Reviews are detective controls that catch what provisioning and offboarding missed, and if they keep finding the same problem, the preventive control is the thing to fix (SecureSlate). Use expiry as the default and keep the review narrow: ownerless credentials, renewals without reasons, and scope drift.

What should the credential record contain at minimum?

A fixed expiry timestamp in UTC, one named human owner, a purpose in plain words, the scope granted at creation, a warning time, and an append-only renewal log with a reason and an approver per entry. That is close to the minimum a privileged access review needs to make a decision (Mitti).

What if the platform has no revocation API or no native expiry?

Attach the credential to a dedicated identity you can disable instead, create one dedicated user per tool and name it with the tool and date, and delete it at project end as a manual fallback (UiChemy). Treat that credential as a standing review finding until the platform adds an API, and keep the number of such credentials small.

Sources

Sources

  1. When an Employee Leaves, Who Revokes Their AI Agent's ...
  2. Cyber Essentials | CISA
  3. Chapter 14. Using bound service account tokens
  4. Create short-lived credentials for a service account
  5. Free Access Review Template (Excel)
  6. What to Rotate When a Developer Leaves | MavenHandle
  7. Service accounts
  8. Privileged Users Access Review Checklist - Mitti
  9. The AI agent permissions checklist for SaaS apps (2026)
  10. WordPress AI Agent Security Checklist
  11. How to rotate secrets without downtime
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles