Skip to content
GitHub Agentic Workflows

Security Best Practices

Security is foundational — Agentic Workflows inherits GitHub Actions’ sandboxing model, scoped permissions, and auditable execution. The attack surface of agentic automation can be subtle (prompt injection, tool invocation side‑effects, data exfiltration), so we bias toward explicit constraints over implicit trust: least‑privilege tokens, allow‑listed tools, and execution paths that always leave human‑visible artifacts (comments, PRs, logs) instead of silent mutation.

A core reason for building Agentic Workflows as a research demonstrator is to closely track emerging security controls in agentic engines under near‑identical inputs, so differences in behavior and guardrails are comparable. Alongside engine evolution, we are working on our own mechanisms: highly restricted substitutions, MCP proxy filtering, and hooks‑based security checks that can veto or require review before effectful steps run.

We aim for strong, declarative guardrails — clear policies the workflow author can review and version — rather than opaque heuristics. Lock files are fully reviewable so teams can see exactly what was resolved and executed. This will keep evolving; we would love to hear ideas and critique from the community on additional controls, evaluation methods, and red‑team patterns.

This material documents some notes on the security of using partially-automated agentic workflows.

Review workflow contents before installation, treating prompt templates and rule files as code. Assess compiled .lock.yml files to understand actual permissions and operations.

GitHub Actions’ built-in protections apply to agentic workflows: read-only defaults for fork PRs, restricted secret access, and explicit permissions (unspecified permissions default to none). See GitHub Actions security.

By default, workflows restrict execution to users with admin, maintainer, or write permissions. Use roles: all carefully in public repositories.

Understanding the security risks in agentic workflows helps inform protective measures:

  • Command execution: Workflows run in GitHub Actions’ partially-sandboxed environment. Arbitrary shell commands are disallowed by default; specific commands require manual allowlisting. Misconfiguration enables malicious code execution and data exfiltration.

  • Malicious inputs: Workflows pull data from Issues, PRs, comments, and code that may contain hidden AI payloads. Risk is minimized by restricting expressions in markdown and requiring GitHub MCP access, though returned data can still manipulate AI behavior.

  • Tool exposure: Default access is GitHub MCP in read-only mode. Unconstrained 3rd-party MCP tools enable data exfiltration or privilege escalation.

  • Supply chain: Unpinned Actions, npm packages, and container images are vulnerable to tampering.

Agentic Workflows inherit GitHub Actions’ security model: isolated repository copies, read-only defaults for forked PRs, restricted secret access, and explicit permissions (default none). See GitHub Actions security.

Compilation-time security measures include:

  • Expression restrictions in frontmatter
  • Command allowlisting (explicit only)
  • Tool allowlisting
  • Engine network restrictions via domain allowlists
  • Workflow longevity and iteration limits

Apply defense-in-depth consistently: least privilege by default, default-deny approach, separation of concerns (plan/apply with approval gates), and supply chain integrity (pin to immutable SHAs).

Configure GitHub Actions with defense in depth:

Set minimal read-only permissions for agentic processing; use safe-outputs for write operations:

permissions:
contents: read
actions: read
safe-outputs:
create-issue:
add-comment:

Pull request workflows block forks by default. Workflows triggered by pull_request execute only for same-repository PRs unless explicitly configured:

on:
pull_request:
types: [opened, synchronize]
# Default: blocks all forks
# Allow specific patterns:
forks: ["trusted-org/*"]
# Allow all (use with caution):
# forks: ["*"]

The compiler generates repository ID comparison conditions (github.event.pull_request.head.repo.id == github.repository_id) for reliable fork detection unaffected by repository renames.

Workflows triggered by workflow_run include automatic protections against cross-repository attacks and fork execution. The compiler injects repository ID and fork detection checks:

on:
workflow_run:
workflows: ["CI"]
types: [completed]
branches: [main, develop] # Required to prevent execution on all branches

The generated safety condition prevents execution if the triggering workflow_run is from a different repository or fork:

if: >
(user_condition) &&
((github.event_name != 'workflow_run') ||
((github.event.workflow_run.repository.id == github.repository_id) &&
(!github.event.workflow_run.repository.fork)))

This prevents cross-repository attacks, blocks fork execution, and combines with user conditions via AND logic. Without branch restrictions, compilation emits warnings (or errors in strict mode).

Production workflows should use strict mode:

strict: true
permissions:
contents: read
timeout-minutes: 10
network:
allowed: ["api.example.com"]

Strict mode blocks write permissions and requires explicit network configuration. Use safe-outputs for GitHub API interactions. See Strict Mode Validation.

Critical operations require human review. Use manual-approval to require approval before execution—configure environment protection rules in repository settings. See Manual Approval Gates.

GitHub Actions cannot approve or merge PRs, ensuring human involvement. Implement plan-apply separation for previewing changes via output issues or PRs. Regularly audit workflow history, permissions, and tool usage.

Enable strict mode for production workflows via frontmatter or CLI (gh aw compile --strict):

strict: true
permissions:
contents: read
network:
allowed: ["api.example.com"]

Strict mode enforces:

  1. Blocks write permissions (contents:write, issues:write, pull-requests:write)—use safe-outputs instead
  2. Requires explicit network configuration (no defaults)
  3. Refuses wildcard * in network domains
  4. Requires network config for custom MCP containers
  5. Enforces Action pinning to commit SHAs
  6. Refuses deprecated frontmatter fields

CLI flag takes precedence over frontmatter. See Frontmatter Reference.

Use stop-after: to set workflow expiration:

on:
schedule:
- cron: "0 9 * * 1"
stop-after: "+7d"

This workflow expires 7 days after compilation. See Trigger Events.

Use gh aw logs to monitor workflow costs—turns, tokens, and other metrics that help track resource usage.

Workflows restrict execution to users with admin, maintainer, or write permissions by default. Checks auto-apply to unsafe triggers (push, issues, pull_request) but skip safe triggers (schedule, workflow_run).

Customize via roles::

roles: [admin, maintainer, write] # Default
roles: [admin, maintainer] # Recommended for sensitive operations
roles: all # High risk in public repos

Permission checks occur at runtime. Failed checks auto-cancel with warnings. Use roles: all with caution.

Token precedence (highest to lowest): individual safe-output github-token → safe-outputs global → top-level → default (${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}).

github-token: ${{ secrets.CUSTOM_PAT }}
safe-outputs:
github-token: ${{ secrets.SAFE_OUTPUT_PAT }}
create-issue:
github-token: ${{ secrets.ISSUE_SPECIFIC_PAT }}

Use least privilege, rotate PATs regularly, prefer fine-grained PATs, monitor via audit logs, and store as secrets only.

Run MCP servers in sandboxed containers: non-root UIDs, dropped capabilities, seccomp/AppArmor profiles, no privilege escalation. Pin images to digests, scan for vulnerabilities, track SBOMs.

tools:
web:
mcp:
container: "ghcr.io/example/web-mcp@sha256:abc123..."
allowed: [fetch]

Configure explicit allow-lists:

tools:
github:
allowed: [issue_read, add_issue_comment]
bash: ["echo", "git status"]
# Avoid: ["*"] or [":*"] (unrestricted access)

Declarative network allowlists for containerized MCP servers:

mcp-servers:
fetch:
container: mcp/fetch
network:
allowed: ["example.com"]
allowed: ["fetch"]

The compiler generates per-tool Squid proxies; MCP egress is forced through iptables. Only listed domains are reachable. Applies to mcp.container stdio servers only.

Agent Security and Prompt Injection Defense

Section titled “Agent Security and Prompt Injection Defense”

CRITICAL: Always use ${{ needs.activation.outputs.text }} instead of raw github.event fields. Raw fields enable prompt injection, @mentions, bot triggers, and XML/HTML injection.

Sanitized output provides neutralized @mentions, safe XML format, HTTPS URIs from trusted domains only, 0.5MB/65k line limits, and removed control characters.

# SECURE
Analyze: "${{ needs.activation.outputs.text }}"
# INSECURE
Title: "${{ github.event.issue.title }}"

Safe outputs separate AI processing from write operations. The agentic portion runs with minimal read-only permissions, while separate jobs handle validated GitHub API operations.

This ensures AI never has direct write access to your repository, preventing unauthorized changes while enabling automation. Agent output is automatically sanitized and validated.

See Safe Outputs Reference.

Automatic threat detection analyzes agent output for prompt injection, secret leaks, and malicious patches. Auto-enabled with safe outputs; uses AI-powered analysis to reduce false positives.

safe-outputs:
create-pull-request:
threat-detection:
enabled: true
prompt: "Focus on SQL injection" # Optional
steps: # Optional additional scanning
- name: Run TruffleHog
uses: trufflesecurity/trufflehog@main

Add specialized scanners for defense-in-depth. See Threat Detection Guide.

zizmor scans compiled workflows:

gh aw compile --zizmor # Scan with warnings
gh aw compile --strict --zizmor # Block on findings

Analyzes .lock.yml for excessive permissions, insecure practices, supply chain vulnerabilities, and misconfigurations. Reports include severity, location, and context in IDE-parseable format. Requires Docker. Best practices: run during development, use --strict --zizmor in CI/CD, address High/Critical findings.

Network isolation operates at two layers:

  1. MCP Tool Network Controls: Containerized tools with domain allowlisting
  2. AI Engine Network Permissions: Configurable network access for engines

See Network Reference and Engine Network Permissions.

Fine-grained control over AI engine network access, separate from MCP tool permissions.

Copilot Engine with AWF: Uses AWF firewall wrapper for process-level domain allowlisting, execution wrapping, and activity logging. See Copilot Engine - Network Permissions.

Best Practices: Start with defaults, add needed ecosystems; prefer ecosystem identifiers over individual domains; listing a domain includes all subdomains; test thoroughly and monitor logs.

# Basic infrastructure (default)
engine:
id: copilot
network: defaults
# Ecosystem-based
network:
allowed: [defaults, python, node, containers]
# Granular domains
network:
allowed:
- "api.github.com"
- "*.company-internal.com"
# Complete denial
network: {}