Skip to content

    Back to articles

    6 min read

    Give workflows an access model before secrets get finer-grained

    Start with what the workflow does

    A workflow name is not an access policy. “CI” might run tests on an external pull request, build a release artifact, or deploy to production; those jobs should not inherit the same credentials just because they share a repository or a reusable workflow.

    I would classify workflows by purpose before deciding which secrets or tokens they need. Tests should usually prove something about the code without gaining authority to publish or deploy. Builds may need access to dependencies or a package registry, but that does not make them release jobs. Scans need to read the code and report findings. Releases may need permission to publish an artifact. Deployments need access to a target environment, and production access should be treated as a separate trust decision.

    That classification is the policy model. It gives the team a way to answer, for every workflow: what is this allowed to do, what credential would make that possible, and what condition must be true before the credential is available? I would write those answers down before changing secret settings. Otherwise, a new control can make an existing convention look more rigorous without correcting the underlying assumptions.

    This matters now because GitHub’s 2026 Actions security roadmap describes scoped secrets tied to execution contexts such as branches, environments, workflow identities or paths, and trusted reusable workflows. The architectural opportunity is not just to move secrets into a more precise setting. It is to decide what those boundaries should mean for the work we run.

    Make current access resemble the intended boundary

    The current model already offers some useful boundaries, but they do not automatically describe a team’s workflow policy. GitHub documents repository- and organization-level secrets, along with environment secrets. Environment secrets are available to jobs that reference the environment, and configured protection rules must pass before the job can access them. That makes environments useful for controlling deployment credentials, but an environment name alone does not decide whether a job belongs there or whether its permissions are appropriate. See GitHub’s documentation on managing environments.

    Reusable workflows need particular care. A caller can pass specific secrets to a called workflow, or use inherit to pass all secrets it can access. GitHub’s workflow syntax reference for reusable-workflow secrets describes both options. I prefer explicit passing: the call site then shows which credential crosses the boundary. inherit is a convenience, not a policy statement. If the called workflow only needs a publishing credential, inheriting every secret available to the caller makes the boundary harder to reason about.

    I would begin by inventorying secrets and their consumers, then map each consumer to a purpose. A small table can make the intended policy reviewable:

    Workflow purposeTypical access to allowAccess to keep out
    TestsRead source and run checksPublish or deploy credentials
    BuildsRead required inputs and produce artifactsProduction credentials
    ScansRead source and report findingsRelease and deployment credentials
    ReleasesPublish the approved artifactUnrelated environment credentials
    DeploymentsAccess the intended target environmentCredentials for other environments

    These are starting boundaries, not a universal permission matrix. A test may need a narrowly scoped service credential, for example; the point is to make that exception deliberate and visible. For each exception, I would record why the workflow needs it and whether it can be replaced with a narrower token, an environment secret, or a short-lived identity.

    Treat workflow code as one layer, not the whole policy

    A workflow can request narrow permissions and pass only selected secrets, but its YAML is still code that can change. I would use workflow configuration to express the job’s needs, then use repository and organization settings, environment protection, and review practices to reinforce the boundary. The roadmap itself frames the direction as policy beyond YAML alone; GitHub’s roadmap discussion of execution protections and secret governance is a useful reminder not to equate a workflow convention with an enforceable organization-wide rule.

    For example, a test workflow can make its token permissions explicit and keep release secrets out of the job:

    name: Tests
    
    on:
      pull_request:
    
    permissions:
      contents: read
    
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - run: dotnet test

    The example is intentionally limited: it tests code and does not request publishing or deployment access. In a real repository, I would also review which actions and scripts run in the job, because a secret exposed to a job is available to code running there, not just to the line that appears to use it. GitHub’s secure use reference for Actions covers the risks of untrusted code and recommends auditing how secrets are handled.

    I would also avoid using a long-lived secret when a workflow can authenticate through a short-lived identity. GitHub documents using OpenID Connect to authenticate to supported cloud providers without storing those credentials as long-lived secrets in Using secrets in GitHub Actions. That does not remove the need for an access model: the identity still needs a defined audience, trust conditions, and permissions at the target. It changes the credential mechanism, not the question of which workflow should be trusted.

    Carry the model into finer-grained controls

    When finer-grained secret controls arrive, I would translate the existing purpose model into them rather than start by clicking through configuration. A test workflow should be scoped to test needs; a release workflow should receive publishing access only when the release conditions are satisfied; deployment credentials should follow the intended environment and approval boundary. The new feature should encode decisions the team has already made.

    That translation also exposes awkward cases. If one reusable workflow both builds and deploys, does it have two purposes or has it accumulated responsibilities that should be split? If a release path uses the same secret as routine builds, is that because the systems require it or because the distinction was never designed? If a workflow is authorized by its file path, who can change that file, and what review protects the change? A finer control can make the answer enforceable, but it cannot supply the answer.

    I would keep the policy legible in three places: a short purpose-to-access map, workflow files that request only the permissions they need, and platform controls that enforce boundaries outside an individual job’s convention. Then I would test changes against the map: a new workflow should declare its purpose, a new secret should name its consumers, and a reusable workflow should receive only what its contract requires.

    The useful preparation is not guessing the exact shape of a future settings page. It is agreeing now that tests, builds, scans, releases, and deployments are different kinds of work, and that their access should differ accordingly. When the platform offers finer-grained controls, I want to bring it a policy to implement—not ask its configuration screen to invent one for me.

    Drafted with AI assistance, reviewed and edited by Ali.