Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/minor-remove-strict-mode-timeout-requirement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"githubnext/gh-aw": minor
---

Remove timeout requirement for strict mode and set default timeout to 20 minutes

This change makes strict mode more flexible by removing the requirement to specify `timeout_minutes`. Workflows can still set a timeout for cost control, but it's no longer mandatory for enabling strict mode's security features. The default timeout for agentic workflows has also been increased from 5 to 20 minutes to better accommodate typical workflow execution times.
2 changes: 1 addition & 1 deletion .github/workflows/artifacts-summary.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/audit-workflows.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/brave.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/changeset-generator.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/ci-doctor.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/cli-version-checker.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/go-pattern-detector.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/pdf-summary.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/poem-bot.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/scout.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/smoke-claude.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/smoke-copilot.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/technical-doc-writer.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/tidy.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 5 additions & 8 deletions docs/src/content/docs/guides/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ network:
- "api.example.com"
```

Strict mode prevents write permissions (`contents:write`, `issues:write`, `pull-requests:write`) and requires explicit network configuration and timeouts. Use `safe-outputs` configuration instead for controlled GitHub API interactions. See [Strict Mode Validation](#strict-mode-validation) for details.
Strict mode prevents write permissions (`contents:write`, `issues:write`, `pull-requests:write`) and requires explicit network configuration. Use `safe-outputs` configuration instead for controlled GitHub API interactions. See [Strict Mode Validation](#strict-mode-validation) for details.

### Human in the Loop

Expand All @@ -113,7 +113,6 @@ For production workflows, use strict mode to enforce enhanced security and relia
```yaml
# Enable strict mode declaratively in frontmatter
strict: true
timeout_minutes: 10
permissions:
contents: read
network:
Expand All @@ -129,14 +128,12 @@ gh aw compile --strict

**Strict mode enforces:**

1. **Timeout Required**: All workflows must specify `timeout_minutes` to prevent runaway executions
2. **Write Permissions Blocked**: Refuses `contents:write`, `issues:write`, and `pull-requests:write` (use `safe-outputs` instead)
3. **Network Configuration Required**: Must explicitly configure network access (cannot rely on defaults)
4. **No Network Wildcards**: Cannot use wildcard `*` in `network.allowed` domains
5. **MCP Network Configuration**: Custom MCP servers with containers must have network configuration
1. **Write Permissions Blocked**: Refuses `contents:write`, `issues:write`, and `pull-requests:write` (use `safe-outputs` instead)
2. **Network Configuration Required**: Must explicitly configure network access (cannot rely on defaults)
3. **No Network Wildcards**: Cannot use wildcard `*` in `network.allowed` domains
4. **MCP Network Configuration**: Custom MCP servers with containers must have network configuration

**Benefits:**
- **Cost Control**: Prevents unbounded execution times through mandatory timeouts
- **Security**: Minimizes attack surface by blocking write permissions and requiring explicit network access
- **Compliance**: Ensures workflows meet organizational security standards
- **Auditability**: Clear security requirements make workflows easier to review
Expand Down
13 changes: 5 additions & 8 deletions docs/src/content/docs/reference/frontmatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,10 @@ strict: false

When `strict: true`, the workflow must satisfy these requirements:

1. **Timeout Required**: Must specify `timeout_minutes` with a positive integer value to prevent runaway executions
2. **Write Permissions Blocked**: Cannot use `contents: write`, `issues: write`, or `pull-requests: write` permissions (use `safe-outputs` instead for controlled GitHub API interactions)
3. **Network Configuration Required**: Must explicitly configure network access (cannot rely on default behavior)
4. **No Network Wildcards**: Cannot use wildcard `*` in `network.allowed` domains
5. **MCP Network Configuration**: Custom MCP servers with containers must have network configuration
1. **Write Permissions Blocked**: Cannot use `contents: write`, `issues: write`, or `pull-requests: write` permissions (use `safe-outputs` instead for controlled GitHub API interactions)
2. **Network Configuration Required**: Must explicitly configure network access (cannot rely on default behavior)
3. **No Network Wildcards**: Cannot use wildcard `*` in `network.allowed` domains
4. **MCP Network Configuration**: Custom MCP servers with containers must have network configuration

**Example Strict Mode Workflow:**

Expand All @@ -311,7 +310,6 @@ on: push
strict: true
permissions:
contents: read
timeout_minutes: 10
engine: claude
network:
allowed:
Expand All @@ -334,7 +332,6 @@ The CLI `--strict` flag takes precedence over frontmatter settings. If the CLI f
**Use Cases:**
- Production workflows that require enhanced security validation
- Workflows with elevated permissions that need extra scrutiny
- Cost-sensitive workflows where timeout enforcement is critical
- Workflows that need to comply with security policies

## AI Engine (`engine:`)
Expand Down Expand Up @@ -595,7 +592,7 @@ Standard GitHub Actions properties:
```yaml
run-name: "Custom workflow run name" # Defaults to workflow name
runs-on: ubuntu-latest # Defaults to ubuntu-latest
timeout_minutes: 30 # Defaults to 15 minutes
timeout_minutes: 30 # Defaults to 20 minutes
```

## Concurrency Control (`concurrency:`)
Expand Down
3 changes: 3 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ const DefaultElixirVersion = "1.17"
// DefaultHaskellVersion is the default version of GHC for runtime setup
const DefaultHaskellVersion = "9.10"

// DefaultAgenticWorkflowTimeoutMinutes is the default timeout for agentic workflow execution in minutes
const DefaultAgenticWorkflowTimeoutMinutes = 20

// DefaultAllowedDomains defines the default localhost domains with port variations
// that are always allowed for Playwright browser automation
var DefaultAllowedDomains = []string{"localhost", "localhost:*", "127.0.0.1", "127.0.0.1:*"}
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/claude_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func (e *ClaudeEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str
if workflowData.TimeoutMinutes != "" {
stepLines = append(stepLines, fmt.Sprintf(" timeout-minutes: %s", strings.TrimPrefix(workflowData.TimeoutMinutes, "timeout_minutes: ")))
} else {
stepLines = append(stepLines, " timeout-minutes: 5") // Default timeout
stepLines = append(stepLines, fmt.Sprintf(" timeout-minutes: %d", constants.DefaultAgenticWorkflowTimeoutMinutes)) // Default timeout for agentic workflows
}

// Build the run command
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/copilot_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ copilot %s 2>&1 | tee %s`, shellJoinArgs(copilotArgs), logFile)
if workflowData.TimeoutMinutes != "" {
stepLines = append(stepLines, fmt.Sprintf(" timeout-minutes: %s", strings.TrimPrefix(workflowData.TimeoutMinutes, "timeout_minutes: ")))
} else {
stepLines = append(stepLines, " timeout-minutes: 5") // Default timeout
stepLines = append(stepLines, fmt.Sprintf(" timeout-minutes: %d", constants.DefaultAgenticWorkflowTimeoutMinutes)) // Default timeout for agentic workflows
}

stepLines = append(stepLines, " run: |")
Expand Down
59 changes: 3 additions & 56 deletions pkg/workflow/strict_mode.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,77 +10,24 @@ func (c *Compiler) validateStrictMode(frontmatter map[string]any, networkPermiss
return nil
}

// 1. Require timeout_minutes
if err := c.validateStrictTimeout(frontmatter); err != nil {
return err
}

// 2. Refuse write permissions
// 1. Refuse write permissions
if err := c.validateStrictPermissions(frontmatter); err != nil {
return err
}

// 3. Require network configuration and refuse "*" wildcard
// 2. Require network configuration and refuse "*" wildcard
if err := c.validateStrictNetwork(networkPermissions); err != nil {
return err
}

// 4. Require network configuration on custom MCP servers
// 3. Require network configuration on custom MCP servers
if err := c.validateStrictMCPNetwork(frontmatter); err != nil {
return err
}

return nil
}

// validateStrictTimeout ensures timeout_minutes is specified in strict mode
func (c *Compiler) validateStrictTimeout(frontmatter map[string]any) error {
timeoutValue, exists := frontmatter["timeout_minutes"]
if !exists {
return fmt.Errorf("strict mode: 'timeout_minutes' is required in workflow frontmatter")
}

// Validate it's a positive integer (handle various numeric types)
switch v := timeoutValue.(type) {
case int:
if v <= 0 {
return fmt.Errorf("strict mode: 'timeout_minutes' must be a positive integer, got %d", v)
}
case int32:
if v <= 0 {
return fmt.Errorf("strict mode: 'timeout_minutes' must be a positive integer, got %d", v)
}
case int64:
if v <= 0 {
return fmt.Errorf("strict mode: 'timeout_minutes' must be a positive integer, got %d", v)
}
case uint:
if v == 0 {
return fmt.Errorf("strict mode: 'timeout_minutes' must be a positive integer, got %d", v)
}
case uint32:
if v == 0 {
return fmt.Errorf("strict mode: 'timeout_minutes' must be a positive integer, got %d", v)
}
case uint64:
if v == 0 {
return fmt.Errorf("strict mode: 'timeout_minutes' must be a positive integer, got %d", v)
}
case float64:
if v <= 0 {
return fmt.Errorf("strict mode: 'timeout_minutes' must be a positive integer, got %f", v)
}
case float32:
if v <= 0 {
return fmt.Errorf("strict mode: 'timeout_minutes' must be a positive integer, got %f", v)
}
default:
return fmt.Errorf("strict mode: 'timeout_minutes' must be an integer, got type %T", timeoutValue)
}

return nil
}

// validateStrictPermissions refuses write permissions in strict mode
func (c *Compiler) validateStrictPermissions(frontmatter map[string]any) error {
permissionsValue, exists := frontmatter["permissions"]
Expand Down
Loading