Try reloading the player or open this episode directly on YouTube.
In this episode of Ship It Weekly, listeners learn about GitHub Actions' expansion to AWS, which may disrupt existing allowlists. The podcast also covers the exploitation of a Brotli decoder bug and a campaign stealing GitHub Actions secrets.
Now Playing
GitHub Actions Jobs Move to AWS, Brotli RCE Exploited, Gheist Steals Actions Secrets & AGENTS.md Hijacks AI Coding Agents
Ship It Weekly
0:0016:53
Chapters
Jump to a section in this episode.
Speed & share
Transcript
GitHub Actions jobs can now run on AWS, and if
your firewall only trusts Azure IP ranges, some
pipelines could start failing. A bug in the Brotli
decoder is being exploited, and your code might
be decompressing Brotli without ever mentioning
it. And attackers with stolen GitHub tokens are
pulling Actions secrets out of private repos,
then... deleting the evidence. I'm Brian Teller
from Teller's Tech, and this is Ship It Weekly.
Welcome back to Ship It Weekly, the show about
the DevOps, SRE, cloud, platform, and security
stories that matter when you are the person keeping
the thing running at three in the morning. For
the weekly story list and source links, check
out oncallbrief.com. For past episodes and show
notes, head over to shipitweekly.fm. This week,
GitHub Actions goes multi-cloud, the Brotli
bug lands on CISA's exploited list, a campaign
called Gheist steals action secrets, and attackers
are dropping AGENTS.md files into Downloads
folders. Then we have a quick lightning round
and a human closer about the 20 minutes you lose
finding out who owns a service. Let's get into it.
First up, GitHub Actions is spreading hosted
runner jobs across more than one cloud. At GitHub
Universe on Wednesday, GitHub said eligible Linux
jobs may now run on AWS, in addition to Microsoft
Azure. This covers the standard two-core and
four-core Linux runners, and the larger runners
of the same sizes without special configuration.
Windows runners and private networking aren't
part of it. On Free, Pro, and Team plans, eligible
jobs move automatically. On Enterprise Cloud,
it's opt-in under Hosted compute cloud providers
in the enterprise action settings. Your labels
and workflow files don't change. GitHub's reason
is capacity and shorter queue times. The part
that I'd care about is the network. Jobs running
on AWS leave from the new outbound IP addresses.
If anything your workflows talk to only allows
Azure or Microsoft ranges, those connections
can fail. GitHub specifically calls out Microsoft
Entra Conditional Access policies here. So
authentication can break too, not just a firewall
rule. And because it's a rolling change, it won't
break all at once. You could see the same job
pass on one run and fail on the next, depending
on which cloud it landed on. That's a miserable
thing to debug if you don't know this change
is happening. So go find your allowlist now.
Database firewalls, registries, internal APIs,
anything keyed to GitHub's IP ranges. GitHub publishes
every runner range in the actions list from its
meta API at api.github.com/meta. Sync from that, not
from one cloud's ranges. If you've been treating
"it comes from Azure" as a security control, this
is the week that assumption stops holding.
Next, the Brotli decoder bug is now being exploited.
CVE-2026-111497 is an out-of-bounds write
in Brotli, the compression format a lot of HTTP
traffic uses. Node.js fixed its bundled copy
in last week's security release, rated high.
On Tuesday, CISA added it to its Known Exploited
Vulnerabilities catalog with a November 10th
deadline for federal agencies. And it isn't just
Node. Google's Brotli library is affected from
1.0.3 through 1.2.0, except the patched
1.0.10 and 1.1.1 releases. So are the Python
Brotli package, Bun, Envoy, Istio, and a popular
Go port. And Microsoft shipped out-of-band
.NET patches last weekend. Here's how it works,
per Node's advisory. Brotli data comes in blocks
and the decoder switches between them as it goes.
When it hit an invalid block switch, it treated
that as "I need more input" instead of "this data
is broken." So the decoder kept appending bytes
into an eight-byte internal buffer, writing right
past the end of it. That can mean a crash, leaked
memory, or code execution. What makes this one
sneaky is that you probably never call Brotli
yourself. Some HTTP clients and frameworks decompress
automatically once Brotli support is turned on.
So the exposure could be an incoming request,
an API response, or anything compressed you didn't
create. Upgrade Node to 22.23.4, 24.22.1,
or 26.12.1 or newer. Get Python Brotli to
1.2.1. And if your .NET apps decompress Brotli,
move to 8.0.33, 9.0.22, or 10.0.14. And
check your Envoy and Istio versions because gateways
and sidecars can decompress too. Then rebuild
images and restart processes because a patched
package on disk doesn't help a process running
the old one. If you can't patch everything this
week, turning off Brotli decompression or rejecting
Brotli-encoded requests at the edge buys you
time. It doesn't replace the patch.
Third, a campaign called Gheist is using stolen GitHub
tokens to walk off with your action secrets.
Wiz published this on Sunday. They've tracked
it since mid-September and recovered more than
13,500 planted branches from over 850 compromised
accounts. Here's the playbook. For every repo
a stolen token can reach, the tool pushes a branch
named ci-sync plus six random characters. On
it is a workflow called sync-check that prints
the repo secrets, base32-encoded, so GitHub's
log masking doesn't catch them. The tool downloads
the log through the API then deletes the run
seconds later. Newer variants add environment
matrices so environment-scoped secrets like
production credentials come out too. In enterprise
cases, Wiz saw it use the cloud credentials it
found to go through AWS Secrets Manager. Now,
GitHub already holds suspicious workflow runs
in public repos until a collaborator approves
them. Every public Gheist run Wiz checked was
stuck waiting. So that control worked. But the
hold doesn't apply to private repos or GitHub
Enterprise Server. There, the run completed and
got deleted. And Wiz saw organizations' own workflows
run on those attacker branches and assume AWS
roles through OIDC. So... hunt first. Look for
ci-sync branches whose only change is sync-check.yml,
runs deleted right after they finish, and
a user agent of gheist in your audit log. If you
use OIDC, check your cloud logs for role assumptions
from ci-sync branches. If you find any, rotate
every secret those repos could reach. Deleting
the branch doesn't undo the exposure. Then harden:
require reviewers on production environments,
scope OIDC trust to protected branches, and move
off long-lived personal access tokens.
Fourth, attackers have found a new place to put instructions
for your AI coding agent, your Downloads folder.
Forever Security published this last week after
catching it on a real customer's machine. Their
tool stopped a coding agent mid-session from
installing function-color, a known malicious
npm package. The instructions came from an AGENTS.md
file sitting in Downloads. Here's why that
works. Coding agents automatically read the instruction
file in whatever folder you start them in. And
Downloads is the one folder a website can write
to on its own. Visit the wrong page and a file
called AGENTS.md can quietly land there. Then
the day you open an agent in Downloads, maybe
to sort some files, it reads those instructions
and follows them. In their testing, Codex ran
the payload in more than 90% of attempts. And it
wrote to Codex's machine-wide config file, so
the damage doesn't stay in that one folder. With
Claude Code, a CLAUDE.md file redirected its
API traffic to a server the researchers controlled,
which then fed back commands for it to run. That
worked about half the time. The researchers say
there's nothing here for OpenAI or Anthropic
to patch, because the agents are doing what they
were built to do. So do what both vendors recommend
and start agents in your project directory, not
your home folder. And definitely not in Downloads.
Keep the approval prompts and the sandbox turned
on. And if your team uses these tools, an instruction
file is code now. Treat one you didn't write
the way you'd treat a script you didn't write.
Quick lightning round. First, a follow-up. In
episode 72, we covered GitHub Actions workflow
lockfiles going into public preview. As of Wednesday,
they are generally available. Dependabot now
updates the lockfile in the same pull request
when it bumps an action. And admins can require
lockfiles with a policy. Second, Railway disclosed
a breach on October 21st. Someone got into internal
systems and may have accessed customers' encrypted
environment variables. And for some, Railway
says the decrypted values were taken. If you
run anything there, rotate every credential stored
on Railway from before October 21st and review
your GitHub audit logs and installed app access.
Third, Tailscale shipped version 1.104.4 on
Tuesday with fixes for four security bulletins.
The worst hits nodes where Tailscale SSH allows
forwarding. There, someone already allowed to
SSH in could forward sockets as root, reaching
things like Docker socket. Upgrade or set
TS_SSH_DISABLE_FORWARDING
to 1 and restart tailscaled until
you can. And fourth, Datadog's US1 status page
switched over to Datadog's own status page product
on Thursday. But if you scrape the old page or
its API in your own tooling, that can break.
Point it at the old page's address or update
it to read the new format.
The human closer this week comes from Bill Duncan and a piece featured
in SRE Weekly called "Who Owns This? Why SREs
Want a Service Catalog." It opens at two in the
morning in a scenario most of us have lived.
An alert fires for a service you've never touched.
The README is two years old. The Slack thread
you find names a team that's since been reorganized.
You page the rotation you think is right. And
they tell you politely, it isn't theirs. But
Sam used to work on it. Sam is on vacation. His
line is, "20 minutes in, you haven't started diagnosing
anything. You've been doing archaeology." He boils
it down to five questions. From who owns this
service to who's on call for it right now. Ownership
written down as a person goes stale fastest.
So... it should point at a team that still exists.
CODEOWNERS tells you who reviews the code, not
who gets woken up. And most teams can answer
"what does this depend on" by reading the code.
Almost nobody can answer "what depends on me" without
asking around. His fix is a small catalog file
in each service's repo, maintained by the team
that owns it. And he's honest about the catch.
The people who want it most, the SREs, aren't
the ones who can keep it accurate. Now, look
at this week's stories. Which of your services
decompress Brotli? Which allowlists assume
your CI runs on Azure? Which OIDC trust policies
accept any branch? Which laptops have an agent
that's been started in Downloads? None of those
are hard questions. They are inventory questions,
and every one of them is easier to answer if
somebody wrote it down before this week. That's
it for this week of Ship It Weekly. We covered
GitHub Actions jobs moving to AWS, the Brotli
decoder bug landing on CISA's exploited list,
the Gheist campaign stealing Actions secrets,
and AGENTS.md files hijacking coding agents
from the Downloads folder. Plus, Actions lockfiles
going GA, the Railway breach, Tailscale's
SSH fixes, and Datadog's new status page. Follow
or subscribe wherever you are watching or listening.
Every main story this week came down to something we trusted without ever deciding to.
Nobody sat down and decided that Azure's IP ranges were safe because CI runs there. It was just true for long enough to turn into a firewall rule. Nobody chose to run a Brotli decoder on bytes from strangers. It came along with an HTTP client or a sidecar. Nobody decided that any branch in a repo should be able to become a production AWS role. That's just what a loose OIDC trust policy does. And nobody decided their coding agent should take orders from whatever file sits in the folder it starts in. That's how the tools work.
None of those were choices. They were defaults that turned into assumptions.
Start with GitHub Actions.
Eligible Linux jobs can now run on AWS as well as Azure. Free, Pro and Team plans move automatically, and Enterprise Cloud opts in. GitHub's reason is capacity and queue times, and that's a good reason. Anyone who has watched a queue of jobs sit at "waiting for a runner" on a busy morning will take it.
The catch is the network. Jobs on AWS leave from new outbound addresses, and GitHub specifically calls out Microsoft Entra Conditional Access, so this isn't only a database firewall problem. Sign-ins can fail too.
What worries me is how it fails. It's a rolling change, scheduled job by job, so you don't get one clean break you can bisect. You get a job that passes, then fails, then passes again, and a team that spends a day blaming the network, then the runner image, then each other. GitHub didn't say that part; it's my read of how a rolling change behaves. But if you've ever chased a flaky pipeline, you know how that week goes.
The fix is boring, which is good. Sync your allowlists from the actions list in GitHub's meta API instead of from one cloud's ranges. And if "it came from Azure" was ever part of your security story, it was always a weak control. A job should prove who it is with something like an OIDC token, not with where its packets happen to leave from.
Which leads straight into Gheist.
Wiz has tracked this campaign since mid-September and recovered more than 13,500 planted branches from over 850 compromised accounts. The playbook is almost dull. For every repo a stolen token can reach, push a ci-sync branch with a sync-check workflow that prints the secrets base32-encoded, so log masking doesn't catch them. Download the log. Delete the run.
The part I keep coming back to is the split between public and private repos. GitHub's hold on suspicious runs in public repos worked: every public Gheist run Wiz checked sat waiting for a collaborator's approval. Private repos and GitHub Enterprise Server don't get that hold. There, the runs completed and got deleted, and Wiz saw organizations' own workflows run on attacker branches and assume AWS roles through OIDC.
A lot of us treat private repos as the safe ones, because the people who can push there are our people. A stolen token makes the attacker one of our people. And an OIDC trust policy that accepts any branch means anyone who can push a branch can become your cloud role.
So if you do one thing after this story, open your OIDC trust policies and look at the subject condition. If it's a wildcard on the repo, scope it to protected branches or environments. Then put required reviewers on the environments that hold production secrets. Those two changes turn "anyone with push access" back into "someone who got a review."
And hunt first. Wiz's indicators are specific: ci-sync branches whose only change is sync-check.yml, runs deleted right after they finish, and a gheist user agent in the audit log. If you find any, rotate everything those repos could reach. Deleting the branch doesn't undo the exposure.
Brotli is the "you never called it" story.
CVE-2026-111497 is an out-of-bounds write in the Brotli decoder. Node.js fixed its copy on October 21, CISA added it to the Known Exploited Vulnerabilities catalog on October 27, and the affected list keeps going: Google's library, Python Brotli, Bun, Envoy, Istio, a Go port, and .NET, which got out-of-band patches.
Most of us will never write a line of code that calls Brotli. That's the point. Some HTTP clients and frameworks decompress automatically once Brotli support is on, and gateways and sidecars can too. So the real question isn't "do we use Brotli?" It's "what in our stack decompresses things other people send us?" I'd bet most teams can't answer that today.
Two practical notes. On .NET, Microsoft says Brotli decompression is off by default; you're exposed if you turned on request or response decompression, or call the Brotli APIs yourself. And everywhere else, patching the package isn't the end. Rebuild the images and restart the processes, because a fixed library on disk does nothing for a process that loaded the old one.
Then AGENTS.md.
Forever Security caught a coding agent on a real customer's machine trying to install a known malicious npm package, because an AGENTS.md file in Downloads told it to. Downloads is the one folder a website can write to on its own. Coding agents read the instruction file in whatever folder you start them in. Put those two facts together and you have an attack.
In their testing, Codex ran the payload in more than 90 percent of attempts. With Claude Code, a CLAUDE.md file redirected its API traffic to a server the researchers controlled, and that worked about half the time.
The researchers say there's nothing for OpenAI or Anthropic to patch, and I think they're right. The agents did what they were built to do. That's exactly why it matters. We gave these tools a convention, read the instructions in this folder, and the convention became the attack surface.
My take: an instruction file is code now. It changes what a program with your shell and your credentials will do. Review one you didn't write the way you'd review a script you didn't write. Start agents in the project directory, never your home folder or Downloads, and keep the approval prompts and the sandbox on, even when they feel slow.
The lightning round fits the same pattern. Actions lockfiles going GA gives you a way to pin exactly what your workflows run. Railway's breach is a reminder that secrets stored on a platform are only as safe as that platform's internal systems, so rotate anything you stored there before October 21. And Tailscale's worst bug only matters where Tailscale SSH allows forwarding, which is exactly the kind of setting people turn on once and forget.
The human closer is where it all lands for me.
Bill Duncan's piece opens at two in the morning: an alert for a service you've never touched, a two-year-old README, and the person who used to work on it is on vacation. His line: "Twenty minutes in, you haven't started diagnosing anything. You've been doing archaeology."
The point I'd underline is about dependencies. Most teams can say what a service depends on by reading the code. Almost nobody can say what depends on it without asking around.
Run this week through that. Which of your services decompress Brotli? Which allowlists assume CI runs on Azure? Which OIDC trust policies accept any branch? Which laptops have an agent that's been started in Downloads?
None of those are hard questions. They're inventory questions, and every one of them is the "what depends on this?" half that Duncan says almost nobody can answer.
That's why I don't think of a service catalog as paperwork. It's what turns a week like this from four investigations into four lookups. His fix is small, a catalog file in each repo kept up by the team that owns it, and he's honest that the people who want it most aren't the ones who can keep it accurate.
Start small anyway. An owner that's a team, not a person. Who's on call. What it depends on. What depends on it.
This week on Ship It Weekly, GitHub Actions can now run eligible Linux jobs on AWS as well as Azure, so allowlists and Entra Conditional Access policies that only trust Microsoft ranges can break. The Brotli decoder bug (CVE-2026-111497) landed on CISA's exploited list, and it reaches past Node.js to Python Brotli, Bun, Envoy, Istio, a Go port and .NET. A campaign called Gheist is using stolen GitHub tokens to push ci-sync branches that print Actions secrets past log masking, then delete the run. And attackers are dropping AGENTS.md files into Downloads folders to hijack AI coding agents.
The thread: each one is an inventory question. Which services decompress Brotli, which allowlists assume CI runs on Azure, which OIDC trust policies accept any branch, and which laptops have started an agent in Downloads?
In the lightning round: GitHub Actions workflow lockfiles are generally available, Railway disclosed a breach of its internal systems, Tailscale 1.104.4 fixes four security bulletins, and Datadog moved its US1 status page to its own Status Pages product.
The human closer, from Bill Duncan: "Twenty minutes in, you haven't started diagnosing anything. You've been doing archaeology." Write down who owns it before the pager asks.
Highlights
• GitHub Actions on AWS: why the same job can pass, then fail
• Sync runner IP ranges from GitHub's meta API, not one cloud's
• Brotli: a decoder bug your HTTP stack may run for you
• Gheist: what private repos and OIDC trust let through
• AGENTS.md in Downloads: an instruction file is code now
• Service catalogs and the "what depends on me?" question
Every main story this week came down to something we trusted without ever deciding to.
Nobody sat down and decided that Azure's IP ranges were safe because CI runs there. It was just true for long enough to turn into a firewall rule. Nobody chose to run a Brotli decoder on bytes from strangers. It came along with an HTTP client or a sidecar. Nobody decided that any branch in a repo should be able to become a production AWS role. That's just what a loose OIDC trust policy does. And nobody decided their coding agent should take orders from whatever file sits in the folder it starts in. That's how the tools work.
None of those were choices. They were defaults that turned into assumptions.
Start with GitHub Actions.
Eligible Linux jobs can now run on AWS as well as Azure. Free, Pro and Team plans move automatically, and Enterprise Cloud opts in. GitHub's reason is capacity and queue times, and that's a good reason. Anyone who has watched a queue of jobs sit at "waiting for a runner" on a busy morning will take it.
The catch is the network. Jobs on AWS leave from new outbound addresses, and GitHub specifically calls out Microsoft Entra Conditional Access, so this isn't only a database firewall problem. Sign-ins can fail too.
What worries me is how it fails. It's a rolling change, scheduled job by job, so you don't get one clean break you can bisect. You get a job that passes, then fails, then passes again, and a team that spends a day blaming the network, then the runner image, then each other. GitHub didn't say that part; it's my read of how a rolling change behaves. But if you've ever chased a flaky pipeline, you know how that week goes.
The fix is boring, which is good. Sync your allowlists from the actions list in GitHub's meta API instead of from one cloud's ranges. And if "it came from Azure" was ever part of your security story, it was always a weak control. A job should prove who it is with something like an OIDC token, not with where its packets happen to leave from.
Which leads straight into Gheist.
Wiz has tracked this campaign since mid-September and recovered more than 13,500 planted branches from over 850 compromised accounts. The playbook is almost dull. For every repo a stolen token can reach, push a ci-sync branch with a sync-check workflow that prints the secrets base32-encoded, so log masking doesn't catch them. Download the log. Delete the run.
The part I keep coming back to is the split between public and private repos. GitHub's hold on suspicious runs in public repos worked: every public Gheist run Wiz checked sat waiting for a collaborator's approval. Private repos and GitHub Enterprise Server don't get that hold. There, the runs completed and got deleted, and Wiz saw organizations' own workflows run on attacker branches and assume AWS roles through OIDC.
A lot of us treat private repos as the safe ones, because the people who can push there are our people. A stolen token makes the attacker one of our people. And an OIDC trust policy that accepts any branch means anyone who can push a branch can become your cloud role.
So if you do one thing after this story, open your OIDC trust policies and look at the subject condition. If it's a wildcard on the repo, scope it to protected branches or environments. Then put required reviewers on the environments that hold production secrets. Those two changes turn "anyone with push access" back into "someone who got a review."
And hunt first. Wiz's indicators are specific: ci-sync branches whose only change is sync-check.yml, runs deleted right after they finish, and a gheist user agent in the audit log. If you find any, rotate everything those repos could reach. Deleting the branch doesn't undo the exposure.
Brotli is the "you never called it" story.
CVE-2026-111497 is an out-of-bounds write in the Brotli decoder. Node.js fixed its copy on October 21, CISA added it to the Known Exploited Vulnerabilities catalog on October 27, and the affected list keeps going: Google's library, Python Brotli, Bun, Envoy, Istio, a Go port, and .NET, which got out-of-band patches.
Most of us will never write a line of code that calls Brotli. That's the point. Some HTTP clients and frameworks decompress automatically once Brotli support is on, and gateways and sidecars can too. So the real question isn't "do we use Brotli?" It's "what in our stack decompresses things other people send us?" I'd bet most teams can't answer that today.
Two practical notes. On .NET, Microsoft says Brotli decompression is off by default; you're exposed if you turned on request or response decompression, or call the Brotli APIs yourself. And everywhere else, patching the package isn't the end. Rebuild the images and restart the processes, because a fixed library on disk does nothing for a process that loaded the old one.
Then AGENTS.md.
Forever Security caught a coding agent on a real customer's machine trying to install a known malicious npm package, because an AGENTS.md file in Downloads told it to. Downloads is the one folder a website can write to on its own. Coding agents read the instruction file in whatever folder you start them in. Put those two facts together and you have an attack.
In their testing, Codex ran the payload in more than 90 percent of attempts. With Claude Code, a CLAUDE.md file redirected its API traffic to a server the researchers controlled, and that worked about half the time.
The researchers say there's nothing for OpenAI or Anthropic to patch, and I think they're right. The agents did what they were built to do. That's exactly why it matters. We gave these tools a convention, read the instructions in this folder, and the convention became the attack surface.
My take: an instruction file is code now. It changes what a program with your shell and your credentials will do. Review one you didn't write the way you'd review a script you didn't write. Start agents in the project directory, never your home folder or Downloads, and keep the approval prompts and the sandbox on, even when they feel slow.
The lightning round fits the same pattern. Actions lockfiles going GA gives you a way to pin exactly what your workflows run. Railway's breach is a reminder that secrets stored on a platform are only as safe as that platform's internal systems, so rotate anything you stored there before October 21. And Tailscale's worst bug only matters where Tailscale SSH allows forwarding, which is exactly the kind of setting people turn on once and forget.
The human closer is where it all lands for me.
Bill Duncan's piece opens at two in the morning: an alert for a service you've never touched, a two-year-old README, and the person who used to work on it is on vacation. His line: "Twenty minutes in, you haven't started diagnosing anything. You've been doing archaeology."
The point I'd underline is about dependencies. Most teams can say what a service depends on by reading the code. Almost nobody can say what depends on it without asking around.
Run this week through that. Which of your services decompress Brotli? Which allowlists assume CI runs on Azure? Which OIDC trust policies accept any branch? Which laptops have an agent that's been started in Downloads?
None of those are hard questions. They're inventory questions, and every one of them is the "what depends on this?" half that Duncan says almost nobody can answer.
That's why I don't think of a service catalog as paperwork. It's what turns a week like this from four investigations into four lookups. His fix is small, a catalog file in each repo kept up by the team that owns it, and he's honest that the people who want it most aren't the ones who can keep it accurate.
Start small anyway. An owner that's a team, not a person. Who's on call. What it depends on. What depends on it.
Write down who owns it before the pager asks.
Additional Links
CISA KEV entry for CVE-2026-111497:
https://www.cisa.gov/known-exploited-vulnerabilities-catalog?search_api_fulltext=CVE-2026-111497
Datadog US1 status page:
https://status.datadoghq.com/