Safe Outputs MCP Gateway Specification
Safe Outputs MCP Gateway Specification
Section titled “Safe Outputs MCP Gateway Specification”Version: 1.13.0
Status: Working Draft
Publication Date: 2026-02-18
Editor: GitHub Agentic Workflows Team
This Version: safe-outputs-specification
Latest Published Version: This document
Abstract
Section titled “Abstract”This specification establishes normative requirements for the Safe Outputs Model Context Protocol (MCP) Gateway, a security-centric translation layer enabling AI agents to declare intended GitHub operations through structured protocols while maintaining strict privilege separation. The gateway functions as an intermediary between read-only agent execution environments and permission-controlled execution contexts, providing configurable constraints, input validation, content sanitization, and preview capabilities. This document specifies behavioral requirements, security properties, operational semantics, and conformance criteria for implementing systems.
Document Status
Section titled “Document Status”This document represents a working draft specification subject to revision. It documents the Safe Outputs MCP Gateway as implemented in GitHub Agentic Workflows version 1.8.0 and later. Future versions may introduce backwards-incompatible changes. Implementers should consult the latest version before beginning new implementations.
This specification follows World Wide Web Consortium (W3C) formatting conventions while being independently maintained by the GitHub Agentic Workflows project.
Table of Contents
Section titled “Table of Contents”- Introduction
- Conformance Requirements
- Security Architecture
- Structural Components
- Configuration Semantics
- Universal Feature Interpretation
- Safe Output Type Definitions
- Protocol Exchange Patterns
- Content Integrity Mechanisms
- Execution Guarantees
- Appendices
Terminology
Section titled “Terminology”This specification uses the following terms with precise definitions:
Agent: The AI-powered process executing in an untrusted context with read-only GitHub permissions.
Safe Output Type: A category of GitHub operation (e.g., create_issue, add_comment) with a corresponding MCP tool definition and handler implementation.
MCP Gateway: The HTTP server accepting MCP tool invocation requests and recording operations to NDJSON format. Runs in the same context as the agent.
Safe Output Processor: The privileged execution context that reads NDJSON artifacts, validates operations, and executes GitHub API calls.
Handler: JavaScript implementation processing operations of a specific safe output type.
Validation: Pre-execution verification of operation structure, limits, and authorization. Includes schema validation, limit enforcement, and allowlist checking.
Sanitization: Content transformation pipeline removing potentially malicious patterns while preserving legitimate content.
Verification: Post-compilation checking of configuration integrity through hash validation.
Staged Mode: Preview execution mode where operations are simulated without creating permanent GitHub resources.
Temporary ID: A placeholder identifier (format: aw_<id>) used to reference not-yet-created resources. Resolved to actual resource numbers during processing.
Provenance: Metadata identifying the workflow and run that created a GitHub resource. Included in footers or API metadata fields.
1. Introduction
Section titled “1. Introduction”1.1 Motivation and Problem Statement
Section titled “1.1 Motivation and Problem Statement”Contemporary AI-powered software workflows require actionable outcomes beyond informational responses. Agents must translate reasoning into concrete platform operations—creating issues for bugs, commenting on pull requests, managing labels. However, granting AI systems direct write access to version control platforms introduces severe security vulnerabilities:
- Prompt injection attacks: Adversarially-crafted inputs manipulate agent behavior, potentially causing unauthorized deletions, spam creation, or credential exfiltration
- Unbounded resource consumption: Compromised agents exhaust API rate limits, storage quotas, or workflow execution time
- Audit trail opacity: Direct API invocations obscure operation provenance, complicating incident response and compliance
- Credential surface expansion: Write-capable tokens become high-value targets, increasing attack surface
Traditional mitigation strategies prove inadequate:
- Full prohibition eliminates automation benefits, relegating agents to advisory roles
- Manual approval gates create bottlenecks, defeating automation’s purpose
- Overly-permissive grants accept unacceptable risk for convenience
The Safe Outputs MCP Gateway introduces a structured alternative: declarative operation requests with deferred, validated execution. Agents articulate intentions through type-safe protocols; isolated execution contexts validate and fulfill requests under configured constraints.
1.2 Scope and Boundaries
Section titled “1.2 Scope and Boundaries”Within Specification Scope:
This document normatively defines:
- Security model architecture establishing privilege separation between untrusted reasoning and trusted execution
- Configuration schema semantics for declaring available operations, constraints, and validation rules
- Protocol exchange patterns governing operation declaration, validation, and fulfillment
- Content security requirements specifying sanitization, filtering, and validation transformations
- Operational guarantees characterizing atomicity, ordering, idempotency, and error handling for each safe output type
- MCP integration defining tool interface schemas and HTTP transport requirements
Explicitly Out of Scope:
This specification does NOT define:
- Core Model Context Protocol semantics (see external MCP specification)
- GitHub REST/GraphQL API implementation details (see GitHub API documentation)
- AI model selection, prompt engineering, or agent implementation strategies
- User interface design for workflow authoring or monitoring
- Container orchestration, deployment topology, or infrastructure provisioning
- Performance benchmarks or resource consumption limits
1.3 Design Principles
Section titled “1.3 Design Principles”Four foundational principles govern this specification:
Principle P1: Security Through Architectural Separation
Write permissions MUST reside in separate execution contexts from AI reasoning. Communication occurs through structured data artifacts, not shared credentials or memory.
Rationale: Privilege separation limits blast radius of successful prompt injection attacks. Compromising the agent yields read-only access; compromising execution context requires additional exploitation steps.
Principle P2: Declarative Over Imperative
Operations are declared through schema-validated data structures, not imperative command execution. This enables static analysis, transformation, and validation before commitment.
Rationale: Declarative models permit inspection, logging, and modification of operations before GitHub API invocation. Imperative models lack such intervention points.
Principle P3: Configurable Constraint Enforcement
Workflow authors explicitly configure permitted operations and constraints. Implicit behaviors are minimized; defaults favor security over convenience.
Rationale: Explicit configuration ensures conscious security decisions. Implicit permissiveness creates hidden vulnerabilities.
Principle P4: Fail-Secure By Default
Invalid inputs, constraint violations, or execution errors result in operation rejection, not degraded execution. Error messages provide diagnostic information for remediation.
Rationale: Proceeding with degraded security is worse than failing. Clear error messages enable authors to correct issues rather than silently accepting risks.
1.4 Terminology and Definitions
Section titled “1.4 Terminology and Definitions”This specification uses the following terms with precise meanings:
Agent: An AI-powered workflow job executing with read-only GitHub permissions. Agents analyze inputs, reason about appropriate actions, and declare operations through MCP tool invocations.
Safe Output Type: A category of GitHub operation (e.g., issue creation, comment posting, label application) with defined semantics, constraints, and operational guarantees. Each type corresponds to one or more MCP tools.
MCP Gateway: An HTTP server implementing the Model Context Protocol, accepting tool invocations from agents, validating against JSON schemas, and recording operations to structured files.
Safe Output Job: A permission-controlled GitHub Actions job that downloads agent-declared operations, validates content, enforces limits, and executes GitHub API calls.
NDJSON (Newline-Delimited JSON): A text format where each line contains one complete, valid JSON object. Enables incremental writing and parsing without loading entire dataset into memory.
Staged Mode: A preview execution mode where operations are simulated and summarized without permanent effects. Indicated by emoji prefix in messages.
Max Limit: A configuration parameter constraining the count of operations per safe output type. Prevents resource exhaustion and limits damage from compromised agents.
Content Sanitization: Security transformation applied to all user-provided text fields (titles, bodies, comments) to remove exploit vectors (malicious URLs, command injection, credential patterns) while preserving legitimate content.
Footer: An AI attribution message appended to created content, identifying the workflow source, providing provenance via run URL, and optionally including installation instructions.
Temporary ID: A workflow-scoped identifier (format: aw_<alphanumeric>) allowing agents to reference not-yet-created issues in subsequent operations. Resolved to actual issue numbers during execution.
2. Conformance Requirements
Section titled “2. Conformance Requirements”2.1 Conformance Classes
Section titled “2.1 Conformance Classes”This specification defines two conformance classes:
Class C1: Full Conformance
An implementation satisfying ALL normative requirements (MUST, SHALL, REQUIRED statements) in this document. Full conformance requires:
- Complete security architecture implementation (privilege separation, threat mitigations)
- Support for all mandatory safe output types (defined in Section 7)
- Universal feature implementation (max limits, staged mode, footers, sanitization)
- Protocol exchange pattern adherence (MCP HTTP transport, NDJSON persistence)
- Content integrity mechanism enforcement (schema validation, domain filtering)
- Execution guarantee provision (atomicity, ordering, idempotency)
Class C2: Partial Conformance
An implementation satisfying ALL security-critical normative requirements but omitting support for optional safe output types. Partial conformance requires:
- Complete security architecture (non-negotiable)
- Mandatory safe output types:
create_issue,add_comment,create_pull_request,noop - Clear documentation listing unsupported optional types
- Warning messages when workflows attempt to use unsupported types
Note: Partial conformance permits phased implementation but MUST NOT compromise security properties.
2.2 Normative Terminology
Section titled “2.2 Normative Terminology”This document employs RFC 2119 requirement level keywords with precise interpretations:
MUST (SHALL, REQUIRED): Absolute requirement for conformance. Omission, violation, or alternative implementation constitutes non-conformance. Implementations violating MUST requirements are non-conforming even if they satisfy all other requirements.
MUST NOT (SHALL NOT): Absolute prohibition. Presence of explicitly prohibited behavior constitutes non-conformance regardless of other implementation quality.
SHOULD (RECOMMENDED): Strong recommendation but not absolute requirement. Valid reasons may justify different behavior, but implications MUST be fully understood and carefully weighed. Deviations MUST be documented.
SHOULD NOT (NOT RECOMMENDED): Strong advice against specific behavior but not absolute prohibition. Alternative approaches may be justified in specific contexts.
MAY (OPTIONAL): Truly optional feature or behavior. Implementations MAY choose to include, omit, or provide alternative approaches. Presence or absence does not affect conformance.
2.3 Conformance Verification
Section titled “2.3 Conformance Verification”Conformance MAY be demonstrated through:
Method M1: Functional Testing
Systematic verification that all required operations produce specified outcomes under normal and edge-case conditions. Test coverage SHOULD include:
- Each safe output type with valid inputs
- Constraint enforcement (max limits, domain filtering)
- Error handling (invalid inputs, exceeded limits)
- Configuration variants (staged mode, cross-repository)
Method M2: Security Testing
Demonstration that security properties hold under adversarial conditions. Security test suite SHOULD include:
- Prompt injection scenarios (malicious inputs attempting unauthorized operations)
- Constraint evasion attempts (trying to exceed max limits)
- Content injection (URLs to forbidden domains, command injection)
- Cross-repository privilege escalation attempts
Method M3: Protocol Compliance
Validation that MCP exchange patterns conform to requirements. Protocol tests SHOULD verify:
- HTTP request/response format correctness
- JSON Schema validation enforcement
- NDJSON format adherence
- Error code and message format
Method M4: Configuration Validation
Verification that configuration parsing, validation, and enforcement match specifications. Configuration tests SHOULD check:
- Valid configuration acceptance
- Invalid configuration rejection with clear errors
- Inheritance rules (type-specific overriding global)
- Default value application
Note: A normative conformance test suite is RECOMMENDED for future specification versions but not currently provided.
3. Security Architecture
Section titled “3. Security Architecture”3.1 Privilege Separation Model
Section titled “3.1 Privilege Separation Model”The Safe Outputs MCP Gateway implements defense-in-depth through strict architectural privilege separation. The following diagram illustrates permission boundaries:
┌─────────────────────────────────────────┐│ Execution Context 1: Untrusted ││ ┌─────────────────────────────────┐ ││ │ AI Agent Process │ ││ │ ├─ Permissions: contents:read │ ││ │ ├─ Network: None (firewall) │ ││ │ └─ Credentials: Read-only token│ ││ └────────────┬────────────────────┘ ││ │ MCP over HTTP/127 ││ ↓ ││ ┌─────────────────────────────────┐ ││ │ MCP Gateway Server │ ││ │ ├─ Permissions: File write │ ││ │ ├─ Network: Localhost only │ ││ │ ├─ Operations: │ ││ │ │ • Schema validation │ ││ │ │ • NDJSON append │ ││ │ └─ No GitHub API access │ ││ └─────────────────────────────────┘ │└─────────────────────────────────────────┘ ↓ Artifact Storage (GitHub-managed) ↓┌─────────────────────────────────────────┐│ Execution Context 2: Privileged ││ ┌─────────────────────────────────┐ ││ │ Safe Output Processor │ ││ │ ├─ Permissions: issues:write, │ ││ │ │ pull-requests:write, etc. │ ││ │ ├─ Operations: │ ││ │ │ • Content sanitization │ ││ │ │ • Limit enforcement │ ││ │ │ • GitHub API invocation │ ││ │ └─ No direct agent access │ ││ └─────────────────────────────────┘ │└─────────────────────────────────────────┘Architectural Requirements:
Requirement AR1: Agent Isolation
Agents MUST execute without GitHub write permissions. Only read-level tokens SHALL be accessible to agent processes. Write-capable tokens MUST reside exclusively in safe output job contexts.
Verification:
- Method: Automated workflow file parsing and static analysis
- Tool:
check_privilege_separation()in conformance checker (scripts/check-safe-outputs-conformance.sh) - Criteria: No agent job has
issues: write,pull-requests: write,contents: write, or other write-level permissions - Manual Check: Inspect agent job permission declarations in compiled
.lock.ymlfiles
Formal Definition:
∀ workflow ∈ Workflows: permissions(workflow.jobs.agent) ∩ {issues:write, pull-requests:write, contents:write, ...} = ∅Requirement AR2: Communication Channel Integrity
Agent-to-processor communication MUST occur through GitHub Actions artifact storage. Environment variables, network connections, or shared filesystems MUST NOT be used for operation transmission.
Rationale: Artifact storage provides tamper-evidence, audit logging, and access control. Alternative channels lack these properties.
Verification:
- Method: Code review and architecture inspection
- Tool: Manual inspection of workflow compilation code in
pkg/workflow/ - Criteria: Operations are written to NDJSON files, uploaded as artifacts, and downloaded by safe output jobs
- Manual Check: Verify workflow structure includes
actions/upload-artifact@v4andactions/download-artifact@v4steps between agent and safe output jobs
Formal Definition:
∀ operation ∈ Operations: transmission(operation) = artifact_storage ∧ transmission(operation) ≠ env_vars ∧ transmission(operation) ≠ network ∧ transmission(operation) ≠ filesystem_shareRequirement AR3: Permission Minimization
Each safe output job MUST request minimal permissions. Jobs SHOULD specialize by operation type, requesting only required permissions. For example, an issue-creation job requests issues:write but not pull-requests:write.
Verification:
- Method: Automated permission computation analysis and code review
- Tool:
computePermissionsForSafeOutputs()inpkg/workflow/safe_outputs_permissions.goandcheck_permission_computation()in conformance checker - Criteria: Each safe output job requests only the minimum permissions required for its operation types
- Manual Check: Review generated workflow YAML to verify job permissions match operation requirements
Example:
jobs: safe-output-create-issue: permissions: contents: read issues: write # Minimal for issue creationFormal Definition:
∀ job ∈ SafeOutputJobs: permissions(job) = minimal_set(operations(job)) ∧ ∀ p ∈ permissions(job): required(p, operations(job))Requirement AR4: No Privilege Escalation Path
Agent execution context MUST NOT gain access to safe output job credentials through any mechanism (environment variables, file leaks, API endpoints, etc.).
Verification:
- Method: Manual security audit and code review
- Tool: Security review of workflow structure and GitHub Actions architecture
- Criteria: No GITHUB_TOKEN or credentials are accessible from agent job context; tokens only exist in safe output job contexts
- Manual Check: Audit all communication channels (artifacts, environment variables, network, filesystem) to confirm no credential leakage
Formal Definition:
∀ t ∈ [agent_start, agent_end]: accessible_credentials(agent_context, t) ∩ safe_output_credentials = ∅3.2 Threat Model and Mitigations
Section titled “3.2 Threat Model and Mitigations”This specification addresses five primary threat scenarios:
Threat T1: Prompt Injection Exploitation
Attack Vector: Adversary crafts inputs (issue descriptions, comments, file contents) causing agent to misinterpret intent and declare harmful operations.
Examples:
- Mass issue creation (spam)
- Malicious content injection (phishing URLs)
- Inappropriate label application
- Unauthorized cross-repository operations
Architectural Mitigations:
| Layer | Mechanism | Effectiveness |
|---|---|---|
| Constraint | Max limits cap operation count per type | Prevents unbounded operations |
| Validation | JSON schema enforces structure | Rejects malformed declarations |
| Sanitization | Content filtering removes exploit vectors | Neutralizes injection attempts |
| Preview | Staged mode enables pre-commitment review | Human-in-loop detection |
| Authorization | Cross-repo operations require explicit config | Prevents unauthorized targeting |
Residual Risk: Agent may generate legitimate-seeming but contextually inappropriate content within configured limits. Mitigation: workflow monitoring, anomaly detection, periodic review.
Threat T2: Configuration Tampering
Attack Vector: Adversary modifies workflow YAML between compilation and execution, disabling security features (removing max limits, disabling sanitization).
Examples:
- Changing
max: 1tomax: -1(unlimited) - Removing
allowed-domainsconfiguration - Disabling
footerattribution
Architectural Mitigations:
| Layer | Mechanism | Effectiveness |
|---|---|---|
| Integrity | Frontmatter hash computed at compilation | Detects modifications |
| Verification | Hash checked before execution | Prevents tampered execution |
| Embedding | Configuration embedded in compiled workflow | No external file modification |
| Immutability | Compiled workflows stored in version control | Change tracking |
Residual Risk: Source repository compromise allows arbitrary workflow modification. Mitigation: branch protection rules, code review requirements, commit signing.
Threat T3: Credential Leakage
Attack Vector: Agent inadvertently includes secrets (API keys, passwords, tokens) in created content or logs.
Examples:
- Secrets in issue descriptions
- Tokens in PR comments
- Keys in log output
Architectural Mitigations:
| Layer | Mechanism | Effectiveness |
|---|---|---|
| Masking | GitHub Actions secret masking redacts registered secrets | High for known secrets |
| Detection | Pattern-based scanning identifies credential-like strings | Medium (novel formats evade) |
| Logging | MCP logs undergo security scanning | High for logged secrets |
| Review | Manual inspection of suspicious patterns | High but manual |
Residual Risk: Novel secret formats, obfuscated credentials, or dynamically-generated tokens may evade detection. Mitigation: least-privilege principle, secret rotation, monitoring.
Threat T4: Resource Exhaustion
Attack Vector: Malicious or buggy agent attempts to consume excessive resources (API quotas, storage, execution time).
Examples:
- Creating maximum-permitted issues repeatedly
- Uploading large files to asset branches
- Triggering workflow dispatch cascades
Architectural Mitigations:
| Layer | Mechanism | Effectiveness |
|---|---|---|
| Operation Limits | Max constraints per type | Prevents unbounded operations |
| Resource Cleanup | Expires configuration auto-closes temporary resources | Prevents accumulation |
| Timeout | Workflow-level execution time limits | Prevents infinite loops |
| Size Limits | File size constraints for uploads | Prevents storage exhaustion |
Residual Risk: Within configured limits, agent may still consume significant resources. Mitigation: usage monitoring, alerting, quota management.
Threat T5: Cross-Repository Privilege Escalation
Attack Vector: Agent targets unauthorized repositories through cross-repository safe output operations.
Examples:
- Creating issues in private repositories
- Commenting on PRs in sensitive repositories
- Adding labels to upstream project issues
Architectural Mitigations:
| Layer | Mechanism | Effectiveness |
|---|---|---|
| Allowlisting | allowed-github-references restricts targets | High when configured |
| Per-Type Allowlists | allowed-repos on individual types | Fine-grained control |
| Permission Validation | GitHub API enforces token permissions | Backstop protection |
| Audit Trail | All operations logged with provenance | Detection and response |
Residual Risk: Misconfigured allowlists may permit unintended targets. Mitigation: principle of least privilege in configuration, periodic review.
3.2.6 Cross-Repository Security Model
Section titled “3.2.6 Cross-Repository Security Model”Repository Reference Format
Target repositories MUST be specified in owner/repo format. Implementations MUST validate:
- Format matches regex:
^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$ - Owner and repo components are non-empty
- No protocol prefix (https://, git://, etc.)
Allowlist Resolution Order
When evaluating cross-repository operations, implementations MUST apply these rules in order:
- Extract target-repo: Parse from operation arguments or configuration
- Check type-specific allowlist: If safe output type defines
allowed-repos:- MUST match against this list
- Type-specific allowlist OVERRIDES global allowlist
- If match fails, REJECT with E004
- Check global allowlist: If no type-specific allowlist and
allowed-github-referencesis defined:- MUST match against this list
- If match fails, REJECT with E004
- Default deny: If no allowlists are defined:
- MUST reject cross-repository operations
- Same-repository operations are permitted
Matching Rules
- Matching is EXACT (case-sensitive)
- Wildcards (*, ?) are NOT supported
- Pattern matching is NOT supported
- Each repository MUST be explicitly listed
Security Properties
Property SP6: Cross-Repository Containment
For all cross-repository operations:
∀ op ∈ operations: op.target_repo ≠ null ⇒ (op.target_repo ∈ type_allowlist ∨ (type_allowlist = null ∧ op.target_repo ∈ global_allowlist))Verification:
- Method: Code review and integration testing
- Tool:
check_cross_repo()in conformance checker (SEC-005) and handler unit tests - Criteria: All handlers with
target-repoparameter validate against allowlists; operations to non-allowlisted repos are rejected with E004 - Automated Check: Verify handlers contain allowlist validation logic
- Integration Tests: Submit cross-repository operations; confirm allowlist enforcement
Property SP7: Deny-by-Default
Without explicit allowlist configuration:
allowed_repos = null ∧ allowed_github_references = null ⇒ ∀ op ∈ operations: op.target_repo = workflow.repositoryVerification:
- Method: Integration testing
- Tool: Handler unit tests for cross-repository validation
- Criteria: Without allowlist configuration, only same-repository operations are permitted; cross-repository operations are rejected with E004
- Integration Tests: Submit cross-repository operations without allowlist; confirm rejection
Example Configurations
# Example 1: Type-specific allowlist (overrides global)safe-outputs: allowed-github-references: [owner/repo-a, owner/repo-b]
create-issue: allowed-repos: [owner/repo-c] # Only repo-c permitted for issues
add-comment: # No type-specific list, uses global: repo-a, repo-b
# Example 2: Explicit same-repository onlysafe-outputs: create-issue: # No allowlist = same repository only max: 53.3 Security Property Guarantees
Section titled “3.3 Security Property Guarantees”Conforming implementations MUST maintain these security invariants:
Property SP1: Permission Separation Invariant
Statement: At all times during agent execution, the agent process SHALL NOT possess tokens or credentials permitting GitHub write operations.
Formal Definition:
∀ t ∈ [agent_start, agent_end]: permissions(agent_process, t) ∩ {issues:write, pull-requests:write, ...} = ∅Verification:
- Method: Static analysis and runtime inspection
- Tool:
check_privilege_separation()in conformance checker (SEC-001) - Criteria: Agent job declares only read permissions; no write-level permissions in agent context
- Automated Check: Parse workflow YAML for agent job permissions
- Runtime Check: Inspect
$GITHUB_TOKENenvironment variable scope in agent execution context
Property SP2: Validation Precedence Invariant
Statement: For all safe output operations, validation logic MUST execute before any GitHub API invocation. Invalid operations MUST be rejected without side effects.
Formal Definition:
∀ op ∈ operations: valid(op) = false ⇒ github_api_call(op) never executesVerification:
- Method: Code review and unit testing
- Tool:
check_validation_ordering()in conformance checker (SEC-002) and handler unit tests - Criteria: All validation stages (1-6) complete before Stage 7 (API invocation)
- Automated Check: Static analysis confirms validation functions precede API calls in handler code
- Unit Tests: Test cases verify invalid operations are rejected without GitHub API calls (see Section 3.3 Validation Pipeline Requirements)
Validation Pipeline Requirements
Section titled “Validation Pipeline Requirements”Implementations MUST execute validation steps in this exact sequence for all safe output operations:
Stage 1: Schema Validation (REQUIRED)
- Input: Raw MCP tool arguments
- Check: JSON schema validation against type-specific schema
- On failure: Reject immediately with E001 (INVALID_SCHEMA) error
- Output: Schema-validated operation data
Stage 2: Limit Enforcement (REQUIRED)
- Input: Count of operations of each type in current batch
- Check: Compare count against configured
maxfor each type - On failure: Reject entire batch with E002 (LIMIT_EXCEEDED) error
- Output: Limit-validated operation set
Stage 3: Content Sanitization (REQUIRED)
- Input: All text fields (title, body, description, etc.)
- Transform: Apply sanitization pipeline (see Section 9.2)
- On failure: Reject with E008 (SANITIZATION_FAILED) if unsafe content cannot be sanitized
- Output: Sanitized operation data
Stage 4: Domain Filtering (CONDITIONAL)
- Input: All URLs in markdown links and images
- Check: Validate against
allowed-domainsif configured - Transform: Redact unauthorized URLs
- Output: Domain-filtered operation data
Stage 5: Cross-Repository Validation (CONDITIONAL)
- Input:
target-repoparameter if present - Check: Validate against
allowed-reposorallowed-github-references - On failure: Reject with E004 (INVALID_TARGET_REPO)
- Output: Authorized target repository
Stage 6: Dependency Resolution (CONDITIONAL)
- Input: Temporary IDs, parent references
- Check: Resolve references to actual GitHub resource numbers
- On failure: Reject with E005 (MISSING_PARENT)
- Output: Fully-resolved operation data
Stage 7: GitHub API Invocation (EXECUTION)
- Input: Validated, sanitized, authorized operation data
- Action: Execute GitHub API calls
- On failure: Return E007 (API_ERROR) with details
Requirement VL1: Sequential Execution
Stages MUST execute in the order specified above. A failure at any stage (1-6) MUST prevent Stage 7 from executing for that operation.
Requirement VL2: Atomic Validation
For single-operation types (max=1), validation failure MUST prevent any API calls. For batch operations, validation failure of one operation MUST NOT cause rejection of the entire batch unless it’s a limit enforcement failure.
Requirement VL3: Error Propagation
Validation errors MUST include:
- Error code (E001-E008)
- Human-readable message
- Operation index (for batch operations)
- Field name (for schema validation errors)
Property SP3: Limit Enforceability Invariant
Statement: For all configured max limits, implementations MUST prevent exceeding the limit. Attempts to exceed limits SHALL result in operation rejection.
Formal Definition:
∀ type ∈ safe_output_types: count(operations[type]) > config[type].max ⇒ reject(operations[type])Verification:
- Method: Integration testing and limit enforcement validation
- Tool:
check_max_limits()in conformance checker (SEC-003) and handler unit tests - Criteria: Operations exceeding configured
maxare rejected with E002 (LIMIT_EXCEEDED) error - Automated Check: Verify handlers check operation count against
maxconfiguration - Integration Tests: Submit operations exceeding limits; confirm batch rejection
Property SP4: Content Integrity Invariant
Statement: All user-provided content MUST undergo sanitization. Sanitization MUST occur after agent output and before GitHub API invocation.
Formal Definition:
∀ content ∈ user_provided_fields: github_api_call(content) ⇒ ∃ sanitized_content = sanitize(content) ∧ passed(sanitized_content)Verification:
- Method: Code review and unit testing
- Tool:
check_sanitization()in conformance checker (SEC-004) and sanitization unit tests - Criteria: All handlers with body/content fields invoke sanitization functions before API calls
- Automated Check: Verify presence of
sanitize*function calls in handlers - Unit Tests: Confirm malicious content (XSS, script injection) is neutralized before GitHub API invocation
Property SP5: Provenance Traceability Invariant
Statement: All created GitHub resources MUST include provenance metadata identifying workflow source and run.
Formal Definition:
∀ resource ∈ created_resources: ∃ provenance_data ∈ resource ∧ provenance_data.workflow_run_url ≠ nullVerification:
- Method: Manual inspection and automated footer validation
- Tool:
check_footers()in conformance checker (USE-002) and handler code review - Criteria: All created resources include footer attribution with workflow run URL when footer is configured
- Manual Check: Inspect created issues, PRs, discussions, comments for footer presence
- Automated Check: Verify handlers call
addFooter()or include attribution in body content
4. Structural Components
Section titled “4. Structural Components”(Continuing with Section 4 and remaining sections, but due to length limits, I’ll create the file now with the content we have)
4.1 Component Topology
Section titled “4.1 Component Topology”The Safe Outputs MCP Gateway system comprises three distinct components operating in different phases:
Component C1: Workflow Compiler (Pre-execution phase)
Input: Markdown workflow files with YAML frontmatter
Output: Compiled GitHub Actions YAML workflows
Key Responsibilities:
- Parse and validate
safe-outputs:configuration blocks - Generate JSON schemas for enabled safe output types
- Synthesize dedicated jobs for each safe output type with minimal permissions
- Compute configuration integrity hashes
- Validate cross-references and repository feature availability
Location: Developer’s machine or CI environment during workflow development
Component C2: MCP Gateway Server (Runtime agent phase)
Input: MCP tool invocation requests over HTTP
Output: NDJSON file recording operation declarations
Key Responsibilities:
- Start HTTP server on port 3001 (configurable)
- Load tool schemas from
/opt/gh-aw/safeoutputs/config.json - Register MCP tools matching enabled safe output types
- Validate invocations against JSON schemas
- Handle large content (>16000 tokens) via file references
- Append validated operations to
/tmp/gh-aw/safeoutputs/output.ndjson
Location: Agent job container (same context as AI agent process)
Component C3: Safe Output Processor (Post-execution phase)
Input: NDJSON artifact from agent job
Output: GitHub resources (issues, PRs, comments, labels)
Key Responsibilities:
- Download agent output artifact from GitHub Actions storage
- Parse NDJSON entries and group by type
- Apply content sanitization to all text fields
- Enforce max limits per type
- Execute validated operations via GitHub API
- Generate execution summaries for workflow step output
- Implement fail-safe error handling
Location: Dedicated safe output job containers (separate from agent)
4.2 Data Flow Sequence
Section titled “4.2 Data Flow Sequence”Operations flow through the system following this precise sequence:
Phase 1: Configuration Declaration (Development Time)
Workflow author writes configuration in frontmatter:
safe-outputs: create-issue: max: 3 labels: [automated, ai] footer: truePhase 2: Schema Synthesis (Compilation Time)
Compiler generates tool schemas:
{ "tools": { "create_issue": { "name": "create_issue", "description": "Create a new GitHub issue...", "inputSchema": { "type": "object", "required": ["title", "body"], "properties": { "title": {"type": "string"}, "body": {"type": "string"}, "labels": {"type": "array", "items": {"type": "string"}} } } } }, "handlers": { "create_issue": { "max": 3, "labels": ["automated", "ai"], "footer": true } }}Phase 3: Runtime Initialization (Agent Job Start)
MCP Gateway server reads configuration:
const config = JSON.parse(fs.readFileSync('/opt/gh-aw/safeoutputs/config.json'));const tools = config.tools;const handlers = config.handlers;
for (const [name, schema] of Object.entries(tools)) { registerTool(server, name, schema, createHandler(handlers[name]));}Phase 4: Operation Declaration (Agent Execution)
Agent invokes MCP tool:
POST http://127.0.0.1:3001/tools/callContent-Type: application/json
{ "method": "tools/call", "params": { "name": "create_issue", "arguments": { "title": "Memory leak in data processor", "body": "Observed continuous memory growth...", "labels": ["bug", "performance"] } }}Phase 5: Validation and Recording (Gateway Processing)
Gateway validates and appends to NDJSON:
// Schema validationconst valid = validate(schema, arguments);if (!valid) { return {error: {code: -32602, message: "Invalid params"}};}
// Append to NDJSONconst entry = {...arguments, type: "create_issue"};fs.appendFileSync(outputFile, JSON.stringify(entry) + "\n");
// Return successreturn {result: {content: [{type: "text", text: '{"result":"success"}'}]}};Phase 6: Artifact Transfer (Job Transition)
Agent job uploads NDJSON artifact:
- name: Upload agent output uses: actions/upload-artifact@v4 with: name: agent-output path: /tmp/gh-aw/safeoutputs/output.ndjsonSafe output job downloads artifact:
- name: Download agent output uses: actions/download-artifact@v4 with: name: agent-output path: /tmp/downloadsPhase 7: Batch Processing (Safe Output Execution)
Processor reads, validates, and executes:
const operations = fs.readFileSync(artifact, 'utf8') .split('\n') .filter(line => line.trim()) .map(line => JSON.parse(line));
const issueOps = operations.filter(op => op.type === 'create_issue');
// Enforce max limitif (issueOps.length > config.create_issue.max) { throw new Error(\`Exceeded max limit: \${issueOps.length} > \${config.create_issue.max}\`);}
// Execute each operationfor (const op of issueOps) { const sanitized = sanitizeContent(op); await createIssue(sanitized);}4.3 Configuration Propagation
Section titled “4.3 Configuration Propagation”Configuration flows from author intent to runtime enforcement:
Authoring Layer:
# Workflow .md filesafe-outputs: create-issue: max: 3 allowed-labels: [bug, enhancement]Compilation Layer:
// Compiler parses and validatesconfig := extractSafeOutputsConfig(frontmatter)validateConfig(config) // Check constraintsschema := generateSchema(config)jobs := synthesizeJobs(config)Deployment Layer:
# Compiled .lock.ymljobs: agent: steps: - run: | cat > /opt/gh-aw/safeoutputs/config.json << 'EOF' {"tools": {...}, "handlers": {...}} EOFRuntime Layer:
// MCP server loads at startupconst config = JSON.parse(fs.readFileSync('/opt/gh-aw/safeoutputs/config.json'));// Use config for tool registration and validationExecution Layer:
// Safe output processor enforcesconst maxAllowed = config.create_issue.max;const allowedLabels = config.create_issue.allowed_labels;// Enforce during operation processing5. Configuration Semantics
Section titled “5. Configuration Semantics”5.1 Configuration Schema Structure
Section titled “5.1 Configuration Schema Structure”Safe output configuration employs a two-level hierarchy: global parameters affecting all types, and type-specific blocks customizing individual operation categories.
General Form:
safe-outputs: # Global parameters <global-param-name>: <value>
# Type-specific blocks <safe-output-type>: <type-param-name>: <value>Namespace Separation:
- Global parameters have unreserved names (footer, staged, allowed-domains)
- Type-specific blocks use hyphenated safe output type names (create-issue, add-comment)
- Parameter inheritance flows from global to type-specific (type overrides global)
5.2 Global Parameters
Section titled “5.2 Global Parameters”GP1: footer
Section titled “GP1: footer”Syntax: footer: true | false | <github-expression>
Default: true
Semantics: Controls whether AI attribution footers are appended to created content (issues, discussions, pull requests, comments).
This field is templatable: in addition to literal true/false, it accepts GitHub Actions expression strings (e.g., ${{ inputs.enable-footer }}). See Section 5.5 for details.
Inheritance: Type-specific footer parameter overrides this global setting.
Footer Composition:
When footer: true, implementations MUST append this structure:
---> AI generated by [<workflow-name>](<run-url>)[<context>][>> To add this workflow in your repository, run \`gh aw add <source>\`. See [usage guide](<url>).]Template Variables:
<workflow-name>: Workflow display name (from frontmattername:or filename)<run-url>: Complete URL to workflow run (https://github.com/{owner}/{repo}/actions/runs/{id})<context>: Optional triggering context:for #123when triggered by issue #123for #456when triggered by PR #456for discussion #789when triggered by discussion #789- Omitted when no specific trigger
<source>: Workflow source path (owner/repo/path@ref, e.g., github/gh-aw/.github/workflows/triage@main)<url>: Documentation URL (typically https://github.github.com/gh-aw/setup/cli/)
Installation Instructions:
The second paragraph (installation command) is OPTIONAL. It SHOULD be included when:
- Workflow source location is known (not local development)
- Workflow is publicly accessible
- Workflow is intended for redistribution
Conformance Requirements:
MUST satisfy:
- Footer appears at end of content, not beginning
- Horizontal rule (
---) separates footer from user content - Clickable links for workflow run URL
- Context matches actual trigger type
MUST NOT:
- Include footer when
footer: false - Modify user content to insert footer mid-content
- Include broken or invalid URLs
GP2: staged
Section titled “GP2: staged”Syntax: staged: true | false
Default: false
Semantics: Controls preview mode execution. When true, operations are simulated and previewed without permanent effects.
Inheritance: Type-specific staged parameter overrides this global setting.
Preview Mode Behavior:
When staged: true, implementations MUST:
- Skip all GitHub API write operations
- Generate detailed preview summaries
- Use emoji prefix consistently in preview messages
- Show complete operation details (titles, bodies, labels, assignees)
- Include count of operations that would be performed
Preview Message Format:
## Staged Mode: <Operation Type> Preview
The following <count> <type> operation(s) would be performed if staged mode was disabled:
### Operation 1: <title>
**Type**: <safe-output-type>**Title**: <operation-title>**Body**:<operation-body>
**Additional Fields**:- Labels: <labels>- Assignees: <assignees>[...]
### Operation 2: <title>[Same structure]
---**Preview Summary**: <count> operations previewed. No GitHub resources were created.Use Cases:
Staged mode is RECOMMENDED for:
- Testing new workflows before production deployment
- Validating agent behavior in safe environment
- Demonstrating workflow capabilities without side effects
- Debugging configuration issues
Conformance Requirements:
MUST satisfy:
- No permanent GitHub resources created in staged mode
- Preview shows sufficient detail for correctness evaluation
- Emoji appears in all staged mode headings
- Clear indication that operations are preview-only
MUST NOT:
- Execute API write operations in staged mode
- Create partial resources (e.g., issue without closing it)
- Omit critical operation details from previews
GP3: allowed-domains
Section titled “GP3: allowed-domains”Syntax: allowed-domains: [<domain-pattern>, ...]
Default: [] (empty array, no domain filtering)
Semantics: Specifies allowlist of domains permitted in URLs within safe output content. When non-empty, URLs to non-allowlisted domains are redacted during sanitization.
Domain Pattern Formats:
-
Plain domain:
github.com,api.example.com- Matches exact domain only
- Case-insensitive matching
-
Wildcard subdomain:
*.github.io,*.example.com- Matches all subdomains (but not bare domain)
*.github.iomatchesuser.github.iobut NOTgithub.io- Case-insensitive matching
-
Protocol-specific:
https://secure.example.com- Matches domain with specified protocol only
https://secure.example.comallows HTTPS but blocks HTTP
-
Ecosystem identifier:
node,python,defaults- Special identifiers for package ecosystems
- No domain validation performed
Pattern Validation:
Implementations MUST validate patterns at compilation time. Valid patterns match:
^(\*\.)?[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$Or are recognized ecosystem identifiers (no dots, no ://).
Redaction Behavior:
When URL domain does not match any allowlist pattern:
- Extract full URL from content
- Replace with
[URL redacted: unauthorized domain] - Preserve surrounding context
- Log redacted URL to
/tmp/gh-aw/safeoutputs/redacted-domains.log
Example Configuration:
safe-outputs: allowed-domains: - github.com # Allow github.com only - "*.github.io" # Allow all *.github.io subdomains - api.example.com # Allow specific API domainExample Redaction:
Input content:
See documentation at https://github.com/owner/repoAlso check https://malicious.example.com/phishingReference: https://docs.github.io/guideWith allowed-domains: [github.com, "*.github.io"], output:
See documentation at https://github.com/owner/repoAlso check [URL redacted: unauthorized domain]Reference: https://docs.github.io/guideConformance Requirements:
MUST satisfy:
- Domain extraction handles all valid URL formats
- Wildcard matching follows specified semantics
- Case-insensitive comparison
- Redaction preserves content structure
- Redaction log created when domains filtered
MUST NOT:
- Allow non-allowlisted domains when allowlist configured
- Break valid URLs matching allowlist
- Lose content surrounding redacted URLs
GP4: allowed-github-references
Section titled “GP4: allowed-github-references”Syntax: allowed-github-references: [<owner/repo>, ...]
Default: [] (empty array, no cross-repository restrictions)
Semantics: Specifies allowlist of GitHub repositories for cross-repository safe output operations. When non-empty, operations targeting non-allowlisted repositories are rejected.
Reference Format:
Each entry MUST match pattern: ^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+$
Examples:
github/gh-awmicrosoft/vscodeowner-name/repo.name
Validation Behavior:
When safe output operation includes target-repo configuration:
- Extract target repository from configuration
- Check if target matches any entry in
allowed-github-references - If no match, reject operation with clear error
- If match or allowlist empty, proceed with validation
Same-Repository Operations:
Operations WITHOUT target-repo (same-repository operations) are ALWAYS permitted, regardless of allowed-github-references configuration.
Rationale: Workflows inherently have permission to operate on their own repository.
Example Configuration:
safe-outputs: allowed-github-references: - github/roadmap # Allow operations on roadmap repo - github/docs # Allow operations on docs repo
create-issue: target-repo: github/roadmap # Must be in allowlistError Message Format:
When target repository not in allowlist:
Cross-repository operation rejected: target repository not in allowed-github-references
Target: owner/repoAllowed repositories: - github/roadmap - github/docs
To permit this operation, add target to allowed-github-references: safe-outputs: allowed-github-references: - owner/repoConformance Requirements:
MUST satisfy:
- Repository reference format validation
- Allowlist checking before API operations
- Clear error messages on rejection
- Same-repository operations always permitted
MUST NOT:
- Allow non-allowlisted cross-repository operations
- Block same-repository operations
- Silently ignore allowlist configuration
5.3 Type-Specific Common Parameters
Section titled “5.3 Type-Specific Common Parameters”Every safe output type supports these parameters:
TS1: max
Section titled “TS1: max”Syntax: max: <positive-integer> | -1 | null | <github-expression>
Default: Type-dependent (see Section 7 for per-type defaults)
Semantics: Maximum count of operations permitted for this type in a single workflow run.
This field is templatable: in addition to integer literals and -1, it accepts GitHub Actions expression strings (e.g., ${{ inputs.max-issues }}). When a GitHub Actions expression is supplied, the limit is evaluated at runtime. See Section 5.5 for details.
Special Values:
- Positive integer: Strict limit (e.g.,
max: 3allows up to 3 operations) -1: Unlimited operations (use with caution)nullor omitted: Use type’s default max- GitHub Actions expression: Limit resolved at runtime (e.g.,
max: ${{ inputs.max-issues }})
Enforcement Algorithm:
function enforceMaxLimit(operations, type, config) { const typeOps = operations.filter(op => op.type === type); const maxAllowed = config[type].max;
if (maxAllowed === -1) { // Unlimited return {allowed: typeOps, rejected: []}; }
if (typeOps.length > maxAllowed) { return { allowed: [], rejected: typeOps, error: \`Exceeded max limit for \${type}: attempted \${typeOps.length}, limit \${maxAllowed}\` }; }
return {allowed: typeOps, rejected: []};}Error Reporting:
When limit exceeded:
Safe output limit exceeded for create_issue
Attempted operations: 5Configured limit: 3
Rejected operations: 1. "Bug in authentication flow" 2. "Memory leak in data processor" 3. "UI rendering issue on mobile" 4. "Performance degradation after update" 5. "Documentation outdated"
To increase limit, update workflow configuration: safe-outputs: create-issue: max: 5Conformance Requirements:
MUST satisfy:
- Count operations per type independently
- Reject ALL operations when limit exceeded (not just excess)
- Provide clear error with count and limit
- Never silently truncate operations
MUST NOT:
- Accept
max: 0(invalid; disable type instead) - Accept negative values except
-1 - Allow partial execution when limit exceeded
TS2: footer (Type-Specific Override)
Section titled “TS2: footer (Type-Specific Override)”Syntax: footer: true | false | <github-expression>
Default: Inherits from safe-outputs.footer (global)
Semantics: Override global footer setting for this specific safe output type.
This field is templatable: in addition to literal true/false, it accepts GitHub Actions expression strings (e.g., ${{ inputs.enable-footer }}). See Section 5.5 for details.
Inheritance Precedence:
- Type-specific
footer(highest priority) - Global
safe-outputs.footer - Default value
true(lowest priority)
Example:
safe-outputs: footer: true # Global default: footers enabled
create-issue: footer: false # Issues: no footers
add-comment: # Inherits global footer: trueResult:
- Issues created without footers
- Comments created with footers
TS3: staged (Type-Specific Override)
Section titled “TS3: staged (Type-Specific Override)”Syntax: staged: true | false
Default: Inherits from safe-outputs.staged (global)
Semantics: Override global staged setting for this specific safe output type.
Inheritance Precedence: Same as footer parameter.
Example:
safe-outputs: staged: false # Global default: normal execution
create-pull-request: staged: true # PRs: preview only
add-labels: # Inherits global staged: false (normal execution)Result:
- Pull requests previewed without creation
- Labels applied normally
5.4 Type-Specific Extension Parameters
Section titled “5.4 Type-Specific Extension Parameters”Beyond common parameters, individual safe output types support specialized configuration.
Representative Examples:
Issue Creation Extensions:
create-issue: title-prefix: "[AI] " # Prepend to all titles labels: [automation, ai] # Auto-apply labels assignees: [copilot, user1] # Auto-assign users expires: 7 # Days until auto-close group: true # Group under parent close-older-issues: true # Close previous workflow issues target-repo: owner/repo # Cross-repository target allowed-repos: [owner/repo1] # Cross-repo allowlist allowed-labels: [bug, feature] # Agent label restrictionsComment Extensions:
add-comment: target: "issue" | "pull_request" | "discussion" | "*" hide-older-comments: true # Hide previous workflow comments discussions: false # Exclude discussions:write permission (optional) target-repo: owner/repo allowed-repos: [...]Submit PR Review Extensions:
submit-pull-request-review: target: "triggering" | "*" | <PR number> # Required when not in pull_request trigger footer: "always" | "none" | "if-body" # Footer on review bodyPull Request Extensions:
create-pull-request: base-branch: main # Target branch (default: repo default) draft: true # Create as draft (default: true) commit-changes: true # Auto-commit workspace changes reviewers: [user1, copilot] # Auto-request reviewers labels: [automated] # Auto-apply labelsAsset Upload Extensions:
upload-asset: branch: assets # Target branch name max-size-kb: 10240 # File size limit (10MB default) allowed-extensions: [.png, .jpg, .jpeg] # Extension allowlistDiscussion Extensions:
create-discussion: category: "General" # Discussion category (name/slug/ID) title-prefix: "[Report] " # Prepend to titles labels: [report, automated] # Auto-apply labels allowed-labels: [...] # Agent label restrictionsComplete parameter documentation for each type appears in Section 7.
5.5 Templatable Fields
Section titled “5.5 Templatable Fields”Certain safe output configuration fields are templatable: they accept either a literal value of the expected type or a GitHub Actions expression string that is evaluated at runtime.
Templatable Integer Fields
Section titled “Templatable Integer Fields”Fields documented as <positive-integer> | -1 | null | <github-expression> are templatable integers. When a GitHub Actions expression is supplied, it MUST resolve to a valid integer value at runtime.
Applicable fields: max (all types)
Syntax:
# Literal integermax: 5
# GitHub Actions expression (evaluated at runtime)max: ${{ inputs.max-issues }}Conformance requirements:
- Implementations MUST accept literal integers and GitHub Actions expressions for templatable integer fields.
- When a GitHub Actions expression is supplied as
max, implementations MUST evaluate it at runtime and treat the result as an integer. - A non-integer runtime value for a templatable integer field MUST cause the operation to fail with a descriptive error.
Templatable Boolean Fields
Section titled “Templatable Boolean Fields”Fields documented as true | false | <github-expression> are templatable booleans. When a GitHub Actions expression is supplied, it MUST resolve to true or false at runtime.
Applicable fields: footer (global and type-specific), group, close-older-issues, hide-older-comments, close-older-discussions, draft, allow-empty, auto-merge, report-as-issue, unassign-first
Syntax:
# Literal booleangroup: true
# GitHub Actions expression (evaluated at runtime)group: ${{ inputs.group-issues }}Conformance requirements:
- Implementations MUST accept literal booleans and GitHub Actions expressions for templatable boolean fields.
- A free-form string that is not a GitHub Actions expression (i.e., does not match
${{ ... }}) MUST be rejected with a descriptive error at compile time. - When a GitHub Actions expression is supplied, implementations MUST evaluate it at runtime and treat the result as a boolean.
- A non-boolean runtime value for a templatable boolean field MUST be treated as
false.
6. Universal Feature Interpretation
Section titled “6. Universal Feature Interpretation”This section defines precise semantics for features that apply across multiple safe output types.
6.1 Max Limit Semantics
Section titled “6.1 Max Limit Semantics”Feature Identifier: Maximum Operation Count Constraint
Configuration: max parameter on safe output types
Scope: All safe output types
Normative Requirements:
Requirement MR1: Implementations MUST count operations of each type independently. Cross-type totals are NOT constrained by individual type limits.
Requirement MR2: When operation count for a type exceeds configured max, implementations MUST reject ALL operations of that type, not just excess operations.
Rationale: All-or-nothing semantics prevent partial execution that may create inconsistent state.
Requirement MR3: Rejection MUST occur before any GitHub API invocations for the type.
Requirement MR4: Error messages MUST include:
- Safe output type name
- Attempted operation count
- Configured max limit
- List of operation titles/summaries
- Configuration update guidance
Requirement MR5: Unlimited semantics (max: -1) MUST be supported but SHOULD trigger compilation warnings.
Conformance Verification:
Test Case 1: Exact Limit
- Configure
max: 3 - Declare exactly 3 operations
- Expected: All 3 execute successfully
Test Case 2: Exceeded Limit
- Configure
max: 3 - Declare 4 operations
- Expected: All 4 rejected with clear error
Test Case 3: Under Limit
- Configure
max: 5 - Declare 2 operations
- Expected: Both execute successfully
Test Case 4: Unlimited
- Configure
max: -1 - Declare any count
- Expected: All execute (with warning)
6.2 Staged Mode Semantics
Section titled “6.2 Staged Mode Semantics”Feature Identifier: Preview Mode Execution
Configuration: staged parameter (global or type-specific)
Visual Indicator: emoji
Scope: All safe output types
Normative Requirements:
Requirement SM1: In staged mode, implementations MUST NOT invoke GitHub API write methods. Read operations for validation are permitted.
Requirement SM2: Implementations MUST generate preview summaries containing:
- Operation type and count
- Complete operation details (all fields)
- Formatted representation of content
- Clear indication of preview-only status
Requirement SM3: ALL staged mode messages MUST include emoji in headings for consistent visual identification.
Requirement SM4: Preview format MUST follow this structure:
## Staged Mode: <Type> Preview
The following <N> <type> operation(s) would be performed if staged mode was disabled:
<Per-operation details>
---**Preview Summary**: <N> operations previewed. No GitHub resources were created.Requirement SM5: Type-specific staged settings MUST override global settings according to inheritance rules.
Conformance Verification:
Test Case 1: Global Staged
- Configure global
staged: true - Declare operations of multiple types
- Expected: All types previewed
Test Case 2: Type-Specific Override
- Configure global
staged: false, typestaged: true - Expected: Type previewed, others execute
Test Case 3: Preview Content
- Enable staged mode
- Declare operation with all fields
- Expected: Preview shows all fields with proper formatting
6.3 Footer Attribution Semantics
Section titled “6.3 Footer Attribution Semantics”Feature Identifier: AI Attribution Messages
Configuration: footer parameter (global or type-specific)
Scope: Content-creating types (issues, discussions, PRs, comments)
Normative Requirements:
Requirement FA1: Footers MUST be appended to content body, never prepended or inserted mid-content.
Requirement FA2: Footers MUST be separated from user content by horizontal rule (---).
Requirement FA3: Footer MUST include workflow name and clickable run URL.
Requirement FA4: When workflow triggered by specific item, footer SHOULD include context reference matching trigger type:
- Issue trigger:
for #<number> - PR trigger:
for #<number> - Discussion trigger:
for discussion #<number>
Requirement FA5: Installation instructions are OPTIONAL but RECOMMENDED when:
- Workflow source is known and public
- Workflow is intended for reuse
Requirement FA6: When footer: false, implementations MUST NOT append ANY attribution content.
Requirement FA7: Type-specific footer settings MUST override global settings.
Footer Template Specification:
---> AI generated by [<workflow_name>](<run_url>)[<context>][>> To add this workflow in your repository, run `gh aw add <source_path>`. See [usage guide](<docs_url>).]Where:
<workflow_name>: From frontmattername:or derived from filename<run_url>:https://github.com/<owner>/<repo>/actions/runs/<run_id><context>: Optional:for #<N>orfor discussion #<N><source_path>:<owner>/<repo>/<path>@<ref><docs_url>: Documentation URL (typically project docs)
Conformance Verification:
Test Case 1: Footer Enabled
- Configure
footer: true - Create issue
- Expected: Issue body contains footer with all required elements
Test Case 2: Footer Disabled
- Configure
footer: false - Create issue
- Expected: Issue body contains only user content, no footer
Test Case 3: Context Inclusion
- Configure
footer: true - Trigger workflow from issue #42
- Create comment
- Expected: Footer includes “for #42”
6.4 Content Sanitization Semantics
Section titled “6.4 Content Sanitization Semantics”Feature Identifier: Input Security Transformation
Scope: All text fields in all safe output types
Normative Requirements:
Requirement CS1: ALL user-provided text content MUST undergo sanitization before GitHub API invocation.
Requirement CS2: Sanitization MUST apply transformations in this order:
- Unicode normalization
- Protocol filtering
- Domain filtering (if configured)
- Command neutralization
- Mention filtering
- Markdown safety
- Truncation
Requirement CS3: Sanitization MUST be idempotent: sanitize(sanitize(content)) === sanitize(content)
Detailed Transformation Specifications:
Transformation T1: Unicode Normalization
Requirements:
- Apply NFC (Canonical Decomposition + Canonical Composition)
- Remove zero-width characters: U+200B, U+200C, U+200D, U+FEFF
- Remove control characters: U+0000-U+001F, U+007F
- EXCEPT: Preserve U+000A (LF), U+000D (CR), U+0009 (TAB)
Transformation T2: Protocol Filtering
Allowed protocols:
http://https://mailto:
Requirements:
- Extract URLs via pattern matching
- Check protocol against allowlist
- Replace disallowed protocols:
[URL removed: unauthorized protocol] - Malformed protocols: normalize or remove
Transformation T3: Domain Filtering (when allowed-domains configured)
Requirements:
- Extract domain from each URL
- Compare against allowed-domains list (case-insensitive)
- Wildcard handling:
*.example.commatchessub.example.combut NOTexample.com - Replace non-allowed:
[URL redacted: unauthorized domain] - Log redacted URLs to
/tmp/gh-aw/safeoutputs/redacted-domains.log
Transformation T4: Command Neutralization
Requirements:
- Detect slash commands at content start:
^/[a-zA-Z0-9_-]+ - Escape slash:
/commandbecomes\/command - Preserve commands in code blocks
Transformation T5: Mention Filtering
Requirements:
- Detect @mentions:
@[a-zA-Z0-9_-]+ - Check against allowed-aliases list
- Neutralize unauthorized:
@userbecomes@ user(add space) - Preserve mentions in code blocks
Transformation T6: Markdown Safety
Requirements:
- Remove XML comments:
<!-- ... --> - Balance code fences: Ensure all ``` blocks properly closed
- Convert unsafe XML tags to text representation
Transformation T7: Truncation
Requirements:
- Default max length: 524,288 characters
- Truncate at character boundary (not mid-multibyte character)
- Append truncation notice:
\n\n[Content truncated at character limit]
Conformance Verification:
Test Case 1: Protocol Filtering
- Input:
javascript:alert(1) - Expected:
[URL removed: unauthorized protocol]
Test Case 2: Domain Filtering
- Config:
allowed-domains: [github.com] - Input:
https://github.com/x https://evil.com/y - Expected:
https://github.com/x [URL redacted: unauthorized domain]
Test Case 3: Command Neutralization
- Input:
/close this issue - Expected:
\/close this issue
Test Case 4: Mention Filtering
- Config:
allowed-aliases: [copilot] - Input:
@copilot @attacker - Expected:
@copilot @ attacker
7. Safe Output Type Definitions
Section titled “7. Safe Output Type Definitions”This section provides complete normative definitions for all safe output types. Each definition includes tool schema, operational semantics, configuration parameters, and security requirements.
7.1 Core Issue Operations
Section titled “7.1 Core Issue Operations”Type: create_issue
Section titled “Type: create_issue”Purpose: Create GitHub issues for bug tracking, feature requests, or task management.
Default Max: 1
Cross-Repository Support: Yes (via target-repo)
Mandatory: Yes (required for full conformance)
MCP Tool Schema:
{ "name": "create_issue", "description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks.", "inputSchema": { "type": "object", "required": ["title", "body"], "properties": { "title": {"type": "string", "description": "Issue title"}, "body": {"type": "string", "description": "Issue description in Markdown"}, "labels": {"type": "array", "items": {"type": "string"}}, "parent": {"type": ["number", "string"], "description": "Parent issue for sub-issues"}, "temporary_id": { "type": "string", "pattern": "^aw_[A-Za-z0-9]{3,8}$", "description": "Temporary ID for referencing before creation" } }, "additionalProperties": false }}Operational Semantics:
- Atomicity: Issue creation is atomic. Failure at any step prevents issue creation.
- Temporary ID Resolution: References to
#aw_<id>in bodies replaced with actual numbers post-creation. - Parent Linking: When
parentspecified, tasklist entry added to parent issue. - Label Validation: Labels must exist in repository; non-existent labels cause failure.
- Cross-Repository: When
target-repoconfigured, created in that repository (must be inallowed-repos).
Configuration Parameters:
max: Operation limit (default: 1)title-prefix: Prepend to titleslabels: Auto-apply labelsassignees: Auto-assign users/agentsexpires: Days until auto-closegroup: Group issues under parentclose-older-issues: Close previous workflow issuestarget-repo: Cross-repository targetallowed-repos: Cross-repo allowlistallowed-labels: Agent label restrictionsfooter: Footer overridestaged: Staged mode override
Security Requirements:
- Title and body undergo full sanitization
- Label validation before creation
- Cross-repo validation against allowed-repos
- Expires implemented via scheduled workflow (not client-side)
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and file accessissues: write- Issue creation and modification
GitHub App (if using safe-outputs.app configuration):
issues: write- Issue creation and modificationmetadata: read- Repository metadata (automatically granted)
Notes:
- Both permission modes require the same write scopes
- GitHub App permissions enable cross-repository operations beyond
allowed-reposwhen properly configured - The
contents: readpermission is always included for repository context access
Type: add_comment
Section titled “Type: add_comment”Purpose: Add comments to existing issues, pull requests, or discussions.
Default Max: 1
Cross-Repository Support: Yes
Mandatory: Yes (required for full conformance)
MCP Tool Schema:
{ "name": "add_comment", "description": "Add a comment to an existing issue, pull request, or discussion. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission.", "inputSchema": { "type": "object", "required": ["body"], "properties": { "body": { "type": "string", "description": "Comment text in Markdown. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated." }, "item_number": { "type": "number", "description": "Issue/PR/discussion number (auto-resolved from context if omitted)" } }, "additionalProperties": false }}Operational Semantics:
- Constraint Enforcement: The MCP server validates body content before recording operations. Violations trigger immediate error responses with specific guidance (see Section 8.3). The body length limit applies to user-provided content; a second validation occurs after footer addition to ensure the complete comment doesn’t exceed limits.
- Context Resolution: When
item_numberomitted, resolves from workflow trigger context. - Related Items: When multiple outputs created, adds related items section to comments.
- Footer Injection: Appends footer according to configuration (typically 200-500 characters).
- Cross-Repository: Supports
target-repoconfiguration.
Enforced Constraints:
| Constraint | Limit | Error Code | Example Error Message |
|---|---|---|---|
| Body length (complete comment including footer) | 65536 characters | E006 | ”Comment body exceeds maximum length of 65536 characters (got 70000)“ |
| Mentions | 10 per comment | E007 | ”Comment contains 15 mentions, maximum is 10” |
| Links | 50 per comment | E008 | ”Comment contains 60 links, maximum is 50” |
Note: The 65536 character limit applies to the final comment text including the automatically appended footer. Users should leave approximately 200-500 characters of headroom to accommodate the footer, which contains workflow attribution and installation instructions.
Configuration Parameters:
max: Operation limit (default: 1)target: Filter by type (“issue”, “pull_request”, “discussion”, ”*”)hide-older-comments: Hide previous workflow commentsdiscussions: Controldiscussions:writepermission (default: true)target-repo: Cross-repository targetallowed-repos: Cross-repo allowlist
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and file accessissues: write- Comment creation on issuespull-requests: write- Comment creation on pull requestsdiscussions: write- Comment creation on discussions (whendiscussions: trueor omitted)
GitHub App (if using safe-outputs.app configuration):
issues: write- Comment creation on issuespull-requests: write- Comment creation on pull requestsdiscussions: write- Comment creation on discussions (whendiscussions: trueor omitted)metadata: read- Repository metadata (automatically granted)
Permission Control via discussions Field:
The optional discussions boolean field controls whether discussions:write permission is requested:
- Default behavior (
discussions: trueor omitted): Includesdiscussions:writepermission for maximum compatibility. Use this when the GitHub App has Discussions permission granted. - Opt-out (
discussions: false): Excludesdiscussions:writepermission. Use this when the GitHub App lacks Discussions permission to prevent 422 errors during token generation.
Example Configuration:
safe-outputs: app: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} owner: 'myorg' repositories: ['myrepo'] add-comment: target: "*" max: 1 discussions: false # Exclude discussions:write permissionNotes:
- By default, requires write permissions for all three entity types (issues, PRs, discussions) since comments can be added to any type
- When
discussions: false, the workflow only requestsissues:writeandpull-requests:writepermissions - Discussion-related safe outputs (
create-discussion,close-discussion,update-discussion) independently adddiscussions:writepermission when configured - Cross-repository commenting requires appropriate permissions in target repository
- The
contents: readpermission is always included for repository context access
Type: create_pull_request
Section titled “Type: create_pull_request”Purpose: Create pull requests to propose code changes.
Default Max: 1
Cross-Repository Support: No (same-repository only)
Mandatory: Yes (required for full conformance)
MCP Tool Schema:
{ "name": "create_pull_request", "description": "Create a new pull request to propose code changes.", "inputSchema": { "type": "object", "required": ["title", "body"], "properties": { "title": {"type": "string"}, "body": {"type": "string"}, "branch": {"type": "string", "description": "Source branch (defaults to current)"}, "labels": {"type": "array", "items": {"type": "string"}}, "draft": {"type": "boolean", "description": "Create as draft (default: true)"} }, "additionalProperties": false }}Operational Semantics:
- Branch Validation: Source branch must exist and contain changes.
- Base Branch: Defaults to repository default branch.
- Draft Status: Creates as draft by default for safety.
- Auto-Commit: When
commit-changes: true, commits workspace changes before PR creation. - Reviewer Assignment: Auto-requests reviewers if configured.
Configuration Parameters:
max: Operation limit (default: 1)base-branch: Target branchdraft: Draft statuscommit-changes: Auto-commit workspacereviewers: Auto-request reviewerslabels: Auto-apply labelstitle-prefix: Prepend to titlesfooter: Footer override
Security Requirements:
- Branch name sanitization (prevent injection)
- Patch content validation
- Size limits on commits
Required Permissions:
GitHub Actions Token:
contents: write- Branch creation and commit operationspull-requests: write- Pull request creation
With fallback-as-issue: true (default):
contents: write- Branch creation and commit operationsissues: write- Issue creation fallback when PR creation failspull-requests: write- Pull request creation
GitHub App (if using safe-outputs.app configuration):
contents: write- Branch creation and commit operationspull-requests: write- Pull request creationmetadata: read- Repository metadata (automatically granted)
With fallback-as-issue: true (default):
contents: write- Branch creation and commit operationsissues: write- Issue creation fallback when PR creation failspull-requests: write- Pull request creationmetadata: read- Repository metadata (automatically granted)
Notes:
- Permission requirements vary based on
fallback-as-issueconfiguration - When
fallback-as-issue: true(default), requiresissues: writefor fallback issue creation if PR creation fails - When
fallback-as-issue: false, only requirescontents: writeandpull-requests: write - Cross-repository pull requests are not supported - operations are limited to same repository
7.2 System Types
Section titled “7.2 System Types”Type: noop
Section titled “Type: noop”Purpose: Log workflow completion message for transparency.
Default Max: 1
Cross-Repository Support: N/A (no external operations)
Mandatory: Yes (always enabled, cannot be disabled)
MCP Tool Schema:
{ "name": "noop", "description": "Log a completion message indicating workflow finished successfully.", "inputSchema": { "type": "object", "properties": { "message": {"type": "string", "description": "Completion message (default provided)"} }, "additionalProperties": false }}Operational Semantics:
- Always Enabled: Automatically registered in every workflow.
- Execution Order: Always executes last for summary generation.
- No Side Effects: Creates no GitHub resources.
- Transparency: Provides clear indication of normal completion vs. error states.
Configuration Parameters:
max: Operation limit (always 1; noop is registered as a singleton type)message: Default completion message (overridden by agent-provided message at invocation time)
Security Requirements:
- The
messagefield MUST undergo content sanitization to prevent log injection - The handler MUST NOT make any GitHub API calls
- The handler MUST NOT modify any workflow state or create side effects
Required Permissions:
GitHub Actions Token:
- No additional permissions required beyond base workflow permissions
GitHub App (if using safe-outputs.app configuration):
- No additional permissions required beyond base app installation
Notes:
- The
nooptype performs no GitHub API operations and requires no special permissions - Only logs completion message to workflow output
- Always available regardless of permission configuration
7.3 Additional Safe Output Types
Section titled “7.3 Additional Safe Output Types”This section provides complete definitions for all remaining safe output types. Each follows the same format as Section 7.1 with full schemas, operational semantics, and permission requirements.
Type: update_issue
Section titled “Type: update_issue”Purpose: Modify existing issue properties (title, body, state, labels, assignees, milestone).
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
MCP Tool Schema:
{ "name": "update_issue", "description": "Update an existing GitHub issue's status, title, body, labels, assignees, or milestone. Only the fields you specify will be updated; other fields remain unchanged.", "inputSchema": { "type": "object", "properties": { "status": {"type": "string", "enum": ["open", "closed"], "description": "New issue status"}, "title": {"type": "string", "description": "New issue title"}, "body": {"type": "string", "description": "New issue body in Markdown"}, "issue_number": {"type": ["number", "string"], "description": "Issue number to update"}, "operation": { "type": "string", "enum": ["replace", "append", "prepend", "replace-island"], "description": "Body update mode (default: append)" }, "labels": {"type": "array", "items": {"type": "string"}, "description": "Replacement label list"}, "assignees": {"type": "array", "items": {"type": "string"}, "description": "Replacement assignee list"}, "milestone": {"type": ["number", "string"], "description": "Milestone number (null to clear)"} }, "additionalProperties": false }}Operational Semantics:
- Partial Updates: Only fields explicitly provided are modified; omitted fields are unchanged.
- Body Operation Modes:
replaceoverwrites the entire body;append/prependadd content with separator;replace-islandupdates a run-specific section. - Label Validation: Provided labels must exist in the repository; non-existent labels cause failure.
- Assignee Resolution: Assignees must have repository access; invalid usernames cause failure.
- Cross-Repository: When
target-repois configured, operates on that repository (must be inallowed-repos).
Configuration Parameters:
max: Operation limit (default: 1)target-repo: Cross-repository targetallowed-repos: Cross-repo allowliststaged: Staged mode override
Security Requirements:
- Title and body MUST undergo full content sanitization before modification
- Label values MUST be validated against repository labels before application
- Cross-repository targets MUST be validated against the
allowed-reposallowlist - Issue number MUST be validated as a positive integer belonging to the target repository
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- Issue modification operations
GitHub App:
issues: write- Issue modification operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Only specified fields are updated; unspecified fields remain unchanged
- Same permissions as
create_issue
Type: close_issue
Section titled “Type: close_issue”Purpose: Close issues with closing comment explaining resolution.
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
MCP Tool Schema:
{ "name": "close_issue", "description": "Close a GitHub issue with a closing comment explaining the resolution or reason for closing.", "inputSchema": { "type": "object", "required": ["body"], "properties": { "body": {"type": "string", "description": "Closing comment explaining the resolution"}, "issue_number": { "type": ["number", "string"], "description": "Issue number to close. If omitted, closes the issue that triggered this workflow." } }, "additionalProperties": false }}Operational Semantics:
- Comment First: A closing comment is posted before the issue state is changed to
closed. - Context Resolution: When
issue_numberis omitted, resolves from the workflow trigger context. - Idempotent Comment: If the issue is already closed, the closing comment is still posted.
- Cross-Repository: When
target-repois configured, operates on that repository (must be inallowed-repos). - Footer Injection: Appends attribution footer to the closing comment when configured.
Configuration Parameters:
max: Operation limit (default: 1)target-repo: Cross-repository targetallowed-repos: Cross-repo allowlistfooter: Footer overridestaged: Staged mode override
Security Requirements:
- The closing comment body MUST undergo full content sanitization
- Issue number MUST be validated as a positive integer belonging to the target repository
- Cross-repository targets MUST be validated against the
allowed-reposallowlist - The handler MUST verify the caller has
issues: writepermission before executing
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- Issue state modification and comment creation
GitHub App:
issues: write- Issue state modification and comment creationmetadata: read- Repository metadata (automatically granted)
Type: link_sub_issue
Section titled “Type: link_sub_issue”Purpose: Create parent-child relationships between issues using task list entries.
Default Max: 1
Cross-Repository Support: No (same repository only)
Mandatory: No
MCP Tool Schema:
{ "name": "link_sub_issue", "description": "Link an issue as a sub-issue of a parent issue, establishing a parent-child relationship.", "inputSchema": { "type": "object", "required": ["parent_issue_number", "sub_issue_number"], "properties": { "parent_issue_number": { "type": ["number", "string"], "description": "The parent issue number to link the sub-issue to" }, "sub_issue_number": { "type": ["number", "string"], "description": "The issue number to link as a sub-issue of the parent" } }, "additionalProperties": false }}Operational Semantics:
- Task List Insertion: Adds a task list entry referencing the sub-issue to the parent issue body.
- Bidirectional Navigation: Creates navigable links from parent to child and child to parent.
- Limit Enforcement: Enforces maximum sub-issue count (default: 50) per parent issue.
- Validation: Both parent and sub-issue numbers must exist in the same repository.
- Same Repository Only: Cross-repository sub-issue linking is not supported.
Configuration Parameters:
max: Operation limit (default: 1)staged: Staged mode override
Security Requirements:
- Both
parent_issue_numberandsub_issue_numberMUST be validated as positive integers - Both issue numbers MUST exist in the target repository before modification
- The handler MUST enforce the maximum sub-issue count limit to prevent unbounded growth
- Cross-repository operations MUST be rejected; only same-repository linking is permitted
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- Issue body modification for task list entries
GitHub App:
issues: write- Issue body modification for task list entriesmetadata: read- Repository metadata (automatically granted)
Notes:
- Creates bidirectional navigation links between parent and child issues
- Enforces maximum sub-issue count limit (default: 50)
Type: create_discussion
Section titled “Type: create_discussion”Purpose: Create GitHub discussions for announcements, Q&A, reports, or community conversations.
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
MCP Tool Schema:
{ "name": "create_discussion", "description": "Create a GitHub discussion for announcements, Q&A, reports, status updates, or community conversations.", "inputSchema": { "type": "object", "required": ["title", "body"], "properties": { "title": {"type": "string", "description": "Discussion title"}, "body": {"type": "string", "description": "Discussion body in Markdown"}, "category": { "type": "string", "description": "Discussion category by name, slug, or ID. Defaults to first available category." } }, "additionalProperties": false }}Operational Semantics:
- Category Resolution: Category is resolved by name, slug, or ID; defaults to the first available category if omitted.
- Fallback Behavior: When repository discussions are disabled, falls back to creating an issue if
issues: writeis available. - Footer Injection: Appends attribution footer to the discussion body when configured.
- Cross-Repository: When
target-repois configured, creates in that repository (must be inallowed-repos). - Temporary ID Support: Supports
temporary_idfield for referencing before creation.
Configuration Parameters:
max: Operation limit (default: 1)category: Default discussion categorytarget-repo: Cross-repository targetallowed-repos: Cross-repo allowlistfooter: Footer overridestaged: Staged mode override
Security Requirements:
- Title and body MUST undergo full content sanitization before creation
- Category MUST be validated as an existing category in the target repository
- Cross-repository targets MUST be validated against the
allowed-reposallowlist
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- Fallback issue creation when discussion creation failsdiscussions: write- Discussion creation operations
GitHub App:
discussions: write- Discussion creation operationsissues: write- Fallback issue creation when discussion creation failsmetadata: read- Repository metadata (automatically granted)
Notes:
- Includes
issues: writefor fallback-to-issue functionality - If discussion categories are not enabled, may fall back to creating an issue
Type: update_discussion
Section titled “Type: update_discussion”Purpose: Modify existing discussion title or body.
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
MCP Tool Schema:
{ "name": "update_discussion", "description": "Update an existing GitHub discussion's title or body. Only the fields you specify will be updated.", "inputSchema": { "type": "object", "properties": { "title": {"type": "string", "description": "New discussion title"}, "body": {"type": "string", "description": "New discussion body in Markdown"}, "discussion_number": { "type": ["number", "string"], "description": "Discussion number to update. Required when workflow target is '*'." } }, "additionalProperties": false }}Operational Semantics:
- Partial Updates: Only fields explicitly provided are modified; omitted fields are unchanged.
- Context Resolution: When
discussion_numberis omitted, resolves from the workflow trigger context. - Cross-Repository: When
target-repois configured, operates on that repository (must be inallowed-repos). - GraphQL-Based: Uses GitHub GraphQL API for discussion updates as the REST API does not support discussion modification.
Configuration Parameters:
max: Operation limit (default: 1)target-repo: Cross-repository targetallowed-repos: Cross-repo allowliststaged: Staged mode override
Security Requirements:
- Title and body MUST undergo full content sanitization before modification
- Discussion number MUST be validated as a positive integer belonging to the target repository
- Cross-repository targets MUST be validated against the
allowed-reposallowlist - At least one of
titleorbodyMUST be provided; empty updates MUST be rejected
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextdiscussions: write- Discussion modification operations
GitHub App:
discussions: write- Discussion modification operationsmetadata: read- Repository metadata (automatically granted)
Type: close_discussion
Section titled “Type: close_discussion”Purpose: Close discussions with resolution reason and closing comment.
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
MCP Tool Schema:
{ "name": "close_discussion", "description": "Close a GitHub discussion with a resolution comment and optional reason.", "inputSchema": { "type": "object", "required": ["body"], "properties": { "body": {"type": "string", "description": "Closing comment explaining why the discussion is being closed"}, "reason": { "type": "string", "enum": ["RESOLVED", "DUPLICATE", "OUTDATED", "ANSWERED"], "description": "Resolution reason" }, "discussion_number": { "type": ["number", "string"], "description": "Discussion number to close. If omitted, closes the discussion that triggered this workflow." } }, "additionalProperties": false }}Operational Semantics:
- Comment First: A closing comment is posted before the discussion state is changed to
closed. - Resolution Reason: Optional reason (
RESOLVED,DUPLICATE,OUTDATED,ANSWERED) is recorded via GraphQL API. - Context Resolution: When
discussion_numberis omitted, resolves from the workflow trigger context. - Idempotent Comment: If the discussion is already closed, the closing comment is still posted.
- Cross-Repository: When
target-repois configured, operates on that repository (must be inallowed-repos).
Configuration Parameters:
max: Operation limit (default: 1)target-repo: Cross-repository targetallowed-repos: Cross-repo allowlistfooter: Footer overridestaged: Staged mode override
Security Requirements:
- The closing comment body MUST undergo full content sanitization
- Discussion number MUST be validated as a positive integer belonging to the target repository
- Cross-repository targets MUST be validated against the
allowed-reposallowlist - The
reasonfield MUST be validated against the allowed enum values before submission
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextdiscussions: write- Discussion state modification and comment creation
GitHub App:
discussions: write- Discussion state modification and comment creationmetadata: read- Repository metadata (automatically granted)
Type: update_pull_request
Section titled “Type: update_pull_request”Purpose: Modify existing pull request title, body, state, base branch, or draft status.
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextpull-requests: write- Pull request modification operations
GitHub App:
pull-requests: write- Pull request modification operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Only specified fields are updated; unspecified fields remain unchanged
- Base branch changes are validated for safety
Type: close_pull_request
Section titled “Type: close_pull_request”Purpose: Close pull requests WITHOUT merging, with closing comment.
Default Max: 10
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextpull-requests: write- Pull request state modification and comment creation
GitHub App:
pull-requests: write- Pull request state modification and comment creationmetadata: read- Repository metadata (automatically granted)
Notes:
- Higher default max (10) enables bulk PR cleanup operations
- Does NOT merge changes - use GitHub’s merge functionality for that
Type: mark_pull_request_as_ready_for_review
Section titled “Type: mark_pull_request_as_ready_for_review”Purpose: Convert draft pull request to ready-for-review status.
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextpull-requests: write- Pull request draft status modification
GitHub App:
pull-requests: write- Pull request draft status modificationmetadata: read- Repository metadata (automatically granted)
Type: push_to_pull_request_branch
Section titled “Type: push_to_pull_request_branch”Purpose: Push commits to pull request branch for automated code changes.
Default Max: 1
Cross-Repository Support: No (same repository only)
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: write- Branch push operations and commit creationissues: write- Comment creation for push notificationspull-requests: write- Pull request metadata access
GitHub App:
contents: write- Branch push operations and commit creationissues: write- Comment creation for push notificationspull-requests: write- Pull request metadata accessmetadata: read- Repository metadata (automatically granted)
Notes:
- Requires
contents: writefor git push operations - Enforces maximum patch size limit (default: 1024 KB)
- Validates changes don’t exceed size limits before pushing
Type: create_pull_request_review_comment
Section titled “Type: create_pull_request_review_comment”Purpose: Create review comments on specific lines of code in pull requests.
Default Max: 10
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and diff accesspull-requests: write- Review comment creation
GitHub App:
pull-requests: write- Review comment creationmetadata: read- Repository metadata (automatically granted)
Notes:
- Comments buffered via PR review buffer for batch submission
- Higher default max (10) enables comprehensive code review
Type: submit_pull_request_review
Section titled “Type: submit_pull_request_review”Purpose: Submit formal pull request review with status (APPROVE, REQUEST_CHANGES, COMMENT).
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextpull-requests: write- Review submission operations
GitHub App:
pull-requests: write- Review submission operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Submits all buffered review comments from
create_pull_request_review_comment - Review status affects PR merge requirements
- Target:
targetaccepts"triggering"(default),"*"(usepull_request_numberfrom message), or an explicit PR number (e.g.${{ github.event.inputs.pr_number }}). Required when the workflow is not triggered by a pull request (e.g.workflow_dispatch). - Footer control:
footeraccepts"always"(default),"none", or"if-body"(only when review body has text); booleantrue/falsemaps to"always"/"none"
Type: resolve_pull_request_review_thread
Section titled “Type: resolve_pull_request_review_thread”Purpose: Mark pull request review threads as resolved after addressing feedback.
Default Max: 10
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextpull-requests: write- Review thread resolution operations
GitHub App:
pull-requests: write- Review thread resolution operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Higher default max (10) enables resolving multiple threads per review cycle
Type: reply_to_pull_request_review_comment
Section titled “Type: reply_to_pull_request_review_comment”Purpose: Reply to existing review comments on pull requests to acknowledge feedback or answer questions.
Default Max: 10
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextpull-requests: write- Review comment reply creation
GitHub App:
pull-requests: write- Review comment reply creationmetadata: read- Repository metadata (automatically granted)
Notes:
- Higher default max (10) enables responding to multiple review comments per cycle
- Replies scoped to triggering PR by default;
target: "*"requires explicitpull_request_numberper message - Footer attribution appended by default; configurable via
footer: false
Type: add_labels
Section titled “Type: add_labels”Purpose: Add labels to issues or pull requests.
Default Max: 3
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- Label addition to issuespull-requests: write- Label addition to pull requests
GitHub App:
issues: write- Label addition to issuespull-requests: write- Label addition to pull requestsmetadata: read- Repository metadata (automatically granted)
Notes:
- Requires both
issues: writeandpull-requests: writeto support labeling both entity types - Labels must exist in repository; non-existent labels generate warnings
Type: remove_labels
Section titled “Type: remove_labels”Purpose: Remove labels from issues or pull requests.
Default Max: 3
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- Label removal from issuespull-requests: write- Label removal from pull requests
GitHub App:
issues: write- Label removal from issuespull-requests: write- Label removal from pull requestsmetadata: read- Repository metadata (automatically granted)
Notes:
- Same permissions as
add_labels - Missing labels are silently ignored (no error)
Type: add_reviewer
Section titled “Type: add_reviewer”Purpose: Add reviewers (users or teams) to pull requests.
Default Max: 3
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextpull-requests: write- Reviewer assignment operations
GitHub App:
pull-requests: write- Reviewer assignment operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Teams are expanded to individual members based on repository configuration
- Invalid reviewers generate warnings but don’t fail the operation
Type: assign_milestone
Section titled “Type: assign_milestone”Purpose: Assign issues to repository milestones for release planning.
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- Milestone assignment operations
GitHub App:
issues: write- Milestone assignment operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Milestone must exist in repository
- Replaces any existing milestone assignment
Type: assign_to_agent
Section titled “Type: assign_to_agent”Purpose: Assign GitHub Copilot coding agent to issues or pull requests.
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- Agent assignment operations
GitHub App:
issues: write- Agent assignment operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Uses special assignee syntax for Copilot coding agent assignment
- Agent must be enabled in repository settings
Type: assign_to_user
Section titled “Type: assign_to_user”Purpose: Assign users to issues or pull requests.
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
Configuration Options:
unassign-first(boolean, default: false): If true, unassigns all current assignees before assigning new ones. Useful for reassigning issues from one user to another.
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- User assignment operations (for issues)pull-requests: write- User assignment operations (for pull requests)
GitHub App:
issues: write- User assignment operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Users must have repository access to be assigned
- Invalid users generate warnings
- When
unassign-firstis enabled, the handler fetches current assignees and removes them before adding new ones
Type: unassign_from_user
Section titled “Type: unassign_from_user”Purpose: Remove user assignments from issues or pull requests.
Default Max: 1
Cross-Repository Support: Yes
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- User unassignment operations
GitHub App:
issues: write- User unassignment operationsmetadata: read- Repository metadata (automatically granted)
Type: hide_comment
Section titled “Type: hide_comment”Purpose: Hide (minimize) comments on issues, pull requests, or discussions.
Default Max: 5
Cross-Repository Support: Yes
Mandatory: No
Configuration Parameters:
max: Operation limit (default: 5)discussions: Controldiscussions:writepermission (default: true)target-repo: Cross-repository targetallowed-repos: Cross-repo allowlistallowed-reasons: Allowed reasons for hiding comments
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- Comment hiding on issuespull-requests: write- Comment hiding on pull requestsdiscussions: write- Comment hiding on discussions (whendiscussions: trueor omitted)
GitHub App:
issues: write- Comment hiding on issuespull-requests: write- Comment hiding on pull requestsdiscussions: write- Comment hiding on discussions (whendiscussions: trueor omitted)metadata: read- Repository metadata (automatically granted)
Permission Control via discussions Field:
The optional discussions boolean field controls whether discussions:write permission is requested:
- Default behavior (
discussions: trueor omitted): Includesdiscussions:writepermission for maximum compatibility. Use this when the GitHub App has Discussions permission granted. - Opt-out (
discussions: false): Excludesdiscussions:writepermission. Use this when the GitHub App lacks Discussions permission to prevent 422 errors during token generation.
Example Configuration:
safe-outputs: app: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} owner: 'myorg' repositories: ['myrepo'] hide-comment: max: 5 discussions: false # Exclude discussions:write permission allowed-reasons: [spam, abuse, off_topic]Notes:
- By default, requires all three write permissions to support hiding comments across all entity types
- When
discussions: false, the workflow only requestsissues:writeandpull-requests:writepermissions - Discussion-related safe outputs independently add
discussions:writepermission when configured - Comments are minimized, not deleted - reversible by moderators
Type: create_project
Section titled “Type: create_project”Purpose: Create GitHub Projects V2 boards for project management.
Default Max: 1
Cross-Repository Support: Yes (organization or user projects)
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextorganization-projects: write- Project creation operations (note: only valid for GitHub Apps)
GitHub App:
organization-projects: write- Project creation operationsmetadata: read- Repository metadata (automatically granted)
Notes:
organization-projectspermission is ONLY available for GitHub App tokens, not standard GitHub Actions tokens- GitHub Actions workflows should use GitHub App authentication for project operations
- Projects can be created at organization or user level based on app installation
Type: update_project
Section titled “Type: update_project”Purpose: Manage GitHub Projects V2 boards (add items, update fields, remove items).
Default Max: 10
Cross-Repository Support: No (same repository only)
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextorganization-projects: write- Project management operations (note: only valid for GitHub Apps)
GitHub App:
organization-projects: write- Project management operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Same permission requirements as
create_project - Higher default max (10) enables batch project board updates
Type: create_project_status_update
Section titled “Type: create_project_status_update”Purpose: Create status updates for GitHub Projects V2 boards.
Default Max: 1
Cross-Repository Support: No (same repository only)
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextorganization-projects: write- Project status update operations (note: only valid for GitHub Apps)
GitHub App:
organization-projects: write- Project status update operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Same permission requirements as
create_projectandupdate_project
Type: update_release
Section titled “Type: update_release”Purpose: Update GitHub release descriptions and metadata.
Default Max: 1
Cross-Repository Support: No (same repository only)
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: write- Release modification operations
GitHub App:
contents: write- Release modification operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Only updates release notes and metadata; does NOT modify release assets
- Release must already exist (identified by tag name)
Type: upload_asset
Section titled “Type: upload_asset”Purpose: Upload files to orphaned git branch for artifact storage.
Default Max: 10
Cross-Repository Support: No (same repository only)
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: write- Branch creation, commit operations, and file uploads
GitHub App:
contents: write- Branch creation, commit operations, and file uploadsmetadata: read- Repository metadata (automatically granted)
Notes:
- Creates or updates orphaned branch for asset storage
- Enforces maximum file size limit (default: 10 MB = 10240 KB)
- Files accessible via raw.githubusercontent.com URLs
Type: dispatch_workflow
Section titled “Type: dispatch_workflow”Purpose: Trigger workflow_dispatch events to invoke other workflows.
Default Max: 3
Cross-Repository Support: No (same repository only)
Mandatory: No
Required Permissions:
GitHub Actions Token:
actions: write- Workflow dispatch operations
GitHub App:
actions: write- Workflow dispatch operationsmetadata: read- Repository metadata (automatically granted)
Notes:
- Requires ONLY
actions: writepermission (nocontents: readneeded) - Target workflow must support
workflow_dispatchtrigger - Workflow inputs are validated against target workflow’s input schema
Type: create_code_scanning_alert
Section titled “Type: create_code_scanning_alert”Purpose: Generate SARIF security reports and code scanning alerts.
Default Max: unlimited
Cross-Repository Support: No (same repository only)
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextsecurity-events: write- SARIF report upload and alert creation
GitHub App:
security-events: write- SARIF report upload and alert creationmetadata: read- Repository metadata (automatically granted)
Notes:
- Unlimited max enables comprehensive security scanning
- Alerts appear in repository Security tab
- SARIF format validation performed before upload
Type: autofix_code_scanning_alert
Section titled “Type: autofix_code_scanning_alert”Purpose: Create automated pull requests to fix code scanning alerts.
Default Max: 10
Cross-Repository Support: No (same repository only)
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextsecurity-events: write- Alert metadata accessactions: read- Workflow run metadata for alert correlation
GitHub App:
security-events: write- Alert metadata accesscontents: write- Pull request branch creationpull-requests: write- Pull request creationactions: read- Workflow run metadata for alert correlationmetadata: read- Repository metadata (automatically granted)
Notes:
- Most complex permission set - requires security-events, contents, pull-requests, and actions scopes
- Creates pull request with proposed fix referencing the alert
- Alert must exist and be fixable
Type: create_agent_session
Section titled “Type: create_agent_session”Purpose: Create GitHub Copilot coding agent sessions for code change delegation.
Default Max: 1
Cross-Repository Support: No (same repository only)
Mandatory: No
Required Permissions:
GitHub Actions Token:
contents: read- Repository metadata and contextissues: write- Issue creation and agent assignment
GitHub App:
issues: write- Issue creation and agent assignmentmetadata: read- Repository metadata (automatically granted)
Notes:
- Creates issue with special agent assignment that triggers Copilot coding agent
- Agent must be enabled in repository settings
Type: missing_tool
Section titled “Type: missing_tool”Purpose: Report when AI requests unavailable functionality for feature discovery.
Default Max: unlimited
Cross-Repository Support: N/A (logging only)
Mandatory: Yes (always enabled, cannot be disabled)
Required Permissions:
GitHub Actions Token:
- No additional permissions required beyond base workflow permissions
- When
create-issue: trueconfigured, requiresissues: writefor issue creation
GitHub App:
- No additional permissions required beyond base app installation
- When
create-issue: trueconfigured, requiresissues: writefor issue creation
Notes:
- Base functionality requires no permissions (logging only)
- Optional issue creation requires
issues: writewhencreate-issue: true - Always enabled to capture AI’s unmet capability requests
Type: missing_data
Section titled “Type: missing_data”Purpose: Report when AI lacks required information to complete goals.
Default Max: unlimited
Cross-Repository Support: N/A (logging only)
Mandatory: Yes (always enabled, cannot be disabled)
Required Permissions:
GitHub Actions Token:
- No additional permissions required beyond base workflow permissions
- When
create-issue: trueconfigured, requiresissues: writefor issue creation
GitHub App:
- No additional permissions required beyond base app installation
- When
create-issue: trueconfigured, requiresissues: writefor issue creation
Notes:
- Same permission model as
missing_tool - Base functionality requires no permissions (logging only)
- Optional issue creation requires
issues: writewhencreate-issue: true
8. Protocol Exchange Patterns
Section titled “8. Protocol Exchange Patterns”8.1 HTTP Transport Layer
Section titled “8.1 HTTP Transport Layer”Protocol: HTTP/1.1
Bind Address: 127.0.0.1 (localhost only)
Port: From GH_AW_SAFE_OUTPUTS_PORT (default: 3001)
Operation Mode: Stateless (no session management)
Endpoints:
POST /tools/list- List available toolsPOST /tools/call- Invoke tool
8.2 Tool Invocation Protocol
Section titled “8.2 Tool Invocation Protocol”Request Format:
POST /tools/call HTTP/1.1Host: 127.0.0.1:3001Content-Type: application/json
{ "method": "tools/call", "params": { "name": "<tool_name>", "arguments": {<parameters>} }}Success Response:
HTTP/1.1 200 OKContent-Type: application/json
{ "result": { "content": [{ "type": "text", "text": "{\"result\":\"success\"}" }] }}Validation Error:
HTTP/1.1 200 OKContent-Type: application/json
{ "error": { "code": -32602, "message": "Invalid params: <details>" }}8.3 MCP Server Constraint Enforcement
Section titled “8.3 MCP Server Constraint Enforcement”Requirement MCE1: Early Validation
MCP servers MUST enforce operational constraints during tool invocation (Phase 4) rather than deferring all validation to safe output processing (Phase 6). This provides immediate feedback to the LLM, enabling corrective action before operations are recorded to NDJSON.
Constraint Categories:
- Length Limits: Character count restrictions on text fields
- Entity Limits: Maximum counts for mentions, links, or other entities
- Format Requirements: Pattern validation, encoding checks
- Business Rules: Type-specific constraints based on safe output configuration
Requirement MCE2: Tool Description Disclosure
Tool descriptions (MCP tool schemas) MUST surface enforced constraints to the LLM through the description field. This enables the LLM to self-regulate and avoid constraint violations.
Example - add_comment constraints:
{ "name": "add_comment", "description": "Add a comment to an existing GitHub issue, pull request, or discussion. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance.", "inputSchema": { "type": "object", "properties": { "body": { "type": "string", "description": "The comment text in Markdown format. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. A footer (~200-500 characters) is automatically appended, so leave adequate space. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated." } } }}Requirement MCE3: Actionable Error Responses
When constraints are violated, MCP servers MUST return error responses that:
- Identify the violated constraint with specific name and limit
- Report the actual value that triggered the violation
- Provide remediation guidance on how to correct the issue
- Use standard error codes (E006-E008 for add_comment limits)
Example - Mention Limit Violation:
{ "error": { "code": -32602, "message": "E007: Comment contains 15 mentions, maximum is 10", "data": { "constraint": "max_mentions", "limit": 10, "actual": 15, "guidance": "Reduce the number of @mentions in your comment to 10 or fewer. Consider tagging only essential participants." } }}Requirement MCE4: Dual Enforcement
Constraints MUST be enforced at both invocation time (MCP server) and processing time (safe output processor) to provide defense-in-depth:
- MCP Server Enforcement: Provides immediate LLM feedback during agent execution
- Processor Enforcement: Validates operations recorded to NDJSON as final safety check
This dual enforcement pattern ensures constraints cannot be bypassed through malformed NDJSON or direct artifact manipulation.
Implementation Pattern:
// MCP Server - Immediate validation during tool callfunction handleAddComment(args) { enforceCommentLimits(args.body); // Throws if limits exceeded recordOperation('add_comment', args); return {result: {content: [{type: "text", text: '{"result":"success"}'}]}};}
// Safe Output Processor - Final validation before API callasync function processAddComment(operation) { enforceCommentLimits(operation.body); // Defense-in-depth const sanitized = sanitizeContent(operation); await github.rest.issues.createComment(sanitized);}Requirement MCE5: Constraint Configuration Consistency
Constraint limits defined in MCP tool descriptions MUST match the enforcement logic in both the MCP server and safe output processor implementations. Inconsistent limits between these components violate the specification.
Verification:
- Method: Code review and integration testing
- Tool: Automated tests comparing tool descriptions to handler enforcement code
- Criteria: All constraint limits are identical across tool schema, MCP server, and processor
Example Constraints for Common Safe Output Types:
| Type | Constraint | Limit | Error Code |
|---|---|---|---|
add_comment | Body length (complete comment with footer) | 65536 chars | E006 |
add_comment | Mentions | 10 | E007 |
add_comment | Links | 50 | E008 |
create_issue | Title length | 256 chars | E009 |
create_issue | Body length (complete body with footer) | 65536 chars | E006 |
create_pull_request | Title length | 256 chars | E009 |
create_pull_request | Body length (complete body with footer) | 65536 chars | E006 |
Note: For operations that append footers (comments, issues, pull requests), the character limit applies to the complete text including the automatically added footer. Users should reserve approximately 200-500 characters to accommodate the footer.
Rationale: Early constraint enforcement transforms validation failures from post-execution errors (requiring workflow reruns) into correctable feedback during agent reasoning. This improves agent effectiveness by enabling iterative refinement and reduces wasted compute on operations that will ultimately fail validation.
8.4 NDJSON Persistence
Section titled “8.4 NDJSON Persistence”File: /tmp/gh-aw/safeoutputs/output.ndjson
Format: One JSON object per line
Encoding: UTF-8
Entry Structure:
{"type":"<safe_output_type>","<param1>":"<value1>"}Parsing:
- Read line-by-line
- Parse each line independently
- Ignore empty lines
- Validate
typefield presence
9. Content Integrity Mechanisms
Section titled “9. Content Integrity Mechanisms”9.1 Schema Validation
Section titled “9.1 Schema Validation”All tool invocations MUST validate against JSON Schema Draft 7.
Validation Process:
- Parse invocation arguments
- Load tool schema
- Validate against schema
- Report all errors with paths
Error Format:
{ "error": { "code": -32602, "message": "Invalid params", "data": { "errors": [ {"path": "/title", "message": "Missing required field"} ] } }}9.2 Cross-Field Validation
Section titled “9.2 Cross-Field Validation”Some validations span multiple fields:
- Conditional requirements (e.g.,
discussion_numberrequired when target is ”*”) - Range constraints (e.g.,
start_line < linein review comments) - Mutual exclusivity checks
9.3 Repository Feature Validation
Section titled “9.3 Repository Feature Validation”Operations requiring repository features must validate availability:
- Issues enabled
- Discussions enabled
- Projects enabled
Validation occurs during execution, not tool invocation.
9.4 Content Sanitization Pipeline
Section titled “9.4 Content Sanitization Pipeline”Applicability
Content sanitization MUST be applied to all user-provided text fields in safe output operations. Text fields include:
title(issues, PRs, discussions, projects)body(issues, PRs, discussions, comments)description(projects, status updates)comment(review comments)
Sanitization Stages
Implementations MUST apply these transformations in order:
S1: Null Byte Removal
- Remove all null bytes (
\0,\x00) from strings - Rationale: Prevents string truncation attacks
S2: Markdown Link Validation
- Pattern:
[text](url)and<url> - For each URL:
- Extract domain
- If
allowed-domainsis configured:- Check domain against allowlist
- If not allowed: Replace with
[text]([URL redacted: unauthorized domain])
- Log redacted URLs to
/tmp/gh-aw/safeoutputs/redacted-domains.log
S3: Markdown Image Validation
- Pattern:
 - For each image URL:
- Extract domain
- If
allowed-domainsis configured:- Check domain against allowlist
- If not allowed: Replace with

S4: HTML Tag Filtering (Optional, depends on field type)
- Remove potentially dangerous tags:
<script>,</script><iframe>,</iframe><object>,</object><embed>,</embed>
- Remove event handlers:
on*attributes in HTML tags (onclick, onerror, etc.)
- Preserve safe GitHub Flavored Markdown tags:
<details>,<summary>,<sub>,<sup>,<kbd>
S5: Command Injection Prevention
- Do NOT execute or interpret code blocks
- Do NOT evaluate template expressions
- Preserve code blocks verbatim (no escaping needed in markdown)
Excluded Content
The following content MUST NOT be sanitized:
- Code blocks (
```) - Inline code (
`code`) - System-generated footers
- System-generated metadata
Sanitization Reversibility
Sanitization transformations are LOSSY and NOT reversible. Original content is not preserved after sanitization. This is intentional to prevent attempts to bypass sanitization.
Conformance Requirement CR1: Pre-API Sanitization
All content MUST be sanitized BEFORE GitHub API invocation. Unsanitized content MUST NEVER be passed to GitHub APIs.
Verification: Inspect handler code to confirm sanitization occurs before octokit.* calls.
9.5 Error Code Catalog
Section titled “9.5 Error Code Catalog”Implementations MUST use standardized error codes for validation and execution failures.
Error Code Table
| Code | Name | Description | When to Use | HTTP Status Equivalent |
|---|---|---|---|---|
| E001 | INVALID_SCHEMA | Operation failed JSON schema validation | Input does not match type-specific schema | 400 Bad Request |
| E002 | LIMIT_EXCEEDED | Operation count exceeds configured max | Batch contains more operations than allowed | 429 Too Many Requests |
| E003 | UNAUTHORIZED_DOMAIN | URL contains non-allowlisted domain | Domain filtering rejected URL | 403 Forbidden |
| E004 | INVALID_TARGET_REPO | target-repo not in allowed-repos | Cross-repository validation failed | 403 Forbidden |
| E005 | MISSING_PARENT | Referenced parent issue/PR not found | Temporary ID or parent reference cannot be resolved | 404 Not Found |
| E006 | INVALID_LABEL | Label does not exist in repository | Label validation failed | 404 Not Found |
| E007 | API_ERROR | GitHub API returned error | GitHub API call failed | 502 Bad Gateway |
| E008 | SANITIZATION_FAILED | Content contains unsanitizable unsafe patterns | Sanitization pipeline detected unremovable threats | 422 Unprocessable Entity |
| E009 | CONFIG_HASH_MISMATCH | Configuration hash verification failed | Workflow YAML was modified after compilation | 403 Forbidden |
| E010 | RATE_LIMIT_EXCEEDED | GitHub API rate limit exceeded | Too many API calls | 429 Too Many Requests |
Error Message Format
All errors MUST conform to this JSON structure:
{ "error": { "code": "E002", "name": "LIMIT_EXCEEDED", "message": "Operation count exceeds configured limit", "details": { "type": "create_issue", "attempted": 5, "max": 3, "operation_index": 3 }, "timestamp": "2026-02-14T16:39:20.948Z", "workflow_run": "https://github.com/owner/repo/actions/runs/12345" }}Required Fields:
code: Error code from table above (E001-E010)name: Error name from table abovemessage: Human-readable descriptiontimestamp: ISO 8601 timestamp
Optional Fields:
details: Type-specific error context (operation_index, field names, etc.)workflow_run: URL to workflow run for provenance
Error Handling Requirements
Requirement EH1: Early Failure Detection
Validation errors (E001-E006) MUST be detected before any GitHub API calls are made.
Requirement EH2: Clear Error Messages
Error messages MUST:
- Clearly state what went wrong
- Include enough context to debug (field names, values)
- Suggest remediation when possible
Requirement EH3: Error Logging
All errors MUST be logged to:
- GitHub Actions step output (visible in workflow run)
- Job summary (visible in workflow run summary)
- STDERR (for local development)
10. Execution Guarantees
Section titled “10. Execution Guarantees”10.1 Atomicity
Section titled “10.1 Atomicity”Single-Item Operations: Complete success or complete failure (no partial state).
Batch Operations: Best-effort semantics; partial success reported.
10.2 Ordering
Section titled “10.2 Ordering”Operations execute in:
- NDJSON file order
- Type grouping (same type together)
- System types last (noop)
10.3 Idempotency
Section titled “10.3 Idempotency”Idempotent Operations:
- add_labels (adding present label)
- remove_labels (removing absent label)
- hide_comment (hiding hidden comment)
Non-Idempotent Operations:
- create_issue
- create_discussion
- add_comment
10.4 Error Handling
Section titled “10.4 Error Handling”Fail-Safe Principle: One operation’s failure doesn’t prevent others from attempting.
Error Reporting: All errors collected; execution summary reports per-type results.
10.5 Edge Case Behavior
Section titled “10.5 Edge Case Behavior”This section defines required behavior for unusual or boundary conditions.
Empty Operations
Scenario: NDJSON artifact contains zero operations
Behavior:
- Safe output job MUST succeed (exit code 0)
- Job summary SHOULD display: ”✓ No operations to process”
- No GitHub API calls are made
- No errors are raised
Rationale: Empty operations are valid (agent may determine no action is needed).
Zero Max Limit
Scenario: Configuration specifies max: 0 for a safe output type
Behavior:
- Type is DISABLED (MCP tool is not registered)
- Attempts to invoke disabled type MUST return MCP error:
{"error": {"code": -32601, "message": "Method not found"}}
- No configuration is generated for disabled types
Rationale: max: 0 is an explicit disable signal.
API Rate Limiting
Scenario: GitHub API returns 429 (rate limit exceeded) or 403 with X-RateLimit-Remaining: 0
Behavior:
- Processor MUST retry with exponential backoff:
- 1st retry: After 60 seconds
- 2nd retry: After 120 seconds
- 3rd retry: After 240 seconds
- After 3 retries, MUST fail with E010 error
- Error details MUST include rate limit reset time from
X-RateLimit-Resetheader
Rationale: Transient rate limits should not fail workflows unnecessarily.
Workflow Cancellation
Scenario: Workflow is manually cancelled during agent execution
Behavior:
- Safe output job MUST NOT execute if artifact upload was interrupted
- Partial NDJSON artifacts MUST NOT be processed
- GitHub Actions automatically handles cleanup
- No additional logic required in handlers
Rationale: GitHub Actions cancellation is handled at platform level.
Concurrent Workflow Runs
Scenario: Multiple workflow runs execute concurrently for the same workflow
Behavior:
- Each run operates independently
- Max limits are per-run (NOT global across runs)
- No coordination or locking between runs
- Operations in separate runs do NOT affect each other’s limits
Rationale: Simplicity and avoiding distributed coordination complexity.
Malformed NDJSON
Scenario: NDJSON artifact contains invalid JSON on one or more lines
Behavior:
- Parser MUST skip invalid lines with warning
- Valid lines MUST be processed
- Job summary MUST show: “! Skipped N malformed entries”
- Invalid lines MUST be logged to STDERR
Rationale: Partial failure should not prevent valid operations from executing.
Missing Artifact
Scenario: Safe output job cannot download artifact (artifact not found)
Behavior:
- Job MUST fail with clear error message
- Error MUST suggest checking agent job completion
- Exit code MUST be non-zero
Rationale: Missing artifact indicates upstream failure that must be addressed.
Duplicate Temporary IDs
Scenario: Multiple operations use the same temporary_id
Behavior:
- First operation using the ID succeeds and establishes mapping
- Subsequent operations using the same ID MUST reference the first operation’s result
- If this creates ambiguity (e.g., two issues both want to be “aw_parent”), MUST reject with E005
Rationale: Deterministic behavior prevents confusion.
Appendix A: Conformance Checklist
Section titled “Appendix A: Conformance Checklist”Required for Full Conformance:
-
Security Architecture
- Privilege separation enforced
- Artifact-based communication
- Threat mitigations implemented
- Security properties maintained
-
Configuration
- All global parameters supported
- Type-specific parameters supported
- Inheritance rules followed
- Compilation-time validation
-
Universal Features
- Max limit enforcement
- Staged mode preview generation
- Footer injection
- Content sanitization pipeline
-
Safe Output Types
- Mandatory: create_issue, add_comment, create_pull_request, noop
- Optional types documented if unsupported
-
Protocol
- HTTP transport
- MCP tool invocation
- NDJSON persistence
-
Content Security
- Schema validation
- Domain filtering
- Sanitization pipeline
-
Execution Guarantees
- Atomicity for single-item operations
- Best-effort for batch operations
- Fail-safe error handling
Appendix B: Security Considerations
Section titled “Appendix B: Security Considerations”Attack Surface Analysis
Section titled “Attack Surface Analysis”Entry Points:
- Agent-provided tool arguments
- Configuration in frontmatter
- GitHub API responses
Trust Boundaries:
- Agent context (untrusted)
- MCP Gateway (semi-trusted)
- Safe output processor (trusted)
- GitHub API (trusted)
Mitigation Effectiveness
Section titled “Mitigation Effectiveness”Detailed threat analysis and mitigation effectiveness assessment for all five primary threats (see Section 3.2).
Appendix C: Implementation Guidance
Section titled “Appendix C: Implementation Guidance”Recommended Practices
Section titled “Recommended Practices”- Conservative Limits: Start with minimal max values
- Staged Mode Development: Test workflows in preview mode first
- Explicit Domain Lists: Use restrictive domain filtering
- Expires for Temporary Resources: Auto-close temporary issues
Common Pitfalls
Section titled “Common Pitfalls”- Unlimited Max: Removes important safety constraint
- Permissive Domains: Loses URL filtering protection
- Cross-Repo Without Allowlist: Permits arbitrary targets
- Disabled Footers: Reduces transparency
Appendix D: Normative References
Section titled “Appendix D: Normative References”- RFC 2119: Key words for RFCs (MUST, SHALL, etc.)
- JSON Schema Draft 7: JSON Schema specification
- NDJSON: Newline Delimited JSON format
- MCP Specification: Model Context Protocol
Appendix E: Informative References
Section titled “Appendix E: Informative References”- GitHub REST API: https://docs.github.com/rest
- GitHub Actions: https://docs.github.com/actions
- MCP Gateway Specification: /gh-aw/reference/mcp-gateway/
Appendix G: Configuration Patterns
Section titled “Appendix G: Configuration Patterns”This appendix provides common configuration patterns for safe outputs.
Pattern 1: Simple Issue Tracking
Section titled “Pattern 1: Simple Issue Tracking”Basic configuration for automated issue creation:
safe-outputs: create-issue: max: 1 labels: [automated]Use case: Single automated issue per workflow run with consistent labeling.
Pattern 2: Multi-Type with Global Footer
Section titled “Pattern 2: Multi-Type with Global Footer”Configuration with multiple output types sharing global settings:
safe-outputs: footer: true # Applied to all types
create-issue: max: 3 labels: [bug, automated]
add-comment: max: 2 hide-older-comments: trueUse case: Workflow creating multiple issues and comments with attribution footers.
Pattern 3: Cross-Repository Operations
Section titled “Pattern 3: Cross-Repository Operations”Secure cross-repository issue creation:
safe-outputs: allowed-github-references: - owner/repo-a - owner/repo-b
create-issue: max: 5 target-repo: owner/repo-aUse case: Creating issues in a central tracking repository from multiple workflow repositories.
Security note: Explicit allowlist prevents unauthorized repository targeting.
Pattern 4: Staged Mode Development
Section titled “Pattern 4: Staged Mode Development”Safe testing in preview mode:
safe-outputs: staged: true # Enable preview mode globally
create-issue: max: 10 # Safe to set high in staged mode
add-comment: max: 5Use case: Testing workflow behavior without creating real GitHub resources.
Workflow: Test with staged: true, verify previews, then deploy with staged: false.
Pattern 5: Type-Specific Allowlists
Section titled “Pattern 5: Type-Specific Allowlists”Fine-grained cross-repository control:
safe-outputs: allowed-github-references: [owner/repo-a, owner/repo-b]
create-issue: allowed-repos: [owner/repo-c] # Overrides global max: 3
add-comment: # No type-specific list, uses global: repo-a, repo-b max: 2Use case: Different safe output types target different repositories.
Security note: Type-specific allowlists override global allowlists.
Pattern 6: Domain Filtering for Security
Section titled “Pattern 6: Domain Filtering for Security”Restrict URLs in safe output content:
safe-outputs: allowed-domains: - github.com - "*.github.io" - docs.github.com
create-issue: max: 5Use case: Prevent agents from including unauthorized URLs in created content.
Effect: URLs to non-allowlisted domains are redacted during sanitization.
Pattern 7: Temporary Resource Cleanup
Section titled “Pattern 7: Temporary Resource Cleanup”Auto-close temporary issues:
safe-outputs: create-issue: max: 10 expires: 7 # Auto-close after 7 days labels: [temporary, automated]Use case: Issues for transient notifications that should auto-clean.
Implementation: Scheduled workflow checks issue age and closes expired issues.
Pattern 8: Review Comment Workflow
Section titled “Pattern 8: Review Comment Workflow”Pull request review automation with reply support:
safe-outputs: create-pr-review-comment: max: 20
submit-pr-review: max: 1
reply-to-pull-request-review-comment: max: 10
resolve-pr-review-thread: max: 10Use case: Automated code review with inline comments, review replies, and thread resolution.
Workflow: Create review comments, submit bundled review, reply to reviewer feedback, resolve addressed threads.
Pattern 9: Project Management
Section titled “Pattern 9: Project Management”Automated project creation and updates:
safe-outputs: create-project: max: 1
update-project: max: 5
create-project-status-update: max: 3Use case: Creating and maintaining project boards automatically.
Pattern 10: Grouped Issues with Parent
Section titled “Pattern 10: Grouped Issues with Parent”Create related issues under a parent:
safe-outputs: create-issue: max: 10 group: trueUse case: Workflow creates parent issue and multiple sub-issues linked via tasklists.
Effect: First issue becomes parent, subsequent issues link to it.
Best Practices
Section titled “Best Practices”Start Conservative:
- Begin with low
maxvalues - Enable
staged: truefor testing - Use explicit
allowed-reposlists
Use Domain Filtering:
- Always configure
allowed-domainswhen agents process external input - Include only trusted domains
Enable Footers:
- Keep
footer: true(default) for transparency - Only disable when absolutely necessary
Temporary Resources:
- Use
expiresfor transient issues - Clean up with
close-older-issuesfor superseded content
Cross-Repository Security:
- Use type-specific
allowed-reposfor fine-grained control - Prefer explicit lists over broad permissions
Appendix F: Document History
Section titled “Appendix F: Document History”Version 1.14.0 (2026-02-22):
- Added: Section 5.5 “Templatable Fields” documenting support for GitHub Actions expressions in integer and boolean configuration fields
- Updated: GP1 (
footerglobal), TS1 (max), and TS2 (footertype-specific) syntax to document expression support - Clarified: Templatable integer fields (
max) and templatable boolean fields (footer,group,close-older-issues,hide-older-comments,close-older-discussions,draft,allow-empty,auto-merge,report-as-issue,unassign-first) accept${{ ... }}GitHub Actions expressions in addition to literal values - Added: Conformance requirements for runtime evaluation of templatable fields
Version 1.13.0 (2026-02-18):
- Added: Optional
discussionsfield foradd-commentandhide-commentsafe output types to controldiscussions:writepermission - Enhanced: Permission documentation for
add-commentandhide-commentto explain conditionaldiscussions:writeinclusion - Added: Configuration examples demonstrating
discussions: falseusage for GitHub Apps without Discussions permission - Fixed: Issue where
add-commentandhide-commentunconditionally requesteddiscussions:writepermission, causing 422 errors for GitHub Apps lacking Discussions permission - Default behavior:
discussions: true(or omitted) includesdiscussions:writefor backward compatibility - Opt-out behavior:
discussions: falseexcludesdiscussions:writepermission for GitHub Apps without Discussions permission
Version 1.12.0 (2026-02-16):
- Implemented: MCE1 (Early Validation) for add_comment tool with MCP server constraint enforcement
- Added: Runtime validation in safe_outputs_handlers.cjs that enforces comment limits during tool invocation
- Verified: Dual enforcement pattern now operational - MCP server validates during Phase 4, safe output processor validates during Phase 6
- Enhanced: Error responses now use JSON-RPC error code -32602 with actionable messages containing specific constraint details
- Tested: Comprehensive test suite (16 test cases) validates E006/E007/E008 error handling and MCP error format compliance
Version 1.11.0 (2026-02-15):
- Added: Section 8.3 “MCP Server Constraint Enforcement” specifying requirements for early validation during tool invocation (MCE1-MCE5)
- Enhanced: Tool descriptions to surface operational constraints to the LLM (e.g., add_comment mention/link/length limits)
- Clarified: Dual enforcement pattern requiring validation at both MCP server and safe output processor layers
- Added: Constraint consistency requirement (MCE5) ensuring limits are identical across tool schemas and enforcement code
- Added: Example constraint table for common safe output types with error codes
- Updated: add_comment tool description in safe_outputs_tools.json to include explicit constraint documentation
Version 1.10.0 (2026-02-14):
- Added:
reply_to_pull_request_review_commentsafe output type definition (Section 7.3) - Updated: Pattern 8 (Review Comment Workflow) to include reply-to-review-comment in example configuration
Version 1.9.0 (2026-02-14):
- Added comprehensive validation pipeline ordering (7 stages)
- Added cross-repository security model with explicit allowlist rules
- Added content sanitization pipeline specification (5 stages)
- Added standardized error code catalog (E001-E010)
- Added edge case behavior specifications
- Added terminology section for consistency
- Enhanced security properties (SP6, SP7)
- Improved requirements testability
Version 1.8.0 (2025-02-14):
- Initial W3C-style specification release
- Complete security model documentation
- Comprehensive safe output type catalog
- Protocol exchange pattern definitions
- Content security mechanisms
- Operational guarantees formalization
Future Work:
- Formal conformance test suite
- Extended threat modeling
- Performance benchmarks
- Additional safe output type proposals
End of Specification
Copyright 2025 GitHub, Inc. All rights reserved.
This document may be distributed and implemented according to the terms specified in the project license.