The Story

On December 16th, 2022, an engineer at CircleCI opened something that delivered information-stealing malware onto their laptop. CircleCI's antivirus software did not flag it. For days, the infection sat quietly, doing what that category of malware is built to do: waiting for something valuable to steal. It found a that was already authenticated past two-factor authentication. A cookie like that doesn't ask for a password again. It just works, for whoever holds it.

CircleCI's CI/CD platform was used by roughly one million engineers worldwide at the time of the incident, integrated deeply with customers' GitHub, Bitbucket, and AWS accounts via OAuth tokens and API keys -- meaning a compromise of CircleCI's own production systems had a blast radius extending into every connected customer pipeline, not just CircleCI's infrastructure.

THE INSIGHT: ENCRYPTION AT REST DOESN'T HELP IF THE KEY IS IN MEMORY

Because the targeted employee had privileges to generate production access tokens as part of their regular job, the unauthorized third party used the stolen session to do the same -- then used those tokens to exfiltrate data from a subset of CircleCI's databases and credential stores, including customer environment variables, tokens, and keys. The stolen data was -- but the attacker also extracted the encryption keys directly from a running process, which meant the at-rest protection alone could not stop the data from potentially being decrypted afterward.

Problem

Malware Landed on an Engineer's Laptop, Undetected

On December 16, 2022, malware was deployed to a CircleCI engineer's laptop. CircleCI's antivirus software did not detect it at the time, and the infection went unnoticed by both the employee and the security team for the following days.

Cause

A 2FA-Backed Session Cookie Was Stolen and Reused

The malware executed session cookie theft, capturing a valid, already 2FA-authenticated SSO session. An unauthorized third party used that stolen session to impersonate the employee from a remote location and escalate access into a subset of CircleCI's production systems -- reconnaissance activity is believed to have started around December 19.

Solution

Production Tokens Were Generated, Then Used to Exfiltrate Data

Because the impersonated employee had legitimate privileges to generate production access tokens, the attacker generated them too, then used them to access and exfiltrate data from a subset of databases and credential stores -- including customer environment variables, tokens, and keys -- on December 22, the last recorded date of unauthorized activity.

Result

A Customer's Bug Report Surfaced the Breach a Week Later

CircleCI learned of the intrusion on December 29, when a customer reported suspicious activity on their GitHub OAuth token. CircleCI rotated that customer's tokens immediately, then began an internal investigation that, by January 4, 2023, had traced the full scope back to the December 16 laptop compromise -- prompting a public disclosure and a directive for every customer to rotate all secrets.

Why It Took 13 Days to Notice

The malware bypassed CircleCI's antivirus software at the point of infection, and the subsequent session-cookie theft and impersonation likewise went unnoticed by CircleCI's internal monitoring. The detection signal that finally surfaced the incident came from outside CircleCI entirely: a customer noticed unauthorized activity tied to their own GitHub OAuth token and flagged it. Internal tooling missed every stage; an external party caught the downstream symptom.

Why CircleCI Resisted Blaming the Employee

CircleCI's CTO, Rob Zuber, was explicit in the public incident report that this was not a story about one person's mistake. The employee didn't do anything outside their normal job: their laptop got infected with malware that bypassed antivirus protection, and their job legitimately required the ability to generate production tokens. Every step the attacker took relied on systems and processes functioning exactly as designed for a legitimate employee -- which is precisely the point.

THE CORE TECHNICAL INSIGHT

The most useful reframe CircleCI offered in its own incident report is that this wasn't one engineer's mistake -- it was a systems-level gap. Every individual step in this attack used a legitimate credential behaving exactly as designed. The fix isn't 'don't click on malware'; it's making sure no single stolen credential carries unlimited, unverified trust.

The Fix

Three Changes That Closed the Window

CircleCI's response treated the privileged-session problem as the actual root cause, not the malware itself -- malware on a laptop is an ongoing reality every company faces; the design choice that turned one infected laptop into a production data breach was a long-lived, broadly-privileged session with no additional verification layer.

13 days
From malware infection (Dec 16) to internal investigation completing the full scope (Jan 4)
1 cookie
All that was needed to bypass 2FA entirely, since the session was already authenticated
All tokens
Project API Tokens, Personal API Tokens, and GitHub OAuth tokens rotated platform-wide as a precaution
< 5
Customers who reported experiencing unauthorized third-party access as a downstream result

Session and Token Handling: Before vs. After the Incident

Session and Token Handling: Before vs. After the Incident
PropertyBefore the incidentAfter the incident
SSO session lifetimeLong-lived once 2FA-authenticatedShorter-lived sessions with periodic re-authentication
Device trust verificationNot required beyond initial loginAdditional authentication guardrails added for production access
Token rotation cadenceManual, customer-initiatedPeriodic automatic OAuth token rotation introduced as a platform default
Encryption key handlingReachable from a running production processInvestigation into reducing in-memory key exposure during data access
Detection sourceRelied on internal monitoring, which missed itExpanded detection coverage for the specific malware behavior observed
javascript
// Illustrative: the class of safeguard introduced after this incident --
// not CircleCI's actual implementation. Models a short-lived, device-bound
// session check in front of any privileged token-generation action.

async function authorizeProductionTokenGeneration(session, request) {
  // Before: a valid 2FA-backed SSO session cookie was sufficient on its own,
  // regardless of session age or originating device.

  // After: session age is checked explicitly, not assumed fresh because
  // 2FA happened at some point in the past.
  const sessionAgeMinutes = (Date.now() - session.authenticatedAt) / 60000;
  if (sessionAgeMinutes > MAX_PRIVILEGED_SESSION_AGE_MINUTES) {
    throw new ReauthRequiredError("Session too old for a privileged action; re-authenticate.");
  }

  // After: the device fingerprint must match the one the session
  // was originally issued to -- a session cookie copied to a new
  // machine (as malware-based theft does) won't pass this check.
  if (request.deviceFingerprint !== session.originalDeviceFingerprint) {
    await flagForSecurityReview(session, request);
    throw new DeviceMismatchError("Request device does not match session origin.");
  }

  return issueScopedProductionToken(session.userId, ttl: SHORT_LIVED_TOKEN_TTL);
}

THE COUNTERINTUITIVE PART: 2FA WAS NEVER ACTUALLY BYPASSED

It's tempting to describe this as a two-factor authentication failure. It wasn't. The employee's 2FA worked exactly as designed at login. The vulnerability was that a successfully-completed 2FA session, once captured as a cookie, carried the same trust indefinitely, with no mechanism checking whether the device using it was still the device it was issued to. The fix wasn't stronger authentication at login -- it was making the proof of that authentication expire and stay bound to its original device.

Architecture

The attack path here is short and almost entirely about identity, not exploitation of any CircleCI product vulnerability. Two diagrams show it clearly: the exact sequence the attacker followed, and what changed in CircleCI's session-trust model afterward.

The Attack Sequence: Laptop to Customer Data

Before vs. After: The Session-Trust Model

What to Notice in the Sequence

Every node in the attack sequence after the initial malware infection used a legitimate, working feature of CircleCI's systems exactly as it was designed to work for an authorized employee. There's no exploit, no broken access control, no SQL injection in this story -- just one stolen credential that the rest of the system trusted completely, for as long as the attacker chose to use it.

Lessons

This incident generalizes well beyond CI/CD platforms because the actual failure -- a privileged session that doesn't expire or re-verify -- is a pattern present in nearly every SaaS company's internal tooling, regardless of what the product does.

What to remember

  1. Treat a security incident as a systems failure, not a personal one. CircleCI's own framing -- the malware bypassed antivirus, and the employee's privileges were legitimate -- matters because blaming the individual would have left the actual fix (session trust duration and device binding) untouched.
  2. Encryption at rest does not protect data once it's decrypted in a running process's memory. The attacker here extracted encryption keys directly from memory, which meant 'encrypted at rest' alone could not guarantee the stolen data stayed unreadable.
  3. Any account with the privilege to generate production access tokens is a high-value target regardless of seniority. Scope and time-limit that privilege specifically, rather than treating it as equivalent to ordinary login access.
  4. Customer-reported anomalies can be your most reliable detection signal precisely because they're independent of your own blind spots. CircleCI's internal monitoring missed every stage of this intrusion for 13 days; a customer noticing one compromised OAuth token is what actually triggered the investigation.
  5. When a breach touches third-party integrations, rotation has to extend past your own systems. CircleCI coordinated with GitHub for rate-limit headroom and with Atlassian to rotate Bitbucket tokens, recognizing that a CI/CD platform's blast radius runs through every connected provider, not just its own database.

Periodic Rotation Became the Default, Not the Exception

The most lasting outcome of this incident wasn't a one-time secret rotation -- it was CircleCI committing to periodic, automatic OAuth token rotation as a standing platform feature, rather than something customers had to remember to do themselves. A breach response that only fixes the specific hole tends to age poorly; making rotation the default going forward closes an entire category of future incidents, not just this one.

A security incident is a systems failure.
— Rob Zuber, CircleCI CTO -- in the company's January 13, 2023 incident report
Two-factor authentication worked perfectly. It just authenticated a session that, once stolen, never had to prove it was still being used by the person it was issued to.TechLogStack -- built at scale, broken in public, rebuilt by engineers