Detection rules › Panther

GitHub Workflow Using Self-Hosted Runner

Severity
informational
Log types
GitHub.Webhook
Tags
CI/CD, Workflow, Self-Hosted, Infrastructure
Reference
https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#self-hosted-runner-security
Source
github.com/panther-labs/panther-analysis

Detects when a GitHub Actions workflow runs on a self-hosted runner.

MITRE ATT&CK coverage

Rules detecting the same action

Other rules on this platform that filter on the same API call or operation.

Rule body yaml

AnalysisType: rule
Filename: github_self_hosted_runner_used.py
RuleID: "GitHub.Webhook.SelfHostedRunnerUsed"
DisplayName: "GitHub Workflow Using Self-Hosted Runner"
Enabled: true
LogTypes:
  - GitHub.Webhook
Reports:
  MITRE ATT&CK:
    - TA0001:T1195.002  # Supply Chain Compromise: Compromise Software Supply Chain
    - TA0002:T1072  # Execution: Software Deployment Tools
    - TA0008:T1021  # Lateral Movement: Remote Services
Tags:
  - CI/CD
  - Workflow
  - Self-Hosted
  - Infrastructure
CreateAlert: false
Severity: Info
DedupPeriodMinutes: 60
Description: Detects when a GitHub Actions workflow runs on a self-hosted runner.
Reference: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners#self-hosted-runner-security
Tests:
  - Name: "Self-hosted runner on public repository"
    ExpectedResult: true
    Log:
      action: "completed"
      workflow_job:
        id: 52841003143
        name: "Build"
        status: "completed"
        conclusion: "success"
        run_url: "https://github.com/example-org/example-repo/actions/runs/12345678/job/52841003143"
        head_branch: "main"
        head_sha: "abc123"
        run_id: 12345678
        runner_group_id: 1
        runner_group_name: "Default"
        runner_id: 42
        runner_name: "my-runner-01"
        started_at: "2025-10-15T18:31:06Z"
        completed_at: "2025-10-15T18:41:54Z"
        steps:
          - name: "Setup"
            status: "completed"
            conclusion: "success"
      repository:
        id: 123456789
        full_name: "example-org/example-repo"
        private: false
        visibility: "public"
        allow_forking: true
      sender:
        login: "developer"

  - Name: "Self-hosted runner on private forkable repository"
    ExpectedResult: true
    Log:
      action: "completed"
      workflow_job:
        id: 52841003144
        name: "Deploy"
        status: "completed"
        conclusion: "success"
        run_id: 12345679
        runner_group_id: 2
        runner_group_name: "Production"
        runner_id: 43
        runner_name: "prod-runner-vm-02"
      repository:
        id: 123456790
        full_name: "example-org/private-repo"
        private: true
        visibility: "private"
        allow_forking: true
      sender:
        login: "developer"

  - Name: "Self-hosted runner on private non-forkable repository"
    ExpectedResult: true
    Log:
      action: "completed"
      workflow_job:
        id: 52841003145
        name: "Test"
        status: "completed"
        conclusion: "success"
        run_id: 12345680
        runner_group_id: 1
        runner_group_name: "Default"
        runner_id: 44
        runner_name: "internal-runner"
      repository:
        id: 123456791
        full_name: "example-org/internal-repo"
        private: true
        visibility: "private"
        allow_forking: false
      sender:
        login: "developer"

  - Name: "GitHub-hosted runner (not self-hosted)"
    ExpectedResult: false
    Log:
      action: "completed"
      workflow_job:
        id: 52841003146
        name: "Lint"
        status: "completed"
        conclusion: "success"
        run_id: 12345681
        runner_group_id: 0
        runner_group_name: "GitHub Actions"
        runner_id: 1000000001
        runner_name: "GitHub Actions 1000000001"
      repository:
        id: 123456792
        full_name: "example-org/example-repo"
        private: true

  - Name: "Workflow job in progress (not completed)"
    ExpectedResult: false
    Log:
      action: "in_progress"
      workflow_job:
        id: 52841003147
        name: "Build"
        status: "in_progress"
        run_id: 12345682
        runner_name: "my-runner-01"
      repository:
        id: 123456793
        full_name: "example-org/example-repo"

  - Name: "Self-hosted runner with empty name"
    ExpectedResult: false
    Log:
      action: "completed"
      workflow_job:
        id: 52841003148
        status: "completed"
        run_id: 12345683
        runner_name: ""
      repository:
        id: 123456794
        full_name: "example-org/example-repo"

Detection logic

Condition

action eq "completed"
workflow_job.runner_name is_not_null
workflow_job.runner_name not starts_with "GitHub Actions"

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
workflow_job.runner_namestarts_withGitHub Actions

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
actioneq
  • completed
workflow_job.runner_nameis_not_null
  • (no value, null check)

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
workflow_nameworkflow_job.name
workflow_job_idworkflow_job.id
workflow_run_idworkflow_job.run_id
workflow_urlworkflow_job.html_url
repositoryrepository.full_name
repository_privaterepository.private
repository_visibilityrepository.visibility
head_branchworkflow_job.head_branch
head_shaworkflow_job.head_sha
conclusionworkflow_job.conclusion
runner_nameworkflow_job.runner_name
runner_group_nameworkflow_job.runner_group_name
runner_idworkflow_job.runner_id
runner_group_idworkflow_job.runner_group_id
actorsender.login