Skip to main content
This page is written to be read adversarially. It says what the design protects, and then — in its own section, not buried — what it does not.

Trust boundaries

What never crosses a boundary. Your source code, your Actions secrets, your artifacts and your job logs never reach the control plane — the runner talks to GitHub directly. Your cloud credential never reaches a runner machine. Airship holds no SSH key for your machines, and there is no inbound network path to them.

What Airship stores

Never stored: source code, artifacts, build output, Actions secrets or variables, job logs, any cloud credential in plaintext anywhere including logs, or cache contents — those live in your bucket, in your account. Validation errors are shaped so they cannot echo the value that failed: the API’s error details carry a location and a message only.

The cloud credential

The sharpest edge of the design, so it gets the most rules. What the credential can do is your choice, and it should be the minimum. The least-privilege policies are on AWS, Google Cloud and Azure. On AWS the terminate permission can be conditioned on Airship’s own tag, so the credential cannot destroy anything Airship did not create.

Job isolation

The GitHub registration

Who may spend your money

Control plane surfaces

Sign-in is GitHub OAuth only. There is no password to leak and no local account to brute-force.

The boot script

The script runs as root. Anything interpolated into it would be remote code execution on your machine if it could break its quoting, so nothing is interpolated without validation: the runner version, the registration, the control plane URL, the cache token and the Windows paths and account names all match strict patterns, and env_vars travels base64 and is decoded on the machine, so nothing in it is ever shell or PowerShell. Variable names starting with GITHUB_, RUNNER_ or ACTIONS_ are refused: a config file must not be able to change how the runner talks to GitHub.

Cache security

Known exposures

These follow from the model. None is a surprise, and each has a mitigation.
Anyone in your cloud account who can read an instance’s metadata or user data can read the configuration until the runner registers. The window is seconds, the configuration is single-use, and using it would steal one job rather than grant access to anything.provision_via: pull_from_api avoids it entirely, at the cost of putting an API token in your image.
There is no per-branch or per-repository isolation: a pull request’s job can write an entry a main-branch job later restores, and one repository can restore another’s keys.Keep public repositories with fork pull requests off cache runners, or make their keys unguessable. Airship enforces the bucket, not the key space.
Including an expensive one. It is the same authority they have over workflow content, and max_runners is the bound. If that is not acceptable for a repository, do not activate it.
If a definition attaches an instance profile or service account, every step of every job on that runner can use it — including a fork pull request’s code on a public repository activated for public access. Treat it exactly as you would a repository secret.
There is no image allowlist. Architecture mismatches are refused, and so are images a publisher has deprecated, but trustworthiness is not something Airship can judge.
Isolation then rests on the installation tag: listing filters on it, and terminating reads the machine first and refuses one without the expected tag. It holds. A dedicated account, project or subscription per installation is the stronger boundary and what we recommend — on Azure in particular, where installations would otherwise share the resource group, networks and security groups.
By design: it is the only thing that can open the sealed credentials. Recovery is reconnecting each cloud. If you run your own control plane, back that key up as you would a root key and rotate it with the two-key procedure rather than by replacement.

Tenancy

The tenant is the GitHub App installation — one organization or user account.
  • Every database query is scoped by installation ID.
  • Every machine carries the installation as a tag or label, and every list filters on it.
  • Every teardown reads the machine first and refuses when the tag is not the installation’s, so an ID recorded wrongly — or reused by the cloud for someone else’s machine — can never destroy the wrong thing.
  • Cache buckets are named from a hash of the installation ID and owner-checked on every call.
  • API tokens are scoped to one installation; runner tokens to one runner.

Straight answers

Job metadata in the control plane’s database. Job content — code, secrets, logs, artifacts, caches — stays in GitHub and in your cloud account, in the region your airship.yaml names.
Whoever administers the control plane deployment, as with any hosted service. If that is not acceptable, run the control plane yourself: the same code, your infrastructure, your sealing key.
Yes — a credential you scope and can revoke at any time, held sealed and opened per call. The least-privilege policies are the floor it can operate on. On AWS you can condition its terminate permission so it can only ever destroy Airship’s own machines.
Only what the GitHub App’s permissions allow: airship.yaml, airship.global.yaml, access.yaml, and job and runner metadata. Contents is a read permission; write and pull-request permissions are optional and used only by the access-control API, when you ask it to open a pull request.
We do not claim any certification on this page. Ask us directly and we will tell you exactly where we are rather than point at a badge.