Safeguard
DevSecOps

CircleCI Security Best Practices After the 2023 Breach

The January 2023 CircleCI incident forced every customer to rotate every secret. Here is what it taught us — plus hardened config.yml examples for orb pinning, restricted contexts, OIDC, and adding scanning.

Daniel Osei
DevSecOps Engineer
6 min read

CircleCI's January 2023 security incident is the case study every CI/CD team should study, because it was not a bug in a build step — it was a compromise of the platform's trust. An engineer's laptop was infected with malware that stole a valid, two-factor-backed single sign-on session token, and attackers used that session to reach CircleCI's systems and exfiltrate customer environment variables, contexts, and project keys over a period spanning late December 2022 into early January 2023. On January 4, 2023, CircleCI told all customers to rotate every secret they had stored, and published a detailed post-incident report on January 13. The lesson is blunt: any secret you hand to a CI provider is a secret that can be stolen wholesale, and the best-defended secret is the one that is short-lived, scoped, and never stored at all. This guide walks CircleCI's attack surface and the hardening that limits blast radius.

The attack surface

CircleCI risk concentrates in four places. Stored secrets: project and organization environment variables and contexts are long-lived credentials sitting in the platform, exactly what the 2023 breach exfiltrated. Orbs: reusable config packages published by third parties that execute inside your pipeline; a compromised or floating orb is remote code in your build. Contexts: shared secret bundles that, if unrestricted, are readable by any project and any engineer in the org. Debug and rerun paths: SSH-enabled reruns and overly broad env vars expose secrets to anyone who can trigger a build.

Hardening step 1: pin orbs to exact versions

An orb reference like circleci/aws-cli@4 floats to the newest release in that major line. Pin to an exact version so a new orb release — malicious or merely broken — cannot enter your pipeline without a reviewed change.

version: 2.1

orbs:
  aws-cli: circleci/aws-cli@4.1.3   # exact version, not @4 or a floating alias
  node: circleci/node@5.2.0

Prefer certified and partner orbs over uncertified community orbs, and vendor critical logic into your own config where the risk of a third-party dependency outweighs the convenience.

Hardening step 2: restrict contexts

Contexts are the right place for shared secrets, but only if they are restricted. Use context restrictions (security groups) so a context is usable only by approved projects and teams, and scope the workflow so the job that consumes production secrets runs only on the protected branch.

workflows:
  deploy:
    jobs:
      - deploy:
          context:
            - prod-deploy        # restricted context, limited to approved groups
          filters:
            branches:
              only: main         # production secrets never run on feature branches

An unrestricted context is effectively an org-wide secret; a restricted one limits who can ever cause it to be read.

Hardening step 3: close the debug and env-var gaps

SSH-enabled reruns drop an interactive shell into a running job with all of that job's environment variables live — convenient for debugging, dangerous as a standing capability. Restrict who can run SSH sessions and disable them on jobs that touch production secrets. Scope environment variables to the narrowest level that works: prefer context or per-job variables over broad organization-wide ones, and never echo a secret, because CircleCI's log masking is best-effort and misses transformed values.

Secrets and OIDC

The most durable answer to "what if the platform is breached again" is to store as few long-lived secrets as possible. CircleCI issues an OIDC token to every job, which you exchange for short-lived cloud credentials at runtime — so there is no static cloud key to steal.

jobs:
  deploy:
    docker:
      - image: cimg/aws:2024.03
    steps:
      - run:
          name: Assume role via OIDC
          command: |
            aws sts assume-role-with-web-identity \
              --role-arn "$AWS_ROLE_ARN" \
              --role-session-name "circleci-$CIRCLE_WORKFLOW_ID" \
              --web-identity-token "$CIRCLE_OIDC_TOKEN_V2" \
              --duration-seconds 900

Configure the cloud trust policy to accept the token only from your organization and project (via the token's subject and audience claims), so no other org's CircleCI jobs can assume the role. Rotate any secret that must remain stored on a schedule, and keep an inventory so a forced mass-rotation like 2023's is a runbook, not a scramble.

Adding Safeguard scanning to the pipeline

Hardened config does not tell you whether your dependencies are exploitable. Add a job that runs the Safeguard CLI so software composition analysis runs on every pipeline and gates merges.

jobs:
  safeguard-scan:
    docker:
      - image: cimg/base:2024.06
    steps:
      - checkout
      - run:
          name: Run Safeguard scan
          command: |
            curl -sSfL https://get.safeguard.sh/install.sh | sh
            safeguard scan --fail-on high --sbom cyclonedx

Safeguard's SCA engine ranks findings by reachability so the gate fails on the exploitable minority, not the whole tree, and Auto-Fix opens the upgrade pull request when a fix exists.

Hardening checklist

  • Pin orbs to exact versions; prefer certified orbs over uncertified community ones
  • Restrict contexts to approved projects and teams
  • Run production-secret jobs only on protected branches
  • Restrict SSH debug sessions; disable them on secret-bearing jobs
  • Replace static cloud keys with CircleCI OIDC scoped to your org and project
  • Keep a secret inventory so mass rotation is a documented runbook
  • Scan every pipeline with reachability-aware SCA and fail on exploitable findings

Frequently Asked Questions

What actually went wrong in the 2023 CircleCI breach?

Malware on an engineer's laptop stole a live SSO session token that had already passed two-factor authentication, so the attacker rode an authenticated session rather than cracking a password. That access let them read customer environment variables and contexts. The takeaway is that stored secrets are only as safe as the platform holding them, which is why OIDC and short-lived credentials matter so much.

Should I stop using orbs entirely?

No — orbs are useful, but treat them like any dependency. Pin exact versions, prefer certified and partner orbs, and review the source of anything doing sensitive work. The danger is a floating reference that lets a new orb release execute in your pipeline without review, not the concept of reusable config.

Does CircleCI OIDC work with clouds other than AWS?

Yes. The OIDC token CircleCI issues can be exchanged with any provider that trusts CircleCI's issuer, including GCP and Azure. In every case you scope the cloud-side trust policy to your organization and project so no other tenant can assume your role.

How does Safeguard reduce breach blast radius?

By keeping the risk picture current and the secret surface small: Safeguard ingests SBOMs from every pipeline, prioritizes by reachability, and its investigation engine Griffin AI correlates findings so you can answer "are we affected, and where" fast when the next platform incident lands.

To wire these controls into your pipeline, see Safeguard CLI, SCA, Auto-Fix, Griffin AI, and full setup steps in the documentation at docs.safeguard.sh.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.