Security: what runs in your account, and what RunsOn receives
Where your code runs, what RunsOn receives, how jobs are kept apart, what the GitHub App can do, and which settings to change.
RunsOn runs in your AWS account. Your code, your secrets and your logs are never sent to RunsOn.
- One job, one machine. Every job runs on an EC2 instance that no other job has used. The instance is terminated when the job ends.
- Nothing shared with other customers. The control plane and the runners are yours. RunsOn has no access to your AWS account or your GitHub organization.
- Two requests to RunsOn. A license check, and one event per job. Neither contains code, secrets or logs.
This holds for both Flex and Fleet. They differ in how jobs reach your account.
What RunsOn receives#
Two HTTPS requests go from your account to runs-on.com. RunsOn receives nothing else: no crash reports, no update checks, and no connection from RunsOn into your account.
License check#
The control plane sends it 90 seconds after it starts, then every 72 hours.
{ "org": "your-github-org", "region": "us-east-1", "license_key": "...", "app_version": "v3.4.0"}org is your GitHub organization, or your enterprise slug for Fleet in enterprise mode.
Job event#
The agent on the runner sends it once, when the job starts. RunsOn uses it to count jobs for licensing and to see which versions and features are in use. It cannot be turned off. These are all its fields:
org: the GitHub organization that owns the repository.license_key_hash: a SHA-256 hash of your license key.stack_name,environment: the names you gave the stack and its environment.product,deployment_method:flexorfleet, and how it was deployed, such ascloudformation.version,region: the RunsOn version and the AWS region.platform,architecture: the operating system and CPU architecture.instance_type,instance_lifecycle: the EC2 instance type, and spot or on-demand.timings: the name and time of each boot step.is_private,is_ghes: whether the runner has no public IP, and whether you use GitHub Enterprise Server.networking_stack: how the network was set up (embedded,external, or a stack name).integrations_active,extras: the integrations and extras that are enabled.pool_name,pool_standby_type: for a warm pool runner, the pool’s name, andhotorstopped.features, from v3.5: the image OS; on or off for SSH, lifecycle hooks, OpenTelemetry and the cache broker; the sticky disk’s size, type, IOPS and throughput.sent_at: when it was sent.
stack_name, environment and pool_name are names you chose.
The event never contains a repository, workflow or job name, a run ID, your AWS account ID, code, secrets, logs, or files from the runner.
If runs-on.com were compromised, an attacker would get this data. Both requests go out from your account, so there is nothing to follow back in.
See also the Privacy Notice and the subprocessors.
How jobs are kept apart#
Every job gets:
- Its own instance. No other job has run on it, and it is terminated when the job ends. Warm pool instances are started ahead of time, and still run one job each.
- A single-use registration. The runner joins GitHub with a just-in-time configuration that is valid for one job. Only that instance can read it, and it is deleted after a day.
- No way to the GitHub credentials. The runner’s IAM role cannot read Secrets Manager. Only the control plane can.
- No inbound traffic. The runner’s security group has no inbound rule, unless you allow SSH.
- IMDSv2 only. Instance metadata requires a session token.
Several repositories on one stack#
Repositories that use the same stack share its cache bucket and its runner IAM role. Two things keep them apart:
- Turn on cache isolation. We recommend it on every stack. Each job then gets cache credentials for its own repository and branch, with the same rules as GitHub’s own cache: a job reads and writes its branch, and can read the default branch. Set
EnableCacheIsolationfor Flex, orenable_cache_isolationfor Fleet. Each repository starts with an empty cache once, when you turn it on. - Give untrusted code its own stack. Cache isolation covers the GitHub Actions cache:
actions/cache, the setup actions and Docker’stype=ghabackend. Tools that write to S3 with the runner role, such as sccache, stay shared across the stack. For public repositories, use a separate environment.
How jobs reach your account#
This is the one place where Flex and Fleet differ.
| Flex | Fleet | |
|---|---|---|
| How jobs arrive | GitHub posts webhooks to your stack | fleetd pulls them from GitHub |
| Public endpoint | API Gateway and Lambda | None |
| Repositories that can use runners | Those the GitHub App is installed on | Those in the GitHub runner group |
| What a workflow can choose | Instance type, image, disk and more, with job labels | A runner fleet, by name |
Flex
- The endpoint is public, because GitHub has to reach it. The control plane checks GitHub’s signature on every webhook before it acts on it.
- Limit it to GitHub’s IP ranges with
EnableWAF=true. This works with github.com only, not with GHE.com or GitHub Enterprise Server. The stack rejectsEnableWAF=truewhenGithubEnterpriseUrlis set. - Remove the setup pages once RunsOn is installed, with
EnableAdminRoutes=false. They are served from the same endpoint. - Labels stay inside the stack’s settings. A workflow can ask for a bigger instance or another image. It cannot turn on SSH when
SSHAllowed=false, or get a public runner whenPrivate=only.
Fleet
- There is no endpoint to protect.
fleetdand the runners only make outbound connections. - Runner groups decide who gets in. A GitHub runner group lists the repositories that can use a runner fleet. Use separate groups for different access rules, and a separate stack for different IAM permissions or networks.
- Runner settings are out of reach of workflows. Instance type, image and disk are set in Terraform. A workflow only names the runner fleet.
What the GitHub App can do#
RunsOn talks to GitHub through an app you create in your own organization. The app is private. Its key is stored in AWS Secrets Manager in your account, and RunsOn never sees it.
Flex
| Permission | Level | Access |
|---|---|---|
| Administration | Repository | Read and write |
| Actions | Repository | Read and write |
| Single file | Repository | Read and write, .github/runs-on.yml only |
| Metadata | Repository | Read |
| Deployments | Repository | Read |
| Members | Organization | Read |
- Five of the six are repository permissions. They apply only to the repositories the app is installed on. The app has no organization administration permission. Members only lets it read who is in the organization.
- It cannot read your code. The app has no Contents permission. The one file it can read or write is
.github/runs-on.yml, the repository’s RunsOn configuration. - Administration is the broad one. This is repository administration, which GitHub requires to register a runner on a repository. Limit who can read the app’s secret in Secrets Manager, and install the app only on the repositories that use RunsOn.
Fleet
- Organization mode: a GitHub App with two permissions: organization self-hosted runners (read and write), and Actions (read and write), which Fleet uses to re-run jobs after a spot interruption. It cannot read your code.
- Enterprise mode: a classic personal access token with the
manage_runners:enterprisescope. It is a token only because GitHub does not yet let a GitHub App manage enterprise runners. Re-running jobs after a spot interruption also needs thereposcope, which gives the token access to repositories. - Both are Terraform inputs, so they are also in your Terraform state. Keep the state encrypted, and limit who can read it.
See the Fleet installation guide.
What you have to trust#
Four pieces of RunsOn software run in your account.
| Component | Where it comes from | How to check or replace it |
|---|---|---|
| Control plane | A Docker image from RunsOn’s public ECR registry, run on ECS Fargate | Review the source, or build your own image and run it from a private ECR repository |
| Agent | A binary that runners fetch from your own S3 bucket | Review the source, or rebuild it |
| Bootstrap | A small binary that each runner downloads at boot from the runs-on/bootstrap ↗ releases | The source is public |
| Runner images | Public AMIs built from the public runner-images-for-aws ↗ repository | Build from the same templates, or use your own custom AMIs |
- Source access. The CloudFormation template and the Terraform modules are public. The control plane and the agent are proprietary: their full source comes with the Sponsorship license, for your own security review.
- Image updates. RunsOn rebuilds its AMIs within 15 days of each GitHub release, and scans the published AMIs every night with Amazon Inspector.
- Upgrades are yours. Your stack pins the control plane version. It changes when you upgrade, and not before.
Settings to review#
Out of the box:
- On: webhook signature checks, Session Manager access to runners, and the Flex setup pages.
- Off: SSH, WAF, cache isolation, sticky-disk isolation and disk encryption.
These are the settings to change:
Flex
EnableCacheIsolation=truescopes the Actions cache to the repository and branch. Recommended on every stack.EnableWAF=truelimits webhooks to GitHub’s IP ranges. github.com only.EnableAdminRoutes=falseremoves the setup pages. Set it once RunsOn is installed.EnableStickyDiskIsolation=trueremoves the EBS permissions from the runner role.Private=onlykeeps runners out of public subnets.EncryptEbs=trueencrypts runner disks.SSMAllowed=falseturns off Session Manager access to runners.SSHCidrRangelimits where SSH can come from, if you allow SSH.
These are the CloudFormation parameters. With Terraform, use the matching inputs in the Flex Terraform reference.
Fleet
enable_cache_isolation = truescopes the Actions cache to the repository and branch. Recommended on every stack.enable_stickydisk_isolation = trueremoves the EBS permissions from the runner role.private_mode = "only"keepsfleetdand the runners out of public subnets.ssm_allowed = falseturns off Session Manager access to runners.ssh_cidr_rangelimits where SSH can come from, if you allow SSH.- To encrypt runner disks, turn on EBS encryption by default in the AWS account. Fleet has no setting for it.
Fleet has no public endpoint, so there is no WAF and there are no setup pages to remove.
By default, Flex control-plane SQS queues use SQS-managed server-side encryption. The SNS alerts topic has no server-side encryption and relies on SNS’s own disk encryption. Starting with v3.5.0, you can encrypt the queues and the SNS alerts topic with your own KMS key. Set messaging_kms_key_arn in the Terraform module (Flex or Fleet; Fleet has only the topic). It takes a key ARN, not an alias.
The module grants the RunsOn roles kms:Decrypt and kms:GenerateDataKey on the key through IAM. The key policy must therefore delegate to IAM in your account. Without that, the RunsOn roles can’t use the key: Flex can’t send or receive queue messages, and alerts can’t be published.
The key policy must also allow the AWS services that publish to the queues and the topic:
{ "Sid": "AllowRunsOnServicePublishers", "Effect": "Allow", "Principal": { "Service": ["events.amazonaws.com", "budgets.amazonaws.com"] }, "Action": ["kms:GenerateDataKey", "kms:Decrypt"], "Resource": "*", "Condition": { "StringEquals": { "aws:SourceAccount": "<account-id>" } }}events.amazonaws.comis needed on Flex only. Without it, Spot interruption and instance-state events are dropped. Fleet has no SQS queues, so leave it out there.budgets.amazonaws.comis needed on Flex and Fleet wheneverapp_budget_daily_usdis above 0. It defaults to 10 on both. Without it, budget alerts don’t reach the topic.
The AWS-managed alias/aws/sqs and alias/aws/sns keys can’t be used, because their key policies don’t allow these services.
Two notes on the defaults:
- Runner disks are not encrypted by default, because encryption adds about 10 seconds to every boot.
- Runners are in public subnets by default, with a public IP and no inbound rule. Networking covers private subnets and static egress IPs.
RunsOn does not change what a job’s GITHUB_TOKEN can do. Give each workflow only the permissions: it needs.
To watch or restrict what a job does while it runs (network egress, file changes), see the StepSecurity integration.
Report a vulnerability#
Email security@runs-on.com. The vulnerability disclosure page lists what to include, the rules for testing and the safe harbor.
For a security questionnaire, or a question about a specific data flow, write to ops@runs-on.com.