Who Runs Your Build, and What Can It Reach: Comparing Delivery Systems

Comparisons of delivery platforms usually turn into feature lists, and feature lists go stale in a quarter. Two questions do not: where does a job execute and who guarantees that execution is isolated, and whose authority does a running job carry? The second one decides what a compromised build can reach, and the four systems here answer it in four structurally different ways — which is the most useful thing to know about them and the least often stated.

Everything below is from each vendor’s own documentation, read in September 2026. Defaults, limits, and feature names change; the structural differences have been stable for years. Where a number appears, treat it as a reading from that date rather than a property of the product.

Where the build runs, and who guarantees isolation

Every one of these systems executes your build on a build agent — runner, agent, node, executor, depending on the vocabulary. The question that matters is whether the isolation of that agent is a property of the service or a job you have taken on.

GitHub states the hosted guarantee plainly: its hosted runners “execute code within ephemeral and clean isolated virtual machines, meaning there is no way to persistently compromise this environment, or otherwise gain access to more information than was placed in this environment during the bootstrap process.” And it is equally direct about what changes when you run your own: self-hosted runners “do not have guarantees around running in ephemeral clean virtual machines, and can be persistently compromised by untrusted code in a workflow.”

One precision before leaning on that sentence, because “hosted runner” is not one thing. The runner reference states that “with the exception of single-CPU runners, each GitHub-hosted runner is a new virtual machine (VM) hosted by GitHub,” and that “single-CPU runners are hosted in a container on a shared VM” — the ubuntu-slim label as of this reading. So the execution boundary depends on which label you selected, and a claim about your isolation needs to name the runner type rather than the category. This is not an argument that the container-based type is unsafe; it is an argument that the guarantee you are relying on, and the features available to you, are the ones documented for that specific type.

The consequence is stated as close to a prohibition as documentation gets — self-hosted runners “should almost never be used for public repositories on GitHub, because any user can open pull requests against the repository and compromise the environment.” Note that private repositories are not exempt: “anyone who can fork the repository and open a pull request (generally those with read access to the repository) are able to compromise the self-hosted runner environment, including gaining access to secrets and the GITHUB_TOKEN.”

Jenkins has no hosted mode, so isolation is entirely the operator’s design — and its documentation is unusually honest about both the threat model and its own default. On who really controls a build: “what exactly happens during a build is often controlled by people less trusted than a Jenkins administrator,” namely “Jenkins users with Job/Configure permission,” “build script authors (pom.xml, Makefile, etc.),” and “code authors (for example test code executed during a build).” All of them “have some control over commands executed during a build.”

And on the default: “out of the box, Jenkins is set up to run builds on the built-in node. This is to make it easier to get started with Jenkins, but is inadvisable longer term: any builds running on the built-in node have the same level of access to the controller file system as the Jenkins process.” A team that installed Jenkins and never changed that setting is running untrusted code with the controller’s own file access — not because anything was misconfigured, but because nothing was configured.

So the first axis is not “hosted versus self-hosted” as a preference. It is who holds the obligation, and the test is a question you can answer today: if a pull request from outside your team runs arbitrary code on your agent, what does that code reach, and who was supposed to prevent it?

Whose authority does a job carry

This is the comparison worth doing carefully, because all four give a job an identity automatically, all four call it a token, and the identity comes from a different place in each. That origin determines the blast radius of a compromised job.

SystemWhere the job’s authority comes fromDefault reach
GitHub ActionsA GitHub App installation. “When you enable GitHub Actions, GitHub installs a GitHub App on your repository. The GITHUB_TOKEN secret is a GitHub App installation access token”“The token’s permissions are limited to the repository that contains your workflow,” with permissions declarable in the workflow
GitLab CI/CDThe person who triggered the pipeline. The job token “receives the same access level as the user that triggered the pipeline, but has access to fewer resources than a personal access token”That user’s access, narrowed; cross-project use gated because “by default, the job token’s group or project must be added to the target project’s allowlist”
Azure PipelinesA build service account plus a scope setting. Permissions are “derived from (a) job authorization scope and (b) the permissions you set on project or collection build service account”Depends on the scope chosen: collection lets pipelines “access all repositories in the collection or organization,” project restricts to “repositories that are in the same project as the pipeline”
JenkinsWhatever credentials an operator has stored and the job references. There is no equivalent automatic per-job identity in the same senseWhatever those credentials grant — which is why the controller’s own trust model, above, is load-bearing

Read the second row against the first and the practical difference appears — a difference in the automatic token, which is what this table compares, and not in what a compromised job can reach. On GitHub that token is an app scoped to one repository, so the token is bounded by that repository and by the permissions the workflow declared. On GitLab the equivalent identity is derived from a human, so the same pipeline’s token carries more authority when a maintainer triggers it than when someone with narrower access does — the token’s reach is that person’s, narrowed but not repository-bounded. Neither is worse; they fail differently, and a team migrating between them carries assumptions that no longer hold.

Keep that scoped to the token, because a job is rarely holding only the token. GitHub’s own hardening guidance describes the job-level reality: “a compromise of a single action within a workflow can be very significant, as that compromised action would have access to all secrets configured on your repository, and may be able to use the GITHUB_TOKEN to write to the repository.” The default identity is one item in that inventory, and the section after the next returns to the full one.

Both are short-lived, which limits exposure after the fact. GitLab’s token “is valid only while the job is running. After the job finishes, the token access is revoked and you cannot use the token anymore.” GitHub’s “expires when the job finishes or after its effective maximum lifetime” — with the maximum tied to the runner: hosted jobs cap at six hours, while for self-hosted runners the job may run up to five days but the token “can only be refreshed for up to 24 hours.”

Azure’s model is the one most likely to be wrong by default in a large organization, because the scope is a setting rather than a boundary: choosing collection scope means pipelines can “access all repositories in the collection or organization.” That is occasionally what you want and usually more than a given pipeline needs, which makes it the first thing to check in an inherited estate — the least privilege question asked of the delivery system rather than of the application.

But do not read project scope as a ceiling. The documentation is explicit that permissions come from two places — “(a) job authorization scope and (b) the permissions you set on project or collection build service account” — and the scope selects the identity rather than fixing its reach. Because the build service account “is a user in your organization or collection, you can add this account explicitly to any resource,” and the docs walk through granting a project-scoped build identity access to a different project and to a repository inside it. So auditing the scope setting answers half the question; the other half is what has been granted to that identity, including cross-project grants made by someone else in a project you do not administer.

One GitHub detail deserves separate mention because it defeats a reasonable assumption: “an action can access the GITHUB_TOKEN through the github.token context even if the workflow does not explicitly pass the GITHUB_TOKEN to the action.” Not passing the token is not the same as withholding it. Restricting what the token can do is the control that works, which is why the guidance is to “make sure that actions only have the minimum access they require by limiting the permissions granted to the GITHUB_TOKEN.”

Now the correction that the table above invites and does not supply. “The token’s permissions are limited to the repository that contains your workflow” is a statement about that token, and it is not a statement about what a compromised job reaches. A job’s blast radius is the union of every credential and identity it can obtain.

  • Every secret configured for the repository — and note who that already implies: “any user with write access to your repository has read access to all secrets configured in your repository.”
  • Cloud roles assumed through federated identity. A job that exchanges an identity token for a cloud role reaches whatever that role permits, which is frequently far more than one repository.
  • Other tokens stored as secrets — a personal access token, a registry credential, a deployment key — each with its own, unrelated scope.
  • Whatever the agent itself can reach on the network, which for a self-hosted runner inside a corporate network is a question about the network rather than about the token.

So compare the four systems on their default token as the table does, and then audit each pipeline on the union above — the same standard for all of them, because the differences between the products stop mattering once a job holds a cloud role. Secrets beyond the automatic identity are a separate discipline in all four and the rules do not change with the product; see secrets management. What does change with the product is what the automatic identity already grants before you add anything.

Reuse is a governance mechanism

All four support pipeline as code — definitions living in the repository, versioned with what they build. All four also have some form of pipeline template so that fifty repositories do not each maintain their own copy: GitHub has reusable workflows and composite actions, GitLab has includes and extends, Azure Pipelines has templates, Jenkins has shared libraries.

Comparing their syntax is not worth the page. What is worth deciding is what you intend the mechanism to do, because these features are how a platform team either enables or controls — and the two need different designs.

  • As a convenience, a template is a starting point teams copy and diverge from. Cheap, and it means a fix has to be applied fifty times.
  • As a standard, a template is referenced rather than copied, so an improvement reaches everyone. This is the version that pays back, and it requires the template to be versioned and owned like any other dependency.
  • As a control, a template is the only sanctioned path to something — a deployment credential, a production environment — and bypassing it is prevented rather than discouraged. This is where a delivery system becomes a place policy is enforced, and where the questions get organizational rather than technical.

A team that has not decided which of the three it is building usually gets the first while believing it has the third. And whichever it is, the template is production code with consumers: it needs a version, an owner, a test, and a deprecation path, exactly as branch and merge discipline applies to anything else many people depend on.

Artifacts, and where the boundary sits

All four can produce and store outputs, and all four distinguish — with different names and different retention behaviour — between short-lived job outputs passed between stages and durable packages you deploy from. The design question is the same regardless of product and is settled in the pipeline rather than by the tool: one build artifact, identified by something a rebuild cannot reissue, promoted unchanged through environments.

Two product-shaped questions are worth asking of any of them, because they are the ones that hurt later. How long are artifacts retained by default, and is that long enough to roll back to? Retention is a recovery decision, and defaults are set for storage cost. And is the thing you deploy from the same object the tests examined, or does the deployment step fetch and assemble something? The second question is really about your configuration rather than the tool, but tools differ in how easy they make the wrong answer — the mechanics are covered under environment promotion.

Where builds must be verifiable by someone outside the team, the relevant property belongs to how you build rather than to which system runs it — see reproducible build.

The automation itself has to be operated

The cost that gets left out of comparisons is that a delivery system is production infrastructure. When it is down, nobody ships — including the fix for whatever is currently broken. Three burdens differ meaningfully between these systems, and none of them appears on a feature matrix.

  • Who patches the agents. With hosted runners this is the vendor’s problem and the isolation guarantee quoted earlier is theirs to keep. With self-managed agents it is yours, and the documented consequence of getting it wrong is persistent compromise rather than an outage.
  • The extension surface. Every one of these systems is extended by third-party code that runs inside your build. That code is a dependency with your credentials in scope, and the question to ask is not how many extensions exist but who reviews the ones you use and how you would find out one had changed.
  • Upgrades and configuration decay. A hosted service upgrades under you, which removes work and occasionally changes behaviour you depended on. A self-managed one upgrades when you do it. Jenkins’ documentation names the decay directly: “most Jenkins environments grow over time requiring their trust models to evolve as the environment grows. Please consider scheduling regular ‘check-ups’ to review whether any disabled security settings should be re-enabled.” That sentence generalizes to all four — every temporary exception becomes permanent unless someone has a standing reason to look.

Two things worth having regardless of product. An audit log that answers who changed a pipeline definition, who approved a deployment, and which identity performed it — because the delivery system is a path to production and will be asked about in any security review. And an honest accounting of operating cost, where the line items people forget are agent capacity, the people who maintain templates and plugins, and the queue time engineers spend waiting; the framing is total cost of ownership rather than licence price.

Choosing, and what not to choose on

Four criteria decide this in practice, roughly in this order.

  1. Where your source already lives. The integration you get for free by using the delivery system attached to your forge is substantial, and the coordination cost of splitting them is paid on every change.
  2. Whether the permission model matches your organization. An identity derived from the triggering user and an identity scoped to a repository imply different review processes and different answers to “what can this pipeline reach.” Pick the one you can reason about, then configure it.
  3. Whether you can accept hosted execution. If regulation or network placement requires your own agents, you have taken on the isolation obligation and should plan it rather than discover it.
  4. Who will operate it. A self-managed system with no owner degrades quietly, and the degradation is in the security settings rather than in the uptime.

Three things are poor bases for the decision. Feature counts — every one of these can express a pipeline that builds once, verifies, and promotes, which is what continuous integration and continuous delivery actually require. Benchmarks — build duration is dominated by your build and your agent sizing, not by the orchestrator. And syntax preference, which is the most-discussed and least consequential difference: teams adapt to YAML dialects in a week and live with a permission model for years.

If a migration is on the table, the honest framing is that you are trading one set of defaults for another, and the work is mostly in the two structural areas above rather than in translating pipeline files. Record why you chose what you chose — an architecture decision record is the cheapest way to save the next team from relitigating it — and include what you decided not to optimize for.

A closing caution about this comparison specifically. Product documentation changes, and the descriptions here were read in September 2026; defaults in particular are worth re-checking rather than trusted from any article, including this one. What is unlikely to change soon is the shape of the question: a build job runs somewhere, carries an identity from somewhere, and can reach whatever that identity can reach. Knowing those three answers for your own system is worth more than knowing all four products.

References: GitHub Docs, Secure use reference; GitHub Docs, GITHUB_TOKEN; GitHub Docs, Use GITHUB_TOKEN for authentication in workflows; GitLab Docs, CI/CD job token; Microsoft Learn, Access repositories, artifacts, and other resources; GitHub Docs, GitHub-hosted runners reference; Jenkins Handbook, Controller Isolation. All read September 2026.


Discover more from Insightful Data Lab

Subscribe to get the latest posts sent to your email.

Similar Posts

Questions, corrections, or additional insights?

This site uses Akismet to reduce spam. Learn how your comment data is processed.