Skip to content
 
 

Repository files navigation

Safer Runner Action

Multi-layer security for GitHub Actions runners with network filtering (DNS + iptables), privilege control (sudo management), and integrity validation (tampering detection). Implements default-deny policy with comprehensive security reporting.

Who This Is For

✅ Use This Action If You:

  • Run code you did not write: external PRs, third-party actions, or dependencies from npm, PyPI and similar registries
  • Need compliance evidence showing network access controls and audit trails
  • Want observability before enforcement: analyze mode reports what your build reaches without blocking anything

❌ This Action Is NOT For You If:

  • You only run trusted, first-party code with no external dependencies
  • Your workflow needs unrestricted network access to arbitrary domains
  • Your runner is not Ubuntu, or your job runs in a container: see Platform Support
  • Setup time matters more than the coverage: the action installs dnsmasq and ipset from the Ubuntu archive before your first step runs, so the overhead is dominated by apt and is measured in tens of seconds, not milliseconds
  • You need protection against a determined attacker abusing an allowed domain: this is a first line of defense, not a complete one

💡 Common Use Cases

Open Source Maintainers: Protect against malicious PRs that install compromised dependencies attempting to exfiltrate repository secrets or tokens.

Enterprise CI/CD: Add network observability and control to GitHub Actions workflows handling sensitive data or credentials.

Security Compliance: Generate audit trails showing network access controls were enforced during builds and deployments.

Dependency Analysis: Use analyze mode to understand what external services your build dependencies are contacting.

Features

  • Dual modes: analyze (monitoring) or enforce (blocking)
  • DNS filtering: DNSMasq with Quad9 upstream resolver
  • Firewall rules: iptables prevents DNS bypass via direct IP connections
  • Sudo logging: All sudo usage logged to /var/log/safer-runner/main-sudo.log
  • Sudo disabling: Optionally disable sudo access after setup (prevents privilege escalation)
  • Docker disabling: Optionally disable Docker access (prevents container escape attacks)
  • Custom domains: Add trusted domains via input parameter
  • Automatic reporting: Network access provenance in job summaries
  • Risky subdomain blocking: Blocks gist.github.com and raw.githubusercontent.com by default in enforce mode

Usage

⚠️ Important: Always place this action as the first step in your workflow to maximize security coverage.

ℹ️ How it works: This action uses a pre hook to establish security monitoring in analyze mode before any workflow steps run, then the main action applies your desired configuration (analyze or enforce mode).

Analyze mode (default)

steps:
  - uses: PortSwigger/safer-runner-action@b2208f653b6bf422e08501155f4df82bad008184 # v1.2.2
  - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
  - run: |
      curl https://example.com  # Logged but not blocked

Enforce mode

steps:
  - uses: PortSwigger/safer-runner-action@b2208f653b6bf422e08501155f4df82bad008184 # v1.2.2
    with:
      mode: 'enforce'
      allowed-domains: |
        example.com
        api.trusted-service.com
  - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
  - run: |
      curl https://api.trusted-service.com  # ✅ Allowed
      curl https://malicious.com  # ❌ Blocked

Inputs

Input Description Default
enabled Whether to apply protection at all. Set false on runners that cannot support it - see Platform Support true
mode analyze (log only) or enforce (block). Case-insensitive; any other value fails the job analyze
allowed-domains Additional domains to allow ''
primary-dns-server Primary DNS server for allowed domains 9.9.9.9 (Quad9)
secondary-dns-server Secondary DNS server for redundancy 149.112.112.112 (Quad9)
fail-on-tampering Fail workflow if security config is tampered false
block-risky-github-subdomains Block gist.github.com and raw.githubusercontent.com in enforce mode true
disable-sudo Disable sudo access for runner user after setup false
sudo-config Custom sudoers configuration for runner user (multi-line string) ''
disable-docker Remove runner user from docker group (prevents container usage) false

Network Access Reports

Job summaries include a Network Access Provenance table showing:

  • Domain/IP addresses accessed
  • Ports used
  • Status (✅ Allowed, ❌ Denied, 📊 Analyzed)
  • Source (GitHub Required, User Defined)

In analyze mode, the report suggests an allowed-domains configuration based on non-GitHub domains accessed, making it easy to transition to enforce mode.

Security Model

This action implements multi-layer security with DNS filtering, firewall rules, and privilege control. GitHub Actions required domains are automatically allowed. Sudo usage is always logged for auditability.

Privilege Control (Optional)

Control sudo and Docker access to prevent privilege escalation and container escape attacks:

steps:
  - uses: PortSwigger/safer-runner-action@b2208f653b6bf422e08501155f4df82bad008184 # v1.2.2
    with:
      mode: 'enforce'
      disable-sudo: 'true'    # Prevents sudo usage after setup
      disable-docker: 'true'  # Prevents Docker/container usage
      allowed-domains: |
        registry.npmjs.org
  - run: npm ci && npm test   # ✅ Works without elevated privileges
  - run: sudo apt install x   # ❌ Fails (sudo disabled)
  - run: docker build .       # ❌ Fails (Docker disabled)

Note: Only disable sudo/Docker if your workflow doesn't require them. These are advanced security features that prevent malicious code from bypassing security controls.

Custom DNS Servers

Configure alternative DNS providers while maintaining the same security guarantees:

steps:
  - uses: PortSwigger/safer-runner-action@b2208f653b6bf422e08501155f4df82bad008184 # v1.2.2
    with:
      mode: 'enforce'
      allowed-domains: 'npmjs.org pypi.org'
      primary-dns-server: '1.1.1.1'      # Cloudflare DNS
      secondary-dns-server: '1.0.0.1'    # Cloudflare secondary
  - run: npm install  # DNS queries use Cloudflare instead of Quad9

Default DNS: Quad9 (9.9.9.9 / 149.112.112.112), which filters known-malicious domains upstream of the action's own allowlist

Disable secondary DNS by passing an empty string (single DNS server only):

steps:
  - uses: PortSwigger/safer-runner-action@b2208f653b6bf422e08501155f4df82bad008184 # v1.2.2
    with:
      mode: 'enforce'
      allowed-domains: 'example.com'
      primary-dns-server: '8.8.8.8'
      secondary-dns-server: ''  # Disable secondary DNS

Note: Both DNS servers must be explicitly allowed in firewall rules. The action automatically configures iptables to allow DNS traffic only from the dnsmasq process to the configured servers.

Limitations

Platform Support

  • GitHub-hosted runners: Ubuntu only. Windows and macOS are not supported.

  • Self-hosted runners: Ubuntu only, on a VM with passwordless sudo and systemd.

  • Container-based runners: not supported. Actions Runner Controller pods and Docker executors cannot run this action, and no configuration of the action changes that. The blocker is the privilege model: such pods usually set securityContext.allowPrivilegeEscalation: false, which sets the kernel's no_new_privs bit; the kernel then ignores the setuid bit on /usr/bin/sudo, and sudo exits before it has read sudoers, so no sudoers rule helps. There is also no systemd to restart dnsmasq and rsyslog, and /etc/resolv.conf is a bind mount owned by the kubelet.

    The action detects this and says so, rather than retrying apt-get and then running the job unprotected. Because GitHub defaults pre-if and post-if to always(), a step-level if: does not prevent the hooks from running - set enabled: false instead.

    Restricting egress on runners you own is better done at the platform layer, where the control sits outside the job and cannot be tampered with by workflow code.

Security Limitations

Network filtering provides a first line of defense but has limitations:

Timing Window

  • Pre-hook ordering: This action's pre: hook establishes analyze mode monitoring before the main action step runs, providing early visibility
  • Other actions' pre-hooks: Actions that appear later in the workflow will have their pre: hooks run after this action's pre-hook, so they are monitored
  • Actions before this one: Any actions placed before this action in the workflow will have their pre: hooks run before monitoring is established
  • Mitigation: Place this action as the first step in your workflow and carefully vet all actions used

Attack Vectors

  • Data exfiltration via allowed domains: Abuse GitHub/npm/PyPI to upload secrets
  • Local file system attacks: Stage data for later exfiltration
  • Process/system call abuse: Container escapes, privilege escalation

Defense in Depth

Combine with additional security layers:

  • Runtime security: Falco, Tracee
  • Container security: Distroless images, read-only filesystems, non-root users
  • Secrets management: GitHub secrets, secure credential handling
  • Dependency scanning: Snyk, Dependabot, GitHub native scanning
  • Action security: Pin to commits, use trusted publishers

Debugging

View DNS and firewall logs:

# DNS logs (no sudo required)
cat /var/log/safer-runner/pre-dns.log      # Pre-hook DNS activity
cat /var/log/safer-runner/main-dns.log     # Main action DNS activity

# Network logs (no sudo required)
cat /var/log/safer-runner/pre-iptables.log  # Pre-hook network activity
cat /var/log/safer-runner/main-iptables.log # Main action network activity

# Sudo logs (no sudo required)
cat /var/log/safer-runner/pre-sudo.log      # Pre-hook sudo commands
cat /var/log/safer-runner/main-sudo.log     # Main action sudo commands

License

MIT. See LICENSE.

About

A GitHub action to lockdown DNS, Network, sudo and Docker

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages