ghalint

repository·main·Indexed 42 days ago

https://github.com/suzuki-shunsuke/ghalint

A command-line linter for GitHub Actions workflows and action.yaml files designed to enforce security best practices. It validates workflows and actions against security policies, such as ensuring the principle of least privilege through job-level permissions and restricting the use of read-all, write-all, and secrets: inherit.

Tokens
9.3K
Snippets
52
Records
58
Agent score
54%

What's inside ghalint

  1. Policy: action_ref_should_be_full_length_commit_sha

    main

    The action_ref_should_be_full_length_commit_sha policy enforces security hardening by requiring GitHub Actions to be pinned to a full-length commit SHA rather than a tag or branch. This mitigates the risk of supply chain attacks where a tag might be moved to a malicious commit.

    Invalid usage (Tag/Branch):

    actions/checkout@v3

    Valid usage (Full-length SHA):

    actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
    # Valid example
    actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
  2. Policy: job_timeout_minutes_is_required

    main

    This policy enforces that all GitHub Actions jobs specify a timeout-minutes value. Setting a timeout prevents jobs from hanging for the default 6 hours, which saves CI resources and provides faster feedback on failing or stuck workflows. A typical recommended value is 30 minutes.

    Exceptions

    You can bypass this requirement in two scenarios:

    1. Step-level timeouts: If every individual step within the job defines its own timeout-minutes.
    2. Reusable workflows: If the job uses the uses keyword to call a reusable workflow, as timeout-minutes is not available at the job level for these calls.
    # Example of a compliant job
    jobs:
      foo:
        runs-on: ubuntu-latest
        timeout-minutes: 30
        steps:
          - run: echo hello
  3. Avoid setting secrets at the workflow level

    main

    To comply with the workflow_secrets policy, do not define secrets in the top-level env block of a workflow. Secrets should be scoped to specific jobs or individual steps to minimize exposure.

    Incorrect Pattern: Defining secrets in the global env block makes them available to every job in the workflow.

    Correct Pattern: Define the env block within a specific job or a specific step to ensure the secret is only exposed where it is strictly necessary.

    # Correct: Scoping secrets to specific jobs or steps
    name: test
    jobs:
      foo:
        runs-on: ubuntu-latest
        env:
          GITHUB_TOKEN: ${{github.token}}
        steps:
          - run: echo foo
      bar:
        runs-on: ubuntu-latest
        env:
          DATADOG_API_KEY: ${{secrets.DATADOG_API_KEY}}
        steps:
          - run: echo bar
  4. Exceptions to the job_permissions policy

    main

    The job_permissions policy allows for two specific exceptions where a job does not need its own explicit permissions field:

    1. Workflow-level permissions are defined: If the workflow itself has a permissions key set to an empty object {}.

      permissions: {}
      jobs:
        foo:
          runs-on: ubuntu-latest
          steps:
            - run: echo hello
    2. Single job workflow with workflow-level permissions: If the workflow contains exactly one job and the workflow has a permissions block defined.

      permissions:
        contents: read
      jobs:
        foo:
          runs-on: ubuntu-latest
          steps:
            - run: echo hello
    permissions: {}
    jobs:
      foo:
        runs-on: ubuntu-latest
        steps:
          - run: echo hello
  5. Policy: github_app_should_limit_repositories

    main

    The github_app_should_limit_repositories policy ensures that GitHub Actions using GitHub Apps to issue access tokens are restricted to specific repositories. This follows the principle of least privilege by limiting the scope of the issued tokens.

    This policy specifically monitors the following actions:

    • tibdex/github-app-token
    • actions/create-github-app-token
  6. Policy: deny_inherit_secrets

    main

    The deny_inherit_secrets policy prevents the use of secrets: inherit in GitHub Actions workflows. Using inherit exposes all secrets available in the calling workflow to the called workflow, which violates the principle of least privilege. Instead, you should explicitly pass only the specific secrets required by the job.

    Incorrect usage:

    jobs:
      release:
        uses: suzuki-shunsuke/go-release-workflow/.github/workflows/release.yaml@v0.4.4
        secrets: inherit

    Correct usage:

    jobs:
      release:
        uses: suzuki-shunsuke/go-release-workflow/.github/workflows/release.yaml@v0.4.4
        secrets:
          gh_app_id: ${{ secrets.APP_ID }}
          gh_app_private_key: ${{ secrets.APP_PRIVATE_KEY }}
  7. Policy: checkout_persist_credentials_should_be_false

    main

    This policy ensures that the persist-credentials input for the actions/checkout action is explicitly set to false.

    Why this is required

    By default, or when set to true, actions/checkout persists the authentication token, allowing every subsequent step in the job to access it. This presents a security risk. To mitigate this, you should explicitly set persist-credentials: "false".

    Violations

    • Not set: Using actions/checkout@v4 without the with block.
    • Set to true: Using persist-credentials: "true".
    # Violation: persist-credentials is not set
    - uses: actions/checkout@v4
    
    # Violation: persist-credentials is true
    - uses: actions/checkout@v4
      with:
        persist-credentials: "true"
    
    # Correct usage
    - uses: actions/checkout@v4
      with:
        persist-credentials: "false"
  8. Policy: job_secrets

    main

    The job_secrets policy prevents setting GitHub secrets as environment variables at the job level. To minimize the blast radius of exposed secrets, secrets should only be injected into the specific steps that require them, rather than being available to every step in the job.

    Violation Example: Setting a secret in the env block of a job.

    Correct Usage: Setting the secret in the env block of an individual step within the job.

    # Violation: Secret set at job level
    jobs:
      foo:
        env:
          GITHUB_TOKEN: ${{github.token}}
        steps:
          - run: echo foo
    
    # Correct: Secret set at step level
    jobs:
      foo:
        steps:
          - run: gh label create bug
            env:
              GITHUB_TOKEN: ${{github.token}}
  9. Enforce limited permissions for GitHub App tokens

    main

    The github_app_should_limit_permissions policy ensures that GitHub Actions issuing access tokens from GitHub Apps do not request excessive permissions. It flags actions that do not explicitly define a permissions scope, which helps follow the principle of least privilege.

    This policy supports the following actions:

    • tibdex/github-app-token
    • actions/create-github-app-token (supported since ghalint v1.3.0)

    Note for actions/create-github-app-token users: You must use version v1.12.0 or later of this action to support custom permissions. If you are using an older version, update it to ensure the policy passes.

  10. Enforce job-level permissions for least privilege

    main

    The job_permissions policy requires that all jobs in a GitHub Actions workflow explicitly define a permissions field. This is a security best practice to ensure the principle of least privilege is applied to each job.

    Compliant Example

    To comply, add a permissions key to your job definition. Even an empty object {} is acceptable to explicitly declare the permission state.

    jobs:
      foo:
        runs-on: ubuntu-latest
        permissions: {}
        steps:
          - run: echo hello

    Non-compliant Example

    A job is considered non-compliant if the permissions field is missing entirely.

    jobs:
      foo:
        runs-on: ubuntu-latest
        steps:
          - run: echo hello
  11. Exclude actions from the action_ref_should_be_full_length_commit_sha policy

    main

    If certain actions or reusable workflows do not support pinning to a SHA (e.g., they require a specific tag format), you can exclude them in your ghalint.yaml configuration.

    When defining an exclusion, both policy_name and action_name are mandatory. The action_name field supports glob patterns (as defined by Go's path.Match).

    excludes:
      # Exclude a specific workflow file
      - policy_name: action_ref_should_be_full_length_commit_sha
        action_name: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml
    
      # Exclude using a glob pattern
      - policy_name: action_ref_should_be_full_length_commit_sha
        action_name: suzuki-shunsuke/tfaction/*
  12. Ignore the job_secrets policy violation

    main

    If you must set a secret at the job level (for example, if the job contains only a single step), you can ignore this violation by adding an entry to the excludes section of your ghalint.yaml configuration file.

    When configuring an exclusion, you must provide the following keys:

    • policy_name: The name of the policy (e.g., job_secrets).
    • workflow_file_path: The path to the workflow file relative to the repository root.
    • job_name: The name of the specific job to exclude.
    # ghalint.yaml
    excludes:
      - policy_name: job_secrets
        workflow_file_path: .github/workflows/actionlint.yaml
        job_name: actionlint