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, Model Context Protocol (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.
Security Architecture Overview
Section titled “Security Architecture Overview”The following diagram illustrates the multi-layered security architecture of GitHub Agentic Workflows, showing how agent processes, MCP servers, and skills are isolated within sandboxes and communicate through proxy/firewall layers:
flowchart TB
subgraph ActionJobVM["Action Job VM"]
subgraph Sandbox1["Sandbox"]
Agent["Agent Process"]
end
Proxy1["Proxy / Firewall"]
Gateway["Gateway<br/>(mcpg)"]
Agent --> Proxy1
Proxy1 --> Gateway
subgraph Sandbox2["Sandbox"]
MCP["MCP Server"]
end
subgraph Sandbox3["Sandbox"]
Skill["Skill"]
end
Gateway --> MCP
Gateway --> Skill
Proxy2["Proxy / Firewall"]
Proxy3["Proxy / Firewall"]
MCP --> Proxy2
Skill --> Proxy3
end
Service1{{"Service"}}
Service2{{"Service"}}
Proxy2 --> Service1
Proxy3 --> Service2
Key Security Layers:
- Agent Sandbox: The agent process runs in an isolated sandbox environment with restricted permissions
- Primary Proxy/Firewall: Filters outbound traffic from the agent to the MCP Gateway
- MCP Gateway (mcpg): Central routing component that manages communication between agents and backend services
- MCP Server & Skill Sandboxes: Each MCP server and skill runs in its own isolated sandbox
- Secondary Proxy/Firewalls: Additional proxy layers control egress traffic from MCP servers and skills to external services
- Service Layer: External services accessed through multiple layers of security controls
This defense-in-depth architecture ensures that even if one layer is compromised, multiple additional security controls remain in place to protect sensitive resources.
Before You Begin
Section titled “Before You Begin”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.
Threat Model
Section titled “Threat Model”Understanding the security risks in agentic workflows helps inform protective measures:
Primary Threats
Section titled “Primary Threats”-
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.
Core Security Principles
Section titled “Core Security Principles”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).
Implementation Guidelines
Section titled “Implementation Guidelines”Workflow Permissions and Triggers
Section titled “Workflow Permissions and Triggers”Configure GitHub Actions with defense in depth:
Permission Configuration
Section titled “Permission Configuration”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:Fork Protection for Pull Request Triggers
Section titled “Fork Protection for Pull Request Triggers”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.
workflow_run Trigger Security
Section titled “workflow_run Trigger Security”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 branchesThe 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: truepermissions: contents: readtimeout-minutes: 10# Network automatically uses secure defaults# Optionally customize: network: { allowed: ["api.example.com"] }Strict mode blocks write permissions and applies secure network defaults automatically (or you can specify custom domains). Use safe-outputs for GitHub API interactions. See Strict Mode Validation.
Human in the Loop
Section titled “Human in the Loop”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.
Limit operations
Section titled “Limit operations”Strict Mode Validation
Section titled “Strict Mode Validation”Enable strict mode for production workflows via frontmatter or CLI (gh aw compile --strict):
strict: truepermissions: contents: read# Network defaults to secure defaults if not specifiednetwork: allowed: ["api.example.com"] # Optional: customize allowed domainsStrict mode enforces:
- Blocks write permissions (
contents:write,issues:write,pull-requests:write)—usesafe-outputsinstead - Applies secure network defaults when not explicitly configured (use
network: defaultsornetwork: { allowed: [...] }to customize) - Refuses wildcard
*in network domains - Requires network config for custom MCP containers
- Enforces Action pinning to commit SHAs
- Refuses deprecated frontmatter fields
CLI flag takes precedence over frontmatter. See Frontmatter Reference.
Limit workflow longevity
Section titled “Limit workflow longevity”Use stop-after: to set workflow expiration:
on: weekly on monday stop-after: "+7d"This workflow expires 7 days after compilation. See Trigger Events.
Monitor costs
Section titled “Monitor costs”Use gh aw logs to monitor workflow costs—turns, tokens, and other metrics that help track resource usage.
Repository Access Control
Section titled “Repository Access Control”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] # Defaultroles: [admin, maintainer] # Recommended for sensitive operationsroles: all # High risk in public reposPermission checks occur at runtime. Failed checks auto-cancel with warnings. Use roles: all with caution.
Authorization and Token Management
Section titled “Authorization and Token Management”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.
MCP Tool Hardening
Section titled “MCP Tool Hardening”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]Tool Allow/Disallow
Section titled “Tool Allow/Disallow”Configure explicit allow-lists:
tools: github: allowed: [issue_read, add_issue_comment] bash: ["echo", "git status"]
# Avoid: ["*"] or [":*"] (unrestricted access)Egress Filtering
Section titled “Egress Filtering”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.
Automatic GitHub Lockdown on Public Repositories
Section titled “Automatic GitHub Lockdown on Public Repositories”When using the GitHub MCP tool with a custom token (GH_AW_GITHUB_MCP_SERVER_TOKEN), lockdown mode is automatically determined based on repository visibility to prevent accidental data leakage. This security feature restricts the GitHub token from accessing private repositories when running in public repositories.
How Automatic Determination Works:
When GH_AW_GITHUB_MCP_SERVER_TOKEN is defined, the system automatically determines lockdown mode at workflow runtime based on repository visibility:
- Public repositories: Lockdown mode is automatically enabled. The GitHub MCP server limits surfaced content to items authored by users with push access to the repository.
- Private/internal repositories: Lockdown mode is automatically disabled since there’s no risk of exposing private repository access.
- Detection failure: If repository visibility cannot be determined, the system defaults to lockdown mode for maximum security.
When using default GITHUB_TOKEN: Automatic determination is skipped and lockdown defaults to disabled (no restriction).
Minimal Configuration:
tools: github: # Lockdown is automatically determined for public repos # when GH_AW_GITHUB_MCP_SERVER_TOKEN is definedManual Override (Optional):
You can explicitly set lockdown mode if needed:
tools: github: lockdown: true # Force enable lockdown # or lockdown: false # Explicitly disable (use with caution in public repos)Security Benefits:
- Prevents token scope leakage: When using a custom token with private repository access, lockdown mode prevents that access from being used in public repository workflows
- Defense in depth: Adds an additional layer of protection beyond token scoping
- Automatic and transparent: Works automatically when
GH_AW_GITHUB_MCP_SERVER_TOKENis defined - Safe by default: Detection failures default to the most secure setting
See also: GitHub MCP Tool Configuration for complete tool configuration options.
Agent Security and Prompt Injection Defense
Section titled “Agent Security and Prompt Injection Defense”Sanitized Context Text Usage
Section titled “Sanitized Context Text Usage”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.
# SECUREAnalyze: "${{ needs.activation.outputs.text }}"
# INSECURETitle: "${{ github.event.issue.title }}"Safe Outputs Security Model
Section titled “Safe Outputs Security Model”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.
Threat Detection
Section titled “Threat Detection”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@mainAdd specialized scanners for defense-in-depth. See Threat Detection Guide.
Automated Security Scanning
Section titled “Automated Security Scanning”zizmor scans compiled workflows:
gh aw compile --zizmor # Scan with warningsgh aw compile --strict --zizmor # Block on findingsAnalyzes .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
Section titled “Network Isolation”Network isolation operates at two layers:
- MCP Tool Network Controls: Containerized tools with domain allowlisting
- AI Engine Network Permissions: Configurable network access for engines
See Network Reference and Engine Network Permissions.
Engine Network Permissions
Section titled “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 Sandbox Configuration.
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.
Permission Modes
Section titled “Permission Modes”# Basic infrastructure (default)engine: id: copilotnetwork: defaults
# Ecosystem-basednetwork: allowed: [defaults, python, node, containers]
# Granular domainsnetwork: allowed: - "api.github.com" - "*.company-internal.com"
# Complete denialnetwork: {}See also
Section titled “See also”- Threat Detection Guide - Comprehensive threat detection configuration and examples
- Safe Outputs Reference
- Network Configuration
- Tools
- MCPs
- Workflow Structure
References
Section titled “References”- Model Context Protocol: Security Best Practices (2025-06-18) — https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices