Employee access reviews are a solved-enough problem for SSO-connected apps. Contractor access review automation is where mid-market SOC 2 audits still fail: a vendor engineer who finished a three-week integration still has production database access because nobody owns "non-employee" as a lifecycle.
Quarterly campaigns assume a stable population. Contractors are not a stable population. If your review tool only certifies full-time employees from HRIS, the auditor will sample the other list — GitHub outside collaborators, IdP groups named vendors, AWS IAM users created for a partner, and the shared "support" login someone created in a hurry.
The Population Your HRIS Does Not Contain
We treat contractors as a first-class identity class with four attributes the review must carry:
- Sponsor — a named employee who is accountable for the access, not "the vendor company"
- End date — a contract or SOW date, even if it gets extended
- System set — the actual entitlements, including out-of-SSO tools
- Data scope — production vs staging, PII vs none
HRIS often has a contractor flag. It rarely has end dates that match the legal SOW, and it almost never lists the GitHub org or the RDS IAM database user. That gap is why a user access review automation pipeline that only ingests Okta groups still fails sampling.
Expiry Beats Recertification for Temporary Identities
Employees get recertified. Contractors should get time-boxed grants. Recertifying a 90-day contractor every quarter is theater: the campaign lands after the contract ended, or it rubber-stamps access that should have died on Friday.
The control we build:
- Provisioning requires
sponsor_email+access_ends_at. - A daily job (EventBridge + Lambda, in our AWS-shaped builds) diffs
access_ends_at < now()against active IdP groups and tickets remaining access as a severity event, not a newsletter. - Reviewers still see contractors in the quarterly campaign — as a backstop — but the primary control is the expiry ticket with a close timestamp.
# contractor_expiry.py — fail closed: missing end date is already expired
from datetime import datetime, timezone
def expired(row: dict, now: datetime | None = None) -> bool:
now = now or datetime.now(timezone.utc)
raw = row.get("access_ends_at")
if not raw:
return True
ends = datetime.fromisoformat(raw.replace("Z", "+00:00"))
return ends <= now
Missing end dates fail closed. Making "TBD" a valid end date is how standing vendor access returns under a new name.
What Auditors Sample on Vendor Access
From AICPA TSC CC6.2 / CC6.3 framing (logical access administration and removal): they want a trail that a non-employee was approved, time-limited, and removed. Typical samples:
- The SOW or ticket that authorized access
- IdP or AWS evidence the account is disabled
- Time between
access_ends_atand actual revocation - Whether the contractor's personal email was the login (it should not be, if you can avoid it)
Shared vendor logins fail this immediately. One identity per human, sponsored, expiring.
Deprovisioning contractors is the same JML problem as employees, with a worse source of truth. Pair this with access deprovisioning automation so expiry is an event, not a spreadsheet column.
Custom Apps and Cloud Roles Vendors Always Get
The access SaaS review tools miss is the same list as always: admin panels with local users, AWS IAM users created "just for the integration," database roles granted by SQL, and CI secrets in a vendor's runner.
For those, contractor reviews need the same export adapters we use for access reviews on custom applications. The contractor flag is just another column on the entitlement record. If your campaign cannot filter identity_class=contractor, you are not reviewing vendors — you are hoping they appear in the employee list.
See what an Access & Credential Governance Diagnostic finds in your environment → rutagon.com/contact · 907-841-8407 · contact@rutagon.com.
Personal Email Ban
Contractor logins on gmail/company-we-never-heard-of domains are how offboarding misses them. Prefer IdP-managed identities (vendor+project@yourdomain) that you can disable. If the vendor insists on their domain, SCIM from a contractor directory or a named list with end dates.
Production Data Access
If a vendor needs prod data, time-box a copy or a masked environment. Standing prod DB users for "support" are the finding. JIT with session recording (where you already have it) beats a password in a ticket.
Procurement SOWs should name the access end date. Security cannot invent an end date the contract left open. We flag missing SOW dates as a GRC exception, fail-closed in the provisioner.
Contractor access review automation tied to contract end dates
Contractor access review automation is expiry, not another quarterly campaign. HRIS often does not contain vendors. The provisioner must require an SOW end date. Missing dates are a GRC exception, fail-closed. Personal Gmail logins are how offboarding misses people — prefer IdP-managed vendor+project@yourdomain.
AICPA SOC 2 sampling on vendors is ugly when access outlives the PO. We emit a ticket 14 days before end, disable on end, and keep the timestamp join (contract end vs IdP disable vs AWS assignment delete).
Standing prod database users for “vendor support” are the finding. Time-box a masked environment or JIT with a session record if you already have that stack. Custom admin apps vendors always get (billing tools, CMS) need export adapters if there is no SCIM.
Procurement owns the date. Security cannot invent one. We flag open-ended SOWs as the root cause, not “the IdP.”
Frequently Asked Questions
How often should contractor access be reviewed?
Time-box the grant to the SOW and review extensions, not a separate annual vendor campaign. Quarterly recertification is a backstop for missed expiries, not the primary control.
Can we put all vendors in one IdP group and recertify the group?
A group named vendors without per-person end dates is how access outlives the contract. Review the person and the sponsor, or you are certifying a bucket.
What if a vendor needs break-glass production access?
Issue a just-in-time role with a session limit and a ticket. Standing production IAM users for vendors are the finding. Log the session; do not leave the role attached.
Do we need the vendor's SOC 2 report instead of reviewing their people?
A vendor report covers their controls. It does not prove your production accounts for their engineers were removed. You still review identities in your systems.
How do we handle agencies that rotate people every two weeks?
The sponsor updates the named human and the end date on each rotation. The old identity expires. If the agency cannot name the human, they do not get production access.