DEF CON SG1 - CSIT Village - Daryl Lim, Zhi Kai - Rolling Api Tokens A Thought Experiment
Watch on YouTubeVideo summary
Daryl Lim and Zhi Kai present a thought experiment centered on replacing long-lived API tokens with a system of continuously rotating secrets, drawing inspiration from the Double Ratchet protocol used in secure messaging. The core argument is that modern software ecosystems, where machine-to-machine interactions vastly outnumber human ones, rely heavily on static credentials that create catastrophic single points of failure. When these high-privilege keys leak, as seen in recent incidents involving CI/CD pipelines and major breaches like those at GitHub or Cloudflare, the damage persists until manual rotation occurs. The proposed solution shifts security focus from protecting data at rest and in transit to securing "data in use," aiming to reduce the window of exposure for any compromised credential to mere seconds rather than days or months.
The technical design employs a two-layered approach combining a Diffie-Hellman ratchet for periodic key agreement and a token ratchet for generating ephemeral credentials within each session. During initialization, a root key acts as a security deposit to authenticate the initial exchange, after which new shared secrets are derived every 60 seconds via elliptic curve operations. Within each epoch, a chain of tokens is generated based on request sequences, ensuring that every API call uses a unique, short-lived credential. The system also incorporates "look-ahead generation" to handle out-of-order requests caused by network latency or retries, while strictly limiting the number of pre-generated keys to prevent denial-of-service attacks and ensuring that once a token is consumed, it is immediately discarded and never reused.
However, the presentation acknowledges significant practical limitations, particularly the lack of Trusted Platform Modules (TPMs) in common cloud runners like GitHub Actions or AWS Lambda, which are essential for keeping private keys out of volatile memory. Without hardware-based isolation, maintaining security would require complex stateful authentication mechanisms involving shared caches that must match TPM-level protection, effectively trading operational simplicity for enhanced security. Furthermore, the current prototype is not formally verified nor deployed at scale, and it does not fully solve issues related to horizontal scaling across multiple backend instances. Despite these hurdles, the concept offers a promising path forward for air-gapped environments, zero-trust architectures, and agentic AI systems where preventing prompt injection from leaking long-lived credentials in context windows is critical.
In conclusion, the talk advocates for a paradigm shift where static secrets are no longer viewed as "golden tickets" but as dynamic, transient resources that assume an environment of assumed compromise rather than trust. By moving from stateless bearer tokens to dynamic, time-bound credentials, this approach dramatically shrinks the attack surface even when the runtime environment itself is breached. While not a finished product ready for immediate production deployment, the design highlights that relying on human detection or hoping for perfect configurations is insufficient in today's threat landscape. The ultimate goal is to create leak-resistant systems where possession of a credential does not equate to authorization, thereby mitigating the impact of inevitable leaks and weaponized dependencies in modern software supply chains.
Read the full video transcript
And now to wrap up day two, Darrell Liam
explores a world without long-lived API
tokens using concepts inspired by the
double ratchet protocol to continuously
rotate secrets. It's about reducing risk
by design. If you're thinking about
authentication and future-proofing
systems, this is one to catch. Let's
welcome our final speaker for today.
>> Hello.
You all can hear me at the back, right?
All right, great. All right, yeah. So,
today's talk is about a thought
experiment on rolling API tokens, right?
And it's like mentioned earlier, it's
inspired by the double ratchet protocol.
And I was really thinking about it like
how can we like the fact that how how
can we apply this to like API tokens,
right? For machine-to-machine
authentication, right? So, this talk
will be going through about that. And
just a little disclaimer is that this is
not a finished product, right? It's just
a design idea that I think might be
worth a conversation.
All right, so just to give some
background, APIs are the glue to modern
software, right? Like microservices,
CI/CD, mobile applications, and all of
them are just talking through APIs
constantly, right?
And non-human identities outnumber
humans 50 to 1.
Your organization might have 200
engineers and 10,000 service accounts.
Most of them
authenticate using static API tokens.
Now, these tokens are usually created
once, stored somewhere, and used
forever, probably, right? And in March
2025 last year,
uh one of these secrets one of these
leaked secrets, right? Actually cascaded
into 23,000 compromised CI workflows,
which I'll go through a little more
later.
And this is not really a
misconfiguration. This is more of a
design flaw.
So, how can we design leak-resistant
systems via ephemeral just-in-time
credentials.
Right. So, currently how is standard API
secrets are used nowadays is that CICD
pipelines need highly privileged
credentials for legitimate reasons. They
such as pushing Docker images, deploying
the broad, running Terraform, all
require powerful keys.
And the problem is not that these keys
exist, it's more that they are static
and of high privilege.
So, that combination is what makes leaks
catastrophic.
Now, if someone got the CI runner's
environment variables right now, what
could they do with those environment
variables?
So, here's an example workflow that you
might see.
Developer commits to a Git repo and the
GitHub action builds it and then pushes
it to Docker image.
So, this Docker token has to live
somewhere accessible to the runner and
during execution that secret is in
memory.
So, what could these rolling tokens be
used for? Uh we use in M2M and NHI API
interactions, not really used as like a
OAuth replacement. And runtime memory
should not be guaranteed to be private,
right? Because you could always have a
compromised runner, container escape, or
misconfigured systems.
And most security protects data at rest
and in transit. So, these rolling tokens
focuses more on data in use.
And what it does is that it minimizes
the window of exposure exposure to
seconds.
So, why does this matter?
Some of these uh
scenarios has happened based on the
OWASP NHI top 10. For example, The New
York Times had exposed the GitHub token
and 270 GB of source code was stolen in
January 2024.
And nobody noticed it until it showed up
on 4chan in June, like 5 months later.
Right.
Uh for Cloudflare, if Cloudflare failed
to rotate its stolen credentials after
Octa got breached. Uh for hugging face,
they had their spaces secret exposed.
They did manage to remove the odd level
tokens entirely after before anything
actually happened, but it kind of tells
you like how bad it could have been.
The US Treasury Department affected by
one compromised beyond trust API key
allowed attackers to have remote access
to some of the agency's workstations.
And finally, Microsoft had an OAuth
application hijack and then it just had
full exchange access.
So,
uh going to the TJ actions incident that
I talked about for the March 2025
incident uh yeah, March 2025 incident.
Uh TJ actions changed files is really is
an action that is widely used on GitHub
actions and it was compromised by a
stolen GitHub personal access token.
And the payload was elegantly simple.
What it did was that it downloaded
memory scripts, it grabbed for secrets,
base64 encoded them, and then print to
the workflow log. And for those who used
the this action, you did nothing wrong.
You kind of just used a trusted
dependency and it was weaponized.
So, this is the attack flow and this
image is provided by Palo Alto Networks.
It's
uh what happens is that it the attacker
pushes a malicious commit and then
base64 the encoded payload. Right, and
they updated all existing Git tags to
put to point to the malicious commit.
So, if you set a tag at V1, basically
you were still vulnerable and most
people pin to tags rather than the
commit chars.
And we've also documented the entire
full flow and the workflow log is
basically the exfiltration channel,
which shows that the attack surface is
the entire execution environment and
only the
only the execution environment.
All right, and how did he manage to even
get control of this TJ actions?
They actually This was actually caused
by another personal access token leak.
Right, and review dog action setup was
infected at first and gave attackers the
personal access token used to compromise
TJ actions. So, we're kind of like
trusting everyone our depend uh our
dependencies trust and trusting them
with that they had to use the API keys
uh that they secure the API keys
securely.
All right, so what can we do about this?
All right, so introducing like the
rolling API token specifications. For
those who are familiar with the signal
double ratchet protocol, this might look
a little familiar to y'all. Yeah, but
basically for those that don't know,
uh there's two ratchets.
Uh the two the two ratchets work
together. The Diffie-Hellman ratchet
governs key agreement between client and
server and rotates periodically.
While the token ratchet governs token
generation within each key period. You
can really think about it as the
Diffie-Hellman ratchet changes the lock
while the token ratchet generates a new
key per door opened.
All right, so for the initialization,
step one, you can use an existing API uh
use an existing API token such as the
Docker personal access token and that
becomes your root key.
Step two is that both sides will perform
some form of Diffie-Hellman key
exchange. You can use something like
ECDH or something better.
And we can use the root key for
authentication using a HMAC on the DH
pub and uh root key as the secret.
And once this is done, the client and
server has the secret DH uh and is
authenticated.
So, the root key is kind of like a uh
security deposit. You just kind of need
to use it once and that's it.
So, what happens in a Diffie-Hellman
ratchet is that every 60 seconds, the
client checks its DH pair is due for
rotation.
And the client generates a new DH pair,
sends the new public key to the server.
The server does the same, generates its
own DH pair and sends its own public key
back to the client.
And both clients basically both sides
independently compute new shared secret
and the old secret is discarded.
So for on the token ratchet KDF and
basically you will create a you will run
the KDF on the DH out and the root key
to generate the first chain key and each
subsequent chain key is derived from the
previous one.
So for the full token generation flow we
have two terms epoch and chain. So the
epoch is a period or a period given
where the DH out is valid and is bounded
by the 60 second DH rotation mentioned
earlier.
While the chain is a sequence of tokens
within one epoch. So it's like two
layers of freshness epoch level from the
DH ratchet and the request level from
the chain key.
All right, so here are just some
metadata in the token. It's not too
important.
So here is a sample a program that is uh
implementing the protocol.
Let me just pause it.
All right, so as you can see over here
during the initialization uh section the
left side is the client and the right
side is the response on the server. And
notice that the client sends the the DH
pub over and the server sends its DH pub
over to the client. And there's also the
authentication that I mentioned earlier
which uses the KDF with the root key and
the DH pub to make sure that they are
not being man in the middle.
So we're going to run a few pings and I
want you to notice that the tokens are
actually changing every single request.
All right, so what we'll do now is I'll
run an intercept and I will s-
I'll intercept one of the requests and
send it to a repeater.
And notice that the first request is
okay.
But subsequent requests uh basically it
fails. Uh so basically the request fails
immediately and once it's consumed, it's
basically gone.
Finally, there is one more thing I want
to show you, which I'll go through a
little bit later, which is the out of
order request. So most likely and more
likely than not, you are probably
not
come in a oops, sorry.
Yeah. Yeah, so notice over here, I sent
the request in randomized order and it's
still able to recover itself.
Uh I'll be going through this a little
bit more how it handles the out of order
like shortly.
All right.
So for the out of the order request, in
the real world, requests don't usually
arrive in sequence. It could be due to
network conditions, retries,
asynchronous jobs. And it kind of makes
like sequence maybe it makes like
sequence five might land before sequence
four.
So the solution for this is look ahead
generation.
So if a token arrives with a higher
sequence number, uh gener- basically
you'll generate the intermediate chain
keys in between them, store them and
accept matching ones. And once the token
is used, it's discarded immediately and
is never ever reused.
And the look ahead is also bounded,
which I will cover next in the security
considerations.
All right. So there are a couple of
guardrails to make this safe in
practice.
Number one is that key must be device
bound. So the root key and the
Diffie-Hellman private keys should live
somewhere in the TPM so that because if
you put it inside the memory, if they
can dump the root key and the chain key,
you're kind of back to square one.
And operations must happen inside the
chip, right? Keys can never touch at
memory.
Chain keys must also be short-lived. And
previous epoch 30 second uh sorry, for
the previous epochs, we are setting the
time to live to 30 seconds and the epoch
before that to be zero immediately. This
is so that it can handle some form of
out of order when the when the epoch is
being changed.
And
there is the maximum chain key look
ahead cap at 100 like I mentioned
earlier. Right, so without this, the
look ahead can become a
denial-of-service vector. Right, so if
the attacker sends like a sequence
number of like 1 million, all right, if
without the cap, the server will just
generate 1 million keys, right? And the
cap basically kills that.
And the force epoch change every 60
seconds kind of just make sure that the
attacker who steals a chain key cannot
write on it indefinitely.
Okay, so before I go through some of the
comparisons, let me address a couple of
things that this design does not solve.
Right, so for one, TPMs aren't really
everywhere, right? Most cloud runners
don't really have a TPM, all right? But
I think about GitHub hosted runners or
AWS Lambdas. And this is a real
deployment limitation. All right, so we
can have some partial substitutes. I
believe there's some software TPM that
you can implement by virtualization. And
without this isolation primitives, the
chain key sits in process memory.
Another thing is about stateful
authentication. Right, the static keys
are basically stateless in stateless in
design. And stateless bearer tokens
scale trivially, right? And this doesn't
really does that. And if we scale it
like horizontally, let's say for
example, if we have 50 back-end
instances, they need to share chain key
state, right? So they may need like
sticky sessions or shared cache like
Redis. And this shared store now needs
to be on the same level of protection as
the TPM, right? So it's kind of like a a
situation where there's no free lunch,
right? Security kind of gain a security
gain costs operational simplicity. And
finally, talking about maturities that
this is not a just a thought experiment
and a working prototype. It's not really
formally verified kind of and also not
production deployed at scale.
All right. So, going on to the current
technologies,
how does this compare to stuff that we
already have?
So, OAuth 2.0 is great for human
authentication. Tokens are static
bearers and vulnerable to replay until
expiry. And JWTs are generally great for
verification without DB lookup, but it's
also nearly impossible to revoke before
expiry.
And GitHub secret manager protects keys
at rest. Runtime injection makes them
static bearers again. And mTLS is also
very strong, but the thing is that PKI
is operationally very heavy. All right,
with cert life cycles, revocation, and
intermediate CAs, and etc.
So, what rolling tokens hope to achieve
is that is an application layer
uh security without a need for a PKI.
And it's this is designed to
designed for assumed compromise
runtimes.
So, where could this be used?
Well, for starters, we can use it in
air-gapped environments where tokens can
be issued and validated in fully
isolated environments and continue to
operate without external network access.
So, there's no need to phone home.
Another thing is that in zero-trust
proxy-heavy environments, application uh
basically
is a application layer security where
infrastructure is not part of the trust
boundary anymore.
And I think more interestingly about
agentic AIs and MCPs nowadays, it kind
of prevents the agent from remembering
long-lived credentials in context
windows because this context can be
exfiltrated by a prompt injection or by
logging. All right. So, this rolling
tokens ensure the agent never holds the
credentials useful for more than a few
seconds.
So, thinking about this, we could go a
little further. We could implement
fine-grained permissions.
So, we could bind permissions
cryptographically into the token, mix
permissions
into uh sorry. We can mix the
permissions that is written in JSON into
the KDF. And also, it kind of moves us
from a situation where we have a valid
session to an authorized for exact
action.
All right. So in conclusion, static
secrets are a single point of failure
and there is no such thing as a truly
trusted environment and human detection
is way too slow and just have because
you possess a token doesn't mean you're
authorized to use it just like the TJ
sorry the just like the TJ action
situation.
And basically for rolling tokens, a
static secret is no longer a golden
ticket and it kind of moves from a state
basically it moves from a static to a
dynamic secret.
Right. And it's secure even in
compromised environments and it kind of
shrinks basically shrinks the exposure
surface dramatically.
And it's also self-contained so you
don't really need a third party to phone
home something. Right. So static secrets
assume the environment is trustworthy
while rolling tokens assume it isn't.
Yeah and with that I come to my end of
my presentation. Thank you very much.
Hope you're enjoying the rest of the Def
Con.