Skip to content

    Back to articles

    6 min read

    Execution policy belongs outside the workflow

    A shared workflow is still workflow code

    A reusable workflow is a good way to stop teams copying pipeline logic. It gives repositories a maintained path for common work: build, test, package, and perhaps deploy. I have helped design pipelines reused across many repositories, and I would still choose reuse over asking every team to maintain its own copy.

    But reuse and enforcement solve different problems. A shared workflow is a building block that repository workflow code can choose to call. If the security boundary is the workflow file itself, a repository that can change that file may also change whether it calls the shared workflow, what it passes to it, and what else runs around it. Requiring every team to use the right YAML is not the same as a control outside that YAML.

    GitHub’s 2026 Actions security roadmap makes that distinction explicit: execution protections are intended to govern whether a workflow runs, rather than relying on the workflow file alone. The feature entered public preview in June 2026 and became generally available on September 17, 2026, according to GitHub’s public-preview announcement and general-availability announcement.

    That is the architectural shift worth paying attention to. A reusable workflow standardizes how an approved run is implemented. An execution policy decides whether the run is allowed to begin.

    Put the decision before the code

    Workflow execution protections let administrators define allowlists for who can trigger workflows and which events can start them. GitHub evaluates those protections before a run, and the generally available version can target individual workflow files. The GitHub documentation on controlling workflow execution describes how policies can be layered across enterprise, organization, and repository levels.

    That moves part of the trust decision out of the file under review. A workflow can say it should run on a pull request, but that declaration is not, by itself, an organization-level guarantee that every actor or event is acceptable. The protection is useful precisely because a policy owner can set limits that a repository’s workflow author cannot quietly remove by editing YAML.

    For example, this repository workflow describes a normal pull-request build:

    name: Build and test
    
    on:
      pull_request:
        branches: [main]
    
    permissions:
      contents: read
    
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - run: dotnet test

    The snippet is workflow configuration, not an execution protection. It expresses the repository’s intent: run tests for pull requests and grant the token read access to repository contents. An administrator’s execution policy is a separate decision about whether that workflow may run for a particular actor and event. GitHub’s workflow syntax reference documents workflow-level permissions; its execution protection guidance describes the distinct policy layer.

    That separation matters most when the workflow file is part of the change being evaluated. A pull request can alter workflow code as well as application code. If I expect the workflow itself to enforce the only restriction, I am trusting code that the policy is meant to constrain. Moving the run/no-run decision outside the file makes that particular bypass harder: the workflow cannot author its way around a centrally managed actor or event rule.

    Policy is not a code auditor

    I would not mistake this boundary for a verdict on everything that happens after execution starts. An allowed workflow can still be badly written, call an unsafe action, expose a credential to untrusted input, or request more token access than its job needs. Execution protections govern who and what may trigger a run; they do not prove that the workflow contents are safe. GitHub’s secure use reference separately recommends minimizing GITHUB_TOKEN permissions and reviewing action code.

    That means I would keep two questions separate in a design review:

    • Is this actor and event allowed to start this workflow?
    • Given that it starts, what code, credentials, permissions, and runner environment can it reach?

    The first is a good fit for an external policy boundary. The second still depends on workflow design, repository protections, credential scoping, and review. A reusable workflow can help with the second question by giving teams a safer implementation to call, but it cannot replace the first. Nor does centralizing the workflow automatically make every caller safe: inputs, permissions, and surrounding jobs still deserve scrutiny.

    I prefer this division because it makes ownership legible. Platform or security owners set the limits on execution. Product teams own the workflow changes within those limits. Shared pipeline maintainers provide well-tested building blocks. Those are related responsibilities, but they should not be collapsed into a single large reusable YAML file that is expected to act as both a library and a policy engine.

    Central control has a shipping cost

    A policy boundary is not free. If administrators make every workflow depend on a centrally approved actor list and event list, an innocent new development path can be blocked along with a dangerous one. A team may need to test a branch, handle a release event, or use a manual run for a legitimate operational task. A rule that does not account for those needs creates friction, workarounds, and pressure to weaken the policy globally.

    I would therefore start with the workflows where the consequences of an unauthorized run are meaningful: deployment, release, or workflows with access to sensitive environments. Keep ordinary contribution checks as open as their risk allows. GitHub’s general-availability announcement describes targeting protections to specific workflow files, which supports this kind of distinction inside a repository.

    Where evaluate mode is available, I would use it to learn which existing runs a proposed policy would affect before enforcing it; the policy documentation describes evaluation as a rollout option. That is not an argument for observing indefinitely. It is a way to find mismatches between the written rule and how teams actually ship, then make a deliberate choice about exceptions and enforcement.

    I would also keep the policy small enough to explain. Broad rules that make sense at the enterprise boundary should be non-negotiable. More specific constraints can live closer to the repositories that understand their release paths, provided lower-level policy adds restrictions rather than weakening the boundary. GitHub recommends layering protections across account levels in its guidance for workflow execution policies. The governance principle is sound even without turning every special case into a global rule: centralize the minimum that must not vary, and let teams control the rest.

    The useful question is not whether shared YAML or centralized policy is better. I want both, doing different jobs. Reusable workflows reduce duplication and give teams a supported implementation path. Execution protections put the permission to run outside the code being constrained. I would not treat the former as a substitute for the latter.

    Drafted with AI assistance, reviewed and edited by Ali.