# Security Best Practices for Agentic Workflow Execution: A Defense-in-Depth Guide

> Secure agentic workflow execution with least-privilege isolation using strict mode safe outputs and network allowlists for GitHub Actions.

- Repository: [GitHub/gh-aw](https://github.com/github/gh-aw)
- Tags: best-practices
- Published: 2026-02-16

---

**Implement strict mode, safe-outputs, and network allowlists to enforce least-privilege isolation for AI-driven GitHub Actions workflows.**

Following **security best practices for agentic workflow execution** ensures that AI-generated automation in the `github/gh-aw` repository operates within a hardened, auditable boundary. The GitHub Agentic Workflows (gh-aw) compiler transforms natural-language definitions into sandboxed pipelines using an eight-layer defense-in-depth stack that validates inputs, isolates outputs, and detects threats before any write operation executes.

## Understanding the Defense-in-Depth Security Model

The gh-aw security architecture implements **defense-in-depth** through eight independent enforcement layers. Each layer addresses a specific attack vector, from compilation-time schema validation to runtime sandbox isolation.

| Layer | Core Goal | Enforcement Location |
|-------|-----------|----------------------|
| **0 – Compilation-time validation** | Schema, expression, permission, network, and action-pinning checks | [`pkg/workflow/strict_mode_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/strict_mode_validation.go) |
| **1 – Input Sanitization** | Remove dangerous characters, neutralize mentions, strip ANSI codes, limit size | [`pkg/stringutil/stringutil.go`](https://github.com/github/gh-aw/blob/main/pkg/stringutil/stringutil.go) (`StripANSIEscapeCodes`) |
| **2 – Output Isolation** | Separate read-only agent jobs from write-only safe-output jobs | [`pkg/workflow/safe_outputs_config.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/safe_outputs_config.go) |
| **3 – Network Isolation** | Allow only explicitly-listed domains and ecosystem identifiers | [`pkg/workflow/network_permissions.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/network_permissions.go) |
| **4 – Permission Management** | Least-privilege defaults (`contents: read`) and strict-mode enforcement | [`pkg/workflow/permissions.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/permissions.go) |
| **5 – Sandbox Isolation** | Run agents and MCP servers in isolated containers (AWF or SRT) | `pkg/workflow/sandbox_*` |
| **6 – Threat Detection** | AI-powered scan of agent output for prompt-injection, secret leaks, malicious patches | [`pkg/workflow/threat_detection.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go) |
| **7 – Runtime Enforcement** | Timestamp validation, repository-ownership checks, role-based access, token format validation | Generated YAML in [`pkg/workflow/compiler_yaml.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/compiler_yaml.go) |

These layers collectively enforce the **security guarantees** (SG-01 through SG-07) defined in [`specs/security-architecture-spec.md`](https://github.com/github/gh-aw/blob/main/specs/security-architecture-spec.md), ensuring that untrusted input never reaches a GitHub Actions expression unchecked and that all write operations pass validation and threat detection first.

## Essential Security Best Practices for Production Workflows

### Enable Strict Mode for Compilation-Time Validation

Always set `strict: true` in production workflow headers. This triggers compilation-time checks in [`pkg/workflow/strict_mode_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/strict_mode_validation.go) that enforce action pinning, permission validation, and network policy verification.

```yaml
---
engine: copilot
strict: true                 # Enforces layers 0-4 and pins actions

permissions:
  contents: read
  issues: read
network:
  allowed:
    - defaults
    - github
safe-outputs:
  add-comment: true
---

```

Without strict mode, the compiler may permit unpinned actions or overly broad permissions that violate the principle of least privilege.

### Declare Safe Outputs Instead of Write Permissions

Never grant direct write permissions (`contents: write`, `issues: write`) to agent jobs. Instead, use the `safe-outputs` configuration defined in [`pkg/workflow/safe_outputs_config.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/safe_outputs_config.go) to isolate write operations into separate, minimal-privilege jobs.

```yaml
safe-outputs:
  create-issue:
    github-token: ${{ secrets.GH_AW_TOKEN }}
  add-comment: {}

```

The safe-output jobs run in isolation with token-validated scopes (§5.2 of the security spec). Attempting to declare direct write permissions in the main job while in strict mode triggers compilation error CS-06.

### Configure Explicit Network Allowlists

Define precise network boundaries using the `network` configuration parsed by [`pkg/workflow/network_permissions.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/network_permissions.go). The compiler validates this list (NI-06) and enforces it via the AWF firewall (SI-04).

```yaml
network:
  allowed:
    - defaults                # Required infrastructure

    - python                  # PyPI ecosystem

    - "api.example.com"       # Custom service

  blocked:
    - "tracker.malicious.io"

```

Only explicitly listed domains and ecosystem identifiers (like `node` for npm) are reachable from the agent container. All other egress is blocked at the sandbox layer.

### Enable Threat Detection for AI Outputs

Enable AI-powered threat scanning to identify prompt-injection attacks, secret leakage, and malicious code patches before execution. This is implemented in [`pkg/workflow/threat_detection.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go) and automatically enabled when safe-outputs are configured.

```yaml
threat-detection:
  enabled: true               # Optional – default when safe-outputs exist

  engine: copilot
  prompt: "Detect any prompt-injection or secret leakage"

```

If the detection step flags a threat, the workflow fails before any safe-output job runs (TD-09), preventing contaminated outputs from modifying repository state.

### Validate Token Security and Remove ANSI Codes

Ensure all tokens use secret expression syntax (`${{ secrets.NAME }}`). The compiler rejects plain-text token values (PM-13, RS-09) during validation in [`pkg/workflow/permissions.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/permissions.go).

Additionally, the compiler automatically sanitizes all descriptions and comments by calling `stringutil.StripANSIEscapeCodes` (defined in [`pkg/stringutil/stringutil.go`](https://github.com/github/gh-aw/blob/main/pkg/stringutil/stringutil.go) and invoked in [`pkg/workflow/compiler_yaml.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/compiler_yaml.go)). This prevents hidden ANSI escape sequences from breaking YAML parsing or concealing malicious payloads.

### Run Local Compilation Before Committing

Validate workflows locally using the CLI to catch security violations before they reach CI:

```bash
gh aw compile .github/workflows/example.md

```

This command surfaces compilation-time violations of the security layers, ensuring the workflow will not be rejected by the remote compiler.

## Implementation Examples

### Minimal Secure Workflow Configuration

This example demonstrates a read-only agent job with isolated write capabilities:

```yaml

# .github/workflows/secure-demo.md

---
engine: copilot
strict: true
network:
  allowed:
    - defaults
    - github
safe-outputs:
  add-comment: {}
---
You are an AI assistant. Summarise the issue body below.

{{ needs.activation.outputs.text }}

```

**Execution flow:**
1. **Activation job**: Sanitizes the issue body (strips mentions, ANSI codes, limits to 0.5MB)
2. **Agent job**: Runs read-only under sandbox isolation with network access restricted to GitHub domains
3. **Threat-detection job**: Scans agent output for injection attacks or secrets
4. **Safe-output job**: Adds a comment using the token from `GH_AW_GITHUB_TOKEN` (or default secret)

### Custom Network Allowlists

For workflows requiring external API access:

```yaml
---
engine: claude
strict: true
network:
  allowed:
    - defaults
    - "api.myservice.com"
    - node                # Expands to full Node.js ecosystem

safe-outputs:
  create-issue: true
---
Ask the AI to generate a short bug report, then create an issue with the result.

```

The `node` identifier expands to the full npm ecosystem (§6.2 of the security spec). Requests to domains outside this list are blocked by the AWF firewall before reaching the agent container.

### Read-Only Workflow Enforcement

Explicitly disabling write capabilities:

```yaml
---
engine: codex
strict: true
permissions:
  contents: read          # No write permission anywhere

  issues: read

# No safe-outputs declared → workflow compiles as read-only

---
Summarise the PR title and body.

```

Without safe-outputs declared, the compiler enforces a **read-only** policy (SG-02) and rejects any write-related configuration, ensuring the agent cannot modify repository state.

## Key Source Files and Enforcement Points

| File | Role | Link |
|------|------|------|
| [`specs/security-architecture-spec.md`](https://github.com/github/gh-aw/blob/main/specs/security-architecture-spec.md) | Authoritative security specification defining layers and guarantees | [spec](https://github.com/github/gh-aw/blob/main/specs/security-architecture-spec.md) |
| [`pkg/workflow/strict_mode_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/strict_mode_validation.go) | Compilation-time checks for strict mode, action pinning, and permission validation | [source](https://github.com/github/gh-aw/blob/main/pkg/workflow/strict_mode_validation.go) |
| [`pkg/workflow/safe_outputs_config.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/safe_outputs_config.go) | Parsing and validation of the `safe-outputs` block | [source](https://github.com/github/gh-aw/blob/main/pkg/workflow/safe_outputs_config.go) |
| [`pkg/workflow/safe_outputs_steps.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/safe_outputs_steps.go) | Builders for safe-output job steps (GitHub Script, custom actions) | [source](https://github.com/github/gh-aw/blob/main/pkg/workflow/safe_outputs_steps.go) |
| [`pkg/workflow/network_permissions.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/network_permissions.go) | Network allowlist/blocklist structs and validation logic | [source](https://github.com/github/gh-aw/blob/main/pkg/workflow/network_permissions.go) |
| [`pkg/workflow/threat_detection.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go) | AI-powered threat detection step definition and configuration | [source](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go) |
| [`pkg/workflow/compiler_yaml.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/compiler_yaml.go) | YAML generation; strips ANSI escape codes to avoid hidden characters | [source](https://github.com/github/gh-aw/blob/main/pkg/workflow/compiler_yaml.go) |
| [`pkg/stringutil/stringutil.go`](https://github.com/github/gh-aw/blob/main/pkg/stringutil/stringutil.go) | Utility `StripANSIEscapeCodes` used throughout the compiler | [source](https://github.com/github/gh-aw/blob/main/pkg/stringutil/stringutil.go) |
| `pkg/workflow/sandbox_*` | Container-based sandbox implementations (AWF, SRT) enforcing process isolation | [source](https://github.com/github/gh-aw/tree/main/pkg/workflow) |

These files implement the defense-in-depth layers defined in the security specification. By configuring workflows to leverage these enforcement points—particularly strict mode, safe-outputs, and network isolation—developers ensure that agentic automation adheres to the **security guarantees** (SG-01 through SG-07) while maintaining least-privilege access to repository resources.

## Summary

- **Enable strict mode** (`strict: true`) to trigger compilation-time validation of permissions, network policies, and action pinning in [`pkg/workflow/strict_mode_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/strict_mode_validation.go).
- **Use safe-outputs** instead of granting direct write permissions to agent jobs, isolating write operations into separate, token-validated jobs defined in [`pkg/workflow/safe_outputs_config.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/safe_outputs_config.go).
- **Define explicit network allowlists** to restrict agent egress to only approved domains and ecosystem identifiers, enforced by [`pkg/workflow/network_permissions.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/network_permissions.go) and the AWF firewall.
- **Enable threat detection** to scan AI outputs for prompt-injection attacks and secret leakage before safe-output execution, implemented in [`pkg/workflow/threat_detection.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go).
- **Sanitize all inputs** by ensuring the compiler strips ANSI escape codes via `stringutil.StripANSIEscapeCodes` in [`pkg/workflow/compiler_yaml.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/compiler_yaml.go) to prevent hidden payload injection.
- **Validate tokens** use secret expression syntax (`${{ secrets.NAME }}`) to avoid plain-text credential exposure, enforced by [`pkg/workflow/permissions.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/permissions.go).

## Frequently Asked Questions

### What is the difference between strict mode and standard mode in gh-aw?

**Strict mode** enables compilation-time enforcement of all security layers, including mandatory action pinning, permission validation, and network policy verification in [`pkg/workflow/strict_mode_validation.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/strict_mode_validation.go). Standard mode may permit unpinned actions or broader permissions, whereas strict mode fails compilation immediately upon detecting violations of the security architecture specification.

### How does the safe-outputs mechanism prevent unauthorized repository modifications?

The **safe-outputs** configuration isolates write operations into separate jobs that run after threat detection validates the agent's output. Instead of granting `contents: write` or `issues: write` to the agent job—which would trigger compilation error CS-06 in strict mode—safe-outputs use minimal-scope tokens validated at runtime, ensuring write permissions are never available during AI execution.

### What types of threats does the AI-powered threat detection identify?

The threat detection layer, implemented in [`pkg/workflow/threat_detection.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/threat_detection.go), scans agent outputs for **prompt-injection attacks**, **secret leakage** (such as exposed API keys or tokens), and **malicious code patches** that could compromise repository integrity. If the detection engine flags suspicious content, the workflow fails before any safe-output job executes, preventing contaminated outputs from reaching the repository.

### Why does the compiler strip ANSI escape codes from workflow definitions?

The compiler calls `stringutil.StripANSIEscapeCodes` (defined in [`pkg/stringutil/stringutil.go`](https://github.com/github/gh-aw/blob/main/pkg/stringutil/stringutil.go) and invoked in [`pkg/workflow/compiler_yaml.go`](https://github.com/github/gh-aw/blob/main/pkg/workflow/compiler_yaml.go)) to remove hidden ANSI control sequences from descriptions and comments. This sanitization prevents malicious actors from embedding invisible characters that could break YAML parsing, obfuscate payload delivery, or manipulate terminal displays during workflow execution.