Requiring IMDSv2 on BYOC Runners
Require IMDSv2 is a per-runner setting on AWS BYOC custom runners. Where it sits in the runner definition, what it blocks, and how to enforce it fleet wide.
Overview
Require IMDSv2 is a documented field on a WarpBuild BYOC custom runner on AWS: switch it on and the EC2 instances launched for jobs on that runner class answer metadata requests only when the caller has opened a session and carries the token. A new custom runner supports both IMDS v1 and v2 by default, so requiring the session-token version is an explicit change you make per runner class, from the Update Runner page under Runner Specs or in the same section when you first create the runner (AWS BYOC configuration guide).
The security hardening guide carries the same toggle as its sixth control and ends its checklist with the line "IMDSv2 required on all runners". Nothing in the workflow file changes: the runs-on label stays as it is, and the instances WarpBuild launches in your AWS account come up with the metadata configuration that runner class specifies.
The setting sits alongside the rest of the runner definition, and two of those fields decide together how much a metadata read is worth to an attacker.
| Runner definition field | Section | Relationship to instance metadata |
|---|---|---|
| Require IMDSv2 | Runner Specs | Instances for this class answer metadata only on a token-carrying request |
| Instance Profile ARN | Custom runner configuration | Decides whether role credentials sit behind the endpoint at all |
| Instance types in priority order | Custom runner configuration | No effect. Every selected type launches with the same metadata configuration |
| Spot instances | Custom runner configuration | No effect. Spot and on-demand instances carry the setting the same way |
| Disk size, IOPS, throughput | Custom runner configuration | No effect |
Because the field lives on the runner class rather than on the stack, one BYOC stack can hold a hardened deploy runner and an unhardened test runner while a rollout is in progress. That is what makes a staged rollout possible without a second stack. Start from the BYOC on AWS overview if the stack itself does not exist yet.
Architecture
A BYOC job runs on a machine in your account, and the metadata endpoint on that machine is the shortest path from a build step to AWS credentials. The chain is short:
- GitHub queues a job for a
warp-custom-label. - The WarpBuild control plane launches an EC2 instance in your AWS account from that runner definition. Instances are named
warp-*and launch templatestmpl-warp-*, per the resource naming conventions. - Every step in the job runs as a process on that instance, so every step can reach
http://169.254.169.254. - The job finishes and the instance is terminated.
Step 3 is the one the setting governs. Under version 1 a plain GET to the credentials path returns JSON containing an access key id, a secret access key, and a session token for whatever role the instance profile carries. Under version 2 the caller has to send a PUT to /latest/api/token with a time-to-live header, read the token out of the response, and attach it as a header on every later GET (AWS instance metadata service documentation, checked on 2026-08-13).
That shape is what defeats server-side request forgery. The bug class in question is a build step that can be persuaded to fetch a URL somebody else controls: a test fixture with an injected host, a screenshot or preview renderer, a link checker, a dependency install script. Such a step issues a GET and cannot set request headers, so it fails at the first hop under version 2. AWS adds two more properties in the same direction. Token requests carrying X-Forwarded-For are rejected, which closes the misconfigured-proxy variant, and the token response carries a hop limit that stops it crossing off the instance to a container network or a downstream host.
The requirement stops forged requests. It does not stop a step that runs arbitrary code of its own, because that code can open the session itself. Two other controls bound that case: the least-privilege profile described on instance profiles for BYOC runners, which decides what the credentials reach, and the fresh instance per job, which ends the exposure window when the job ends.
Configuration
Enforce it one runner class at a time, and verify from AWS rather than from the dashboard.
- Choose the first class. Pick a runner that carries an Instance Profile ARN and runs a low daily job count, so a mistake costs a few reruns.
- Open the runner in the WarpBuild dashboard, go to Runner Specs, select Require IMDSv2, and save. New runners can be created with the field already set.
- Wait for the next job on that class. No fleet restart is involved, since the instance is provisioned when the job is queued.
- Read the enforced value back from AWS. The stack tags every resource it provisions, so a single query covers the fleet.
aws ec2 describe-instances \
--filters "Name=tag:warpbuild-managed-by,Values=warpbuild" \
"Name=instance-state-name,Values=running" \
--query "Reservations[].Instances[].{\
id:InstanceId,\
runner:Tags[?Key=='warpbuild-runner-id']|[0].Value,\
tokens:MetadataOptions.HttpTokens,\
hop:MetadataOptions.HttpPutResponseHopLimit}" \
--output tableHttpTokens reads required on an enforced class and optional on a class that still answers version 1. The warpbuild-managed-by, warpbuild-runner-id, and warpbuild-runner-labels tags are applied by the stack to every runner it launches (AWS BYOC configuration guide), which is what makes the query reliable as an audit rather than a spot check.
Once the first class is green, keep the query running on a schedule so a new runner created without the field is caught the week it appears:
name: imds-audit
on:
schedule:
- cron: "0 7 * * 1"
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
audit:
runs-on: warp-ubuntu-latest-x64-2x
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::111122223333:role/ci-readonly-ec2
aws-region: us-east-1
- name: Fail if any runner instance still accepts tokenless reads
run: |
optional=$(aws ec2 describe-instances \
--filters "Name=tag:warpbuild-managed-by,Values=warpbuild" \
"Name=instance-state-name,Values=running" \
--query "Reservations[].Instances[?MetadataOptions.HttpTokens=='optional'].InstanceId" \
--output text)
if [ -n "$optional" ]; then
echo "tokenless metadata still enabled on: $optional" >&2
exit 1
fiThe audit job itself runs on WarpBuild-hosted capacity at $0.004 per minute for warp-ubuntu-latest-x64-2x (pricing page, checked on 2026-08-13), so the weekly check costs a fraction of a cent and needs no BYOC capacity of its own.
Operations
Run three checks before enforcing the setting across every class, because the blast radius is every tool on the machine that reads metadata.
- Count the tokenless callers first. AWS publishes a per-instance CloudWatch metric,
MetadataNoToken, that counts metadata requests made without a session token. Watch it on the target class through a full week of workflows while version 1 still answers, since that is the only window in which a version 1 caller is visible as a number rather than as a failed job. - Check the container hop. A step that reads metadata from inside a container crosses an extra network hop, and the token response hop limit defaults to 1, so those steps need testing before the switch rather than after.
- Check custom images. Agents and CLIs baked into a custom AMI are the callers nobody remembers. Current AWS SDK and AWS CLI releases request a token automatically, and older vendor agents are the risk.
A rollout order that survives contact with a real fleet:
| Wave | Runner classes | Why this order | Signal to move on |
|---|---|---|---|
| 1 | One low-volume class with an instance profile | Real credentials behind the endpoint, small rerun cost | One day of jobs green, HttpTokens reads required |
| 2 | Remaining classes with instance profiles | Largest credential exposure per instance | MetadataNoToken flat at zero on those classes |
| 3 | Classes with no instance profile | Descriptive metadata only, so no urgency | Audit workflow green across the fleet |
| 4 | Every new runner at creation time | Stops the fleet drifting back | Checklist line ticked on the security checklist |
Rollback is the same field in reverse. Clear Require IMDSv2 on the class and the next job launches an instance that answers both versions again. No CloudFormation change, no stack upgrade, no support ticket.
The hardening carries no price change. BYOC Linux runners and BYOC Windows runners both bill $0.002 per runner minute in WarpBuild fees, and AWS bills your own account for the EC2 capacity (pricing page, checked on 2026-08-13).
BYOC runs on AWS, GCP, and Azure, so treat Require IMDSv2 as the AWS-specific control in a wider set of metadata endpoints; GCP and Azure expose their own endpoints with their own required headers. The same runner definitions carry Linux x64, Linux ARM64, macOS, and Windows runners, and the metadata question applies to the cloud-hosted Linux and Windows classes in that set.
FAQ
Does turning on Require IMDSv2 restart my existing runners?
No. The setting applies to the EC2 instances launched for jobs on that runner class, and every GitHub Actions job runs on a freshly provisioned instance that is terminated when the job finishes. Jobs already running keep the metadata configuration they launched with, and the next job queued to that class launches with the session-token requirement in place.
Which runner classes should be hardened first?
The classes that carry an Instance Profile ARN. Those are the instances whose metadata endpoint holds role credentials, so they are the ones a forged request has something to take. A class with no instance profile still answers descriptive metadata such as instance id and region, which is worth closing later in the rollout rather than first.
How do I prove the whole fleet is enforcing it?
Query AWS rather than the dashboard. WarpBuild tags every resource the stack provisions, so a describe-instances call filtered on the runner tags can report MetadataOptions.HttpTokens per running instance and fail a scheduled workflow when any instance still reports optional.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.