GitHub Actions is not just automation. It is part of your software supply chain.

Your workflows decide:

  • which code gets executed
  • which tokens and secrets are available
  • which packages get installed
  • which artifacts get published
  • which cloud credentials can be minted

That makes workflow security a pull-request concern, not only a platform checklist.

Skylos scans GitHub Actions workflows alongside application code so risky CI/CD changes show up before merge.

What Skylos Checks In GitHub Actions

Skylos looks for workflow patterns that are easy to miss in review but serious when combined with secrets, publishing, or untrusted pull requests.

Privileged Triggers

Some events execute with more trust than developers expect.

Skylos flags risky triggers such as:

  • pull_request_target
  • workflow_run

These are not always wrong. They become dangerous when the workflow checks out or executes code influenced by an untrusted contributor.

on:
  pull_request_target:

jobs:
  test:
    steps:
      - uses: actions/checkout@v4
      - run: npm test

The safer pattern is to keep untrusted pull-request execution separate from jobs that can write to the repository, access secrets, or publish artifacts.

Broad GITHUB_TOKEN Permissions

GitHub Actions permissions should be scoped to the job.

Skylos flags broad permissions such as:

  • permissions: write-all
  • workflow-level write permissions that apply to every job
  • jobs that request powerful scopes without a clear need
permissions: write-all

For most analysis jobs, start small:

permissions:
  contents: read

Then add only the permission the job actually needs.

Unpinned Actions And Reusable Workflows

Tags such as @v4 are convenient, but they are still moving references.

Skylos flags unpinned uses: references so reviewers can see where the workflow depends on mutable external code.

steps:
  - uses: third-party/example-action@v1

For sensitive workflows, pin third-party actions to a full commit SHA.

steps:
  - uses: third-party/example-action@2d3f7a9d1b4c0a6e9f8b7c5d4e3a2f1b0c9d8e7f

This matters most for release, deploy, package publishing, and permission-bearing workflows.

Unsafe actions/checkout Defaults

actions/checkout persists credentials by default unless configured otherwise.

Skylos flags checkout steps that omit:

with:
  persist-credentials: false

That setting is especially important when a job later runs third-party code, dependency scripts, or commands that do not need repository write credentials.

Template Injection In Shell And Action Inputs

GitHub expression interpolation can turn untrusted event data into shell input.

Skylos flags risky use of event-controlled values in places such as:

  • run: commands
  • known command-like action inputs
  • container and service options
- run: echo "${{ github.event.pull_request.title }}"

The safer pattern is to pass untrusted values through environment variables and quote them carefully inside the shell.

Self-Hosted And Dynamic Runners

Self-hosted runners are powerful because they can reach internal systems. That also makes them risky.

Skylos flags jobs that use:

  • self-hosted
  • dynamic runner labels
  • expression-derived runner selection
runs-on: ${{ inputs.runner }}

Dynamic runner selection should be treated as sensitive configuration, especially in reusable workflows.

Unpinned Containers And Docker Images

Workflows often pull Docker images directly through:

  • job containers
  • service containers
  • docker:// actions
  • docker pull
  • docker run

Skylos flags image references that are not pinned by digest.

container:
  image: node:latest

Prefer digest-pinned images for sensitive jobs:

container:
  image: node@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

Secret Exposure And Inheritance

Secrets should be scoped to the smallest surface area possible.

Skylos flags patterns such as:

  • reusable workflows with secrets: inherit
  • broad secret env variables at workflow or job scope
  • dynamic secret lookup with secrets[...]
  • toJSON(secrets)
  • jobs that use secrets without an environment boundary
jobs:
  deploy:
    secrets: inherit

Secret inheritance is convenient, but it can make a reusable workflow much harder to reason about.

Unsafe Writes To $GITHUB_ENV And $GITHUB_PATH

Writing untrusted data to GitHub's environment files can affect later steps in the same job.

Skylos flags unsafe writes such as:

- run: echo "NAME=${{ github.event.issue.title }}" >> "$GITHUB_ENV"

Treat environment-file writes as privileged behavior. Keep untrusted values out unless they are normalized first.

GitHub App Token Misuse

GitHub App tokens are powerful when they can write repository content, open pull requests, or trigger workflows.

Skylos flags risky actions/create-github-app-token usage when token permissions or repository scoping are too broad for the job.

The scanner cannot know every organizational policy, but it can make dangerous defaults visible during review.

Spoofable Actor Checks

Workflows sometimes trust users by checking string values such as:

if: contains(github.actor, 'bot')

Skylos flags spoofable actor checks because usernames and bot-like labels are not a strong authorization boundary.

If a job controls releases, secrets, or repository writes, use explicit allowlists and platform permissions instead.

Release Jobs With OIDC And Repo-Controlled Scripts

OIDC is better than long-lived cloud secrets, but it still creates a credential minting path.

Skylos flags jobs that request:

permissions:
  id-token: write

and then invoke repo-controlled release or build scripts.

That does not mean the job is wrong. It means the script becomes part of the trust boundary and deserves careful review.

Package Installs That Run Lifecycle Scripts

JavaScript package managers can run lifecycle scripts during install.

Skylos flags install commands that do not disable scripts in workflows where dependency execution increases risk.

- run: npm ci

Safer baseline:

- run: npm ci --ignore-scripts

When a workflow truly needs lifecycle scripts, that exception should be intentional.

Missing Timeouts On Privileged Jobs

Privileged jobs should not run forever.

Skylos flags sensitive jobs that omit timeout-minutes, especially release or deploy jobs.

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - run: npm publish

Add an explicit timeout:

jobs:
  publish:
    runs-on: ubuntu-latest
    timeout-minutes: 15

Why Scan Workflows And Code Together

Workflow-only scanning catches CI/CD misconfiguration.

Code scanning catches application bugs.

Real incidents often need both views:

  • a workflow runs on the wrong trigger
  • the job has broad token permissions
  • the job installs untrusted dependencies
  • the repository contains a dangerous release script
  • a pull request changes both workflow and code in one diff

Skylos is designed for that combined review path. The goal is to show the reviewer when a pull request changes the security properties of the repository, not just whether a single file has a known bad pattern.

How To Run It

Run Skylos locally first:

pip install skylos
skylos . --danger --secrets --quality

Then add it to CI as a pull-request gate:

name: skylos-security

on:
  pull_request:
    branches: [main]

permissions:
  contents: read
  pull-requests: write

jobs:
  scan:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install Skylos
        run: pip install skylos

      - name: Scan repository
        run: skylos . --danger --secrets --quality --gate

For Python-specific setup, use:

What To Fix First

Prioritize findings in this order:

  1. Privileged triggers that execute untrusted code
  2. Broad write permissions on pull-request workflows
  3. Release jobs with secrets, OIDC, or package publish access
  4. Unpinned third-party actions and containers
  5. Secret inheritance or workflow-level secret exposure
  6. Dependency installs that execute lifecycle scripts
  7. Missing timeouts and unsafe artifact behavior

That order keeps attention on the paths where a workflow can write, publish, or mint credentials.

Where Skylos Fits

If you already use a dedicated GitHub Actions linter, keep it.

Skylos is useful when you want one review signal for:

  • workflow security
  • Python security
  • secret exposure
  • dead code
  • AI-generated code regressions
  • CI gate enforcement

That makes it a good fit for teams that want GitHub Actions security checks and application security checks in the same pull request workflow.