GITHUB_TOKEN isn’t that Ephemeral

  • GITHUB_TOKEN is an ephemeral (cough) API key used for workflows to authenticate to the Github API
  • If you leak the token and make it available prior to the end of the workflow run, bad things can happen
  • Github states that the token is destroyed at the end of a workflow run, but it isn’t immediately invalidated
  • You can race Github’s invalidation process for fun and profit

Every time a GitHub Actions workflow is triggered, GitHub automatically provisions a token called GITHUB_TOKEN. This ephemeral credential allows jobs in the workflow to authenticate to the GitHub API without requiring manually managed secrets or personal access tokens. The token is injected into the workflow environment and is scoped to the repository context in which the workflow is running.

GITHUB_TOKENs can have multiple different default permission sets though these permissions can be customized within the workflow or job itself. Job-level scoping is prefered as it gives fine-grained control over access.

Dude where’s my token

Despite the ephemeral nature of GITHUB_TOKEN, misuse or mishandling during the workflow execution window can expose projects to significant risk. Many teams assume that temporary credentials are inherently safe, but this assumption breaks down in environments where artifacts, logs, or third-party actions introduce uncontrolled surfaces.

One of the most overlooked issues with GITHUB_TOKENs comes from a subtle behavior in actions/upload-artifact@v4. In this version, artifacts become downloadable as soon as they’re uploaded, even if the workflow that created them is still running. This creates a narrow but potent attack window: if the artifact contains a copy of the GITHUB_TOKEN, an attacker can retrieve it and execute arbitrary API calls in the context of the job until the workflow terminates.

That said, most workflows that upload artifacts tend to do it at the very end. So, in theory, there shouldn’t be much of a window for an attacker to abuse the GITHUB_TOKEN, right? The workflow usually wraps up right after the upload, which should invalidate the token almost immediately. Right? Right!?

Use after free (ish)

While the official documentation states that the GITHUB_TOKEN is revoked immediately after the workflow ends, we discovered that this isn’t strictly true…

Chris Pratt Surprised Meme Generator - Imgflip

During a series of tests conducted in collaboration with my research partner nopcorn, we observed that the token often remains valid for a short period (typically between one and two seconds) after the workflow reports as completed. In other words, the revocation process is not instantaneous. This creates a brief but real post-execution window during which a previously retrieved token can still be used to interact with the GitHub API.

We confirmed the racing by setting up a dummy vulnerable workflow in nopcorn/artifact-exploit-poc that would purposefully write the GITHUB_TOKEN to an artifact using actions/upload-artifact@v4. We then used a script to manually kick off the workflow, monitor for the artifact, download it, extract the value of GITHUB_TOKEN, and use it to authenticate to the Github API out of band for as long as it was valid. Afterwards we compared timestamps:

$ python exploit.py --repo nopcorn/artifact-exploit-poc --polling-interval 0.5
[*] Manually triggering the workflow to start...
[*] Waiting for the workflow run to be detected...
[+] Active workflow run found! Run ID: 15263730946, Status: queued
[*] Waiting for run 15263730946 for artifact 'secret-file'...
[*] Downloading artifact secret-file...
[+] Successfully extracted GITHUB_TOKEN from the artifact -> ghs_PsmNtQuBpes3u9x8MxQx22gE6C7Oc42QTsLj
[+] Monitoring GITHUB_TOKEN validity...
[+] 2025-05-27T00:01:42.289469Z: Valid GITHUB_TOKEN! (status code 200)
[+] 2025-05-27T00:01:42.980345Z: Valid GITHUB_TOKEN! (status code 200)
[+] 2025-05-27T00:01:43.668557Z: Valid GITHUB_TOKEN! (status code 200)
[+] 2025-05-27T00:01:44.360745Z: Valid GITHUB_TOKEN! (status code 200)
[+] 2025-05-27T00:01:45.023419Z: Valid GITHUB_TOKEN! (status code 200)
[+] 2025-05-27T00:01:45.688257Z: Valid GITHUB_TOKEN! (status code 200)
[+] 2025-05-27T00:01:46.353344Z: Valid GITHUB_TOKEN! (status code 200)
[+] 2025-05-27T00:01:47.046626Z: Valid GITHUB_TOKEN! (status code 200)
[+] 2025-05-27T00:01:47.754317Z: Valid GITHUB_TOKEN! (status code 200)
[!] 2025-05-27T00:01:48.423765Z: Invalid GITHUB_TOKEN: Bad credentials

If we take a look at the raw run logs for the workflow, you can see the workflow ends around 2025-05-27T00:01:46, but we’re able to successfully use the GITHUB_TOKEN until at least 2025-05-27T00:01:47 – a whole second later. While this doesn’t seem like a lot there are several techniques a threat actor could use to compromise a repository with only a single API call.

While this is a contrived example, a scan of all Github repositories using SourceGraph proves this is more common than you might think.

Disclosure

We responsibly disclosed this finding to GitHub through their vulnerability reporting program on HackerOne. The issue was triaged and investigated over the course of a month. Ultimately, however, GitHub classified the behavior as “informational” and chose not to pursue it further. Their position was that since the token is eventually revoked, the brief extension does not constitute a vulnerability under their current threat model.

While we respect their decision, it underscores a philosophical gap between how ephemeral secrets are documented and how they behave in practice. For red teamers, this matters. If you can automate artifact retrieval and parse secrets before the revocation race completes, you have a viable short-term credential to pivot or persist.

We approached many of the vulnerable projects we identified during our scans (via bug bounty programs and Github Security disclosures) and we’re happy to say that the vast majority of them were receptive to the exploit vector and promptly fixed their vulnerable repositories.

References

https://unit42.paloaltonetworks.com/github-repo-artifacts-leak-tokens – ArtiPACKED: Hacking Giants Through a Race Condition in GitHub Actions Artifacts

https://www.praetorian.com/blog/codeqleaked-public-secrets-exposure-leads-to-supply-chain-attack-on-github-codeql/ – CodeQLEAKED – Public Secrets Exposure Leads to Supply Chain Attack on GitHub CodeQL

Assume Breach, Not Burnout: The New OSCP+ Experience

Like many in the cybersecurity community, I once viewed the OSCP certification as the gatekeeper to many offensive security roles. But for me, it was more than a technical exam—it became a recurring roadblock. I failed the older version of the OSCP twice, before finally getting it with a bit of luck. When the OSCP+ was released, I thought i would attempt as I wanted to see if I was up for “more realistic” challenge.

I’m sharing this story because something changed recently. Offensive Security released the revamped OSCP+, and after diving into this new version, I found something that finally clicked. Here’s what’s different—and why it made all the difference for me.

Enter OSCP+: A Modernized, Realistic Challenge

Coming from a red teaming background, I’ve spent alot of time inside Active Directory environments, abusing domain misconfigurations, and simulating adversarial behavior across enterprise networks.

Trying Harder

So when I first attempted the original OSCP, I expected a challenge—but not one so disconnected from my daily work. The exam and lab environments were overwhelmingly web-heavy, with a strong emphasis on outdated Linux boxes and classic web application vulnerabilities.

More Windows, More Realism

One of the biggest changes was the increased number of Windows machines, especially in the lab and exam environments. In the real world, most of what I see revolves around Windows and Active Directory—not outdated Linux web servers vulnerable to simple exploits. The OSCP+ lab now reflects this reality.

This shift made all the difference for me. Tools like PowerView, BloodHound, and Mimikatz were not the focus and were central to the experience. Finally, the skills I use in real engagements were the same ones I needed to succeed in the exam

Assumed Breach for Active Directory

Another welcome surprise: the Active Directory section starts with an assumed breach. Instead of spending half the exam time just trying to land a basic foothold, I was dropped into a compromised workstation within a corporate domain.

This approach let me focus on lateral movement, privilege escalation, and domain dominance—more like the average network pentesting assessment. It also made the exam more strategic and realistic, instead of being a time sink filled with outdated enumeration.

No Bonus Points, No Gimmicks

One of the most notable changes in the OSCP+ exam is the updated point structure.

In the older version of OSCP, there were bonus points. If you completed a certain number of exercises and lab machines from the course material, you could earn up to 10 extra points on the exam. In theory, it encouraged people to take shortcuts and only complete the required number of exercises and lab machines.

With OSCP+, bonus points are gone, you know exactly what’s needed to pass. Instead of banking on a few extra points, the focus is on demonstrating real-world offensive skills during the exam itself. It shifts the mindset from “padding my score” to “executing a plan.”

What I Did Differently

With OSCP+, I trained like I was prepping for a real engagement:

  • Completed EVERY SINGLE PWK exercise and practise machine
  • Mapped out attack paths across multi-host Windows environments with BloodHound
  • Practiced using NetExec, the all in one tool to Kerberoast, Password Spray, do domain enumeration and pretty much everything else

Final Thoughts: It’s Still Hard, But It’s Fair

To be clear, OSCP+ is no walk in the park. It still demands hours of focused practice, deep technical knowledge, and solid methodology. But the difference now is that the challenge aligns with reality.

If you’ve struggled with the old OSCP—especially if you’re coming from a Windows-heavy or red team background—don’t give up. The new OSCP+ might be exactly what you need.