Skip to content
DevOpsSociety

The Tokens You Forgot: How a Stale OAuth Token and a Hardcoded Cloudflare Key Became Breaches

A leaver's GitHub OAuth token and a hardcoded full-account Cloudflare key caused two September breaches. What failed, and the controls that limit long-lived credentials.

Published your local timeupdated

The Tokens You Forgot: How a Stale OAuth Token and a Hardcoded Cloudflare Key Became Breaches

Every team has a credential nobody owns any more, on a former colleague's laptop or in a config file from three years ago, and it still works. Two incident write-ups published in September 2026 show what happens when an attacker finds one first. CrowdSec lost roughly 170 private repositories to a GitHub OAuth token that outlived a developer's departure. Brevo had a Cloudflare Worker planted on its own domains by someone holding a hardcoded, account-wide API key. Both came down to long-lived credentials that stayed valid after the thing meant to catch them had stopped paying attention.

This piece walks through what happened, the shared failure, and the GitHub and Cloudflare controls that would have shrunk the damage.

A departing employee leaves with a laptop while a still-valid access token stays plugged into the code repository

CrowdSec: a goodbye that left the door open

CrowdSec's incident analysis starts with someone else's breach. On 11 May 2026, attackers published backdoored versions of TanStack packages to npm carrying Shai-Hulud-style credential-stealing malware. A developer who had just left CrowdSec was infected. CrowdSec had kept his GitHub access alive because they parted on good terms and he wanted to finish some work.

On 22 May, in a window of under ten minutes, a GitHub OAuth token (the gho_ prefix) from that account was used to clone about 170 private repositories. His organisation access was removed on 25 May, three days later. Nobody noticed the clone. CrowdSec found out on 16 September, when the code turned up on a forum, and The Hacker News reports the detailed write-up followed on 18 September.

Exposed: console and other internal source code, one AWS SNS token (scoped to a single topic; CrowdSec says it was probed in August), 83 user emails, and details of 51 prospective investors from 2020.

The token left nothing in CrowdSec's organisation audit log. As the report explains, GitHub only records Git activity such as clones for enterprise accounts, and only keeps it for seven days. CrowdSec was not on that plan and had to ask GitHub Support to reconstruct what the token did.

Brevo: one key, every zone

Brevo's post-mortem names the cause plainly: a long-lived Cloudflare API key with full account permissions, hardcoded in application source. Brevo says the key was first misused in late August 2026, but it found no malicious content served to customers before 14 September.

On 14 September the attacker deployed a Worker, tested it on low-traffic domains, then routed it to brevo.com at 15:01 UTC. At 16:07 the Worker was widened to cover sibforms.com and three JavaScript files that customers embed on their own sites: the forms script, the Conversations widget and the SDK loader. Brevo removed it at 20:30. Sansec, which published its research on 16 September, put the reach at more than 100,000 sites; SecurityWeek reports Sansec's estimate of roughly four hours of malicious activity.

The Worker rewrote responses at the edge and stripped Content-Security-Policy, so Brevo's origin files were never touched. Visitors saw a fake Cloudflare "verify you are human" page that walked them through a ClickFix routine: paste a command, run it, install malware. If a logged-in WordPress administrator loaded an affected page, the script tried to install a plugin. BleepingComputer reports it was called "Web Media Optimizer", hid itself from the plugin list and copied itself into the must-use plugins directory to stick around. Brevo says its app, API, email sending and account data were unaffected.

The shared failure: long-lived credentials outlasting the control

Side by side, the pattern is clear: in each case the credential's lifetime was longer than the window in which some control was watching it.

At CrowdSec, that control was offboarding, delayed as a favour. At Brevo, the control was code review and secret hygiene. The key went into source, nobody rotated it, and it kept full account rights throughout. The write-up doesn't detail how the attacker obtained it.

The second half is detection. Neither company was alerted on the attacker's first use of these long-lived credentials. CrowdSec learned from a forum post nearly four months later; Brevo, after visitors were already being served malware. If you can't shorten a token's life, you need to see it used somewhere odd.

GitHub OAuth token revocation and offboarding security

Several GitHub controls you'd reach for first wouldn't have helped CrowdSec much.

OAuth app access restrictions let org owners block OAuth apps that haven't been approved. But GitHub publishes a list of privileged OAuth apps, including GitHub CLI, Git Credential Manager, VS Code and GitHub Desktop, that users can authorise regardless of those restrictions. GitHub's docs also say these apps show up in the user's security log but not in the organisation audit log. CrowdSec doesn't name the app that issued its gho_ token, but the tools developers most often use to hold one are on that list, so don't count on access restrictions here.

SAML SSO credential authorization is more useful if you're on Enterprise Cloud. With SSO enforced, a token must be authorised for the org, and owners can list and revoke those authorisations per user through the REST API. Deprovisioning through your IdP then cuts off org access in one place, which is where offboarding belongs.

Fine-grained PATs with a maximum lifetime are worth enforcing: org owners can block classic PATs, require approval for fine-grained ones and cap their lifetime. That covers tokens people create by hand. It doesn't touch OAuth tokens, which GitHub only revokes automatically after a year without use.

Git events in the audit log handle detection. On GitHub Enterprise Cloud, clone, fetch and push events are captured but kept for just seven days. You can only pull them through the audit log REST API (include=git) or audit log streaming. Streaming carries Git events alongside audit events and is the only way to keep them longer than a week. If you stream to a SIEM, a single account cloning dozens of repositories within minutes is an easy alert to write.

If a leaver needs time to wrap up, give them an expiring outside-collaborator grant on specific repositories instead of full membership.

Cloudflare API token scopes vs the full-account key

Cloudflare's own documentation says the Global API Key has access to all of a user's resources, can't be limited by time or IP, and is one per user, which makes rotation painful. Cloudflare doesn't recommend it for new customers. Brevo only says its key had full account permissions; Global API Key or overbroad token, the outcome was the same.

API tokens take policies scoped to specific zones and permission groups, an expires_on timestamp and a request.ip condition. A token that can only purge cache on one zone from your CI egress range is nearly useless to whoever lifts it. Account-owned tokens also decouple service credentials from any person's user account.

# Account-owned token: one zone, one permission, CI egress only, 30-day expiry
curl -sS -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/tokens" \
  -H "Authorization: Bearer $CF_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "name": "ci-cache-purge-example-com",
    "policies": [{
      "effect": "allow",
      "resources": { "com.cloudflare.api.account.zone.'"$ZONE_ID"'": "*" },
      "permission_groups": [{ "id": "'"$CACHE_PURGE_PG_ID"'" }]
    }],
    "condition": { "request.ip": { "in": ["203.0.113.0/24"] } },
    "expires_on": "2026-10-22T00:00:00Z"
  }'
# Look up permission group IDs via the permission_groups endpoint; they are not stable names.

For detection, Cloudflare's audit logs v2 retain 18 months of events and export through Logpush. Brevo's remediation included alerting on every audit event touching Workers, routes, DNS and access, and streaming Cloudflare logs into security monitoring. Copy that. A surprise Worker route on your main domain should page someone.

One master key that opens every door compared with scoped, colour-coded keys that each open one door and expire

Keeping secrets out of source, and limiting blast radius at the browser

Brevo's other fix was moving credentials into HashiCorp Vault with automatic rotation. Any managed secret store turns the credential in your repo into a reference rather than a value. Add secret scanning and push protection. We covered GitHub secret-scanning merge blocks separately. Where tokens have to reach deploy jobs, inject them at runtime with a short-lived identity, such as OIDC federation, rather than a stored value. Our walkthrough of a CI/CD pipeline from commit to production shows where those handoffs sit.

If you embed third-party scripts, the vendor's edge is part of your attack surface. Subresource Integrity (integrity="sha384-..." on the <script> tag) makes the browser refuse a modified file. That only works for versioned assets (Brevo says it has added integrity protection for its versioned embedded files); it doesn't fit a "latest" loader URL. With CSP reporting (Reporting-Endpoints plus a report-to directive), a script calling unexpected hosts shows up in your reports. On pages the vendor's edge serves, though, a Worker that strips the header also strips the reporting. So watch the edge's audit trail as well as the page.

Mapping each failure to the control that would have limited it

Incident

Failure

Control that would have limited it

Limit of that control

CrowdSec

Leaver's org membership kept active

IdP-driven deprovisioning; time-boxed collaborator grant

Needs SAML/SCIM on Enterprise Cloud for one-click cutoff

CrowdSec

gho_ token valid for months

SSO credential authorization with per-user revocation

Enterprise Cloud only; must be part of every offboarding

CrowdSec

Mass clone not seen

Audit log streaming with Git events to a SIEM

Git events are enterprise-only, 7-day retention without streaming

Brevo

Full-account key in source

Scoped, account-owned API token with expiry and IP filter

Needs a rotation owner; expiry breaks jobs if nobody renews

Brevo

Key not caught in repo

Secret store plus push protection

Only catches known token formats

Brevo

Worker deploy not alerted

Audit logs via Logpush, alerts on Workers/routes/DNS

Alert fatigue if deploys are frequent and unlabelled

Brevo customers

Modified script executed

SRI on versioned assets; CSP reporting

SRI can't cover unversioned loaders; edge can strip CSP

Offboarding and token-hygiene checklist

  1. Remove org membership on the leaving date via your IdP; grant any wrap-up access per repository, with an end date.

  2. On Enterprise Cloud, list and revoke the leaver's SSO credential authorizations via /orgs/{org}/credential-authorizations.

  3. Enforce a maximum lifetime on fine-grained PATs and block classic PATs for the org.

  4. Stream the GitHub audit log, Git events included, to storage you control. Alert on bulk clones by one actor.

  5. Replace every Cloudflare Global API Key and full-account token in use with account-owned tokens scoped to zone and permission, with expires_on and an IP condition.

  6. Push Cloudflare audit logs through Logpush and alert on Workers, routes and DNS changes.

  7. Run secret scanning across full Git history as well as new pushes. Move anything it finds to a secret store and rotate it.

  8. Pin third-party scripts to versioned URLs with SRI where the vendor supports it, and turn on CSP reporting.

Decision rule: if a credential can't be tied to a named owner, doesn't expire on its own, and has no alert on use from an unexpected location, treat it as already leaked. Rotate it this week and scope the replacement to exactly what it needs. For more incident analysis and controls, see our security coverage.

Written by

DevOpsSociety Editorial Team

Editorial Team

The DevOpsSociety Editorial Team covers DevOps, cloud infrastructure, Kubernetes, AI infrastructure, platform engineering, cybersecurity, FinOps, and modern engineering practices. We publish practical insights, technical guides, architecture analysis, and research for engineers and technology leaders.

More from DevOpsSociety
The Infrastructure Briefing

Get the infrastructure briefing.

Practical DevOps, cloud, AI infrastructure and engineering insights, delivered weekly. Read by engineers and engineering leaders.

No spam. Unsubscribe anytime.