From f7a260158d17e9cde0b3045d9e8a9cb1c830b82b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 01:02:51 +0000 Subject: [PATCH 1/2] Initial plan From 60b82dea2ee30e561cf6a28eed539d5009824536 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 01:11:32 +0000 Subject: [PATCH 2/2] Remove support for disabling sandbox.gateway (schema, parser, validation) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 1726 +++++++++++++---- pkg/workflow/compiler.go | 6 - .../frontmatter_extraction_security.go | 29 +- pkg/workflow/sandbox_disabled_test.go | 258 +-- pkg/workflow/sandbox_validation.go | 16 +- 5 files changed, 1393 insertions(+), 642 deletions(-) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 678c78308b..2fddd308eb 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -5,25 +5,36 @@ "description": "JSON Schema for validating agentic workflow frontmatter configuration", "version": "1.0.0", "type": "object", - "required": ["on"], + "required": [ + "on" + ], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 256, "description": "Workflow name that appears in the GitHub Actions interface. If not specified, defaults to the filename without extension.", - "examples": ["Copilot Agent PR Analysis", "Dev Hawk", "Smoke Claude"] + "examples": [ + "Copilot Agent PR Analysis", + "Dev Hawk", + "Smoke Claude" + ] }, "description": { "type": "string", "maxLength": 10000, "description": "Optional workflow description that is rendered as a comment in the generated GitHub Actions YAML file (.lock.yml)", - "examples": ["Quickstart for using the GitHub Actions library"] + "examples": [ + "Quickstart for using the GitHub Actions library" + ] }, "source": { "type": "string", "description": "Optional source reference indicating where this workflow was added from. Format: owner/repo/path@ref (e.g., githubnext/agentics/workflows/ci-doctor.md@v1.0.0). Rendered as a comment in the generated lock file.", - "examples": ["githubnext/agentics/workflows/ci-doctor.md", "githubnext/agentics/workflows/daily-perf-improver.md@1f181b37d3fe5862ab590648f25a292e345b5de6"] + "examples": [ + "githubnext/agentics/workflows/ci-doctor.md", + "githubnext/agentics/workflows/daily-perf-improver.md@1f181b37d3fe5862ab590648f25a292e345b5de6" + ] }, "tracker-id": { "type": "string", @@ -31,7 +42,11 @@ "maxLength": 128, "pattern": "^[a-zA-Z0-9_-]+$", "description": "Optional tracker identifier to tag all created assets (issues, discussions, comments, pull requests). Must be at least 8 characters and contain only alphanumeric characters, hyphens, and underscores. This identifier will be inserted in the body/description of all created assets to enable searching and retrieving assets associated with this workflow.", - "examples": ["workflow-2024-q1", "team-alpha-bot", "security_audit_v2"] + "examples": [ + "workflow-2024-q1", + "team-alpha-bot", + "security_audit_v2" + ] }, "labels": { "type": "array", @@ -41,9 +56,18 @@ "minLength": 1 }, "examples": [ - ["automation", "security"], - ["docs", "maintenance"], - ["ci", "testing"] + [ + "automation", + "security" + ], + [ + "docs", + "maintenance" + ], + [ + "ci", + "testing" + ] ] }, "metadata": { @@ -77,7 +101,9 @@ { "type": "object", "description": "Import specification with path and optional inputs", - "required": ["path"], + "required": [ + "path" + ], "additionalProperties": false, "properties": { "path": { @@ -106,10 +132,21 @@ ] }, "examples": [ - ["shared/jqschema.md", "shared/reporting.md"], - ["shared/mcp/gh-aw.md", "shared/jqschema.md", "shared/reporting.md"], - ["../instructions/documentation.instructions.md"], - [".github/agents/my-agent.md"], + [ + "shared/jqschema.md", + "shared/reporting.md" + ], + [ + "shared/mcp/gh-aw.md", + "shared/jqschema.md", + "shared/reporting.md" + ], + [ + "../instructions/documentation.instructions.md" + ], + [ + ".github/agents/my-agent.md" + ], [ { "path": "shared/discussions-data-fetch.md", @@ -125,12 +162,17 @@ "examples": [ { "issues": { - "types": ["opened"] + "types": [ + "opened" + ] } }, { "pull_request": { - "types": ["opened", "synchronize"] + "types": [ + "opened", + "synchronize" + ] } }, "workflow_dispatch", @@ -144,7 +186,13 @@ "type": "string", "minLength": 1, "description": "Simple trigger event name (e.g., 'push', 'issues', 'pull_request', 'discussion', 'schedule', 'fork', 'create', 'delete', 'public', 'watch', 'workflow_call'), schedule shorthand (e.g., 'daily', 'weekly'), or slash command shorthand (e.g., '/my-bot' expands to slash_command + workflow_dispatch)", - "examples": ["push", "issues", "workflow_dispatch", "daily", "/my-bot"] + "examples": [ + "push", + "issues", + "workflow_dispatch", + "daily", + "/my-bot" + ] }, { "type": "object", @@ -196,7 +244,16 @@ { "type": "string", "description": "Single event name or '*' for all events. Use GitHub Actions event names: 'issues', 'issue_comment', 'pull_request_comment', 'pull_request', 'pull_request_review_comment', 'discussion', 'discussion_comment'.", - "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"] + "enum": [ + "*", + "issues", + "issue_comment", + "pull_request_comment", + "pull_request", + "pull_request_review_comment", + "discussion", + "discussion_comment" + ] }, { "type": "array", @@ -205,7 +262,16 @@ "items": { "type": "string", "description": "GitHub Actions event name.", - "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"] + "enum": [ + "*", + "issues", + "issue_comment", + "pull_request_comment", + "pull_request", + "pull_request_review_comment", + "discussion", + "discussion_comment" + ] }, "maxItems": 25 } @@ -262,7 +328,16 @@ { "type": "string", "description": "Single event name or '*' for all events. Use GitHub Actions event names: 'issues', 'issue_comment', 'pull_request_comment', 'pull_request', 'pull_request_review_comment', 'discussion', 'discussion_comment'.", - "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"] + "enum": [ + "*", + "issues", + "issue_comment", + "pull_request_comment", + "pull_request", + "pull_request_review_comment", + "discussion", + "discussion_comment" + ] }, { "type": "array", @@ -271,7 +346,16 @@ "items": { "type": "string", "description": "GitHub Actions event name.", - "enum": ["*", "issues", "issue_comment", "pull_request_comment", "pull_request", "pull_request_review_comment", "discussion", "discussion_comment"] + "enum": [ + "*", + "issues", + "issue_comment", + "pull_request_comment", + "pull_request", + "pull_request_review_comment", + "discussion", + "discussion_comment" + ] }, "maxItems": 25 } @@ -336,25 +420,37 @@ }, "oneOf": [ { - "required": ["branches"], + "required": [ + "branches" + ], "not": { - "required": ["branches-ignore"] + "required": [ + "branches-ignore" + ] } }, { - "required": ["branches-ignore"], + "required": [ + "branches-ignore" + ], "not": { - "required": ["branches"] + "required": [ + "branches" + ] } }, { "not": { "anyOf": [ { - "required": ["branches"] + "required": [ + "branches" + ] }, { - "required": ["branches-ignore"] + "required": [ + "branches-ignore" + ] } ] } @@ -364,25 +460,37 @@ { "oneOf": [ { - "required": ["paths"], + "required": [ + "paths" + ], "not": { - "required": ["paths-ignore"] + "required": [ + "paths-ignore" + ] } }, { - "required": ["paths-ignore"], + "required": [ + "paths-ignore" + ], "not": { - "required": ["paths"] + "required": [ + "paths" + ] } }, { "not": { "anyOf": [ { - "required": ["paths"] + "required": [ + "paths" + ] }, { - "required": ["paths-ignore"] + "required": [ + "paths-ignore" + ] } ] } @@ -502,25 +610,37 @@ "additionalProperties": false, "oneOf": [ { - "required": ["branches"], + "required": [ + "branches" + ], "not": { - "required": ["branches-ignore"] + "required": [ + "branches-ignore" + ] } }, { - "required": ["branches-ignore"], + "required": [ + "branches-ignore" + ], "not": { - "required": ["branches"] + "required": [ + "branches" + ] } }, { "not": { "anyOf": [ { - "required": ["branches"] + "required": [ + "branches" + ] }, { - "required": ["branches-ignore"] + "required": [ + "branches-ignore" + ] } ] } @@ -530,25 +650,37 @@ { "oneOf": [ { - "required": ["paths"], + "required": [ + "paths" + ], "not": { - "required": ["paths-ignore"] + "required": [ + "paths-ignore" + ] } }, { - "required": ["paths-ignore"], + "required": [ + "paths-ignore" + ], "not": { - "required": ["paths"] + "required": [ + "paths" + ] } }, { "not": { "anyOf": [ { - "required": ["paths"] + "required": [ + "paths" + ] }, { - "required": ["paths-ignore"] + "required": [ + "paths-ignore" + ] } ] } @@ -567,7 +699,26 @@ "description": "Types of issue events", "items": { "type": "string", - "enum": ["opened", "edited", "deleted", "transferred", "pinned", "unpinned", "closed", "reopened", "assigned", "unassigned", "labeled", "unlabeled", "locked", "unlocked", "milestoned", "demilestoned", "typed", "untyped"] + "enum": [ + "opened", + "edited", + "deleted", + "transferred", + "pinned", + "unpinned", + "closed", + "reopened", + "assigned", + "unassigned", + "labeled", + "unlabeled", + "locked", + "unlocked", + "milestoned", + "demilestoned", + "typed", + "untyped" + ] } }, "names": { @@ -605,7 +756,11 @@ "description": "Types of issue comment events", "items": { "type": "string", - "enum": ["created", "edited", "deleted"] + "enum": [ + "created", + "edited", + "deleted" + ] } }, "lock-for-agent": { @@ -624,7 +779,21 @@ "description": "Types of discussion events", "items": { "type": "string", - "enum": ["created", "edited", "deleted", "transferred", "pinned", "unpinned", "labeled", "unlabeled", "locked", "unlocked", "category_changed", "answered", "unanswered"] + "enum": [ + "created", + "edited", + "deleted", + "transferred", + "pinned", + "unpinned", + "labeled", + "unlabeled", + "locked", + "unlocked", + "category_changed", + "answered", + "unanswered" + ] } } } @@ -639,7 +808,11 @@ "description": "Types of discussion comment events", "items": { "type": "string", - "enum": ["created", "edited", "deleted"] + "enum": [ + "created", + "edited", + "deleted" + ] } } } @@ -664,7 +837,9 @@ "description": "Cron expression using standard format (e.g., '0 9 * * 1') or fuzzy format (e.g., 'daily', 'daily around 14:00', 'daily between 9:00 and 17:00', 'weekly', 'weekly on monday', 'weekly on friday around 5pm', 'hourly', 'every 2h', 'every 10 minutes'). Fuzzy formats support: daily/weekly schedules with optional time windows, hourly intervals with scattered minutes, interval schedules (minimum 5 minutes), short duration units (m/h/d/w), and UTC timezone offsets (utc+N or utc+HH:MM)." } }, - "required": ["cron"], + "required": [ + "cron" + ], "additionalProperties": false }, "maxItems": 10 @@ -714,7 +889,13 @@ }, "type": { "type": "string", - "enum": ["string", "choice", "boolean", "number", "environment"], + "enum": [ + "string", + "choice", + "boolean", + "number", + "environment" + ], "description": "Input type. GitHub Actions supports: string (default), boolean, choice (string with predefined options), number, and environment (string referencing a GitHub environment)" }, "options": { @@ -748,7 +929,11 @@ "description": "Types of workflow run events", "items": { "type": "string", - "enum": ["completed", "requested", "in_progress"] + "enum": [ + "completed", + "requested", + "in_progress" + ] } }, "branches": { @@ -770,25 +955,37 @@ }, "oneOf": [ { - "required": ["branches"], + "required": [ + "branches" + ], "not": { - "required": ["branches-ignore"] + "required": [ + "branches-ignore" + ] } }, { - "required": ["branches-ignore"], + "required": [ + "branches-ignore" + ], "not": { - "required": ["branches"] + "required": [ + "branches" + ] } }, { "not": { "anyOf": [ { - "required": ["branches"] + "required": [ + "branches" + ] }, { - "required": ["branches-ignore"] + "required": [ + "branches-ignore" + ] } ] } @@ -805,7 +1002,15 @@ "description": "Types of release events", "items": { "type": "string", - "enum": ["published", "unpublished", "created", "edited", "deleted", "prereleased", "released"] + "enum": [ + "published", + "unpublished", + "created", + "edited", + "deleted", + "prereleased", + "released" + ] } } } @@ -820,7 +1025,11 @@ "description": "Types of pull request review comment events", "items": { "type": "string", - "enum": ["created", "edited", "deleted"] + "enum": [ + "created", + "edited", + "deleted" + ] } } } @@ -835,7 +1044,11 @@ "description": "Types of branch protection rule events", "items": { "type": "string", - "enum": ["created", "edited", "deleted"] + "enum": [ + "created", + "edited", + "deleted" + ] } } } @@ -850,7 +1063,12 @@ "description": "Types of check run events", "items": { "type": "string", - "enum": ["created", "rerequested", "completed", "requested_action"] + "enum": [ + "created", + "rerequested", + "completed", + "requested_action" + ] } } } @@ -865,7 +1083,9 @@ "description": "Types of check suite events", "items": { "type": "string", - "enum": ["completed"] + "enum": [ + "completed" + ] } } } @@ -958,7 +1178,11 @@ "description": "Types of label events", "items": { "type": "string", - "enum": ["created", "edited", "deleted"] + "enum": [ + "created", + "edited", + "deleted" + ] } } } @@ -973,7 +1197,9 @@ "description": "Types of merge group events", "items": { "type": "string", - "enum": ["checks_requested"] + "enum": [ + "checks_requested" + ] } } } @@ -988,7 +1214,13 @@ "description": "Types of milestone events", "items": { "type": "string", - "enum": ["created", "closed", "opened", "edited", "deleted"] + "enum": [ + "created", + "closed", + "opened", + "edited", + "deleted" + ] } } } @@ -1106,25 +1338,37 @@ "additionalProperties": false, "oneOf": [ { - "required": ["branches"], + "required": [ + "branches" + ], "not": { - "required": ["branches-ignore"] + "required": [ + "branches-ignore" + ] } }, { - "required": ["branches-ignore"], + "required": [ + "branches-ignore" + ], "not": { - "required": ["branches"] + "required": [ + "branches" + ] } }, { "not": { "anyOf": [ { - "required": ["branches"] + "required": [ + "branches" + ] }, { - "required": ["branches-ignore"] + "required": [ + "branches-ignore" + ] } ] } @@ -1134,25 +1378,37 @@ { "oneOf": [ { - "required": ["paths"], + "required": [ + "paths" + ], "not": { - "required": ["paths-ignore"] + "required": [ + "paths-ignore" + ] } }, { - "required": ["paths-ignore"], + "required": [ + "paths-ignore" + ], "not": { - "required": ["paths"] + "required": [ + "paths" + ] } }, { "not": { "anyOf": [ { - "required": ["paths"] + "required": [ + "paths" + ] }, { - "required": ["paths-ignore"] + "required": [ + "paths-ignore" + ] } ] } @@ -1171,7 +1427,11 @@ "description": "Types of pull request review events", "items": { "type": "string", - "enum": ["submitted", "edited", "dismissed"] + "enum": [ + "submitted", + "edited", + "dismissed" + ] } } } @@ -1186,7 +1446,10 @@ "description": "Types of registry package events", "items": { "type": "string", - "enum": ["published", "updated"] + "enum": [ + "published", + "updated" + ] } } } @@ -1228,7 +1491,9 @@ "description": "Types of watch events", "items": { "type": "string", - "enum": ["started"] + "enum": [ + "started" + ] } } } @@ -1260,7 +1525,11 @@ }, "type": { "type": "string", - "enum": ["string", "number", "boolean"], + "enum": [ + "string", + "number", + "boolean" + ], "description": "Type of the input parameter" }, "default": { @@ -1302,7 +1571,9 @@ }, { "type": "object", - "required": ["query"], + "required": [ + "query" + ], "properties": { "query": { "type": "string", @@ -1328,7 +1599,9 @@ }, { "type": "object", - "required": ["query"], + "required": [ + "query" + ], "properties": { "query": { "type": "string", @@ -1354,17 +1627,37 @@ "oneOf": [ { "type": "string", - "enum": ["+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", "eyes", "none"] + "enum": [ + "+1", + "-1", + "laugh", + "confused", + "heart", + "hooray", + "rocket", + "eyes", + "none" + ] }, { "type": "integer", - "enum": [1, -1], + "enum": [ + 1, + -1 + ], "description": "YAML parses +1 and -1 without quotes as integers. These are converted to +1 and -1 strings respectively." } ], "default": "eyes", "description": "AI reaction to add/remove on triggering item (one of: +1, -1, laugh, confused, heart, hooray, rocket, eyes, none). Use 'none' to disable reactions. Defaults to 'eyes' if not specified.", - "examples": ["eyes", "rocket", "+1", 1, -1, "none"] + "examples": [ + "eyes", + "rocket", + "+1", + 1, + -1, + "none" + ] } }, "additionalProperties": false, @@ -1380,25 +1673,37 @@ { "command": { "name": "mergefest", - "events": ["pull_request_comment"] + "events": [ + "pull_request_comment" + ] } }, { "workflow_run": { - "workflows": ["Dev"], - "types": ["completed"], - "branches": ["copilot/**"] + "workflows": [ + "Dev" + ], + "types": [ + "completed" + ], + "branches": [ + "copilot/**" + ] } }, { "pull_request": { - "types": ["ready_for_review"] + "types": [ + "ready_for_review" + ] }, "workflow_dispatch": null }, { "push": { - "branches": ["main"] + "branches": [ + "main" + ] } } ] @@ -1425,7 +1730,10 @@ "oneOf": [ { "type": "string", - "enum": ["read-all", "write-all"], + "enum": [ + "read-all", + "write-all" + ], "description": "Simple permissions string: 'read-all' (all read permissions) or 'write-all' (all write permissions)" }, { @@ -1435,82 +1743,143 @@ "properties": { "actions": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission for GitHub Actions workflows and runs (read: view workflows, write: manage workflows, none: no access)" }, "attestations": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission for artifact attestations (read: view attestations, write: create attestations, none: no access)" }, "checks": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission for repository checks and status checks (read: view checks, write: create/update checks, none: no access)" }, "contents": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission for repository contents (read: view files, write: modify files/branches, none: no access)" }, "deployments": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission for repository deployments (read: view deployments, write: create/update deployments, none: no access)" }, "discussions": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission for repository discussions (read: view discussions, write: create/update discussions, none: no access)" }, "id-token": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission level for OIDC token requests (read/write/none). Allows workflows to request JWT tokens for cloud provider authentication." }, "issues": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission for repository issues (read: view issues, write: create/update/close issues, none: no access)" }, "models": { "type": "string", - "enum": ["read", "none"], + "enum": [ + "read", + "none" + ], "description": "Permission for GitHub Copilot models (read: access AI models for agentic workflows, none: no access)" }, "metadata": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission for repository metadata (read: view repository information, write: update repository metadata, none: no access)" }, "packages": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission level for GitHub Packages (read/write/none). Controls access to publish, modify, or delete packages." }, "pages": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission level for GitHub Pages (read/write/none). Controls access to deploy and manage GitHub Pages sites." }, "pull-requests": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission level for pull requests (read/write/none). Controls access to create, edit, review, and manage pull requests." }, "security-events": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission level for security events (read/write/none). Controls access to view and manage code scanning alerts and security findings." }, "statuses": { "type": "string", - "enum": ["read", "write", "none"], + "enum": [ + "read", + "write", + "none" + ], "description": "Permission level for commit statuses (read/write/none). Controls access to create and update commit status checks." }, "all": { "type": "string", - "enum": ["read"], + "enum": [ + "read" + ], "description": "Permission shorthand that applies read access to all permission scopes. Can be combined with specific write permissions to override individual scopes. 'write' is not allowed for all." } } @@ -1520,7 +1889,10 @@ "run-name": { "type": "string", "description": "Custom name for workflow runs that appears in the GitHub Actions interface (supports GitHub expressions like ${{ github.event.issue.title }})", - "examples": ["Deploy to ${{ github.event.inputs.environment }}", "Build #${{ github.run_number }}"] + "examples": [ + "Deploy to ${{ github.event.inputs.environment }}", + "Build #${{ github.run_number }}" + ] }, "jobs": { "type": "object", @@ -1563,10 +1935,14 @@ "additionalProperties": false, "oneOf": [ { - "required": ["uses"] + "required": [ + "uses" + ] }, { - "required": ["run"] + "required": [ + "run" + ] } ], "properties": { @@ -1779,17 +2155,26 @@ ], "examples": [ "ubuntu-latest", - ["ubuntu-latest", "self-hosted"], + [ + "ubuntu-latest", + "self-hosted" + ], { "group": "larger-runners", - "labels": ["ubuntu-latest-8-cores"] + "labels": [ + "ubuntu-latest-8-cores" + ] } ] }, "timeout-minutes": { "type": "integer", "description": "Workflow timeout in minutes (GitHub Actions standard field). Defaults to 20 minutes for agentic workflows. Has sensible defaults and can typically be omitted.", - "examples": [5, 10, 30] + "examples": [ + 5, + 10, + 30 + ] }, "timeout_minutes": { "type": "integer", @@ -1803,7 +2188,10 @@ { "type": "string", "description": "Simple concurrency group name to prevent multiple runs in the same group. Use expressions like '${{ github.workflow }}' for per-workflow isolation or '${{ github.ref }}' for per-branch isolation. Agentic workflows automatically generate enhanced concurrency policies using 'gh-aw-{engine-id}' as the default group to limit concurrent AI workloads across all workflows using the same engine.", - "examples": ["my-workflow-group", "workflow-${{ github.ref }}"] + "examples": [ + "my-workflow-group", + "workflow-${{ github.ref }}" + ] }, { "type": "object", @@ -1819,7 +2207,9 @@ "description": "Whether to cancel in-progress workflows in the same concurrency group when a new one starts. Default: false (queue new runs). Set to true for agentic workflows where only the latest run matters (e.g., PR analysis that becomes stale when new commits are pushed)." } }, - "required": ["group"], + "required": [ + "group" + ], "examples": [ { "group": "dev-workflow-${{ github.ref }}", @@ -1896,7 +2286,9 @@ "description": "A deployment URL" } }, - "required": ["name"], + "required": [ + "name" + ], "additionalProperties": false } ] @@ -1964,7 +2356,9 @@ "description": "Additional Docker container options" } }, - "required": ["image"], + "required": [ + "image" + ], "additionalProperties": false } ] @@ -2034,7 +2428,9 @@ "description": "Additional Docker container options" } }, - "required": ["image"], + "required": [ + "image" + ], "additionalProperties": false } ] @@ -2046,13 +2442,24 @@ "examples": [ "defaults", { - "allowed": ["defaults", "github"] + "allowed": [ + "defaults", + "github" + ] }, { - "allowed": ["defaults", "python", "node", "*.example.com"] + "allowed": [ + "defaults", + "python", + "node", + "*.example.com" + ] }, { - "allowed": ["api.openai.com", "*.github.com"], + "allowed": [ + "api.openai.com", + "*.github.com" + ], "firewall": { "version": "v1.0.0", "log-level": "debug" @@ -2062,7 +2469,9 @@ "oneOf": [ { "type": "string", - "enum": ["defaults"], + "enum": [ + "defaults" + ], "description": "Use default network permissions (basic infrastructure: certificates, JSON schema, Ubuntu, etc.)" }, { @@ -2102,7 +2511,9 @@ }, { "type": "string", - "enum": ["disable"], + "enum": [ + "disable" + ], "description": "Disable AWF firewall (triggers warning if allowed != *, error in strict mode if allowed is not * or engine does not support firewall)" }, { @@ -2117,14 +2528,27 @@ } }, "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "AWF version to use (empty = latest release). Can be a string (e.g., 'v1.0.0', 'latest') or number (e.g., 20, 3.11). Numeric values are automatically converted to strings at runtime.", - "examples": ["v1.0.0", "latest", 20, 3.11] + "examples": [ + "v1.0.0", + "latest", + 20, + 3.11 + ] }, "log-level": { "type": "string", "description": "AWF log level (default: info). Valid values: debug, info, warn, error", - "enum": ["debug", "info", "warn", "error"] + "enum": [ + "debug", + "info", + "warn", + "error" + ] }, "ssl-bump": { "type": "boolean", @@ -2139,7 +2563,12 @@ "pattern": "^https://.*", "description": "HTTPS URL pattern with optional wildcards (e.g., 'https://github.com/githubnext/*')" }, - "examples": [["https://github.com/githubnext/*", "https://api.github.com/repos/*"]] + "examples": [ + [ + "https://github.com/githubnext/*", + "https://api.github.com/repos/*" + ] + ] } }, "additionalProperties": false @@ -2154,13 +2583,14 @@ "sandbox": { "description": "Sandbox configuration for AI engines. Controls agent sandbox (AWF or Sandbox Runtime) and MCP gateway.", "oneOf": [ - { - "type": "boolean", - "description": "Set to false to completely disable sandbox features (firewall and gateway). Warning: This removes important security protections and should only be used in controlled environments. Not allowed in strict mode." - }, { "type": "string", - "enum": ["default", "sandbox-runtime", "awf", "srt"], + "enum": [ + "default", + "sandbox-runtime", + "awf", + "srt" + ], "description": "Legacy string format for sandbox type: 'default' for no sandbox, 'sandbox-runtime' or 'srt' for Anthropic Sandbox Runtime, 'awf' for Agent Workflow Firewall" }, { @@ -2169,7 +2599,12 @@ "properties": { "type": { "type": "string", - "enum": ["default", "sandbox-runtime", "awf", "srt"], + "enum": [ + "default", + "sandbox-runtime", + "awf", + "srt" + ], "description": "Legacy sandbox type field (use agent instead)" }, "agent": { @@ -2178,7 +2613,10 @@ "oneOf": [ { "type": "string", - "enum": ["awf", "srt"], + "enum": [ + "awf", + "srt" + ], "description": "Sandbox type: 'awf' for Agent Workflow Firewall, 'srt' for Sandbox Runtime" }, { @@ -2187,12 +2625,18 @@ "properties": { "id": { "type": "string", - "enum": ["awf", "srt"], + "enum": [ + "awf", + "srt" + ], "description": "Agent identifier (replaces 'type' field in new format): 'awf' for Agent Workflow Firewall, 'srt' for Sandbox Runtime" }, "type": { "type": "string", - "enum": ["awf", "srt"], + "enum": [ + "awf", + "srt" + ], "description": "Legacy: Sandbox type to use (use 'id' instead)" }, "command": { @@ -2221,7 +2665,12 @@ "pattern": "^[^:]+:[^:]+:(ro|rw)$", "description": "Mount specification in format 'source:destination:mode'" }, - "examples": [["/host/data:/data:ro", "/usr/local/bin/custom-tool:/usr/local/bin/custom-tool:ro"]] + "examples": [ + [ + "/host/data:/data:ro", + "/usr/local/bin/custom-tool:/usr/local/bin/custom-tool:ro" + ] + ] }, "config": { "type": "object", @@ -2336,14 +2785,24 @@ "description": "Container image for the MCP gateway executable (required)" }, "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Optional version/tag for the container image (e.g., 'latest', 'v1.0.0')", - "examples": ["latest", "v1.0.0"] + "examples": [ + "latest", + "v1.0.0" + ] }, "entrypoint": { "type": "string", "description": "Optional custom entrypoint for the MCP gateway container. Overrides the container's default entrypoint.", - "examples": ["/bin/bash", "/custom/start.sh", "/usr/bin/env"] + "examples": [ + "/bin/bash", + "/custom/start.sh", + "/usr/bin/env" + ] }, "args": { "type": "array", @@ -2367,7 +2826,12 @@ "pattern": "^[^:]+:[^:]+:(ro|rw)$", "description": "Mount specification in format 'source:destination:mode'" }, - "examples": [["/host/data:/container/data:ro", "/host/config:/container/config:rw"]] + "examples": [ + [ + "/host/data:/container/data:ro", + "/host/config:/container/config:rw" + ] + ] }, "env": { "type": "object", @@ -2392,11 +2856,16 @@ }, "domain": { "type": "string", - "enum": ["localhost", "host.docker.internal"], + "enum": [ + "localhost", + "host.docker.internal" + ], "description": "Gateway domain for URL generation (default: 'host.docker.internal' when agent is enabled, 'localhost' when disabled)" } }, - "required": ["container"], + "required": [ + "container" + ], "additionalProperties": false } }, @@ -2417,7 +2886,10 @@ "type": "srt", "config": { "filesystem": { - "allowWrite": [".", "/tmp"] + "allowWrite": [ + ".", + "/tmp" + ] } } } @@ -2441,7 +2913,10 @@ "if": { "type": "string", "description": "Conditional execution expression", - "examples": ["${{ github.event.workflow_run.event == 'workflow_dispatch' }}", "${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}"] + "examples": [ + "${{ github.event.workflow_run.event == 'workflow_dispatch' }}", + "${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}" + ] }, "steps": { "description": "Custom workflow steps", @@ -2559,7 +3034,10 @@ "filesystem": { "type": "stdio", "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-filesystem"] + "args": [ + "-y", + "@modelcontextprotocol/server-filesystem" + ] } }, { @@ -2636,13 +3114,24 @@ }, "mode": { "type": "string", - "enum": ["local", "remote"], + "enum": [ + "local", + "remote" + ], "description": "MCP server mode: 'local' (Docker-based, default) or 'remote' (hosted at api.githubcopilot.com)" }, "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Optional version specification for the GitHub MCP server (used with 'local' type). Can be a string (e.g., 'v1.0.0', 'latest') or number (e.g., 20, 3.11). Numeric values are automatically converted to strings at runtime.", - "examples": ["v1.0.0", "latest", 20, 3.11] + "examples": [ + "v1.0.0", + "latest", + 20, + 3.11 + ] }, "args": { "type": "array", @@ -2707,7 +3196,15 @@ "pattern": "^[^:]+:[^:]+(:(ro|rw))?$", "description": "Mount specification in format 'host:container:mode'" }, - "examples": [["/data:/data:ro", "/tmp:/tmp:rw"], ["/opt:/opt:ro"]] + "examples": [ + [ + "/data:/data:ro", + "/tmp:/tmp:rw" + ], + [ + "/opt:/opt:ro" + ] + ] }, "app": { "type": "object", @@ -2733,7 +3230,10 @@ } } }, - "required": ["app-id", "private-key"], + "required": [ + "app-id", + "private-key" + ], "additionalProperties": false, "examples": [ { @@ -2743,7 +3243,10 @@ { "app-id": "${{ vars.APP_ID }}", "private-key": "${{ secrets.APP_PRIVATE_KEY }}", - "repositories": ["repo1", "repo2"] + "repositories": [ + "repo1", + "repo2" + ] } ] } @@ -2751,16 +3254,30 @@ "additionalProperties": false, "examples": [ { - "toolsets": ["pull_requests", "actions", "repos"] + "toolsets": [ + "pull_requests", + "actions", + "repos" + ] }, { - "allowed": ["search_pull_requests", "pull_request_read", "list_pull_requests", "get_file_contents", "list_commits", "get_commit"] + "allowed": [ + "search_pull_requests", + "pull_request_read", + "list_pull_requests", + "get_file_contents", + "list_commits", + "get_commit" + ] }, { "read-only": true }, { - "toolsets": ["pull_requests", "repos"] + "toolsets": [ + "pull_requests", + "repos" + ] } ] } @@ -2768,14 +3285,25 @@ "examples": [ null, { - "toolsets": ["pull_requests", "actions", "repos"] + "toolsets": [ + "pull_requests", + "actions", + "repos" + ] }, { - "allowed": ["search_pull_requests", "pull_request_read", "get_file_contents"] + "allowed": [ + "search_pull_requests", + "pull_request_read", + "get_file_contents" + ] }, { "read-only": true, - "toolsets": ["repos", "issues"] + "toolsets": [ + "repos", + "issues" + ] }, false ] @@ -2802,10 +3330,36 @@ ], "examples": [ true, - ["git fetch", "git checkout", "git status", "git diff", "git log", "make recompile", "make fmt", "make lint", "make test-unit", "cat", "echo", "ls"], - ["echo", "ls", "cat"], - ["gh pr list *", "gh search prs *", "jq *"], - ["date *", "echo *", "cat", "ls"] + [ + "git fetch", + "git checkout", + "git status", + "git diff", + "git log", + "make recompile", + "make fmt", + "make lint", + "make test-unit", + "cat", + "echo", + "ls" + ], + [ + "echo", + "ls", + "cat" + ], + [ + "gh pr list *", + "gh search prs *", + "jq *" + ], + [ + "date *", + "echo *", + "cat", + "ls" + ] ] }, "web-fetch": { @@ -2882,9 +3436,16 @@ "description": "Playwright tool configuration with custom version and domain restrictions", "properties": { "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Optional Playwright container version (e.g., 'v1.41.0', 1.41, 20). Numeric values are automatically converted to strings at runtime.", - "examples": ["v1.41.0", 1.41, 20] + "examples": [ + "v1.41.0", + 1.41, + 20 + ] }, "allowed_domains": { "description": "Domains allowed for Playwright browser network access. Defaults to localhost only for security.", @@ -2926,7 +3487,10 @@ "description": "Enable agentic-workflows tool with default settings (same as true)" } ], - "examples": [true, null] + "examples": [ + true, + null + ] }, "cache-memory": { "description": "Cache memory MCP configuration for persistent memory storage", @@ -3002,7 +3566,10 @@ "description": "If true, only restore the cache without saving it back. Uses actions/cache/restore instead of actions/cache. No artifact upload step will be generated." } }, - "required": ["id", "key"], + "required": [ + "id", + "key" + ], "additionalProperties": false }, "minItems": 1, @@ -3043,7 +3610,11 @@ "type": "integer", "minimum": 1, "description": "Timeout in seconds for tool/MCP server operations. Applies to all tools and MCP servers if supported by the engine. Default varies by engine (Claude: 60s, Codex: 120s).", - "examples": [60, 120, 300] + "examples": [ + 60, + 120, + 300 + ] }, "startup-timeout": { "type": "integer", @@ -3062,7 +3633,14 @@ "description": "Short syntax: array of language identifiers to enable (e.g., [\"go\", \"typescript\"])", "items": { "type": "string", - "enum": ["go", "typescript", "python", "java", "rust", "csharp"] + "enum": [ + "go", + "typescript", + "python", + "java", + "rust", + "csharp" + ] } }, { @@ -3070,14 +3648,24 @@ "description": "Serena configuration with custom version and language-specific settings", "properties": { "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Optional Serena MCP version. Numeric values are automatically converted to strings at runtime.", - "examples": ["latest", "0.1.0", 1.0] + "examples": [ + "latest", + "0.1.0", + 1.0 + ] }, "mode": { "type": "string", "description": "Serena execution mode: 'docker' (default, runs in container) or 'local' (runs locally with uvx and HTTP transport)", - "enum": ["docker", "local"], + "enum": [ + "docker", + "local" + ], "default": "docker" }, "args": { @@ -3101,7 +3689,10 @@ "type": "object", "properties": { "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Go version (e.g., \"1.21\", 1.21)" }, "go-mod-file": { @@ -3128,7 +3719,10 @@ "type": "object", "properties": { "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Node.js version for TypeScript (e.g., \"22\", 22)" } }, @@ -3147,7 +3741,10 @@ "type": "object", "properties": { "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Python version (e.g., \"3.12\", 3.12)" } }, @@ -3166,7 +3763,10 @@ "type": "object", "properties": { "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Java version (e.g., \"21\", 21)" } }, @@ -3185,7 +3785,10 @@ "type": "object", "properties": { "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Rust version (e.g., \"stable\", \"1.75\")" } }, @@ -3204,7 +3807,10 @@ "type": "object", "properties": { "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": ".NET version for C# (e.g., \"8.0\", 8.0)" } }, @@ -3444,11 +4050,19 @@ }, "type": { "type": "string", - "enum": ["stdio", "http", "remote", "local"], + "enum": [ + "stdio", + "http", + "remote", + "local" + ], "description": "MCP connection type. Use 'stdio' for command-based or container-based servers, 'http' for HTTP-based servers. 'local' is an alias for 'stdio' and is normalized during parsing." }, "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Version of the MCP server" }, "toolsets": { @@ -3485,7 +4099,10 @@ "registry": { "type": "string", "description": "URI to installation location from MCP registry", - "examples": ["https://api.mcp.github.com/v0/servers/microsoft/markitdown", "https://registry.npmjs.org/@my/tool"] + "examples": [ + "https://api.mcp.github.com/v0/servers/microsoft/markitdown", + "https://registry.npmjs.org/@my/tool" + ] }, "allowed": { "type": "array", @@ -3493,12 +4110,28 @@ "type": "string" }, "description": "List of allowed tool names (restricts which tools from the MCP server can be used)", - "examples": [["*"], ["store_memory", "retrieve_memory"], ["create-issue", "add-comment"]] + "examples": [ + [ + "*" + ], + [ + "store_memory", + "retrieve_memory" + ], + [ + "create-issue", + "add-comment" + ] + ] }, "entrypoint": { "type": "string", "description": "Optional entrypoint override for container (equivalent to docker run --entrypoint)", - "examples": ["/bin/sh", "/custom/entrypoint.sh", "python"] + "examples": [ + "/bin/sh", + "/custom/entrypoint.sh", + "python" + ] }, "mounts": { "type": "array", @@ -3507,7 +4140,15 @@ "pattern": "^[^:]+:[^:]+:(ro|rw)$" }, "description": "Volume mounts for container in format 'source:dest:mode' where mode is 'ro' or 'rw'", - "examples": [["/tmp/data:/data:ro"], ["/workspace:/workspace:rw", "/config:/config:ro"]] + "examples": [ + [ + "/tmp/data:/data:ro" + ], + [ + "/workspace:/workspace:rw", + "/config:/config:ro" + ] + ] } }, "additionalProperties": true @@ -3575,17 +4216,25 @@ "description": "If true, only checks if cache entry exists and skips download" } }, - "required": ["key", "path"], + "required": [ + "key", + "path" + ], "additionalProperties": false, "examples": [ { "key": "node-modules-${{ hashFiles('package-lock.json') }}", "path": "node_modules", - "restore-keys": ["node-modules-"] + "restore-keys": [ + "node-modules-" + ] }, { "key": "build-cache-${{ github.sha }}", - "path": ["dist", ".cache"], + "path": [ + "dist", + ".cache" + ], "restore-keys": "build-cache-", "fail-on-cache-miss": false } @@ -3646,7 +4295,10 @@ "description": "If true, only checks if cache entry exists and skips download" } }, - "required": ["key", "path"], + "required": [ + "key", + "path" + ], "additionalProperties": false } } @@ -3660,13 +4312,18 @@ { "create-issue": { "title-prefix": "[AI] ", - "labels": ["automation", "ai-generated"] + "labels": [ + "automation", + "ai-generated" + ] } }, { "create-pull-request": { "title-prefix": "[Bot] ", - "labels": ["bot"] + "labels": [ + "bot" + ] } }, { @@ -3689,7 +4346,19 @@ "type": "string", "pattern": "^(repo|[a-zA-Z0-9][-a-zA-Z0-9]{0,38}/[a-zA-Z0-9._-]+)$" }, - "examples": [["repo"], ["repo", "octocat/hello-world"], ["microsoft/vscode", "microsoft/typescript"]] + "examples": [ + [ + "repo" + ], + [ + "repo", + "octocat/hello-world" + ], + [ + "microsoft/vscode", + "microsoft/typescript" + ] + ] }, "create-issue": { "oneOf": [ @@ -3762,7 +4431,9 @@ }, { "type": "boolean", - "enum": [false], + "enum": [ + false + ], "description": "Set to false to explicitly disable expiration" } ], @@ -3783,21 +4454,33 @@ "examples": [ { "title-prefix": "[ca] ", - "labels": ["automation", "dependencies"], + "labels": [ + "automation", + "dependencies" + ], "assignees": "copilot" }, { "title-prefix": "[duplicate-code] ", - "labels": ["code-quality", "automated-analysis"], + "labels": [ + "code-quality", + "automated-analysis" + ], "assignees": "copilot" }, { - "allowed-repos": ["org/other-repo", "org/another-repo"], + "allowed-repos": [ + "org/other-repo", + "org/another-repo" + ], "title-prefix": "[cross-repo] " }, { "title-prefix": "[weekly-report] ", - "labels": ["report", "automation"], + "labels": [ + "report", + "automation" + ], "close-older-issues": true } ] @@ -3897,7 +4580,9 @@ { "type": "object", "description": "Configuration for managing GitHub Projects v2 boards. Smart tool that can add issue/PR items and update custom fields on existing items. By default it is update-only: if the project does not exist, the job fails with instructions to create it manually. To allow workflows to create missing projects, explicitly opt in via the agent output field create_if_missing=true (and/or provide a github-token override). NOTE: Projects v2 requires a Personal Access Token (PAT) or GitHub App token with appropriate permissions; the GITHUB_TOKEN cannot be used for Projects v2. Safe output items produced by the agent use type=update_project Configuration also supports an optional views array for declaring project views to create. Safe output items produced by the agent use type=update_project and may include: project (board name), content_type (issue|pull_request), content_number, fields, campaign_id, and create_if_missing.", - "required": ["project"], + "required": [ + "project" + ], "properties": { "max": { "type": "integer", @@ -3913,7 +4598,10 @@ "type": "string", "description": "Target project URL for update-project operations. This is required in the configuration for documentation purposes. Agent messages MUST explicitly include the project field in their output - the configured value is not used as a fallback. Must be a valid GitHub Projects v2 URL.", "pattern": "^https://github\\.com/(users|orgs)/([^/]+|<[A-Z_]+>)/projects/(\\d+|<[A-Z_]+>)$", - "examples": ["https://github.com/orgs/myorg/projects/123", "https://github.com/users/username/projects/456"] + "examples": [ + "https://github.com/orgs/myorg/projects/123", + "https://github.com/users/username/projects/456" + ] }, "views": { "type": "array", @@ -3921,7 +4609,10 @@ "items": { "type": "object", "description": "View configuration for creating project views", - "required": ["name", "layout"], + "required": [ + "name", + "layout" + ], "properties": { "name": { "type": "string", @@ -3929,7 +4620,11 @@ }, "layout": { "type": "string", - "enum": ["table", "board", "roadmap"], + "enum": [ + "table", + "board", + "roadmap" + ], "description": "The layout type of the view" }, "filter": { @@ -3956,7 +4651,10 @@ "description": "Optional array of project custom fields to create up-front. Useful for campaign projects that require a fixed set of fields.", "items": { "type": "object", - "required": ["name", "data-type"], + "required": [ + "name", + "data-type" + ], "properties": { "name": { "type": "string", @@ -3964,7 +4662,13 @@ }, "data-type": { "type": "string", - "enum": ["DATE", "TEXT", "NUMBER", "SINGLE_SELECT", "ITERATION"], + "enum": [ + "DATE", + "TEXT", + "NUMBER", + "SINGLE_SELECT", + "ITERATION" + ], "description": "The GitHub Projects v2 custom field type" }, "options": { @@ -4027,7 +4731,10 @@ "items": { "type": "object", "description": "View configuration for creating project views", - "required": ["name", "layout"], + "required": [ + "name", + "layout" + ], "properties": { "name": { "type": "string", @@ -4035,7 +4742,11 @@ }, "layout": { "type": "string", - "enum": ["table", "board", "roadmap"], + "enum": [ + "table", + "board", + "roadmap" + ], "description": "The layout type of the view" }, "filter": { @@ -4062,7 +4773,10 @@ "description": "Optional array of project custom fields to create automatically after project creation. Useful for campaign projects that require a fixed set of fields.", "items": { "type": "object", - "required": ["name", "data-type"], + "required": [ + "name", + "data-type" + ], "properties": { "name": { "type": "string", @@ -4070,7 +4784,13 @@ }, "data-type": { "type": "string", - "enum": ["DATE", "TEXT", "NUMBER", "SINGLE_SELECT", "ITERATION"], + "enum": [ + "DATE", + "TEXT", + "NUMBER", + "SINGLE_SELECT", + "ITERATION" + ], "description": "The GitHub Projects v2 custom field type" }, "options": { @@ -4092,7 +4812,9 @@ "description": "Enable project creation with default configuration (max=1)" }, { - "enum": [null], + "enum": [ + null + ], "description": "Alternative null value syntax" } ], @@ -4106,7 +4828,9 @@ { "type": "object", "description": "Configuration for creating GitHub Project status updates. Status updates provide stakeholder communication and historical record of project progress. Requires a Personal Access Token (PAT) or GitHub App token with Projects: Read+Write permission. The GITHUB_TOKEN cannot be used for Projects v2. Status updates are created on the specified project board and appear in the Updates tab. Typically used by campaign orchestrators to post run summaries with progress, findings, and next steps.", - "required": ["project"], + "required": [ + "project" + ], "properties": { "max": { "type": "integer", @@ -4122,7 +4846,10 @@ "type": "string", "description": "Target project URL for status update operations. This is required in the configuration for documentation purposes. Agent messages MUST explicitly include the project field in their output - the configured value is not used as a fallback. Must be a valid GitHub Projects v2 URL.", "pattern": "^https://github\\.com/(users|orgs)/([^/]+|<[A-Z_]+>)/projects/(\\d+|<[A-Z_]+>)$", - "examples": ["https://github.com/orgs/myorg/projects/123", "https://github.com/users/username/projects/456"] + "examples": [ + "https://github.com/orgs/myorg/projects/123", + "https://github.com/users/username/projects/456" + ] } }, "additionalProperties": false, @@ -4154,9 +4881,16 @@ "description": "Optional prefix for the discussion title" }, "category": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Optional discussion category. Can be a category ID (string or numeric value), category name, or category slug/route. If not specified, uses the first available category. Matched first against category IDs, then against category names, then against category slugs. Numeric values are automatically converted to strings at runtime.", - "examples": ["General", "audits", 123456789] + "examples": [ + "General", + "audits", + 123456789 + ] }, "labels": { "type": "array", @@ -4208,7 +4942,9 @@ }, { "type": "boolean", - "enum": [false], + "enum": [ + false + ], "description": "Set to false to explicitly disable expiration" } ], @@ -4235,12 +4971,17 @@ "close-older-discussions": true }, { - "labels": ["weekly-report", "automation"], + "labels": [ + "weekly-report", + "automation" + ], "category": "reports", "close-older-discussions": true }, { - "allowed-repos": ["org/other-repo"], + "allowed-repos": [ + "org/other-repo" + ], "category": "General" } ] @@ -4294,7 +5035,10 @@ "required-category": "Ideas" }, { - "required-labels": ["resolved", "completed"], + "required-labels": [ + "resolved", + "completed" + ], "max": 1 } ] @@ -4393,7 +5137,10 @@ "required-title-prefix": "[refactor] " }, { - "required-labels": ["automated", "stale"], + "required-labels": [ + "automated", + "stale" + ], "max": 10 } ] @@ -4447,7 +5194,10 @@ "required-title-prefix": "[bot] " }, { - "required-labels": ["automated", "outdated"], + "required-labels": [ + "automated", + "outdated" + ], "max": 5 } ] @@ -4501,7 +5251,10 @@ "required-title-prefix": "[bot] " }, { - "required-labels": ["automated", "ready"], + "required-labels": [ + "automated", + "ready" + ], "max": 1 } ] @@ -4555,7 +5308,13 @@ "description": "List of allowed reasons for hiding older comments when hide-older-comments is enabled. Default: all reasons allowed (spam, abuse, off_topic, outdated, resolved).", "items": { "type": "string", - "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"] + "enum": [ + "spam", + "abuse", + "off_topic", + "outdated", + "resolved" + ] } } }, @@ -4624,7 +5383,11 @@ }, "if-no-changes": { "type": "string", - "enum": ["warn", "error", "ignore"], + "enum": [ + "warn", + "error", + "ignore" + ], "description": "Behavior when no changes to push: 'warn' (default - log warning but succeed), 'error' (fail the action), or 'ignore' (silent success)" }, "allow-empty": { @@ -4671,13 +5434,19 @@ "examples": [ { "title-prefix": "[docs] ", - "labels": ["documentation", "automation"], + "labels": [ + "documentation", + "automation" + ], "reviewers": "copilot", "draft": false }, { "title-prefix": "[security-fix] ", - "labels": ["security", "automated-fix"], + "labels": [ + "security", + "automated-fix" + ], "reviewers": "copilot" } ] @@ -4704,7 +5473,10 @@ "side": { "type": "string", "description": "Side of the diff for comments: 'LEFT' or 'RIGHT' (default: 'RIGHT')", - "enum": ["LEFT", "RIGHT"] + "enum": [ + "LEFT", + "RIGHT" + ] }, "target": { "type": "string", @@ -4980,7 +5752,10 @@ "minimum": 1 }, "target": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Target issue/PR to assign agents to. Use 'triggering' (default) for the triggering issue/PR, '*' to require explicit issue_number/pull_number, or a specific issue/PR number. With 'triggering', auto-resolves from github.event.issue.number or github.event.pull_request.number." }, "target-repo": { @@ -5025,7 +5800,10 @@ "minimum": 1 }, "target": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Target issue to assign users to. Use 'triggering' (default) for the triggering issue, '*' to allow any issue, or a specific issue number." }, "target-repo": { @@ -5161,7 +5939,11 @@ "operation": { "type": "string", "description": "Default operation for body updates: 'append' (add to end), 'prepend' (add to start), or 'replace' (overwrite completely). Defaults to 'replace' if not specified.", - "enum": ["append", "prepend", "replace"] + "enum": [ + "append", + "prepend", + "replace" + ] }, "max": { "type": "integer", @@ -5218,7 +6000,11 @@ }, "if-no-changes": { "type": "string", - "enum": ["warn", "error", "ignore"], + "enum": [ + "warn", + "error", + "ignore" + ], "description": "Behavior when no changes to push: 'warn' (default - log warning but succeed), 'error' (fail the action), or 'ignore' (silent success)" }, "commit-title-suffix": { @@ -5260,7 +6046,13 @@ "description": "List of allowed reasons for hiding comments. Default: all reasons allowed (spam, abuse, off_topic, outdated, resolved).", "items": { "type": "string", - "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"] + "enum": [ + "spam", + "abuse", + "off_topic", + "outdated", + "resolved" + ] } } }, @@ -5271,39 +6063,49 @@ }, "dispatch-workflow": { "oneOf": [ - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - }, - "description": "Shorthand format: array of workflow names to dispatch (without .md extension). Workflows must exist in same directory and support workflow_dispatch trigger. Self-reference not allowed. Max defaults to 1." - }, { "type": "object", - "description": "Configuration for dispatching other workflows from this workflow. Allows workflows to trigger other workflows via workflow_dispatch events. Includes self-reference prevention and path traversal protection.", + "description": "Configuration for dispatching workflow_dispatch events to other workflows. Orchestrators use this to delegate work to worker workflows.", "properties": { "workflows": { "type": "array", - "minItems": 1, + "description": "List of workflow names (without .md extension) to allow dispatching. Each workflow must exist in .github/workflows/.", "items": { - "type": "string" + "type": "string", + "minLength": 1 }, - "description": "List of workflow names to dispatch (without .md extension). Workflows must exist in same directory and support workflow_dispatch trigger. Self-reference not allowed." + "minItems": 1, + "maxItems": 50 }, "max": { "type": "integer", + "description": "Maximum number of workflow dispatch operations per run (default: 1, max: 50)", "minimum": 1, "maximum": 50, - "description": "Maximum number of concurrent workflow dispatches (default: 1, maximum: 50)" + "default": 1 + }, + "github-token": { + "$ref": "#/$defs/github_token", + "description": "GitHub token to use for dispatching workflows. Overrides global github-token if specified." } }, - "required": ["workflows"], + "required": [ + "workflows" + ], "additionalProperties": false + }, + { + "type": "array", + "description": "Shorthand array format: list of workflow names (without .md extension) to allow dispatching", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1, + "maxItems": 50 } ], - "$comment": "Self-reference prevention: workflow cannot dispatch itself (prevents infinite loops). Path traversal protection: all paths validated with isPathWithinDir(). Validation: pkg/workflow/dispatch_workflow_validation.go", - "description": "Enable dispatching other workflows from this workflow. Allows workflows to trigger other workflows via workflow_dispatch events with security constraints." + "description": "Dispatch workflow_dispatch events to other workflows. Used by orchestrators to delegate work to worker workflows with controlled maximum dispatch count." }, "missing-tool": { "oneOf": [ @@ -5509,7 +6311,10 @@ "staged": { "type": "boolean", "description": "If true, emit step summary messages instead of making GitHub API calls (preview mode)", - "examples": [true, false] + "examples": [ + true, + false + ] }, "env": { "type": "object", @@ -5525,7 +6330,11 @@ "github-token": { "$ref": "#/$defs/github_token", "description": "GitHub token to use for safe output jobs. Typically a secret reference like ${{ secrets.GITHUB_TOKEN }} or ${{ secrets.CUSTOM_PAT }}", - "examples": ["${{ secrets.GITHUB_TOKEN }}", "${{ secrets.CUSTOM_PAT }}", "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"] + "examples": [ + "${{ secrets.GITHUB_TOKEN }}", + "${{ secrets.CUSTOM_PAT }}", + "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}" + ] }, "app": { "type": "object", @@ -5534,17 +6343,25 @@ "app-id": { "type": "string", "description": "GitHub App ID. Should reference a variable (e.g., ${{ vars.APP_ID }}).", - "examples": ["${{ vars.APP_ID }}", "${{ secrets.APP_ID }}"] + "examples": [ + "${{ vars.APP_ID }}", + "${{ secrets.APP_ID }}" + ] }, "private-key": { "type": "string", "description": "GitHub App private key. Should reference a secret (e.g., ${{ secrets.APP_PRIVATE_KEY }}).", - "examples": ["${{ secrets.APP_PRIVATE_KEY }}"] + "examples": [ + "${{ secrets.APP_PRIVATE_KEY }}" + ] }, "owner": { "type": "string", "description": "Optional: The owner of the GitHub App installation. If empty, defaults to the current repository owner.", - "examples": ["my-organization", "${{ github.repository_owner }}"] + "examples": [ + "my-organization", + "${{ github.repository_owner }}" + ] }, "repositories": { "type": "array", @@ -5552,10 +6369,21 @@ "items": { "type": "string" }, - "examples": [["repo1", "repo2"], ["my-repo"]] + "examples": [ + [ + "repo1", + "repo2" + ], + [ + "my-repo" + ] + ] } }, - "required": ["app-id", "private-key"], + "required": [ + "app-id", + "private-key" + ], "additionalProperties": false }, "max-patch-size": { @@ -5713,7 +6541,13 @@ }, "type": { "type": "string", - "enum": ["string", "boolean", "choice", "number", "environment"], + "enum": [ + "string", + "boolean", + "choice", + "number", + "environment" + ], "description": "Input parameter type. Supports: string (default), boolean, choice (string with predefined options), number, and environment (string referencing a GitHub environment)", "default": "string" }, @@ -5750,52 +6584,81 @@ "footer": { "type": "string", "description": "Custom footer message template for AI-generated content. Available placeholders: {workflow_name}, {run_url}, {triggering_number}, {workflow_source}, {workflow_source_url}. Example: '> Generated by [{workflow_name}]({run_url})'", - "examples": ["> Generated by [{workflow_name}]({run_url})", "> AI output from [{workflow_name}]({run_url}) for #{triggering_number}"] + "examples": [ + "> Generated by [{workflow_name}]({run_url})", + "> AI output from [{workflow_name}]({run_url}) for #{triggering_number}" + ] }, "footer-install": { "type": "string", "description": "Custom installation instructions template appended to the footer. Available placeholders: {workflow_source}, {workflow_source_url}. Example: '> Install: `gh aw add {workflow_source}`'", - "examples": ["> Install: `gh aw add {workflow_source}`", "> [Add this workflow]({workflow_source_url})"] + "examples": [ + "> Install: `gh aw add {workflow_source}`", + "> [Add this workflow]({workflow_source_url})" + ] }, "footer-workflow-recompile": { "type": "string", "description": "Custom footer message template for workflow recompile issues. Available placeholders: {workflow_name}, {run_url}, {repository}. Example: '> Workflow sync report by [{workflow_name}]({run_url}) for {repository}'", - "examples": ["> Workflow sync report by [{workflow_name}]({run_url}) for {repository}", "> Maintenance report by [{workflow_name}]({run_url})"] + "examples": [ + "> Workflow sync report by [{workflow_name}]({run_url}) for {repository}", + "> Maintenance report by [{workflow_name}]({run_url})" + ] }, "footer-workflow-recompile-comment": { "type": "string", "description": "Custom footer message template for comments on workflow recompile issues. Available placeholders: {workflow_name}, {run_url}, {repository}. Example: '> Update from [{workflow_name}]({run_url}) for {repository}'", - "examples": ["> Update from [{workflow_name}]({run_url}) for {repository}", "> Maintenance update by [{workflow_name}]({run_url})"] + "examples": [ + "> Update from [{workflow_name}]({run_url}) for {repository}", + "> Maintenance update by [{workflow_name}]({run_url})" + ] }, "staged-title": { "type": "string", "description": "Custom title template for staged mode preview. Available placeholders: {operation}. Example: '\ud83c\udfad Preview: {operation}'", - "examples": ["\ud83c\udfad Preview: {operation}", "## Staged Mode: {operation}"] + "examples": [ + "\ud83c\udfad Preview: {operation}", + "## Staged Mode: {operation}" + ] }, "staged-description": { "type": "string", "description": "Custom description template for staged mode preview. Available placeholders: {operation}. Example: 'The following {operation} would occur if staged mode was disabled:'", - "examples": ["The following {operation} would occur if staged mode was disabled:"] + "examples": [ + "The following {operation} would occur if staged mode was disabled:" + ] }, "run-started": { "type": "string", "description": "Custom message template for workflow activation comment. Available placeholders: {workflow_name}, {run_url}, {event_type}. Default: 'Agentic [{workflow_name}]({run_url}) triggered by this {event_type}.'", - "examples": ["Agentic [{workflow_name}]({run_url}) triggered by this {event_type}.", "[{workflow_name}]({run_url}) started processing this {event_type}."] + "examples": [ + "Agentic [{workflow_name}]({run_url}) triggered by this {event_type}.", + "[{workflow_name}]({run_url}) started processing this {event_type}." + ] }, "run-success": { "type": "string", "description": "Custom message template for successful workflow completion. Available placeholders: {workflow_name}, {run_url}. Default: '\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.'", - "examples": ["\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.", "\u2705 [{workflow_name}]({run_url}) finished."] + "examples": [ + "\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.", + "\u2705 [{workflow_name}]({run_url}) finished." + ] }, "run-failure": { "type": "string", "description": "Custom message template for failed workflow. Available placeholders: {workflow_name}, {run_url}, {status}. Default: '\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.'", - "examples": ["\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.", "\u274c [{workflow_name}]({run_url}) {status}."] + "examples": [ + "\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.", + "\u274c [{workflow_name}]({run_url}) {status}." + ] }, "detection-failure": { "type": "string", "description": "Custom message template for detection job failure. Available placeholders: {workflow_name}, {run_url}. Default: '\u26a0\ufe0f Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.'", - "examples": ["\u26a0\ufe0f Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.", "\u26a0\ufe0f Detection job failed in [{workflow_name}]({run_url})."] + "examples": [ + "\u26a0\ufe0f Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.", + "\u26a0\ufe0f Detection job failed in [{workflow_name}]({run_url})." + ] }, "append-only-comments": { "type": "boolean", @@ -5848,50 +6711,6 @@ "runs-on": { "type": "string", "description": "Runner specification for all safe-outputs jobs (activation, create-issue, add-comment, etc.). Single runner label (e.g., 'ubuntu-slim', 'ubuntu-latest', 'windows-latest', 'self-hosted'). Defaults to 'ubuntu-slim'. See https://github.blog/changelog/2025-10-28-1-vcpu-linux-runner-now-available-in-github-actions-in-public-preview/" - }, - "dispatch-workflow": { - "oneOf": [ - { - "type": "object", - "description": "Configuration for dispatching workflow_dispatch events to other workflows. Orchestrators use this to delegate work to worker workflows.", - "properties": { - "workflows": { - "type": "array", - "description": "List of workflow names (without .md extension) to allow dispatching. Each workflow must exist in .github/workflows/.", - "items": { - "type": "string", - "minLength": 1 - }, - "minItems": 1, - "maxItems": 50 - }, - "max": { - "type": "integer", - "description": "Maximum number of workflow dispatch operations per run (default: 1, max: 50)", - "minimum": 1, - "maximum": 50, - "default": 1 - }, - "github-token": { - "$ref": "#/$defs/github_token", - "description": "GitHub token to use for dispatching workflows. Overrides global github-token if specified." - } - }, - "required": ["workflows"], - "additionalProperties": false - }, - { - "type": "array", - "description": "Shorthand array format: list of workflow names (without .md extension) to allow dispatching", - "items": { - "type": "string", - "minLength": 1 - }, - "minItems": 1, - "maxItems": 50 - } - ], - "description": "Dispatch workflow_dispatch events to other workflows. Used by orchestrators to delegate work to worker workflows with controlled maximum dispatch count." } }, "additionalProperties": false @@ -5923,7 +6742,9 @@ "oneOf": [ { "type": "string", - "enum": ["all"], + "enum": [ + "all" + ], "description": "Allow any authenticated user to trigger the workflow (\u26a0\ufe0f disables permission checking entirely - use with caution)" }, { @@ -5931,7 +6752,13 @@ "description": "List of repository permission levels that can trigger the workflow. Permission checks are automatically applied to potentially unsafe triggers.", "items": { "type": "string", - "enum": ["admin", "maintainer", "maintain", "write", "triage"], + "enum": [ + "admin", + "maintainer", + "maintain", + "write", + "triage" + ], "description": "Repository permission level: 'admin' (full access), 'maintainer'/'maintain' (repository management), 'write' (push access), 'triage' (issue management)" }, "minItems": 1, @@ -5953,7 +6780,10 @@ "default": true, "$comment": "Strict mode enforces several security constraints that are validated in Go code (pkg/workflow/strict_mode_validation.go) rather than JSON Schema: (1) Write Permissions + Safe Outputs: When strict=true AND permissions contains write values (contents:write, issues:write, pull-requests:write), safe-outputs must be configured. This relationship is too complex for JSON Schema as it requires checking if ANY permission property has a 'write' value. (2) Network Requirements: When strict=true, the 'network' field must be present and cannot contain standalone wildcard '*' (but patterns like '*.example.com' ARE allowed). (3) MCP Container Network: Custom MCP servers with containers require explicit network configuration. (4) Action Pinning: Actions must be pinned to commit SHAs. These are enforced during compilation via validateStrictMode().", "description": "Enable strict mode validation for enhanced security and compliance. Strict mode enforces: (1) Write Permissions - refuses contents:write, issues:write, pull-requests:write; requires safe-outputs instead, (2) Network Configuration - requires explicit network configuration with no standalone wildcard '*' in allowed domains (patterns like '*.example.com' are allowed), (3) Action Pinning - enforces actions pinned to commit SHAs instead of tags/branches, (4) MCP Network - requires network configuration for custom MCP servers with containers, (5) Deprecated Fields - refuses deprecated frontmatter fields. Can be enabled per-workflow via 'strict: true' in frontmatter, or disabled via 'strict: false'. CLI flag takes precedence over frontmatter (gh aw compile --strict enforces strict mode). Defaults to true. See: https://github.github.com/gh-aw/reference/frontmatter/#strict-mode-strict", - "examples": [true, false] + "examples": [ + true, + false + ] }, "safe-inputs": { "type": "object", @@ -5962,7 +6792,9 @@ "^([a-ln-z][a-z0-9_-]*|m[a-np-z][a-z0-9_-]*|mo[a-ce-z][a-z0-9_-]*|mod[a-df-z][a-z0-9_-]*|mode[a-z0-9_-]+)$": { "type": "object", "description": "Custom tool definition. The key is the tool name (lowercase alphanumeric with dashes/underscores).", - "required": ["description"], + "required": [ + "description" + ], "properties": { "description": { "type": "string", @@ -5976,7 +6808,13 @@ "properties": { "type": { "type": "string", - "enum": ["string", "number", "boolean", "array", "object"], + "enum": [ + "string", + "number", + "boolean", + "array", + "object" + ], "default": "string", "description": "The JSON schema type of the input parameter." }, @@ -6030,71 +6868,108 @@ "description": "Timeout in seconds for tool execution. Default is 60 seconds. Applies to shell (run) and Python (py) tools.", "default": 60, "minimum": 1, - "examples": [30, 60, 120, 300] + "examples": [ + 30, + 60, + 120, + 300 + ] } }, "additionalProperties": false, "oneOf": [ { - "required": ["script"], + "required": [ + "script" + ], "not": { "anyOf": [ { - "required": ["run"] + "required": [ + "run" + ] }, { - "required": ["py"] + "required": [ + "py" + ] }, { - "required": ["go"] + "required": [ + "go" + ] } ] } }, { - "required": ["run"], + "required": [ + "run" + ], "not": { "anyOf": [ { - "required": ["script"] + "required": [ + "script" + ] }, { - "required": ["py"] + "required": [ + "py" + ] }, { - "required": ["go"] + "required": [ + "go" + ] } ] } }, { - "required": ["py"], + "required": [ + "py" + ], "not": { "anyOf": [ { - "required": ["script"] + "required": [ + "script" + ] }, { - "required": ["run"] + "required": [ + "run" + ] }, { - "required": ["go"] + "required": [ + "go" + ] } ] } }, { - "required": ["go"], + "required": [ + "go" + ], "not": { "anyOf": [ { - "required": ["script"] + "required": [ + "script" + ] }, { - "required": ["run"] + "required": [ + "run" + ] }, { - "required": ["py"] + "required": [ + "py" + ] } ] } @@ -6152,9 +7027,18 @@ "description": "Runtime configuration object identified by runtime ID (e.g., 'node', 'python', 'go')", "properties": { "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Runtime version as a string (e.g., '22', '3.12', 'latest') or number (e.g., 22, 3.12). Numeric values are automatically converted to strings at runtime.", - "examples": ["22", "3.12", "latest", 22, 3.12] + "examples": [ + "22", + "3.12", + "latest", + 22, + 3.12 + ] }, "action-repo": { "type": "string", @@ -6191,7 +7075,9 @@ } } }, - "required": ["slash_command"] + "required": [ + "slash_command" + ] }, { "properties": { @@ -6201,7 +7087,9 @@ } } }, - "required": ["command"] + "required": [ + "command" + ] } ] } @@ -6220,7 +7108,9 @@ } } }, - "required": ["issue_comment"] + "required": [ + "issue_comment" + ] }, { "properties": { @@ -6230,7 +7120,9 @@ } } }, - "required": ["pull_request_review_comment"] + "required": [ + "pull_request_review_comment" + ] }, { "properties": { @@ -6240,7 +7132,9 @@ } } }, - "required": ["label"] + "required": [ + "label" + ] } ] } @@ -6274,7 +7168,12 @@ "oneOf": [ { "type": "string", - "enum": ["claude", "codex", "copilot", "custom"], + "enum": [ + "claude", + "codex", + "copilot", + "custom" + ], "description": "Simple engine name: 'claude' (default, Claude Code), 'copilot' (GitHub Copilot CLI), 'codex' (OpenAI Codex CLI), or 'custom' (user-defined steps)" }, { @@ -6283,13 +7182,26 @@ "properties": { "id": { "type": "string", - "enum": ["claude", "codex", "custom", "copilot"], + "enum": [ + "claude", + "codex", + "custom", + "copilot" + ], "description": "AI engine identifier: 'claude' (Claude Code), 'codex' (OpenAI Codex CLI), 'copilot' (GitHub Copilot CLI), or 'custom' (user-defined GitHub Actions steps)" }, "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Optional version of the AI engine action (e.g., 'beta', 'stable', 20). Has sensible defaults and can typically be omitted. Numeric values are automatically converted to strings at runtime.", - "examples": ["beta", "stable", 20, 3.11] + "examples": [ + "beta", + "stable", + 20, + 3.11 + ] }, "model": { "type": "string", @@ -6327,7 +7239,9 @@ "description": "Whether to cancel in-progress runs of the same concurrency group. Defaults to false for agentic workflow runs." } }, - "required": ["group"], + "required": [ + "group" + ], "additionalProperties": false } ], @@ -6386,7 +7300,9 @@ "description": "Human-readable description of what this pattern matches" } }, - "required": ["pattern"], + "required": [ + "pattern" + ], "additionalProperties": false } }, @@ -6406,7 +7322,9 @@ "description": "Optional array of command-line arguments to pass to the AI engine CLI. These arguments are injected after all other args but before the prompt." } }, - "required": ["id"], + "required": [ + "id" + ], "additionalProperties": false } ] @@ -6417,13 +7335,18 @@ "properties": { "type": { "type": "string", - "enum": ["stdio", "local"], + "enum": [ + "stdio", + "local" + ], "description": "MCP connection type for stdio (local is an alias for stdio)" }, "registry": { "type": "string", "description": "URI to the installation location when MCP is installed from a registry", - "examples": ["https://api.mcp.github.com/v0/servers/microsoft/markitdown"] + "examples": [ + "https://api.mcp.github.com/v0/servers/microsoft/markitdown" + ] }, "command": { "type": "string", @@ -6438,9 +7361,17 @@ "description": "Container image for stdio MCP connections" }, "version": { - "type": ["string", "number"], + "type": [ + "string", + "number" + ], "description": "Optional version/tag for the container image (e.g., 'latest', 'v1.0.0', 20, 3.11). Numeric values are automatically converted to strings at runtime.", - "examples": ["latest", "v1.0.0", 20, 3.11] + "examples": [ + "latest", + "v1.0.0", + 20, + 3.11 + ] }, "args": { "type": "array", @@ -6452,7 +7383,11 @@ "entrypoint": { "type": "string", "description": "Optional entrypoint override for container (equivalent to docker run --entrypoint)", - "examples": ["/bin/sh", "/custom/entrypoint.sh", "python"] + "examples": [ + "/bin/sh", + "/custom/entrypoint.sh", + "python" + ] }, "entrypointArgs": { "type": "array", @@ -6468,7 +7403,15 @@ "pattern": "^[^:]+:[^:]+:(ro|rw)$" }, "description": "Volume mounts for container in format 'source:dest:mode' where mode is 'ro' or 'rw'", - "examples": [["/tmp/data:/data:ro"], ["/workspace:/workspace:rw", "/config:/config:ro"]] + "examples": [ + [ + "/tmp/data:/data:ro" + ], + [ + "/workspace:/workspace:rw", + "/config:/config:ro" + ] + ] }, "env": { "type": "object", @@ -6514,29 +7457,50 @@ "items": { "type": "string" }, - "examples": [["*"], ["store_memory", "retrieve_memory"], ["brave_web_search"]] + "examples": [ + [ + "*" + ], + [ + "store_memory", + "retrieve_memory" + ], + [ + "brave_web_search" + ] + ] } }, "additionalProperties": false, "$comment": "Validation constraints: (1) Mutual exclusion: 'command' and 'container' cannot both be specified. (2) Requirement: Either 'command' or 'container' must be provided (via 'anyOf'). (3) Type constraint: When 'type' is 'stdio' or 'local', either 'command' or 'container' is required. Note: Per-server 'network' field is deprecated and ignored.", "anyOf": [ { - "required": ["type"] + "required": [ + "type" + ] }, { - "required": ["command"] + "required": [ + "command" + ] }, { - "required": ["container"] + "required": [ + "container" + ] } ], "not": { "allOf": [ { - "required": ["command"] + "required": [ + "command" + ] }, { - "required": ["container"] + "required": [ + "container" + ] } ] }, @@ -6545,17 +7509,24 @@ "if": { "properties": { "type": { - "enum": ["stdio", "local"] + "enum": [ + "stdio", + "local" + ] } } }, "then": { "anyOf": [ { - "required": ["command"] + "required": [ + "command" + ] }, { - "required": ["container"] + "required": [ + "container" + ] } ] } @@ -6568,13 +7539,17 @@ "properties": { "type": { "type": "string", - "enum": ["http"], + "enum": [ + "http" + ], "description": "MCP connection type for HTTP" }, "registry": { "type": "string", "description": "URI to the installation location when MCP is installed from a registry", - "examples": ["https://api.mcp.github.com/v0/servers/microsoft/markitdown"] + "examples": [ + "https://api.mcp.github.com/v0/servers/microsoft/markitdown" + ] }, "url": { "type": "string", @@ -6597,17 +7572,34 @@ "items": { "type": "string" }, - "examples": [["*"], ["store_memory", "retrieve_memory"], ["brave_web_search"]] + "examples": [ + [ + "*" + ], + [ + "store_memory", + "retrieve_memory" + ], + [ + "brave_web_search" + ] + ] } }, - "required": ["url"], + "required": [ + "url" + ], "additionalProperties": false }, "github_token": { "type": "string", "pattern": "^\\$\\{\\{\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*(\\s*\\|\\|\\s*secrets\\.[A-Za-z_][A-Za-z0-9_]*)*\\s*\\}\\}$", "description": "GitHub token expression using secrets. Pattern details: `[A-Za-z_][A-Za-z0-9_]*` matches a valid secret name (starts with a letter or underscore, followed by letters, digits, or underscores). The full pattern matches expressions like `${{ secrets.NAME }}` or `${{ secrets.NAME1 || secrets.NAME2 }}`.", - "examples": ["${{ secrets.GITHUB_TOKEN }}", "${{ secrets.CUSTOM_PAT }}", "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"] + "examples": [ + "${{ secrets.GITHUB_TOKEN }}", + "${{ secrets.CUSTOM_PAT }}", + "${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}" + ] }, "githubActionsStep": { "type": "object", @@ -6668,10 +7660,14 @@ "additionalProperties": false, "anyOf": [ { - "required": ["uses"] + "required": [ + "uses" + ] }, { - "required": ["run"] + "required": [ + "run" + ] } ] } diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index ea2d80e046..e61def7c6f 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -204,12 +204,6 @@ func (c *Compiler) CompileWorkflowData(workflowData *WorkflowData, markdownPath c.IncrementWarningCount() } - // Emit warning for sandbox: false (disables all sandbox features) - if isSandboxDisabled(workflowData) { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage("⚠️ WARNING: Sandbox disabled (sandbox: false). This removes important security protections including the firewall and MCP gateway. The AI agent will have direct network access without any filtering. Only use this for testing or in controlled environments where you trust the AI agent completely.")) - c.IncrementWarningCount() - } - // Emit experimental warning for safe-inputs feature if IsSafeInputsEnabled(workflowData.SafeInputs, workflowData) { fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Using experimental feature: safe-inputs")) diff --git a/pkg/workflow/frontmatter_extraction_security.go b/pkg/workflow/frontmatter_extraction_security.go index f471a58f39..9502c6f05c 100644 --- a/pkg/workflow/frontmatter_extraction_security.go +++ b/pkg/workflow/frontmatter_extraction_security.go @@ -142,19 +142,9 @@ func (c *Compiler) extractSandboxConfig(frontmatter map[string]any) *SandboxConf return nil } - // Handle boolean format: sandbox: false (disables all sandbox features) - if sandboxBool, ok := sandbox.(bool); ok { - if !sandboxBool { - frontmatterExtractionSecurityLog.Print("Sandbox explicitly disabled with sandbox: false") - // Return a marker config with Disabled flag set - return &SandboxConfig{ - Agent: &AgentSandboxConfig{ - Disabled: true, - }, - } - } - // sandbox: true is not meaningful, treat as no configuration - frontmatterExtractionSecurityLog.Print("Sandbox: true specified but has no effect, treating as unconfigured") + // Boolean format is no longer supported - sandbox: false is not allowed + if _, ok := sandbox.(bool); ok { + frontmatterExtractionSecurityLog.Print("Sandbox boolean format is not supported") return nil } @@ -186,13 +176,11 @@ func (c *Compiler) extractSandboxConfig(frontmatter map[string]any) *SandboxConf if agentVal, hasAgent := sandboxObj["agent"]; hasAgent { frontmatterExtractionSecurityLog.Print("Extracting agent sandbox configuration") - // Check if agent is set to false (boolean) - this is no longer supported - if agentBool, ok := agentVal.(bool); ok && !agentBool { - // Return a marker config that will be caught during validation + // Check if agent is set to boolean - this is no longer supported + if _, ok := agentVal.(bool); ok { + frontmatterExtractionSecurityLog.Print("Sandbox agent boolean format is not supported") return &SandboxConfig{ - Agent: &AgentSandboxConfig{ - Disabled: true, // This will be caught by validation - }, + Agent: nil, // Invalid configuration } } @@ -227,9 +215,10 @@ func (c *Compiler) extractSandboxConfig(frontmatter map[string]any) *SandboxConf // extractAgentSandboxConfig extracts agent sandbox configuration func (c *Compiler) extractAgentSandboxConfig(agentVal any) *AgentSandboxConfig { - // Handle boolean format: REJECTED - sandbox.agent: false is no longer supported + // Boolean format is not supported if _, ok := agentVal.(bool); ok { // Both true and false are invalid - must use string or object format + frontmatterExtractionSecurityLog.Print("Sandbox agent boolean format is not supported") return nil } diff --git a/pkg/workflow/sandbox_disabled_test.go b/pkg/workflow/sandbox_disabled_test.go index fa900ce2c4..35a71b0ccb 100644 --- a/pkg/workflow/sandbox_disabled_test.go +++ b/pkg/workflow/sandbox_disabled_test.go @@ -12,9 +12,9 @@ import ( "github.com/stretchr/testify/require" ) -// TestSandboxDisabled tests that sandbox: false disables all sandbox features +// TestSandboxDisabled tests that sandbox: false is now rejected func TestSandboxDisabled(t *testing.T) { - t.Run("sandbox: false is parsed correctly", func(t *testing.T) { + t.Run("sandbox: false is rejected", func(t *testing.T) { workflowsDir := t.TempDir() markdown := `--- @@ -32,20 +32,20 @@ Test workflow with sandbox disabled. require.NoError(t, err) compiler := NewCompiler() - compiler.SetStrictMode(false) // Non-strict mode to allow sandbox: false - compiler.SetSkipValidation(true) + compiler.SetStrictMode(false) err = compiler.CompileWorkflow(workflowPath) - require.NoError(t, err, "Compilation should succeed with sandbox: false in non-strict mode") + require.Error(t, err, "Compilation should fail with sandbox: false") + assert.Contains(t, err.Error(), "disabling the sandbox is no longer supported") }) - t.Run("sandbox: false is refused in strict mode", func(t *testing.T) { + t.Run("sandbox: false is rejected in strict mode", func(t *testing.T) { workflowsDir := t.TempDir() markdown := `--- engine: copilot sandbox: false -strict: false +strict: true on: workflow_dispatch --- @@ -58,126 +58,10 @@ Test workflow with sandbox disabled in strict mode. compiler := NewCompiler() compiler.SetStrictMode(true) - compiler.SetSkipValidation(true) err = compiler.CompileWorkflow(workflowPath) require.Error(t, err, "Expected error when sandbox: false in strict mode") - assert.Contains(t, err.Error(), "strict mode") - assert.Contains(t, err.Error(), "sandbox: false") - }) - - t.Run("sandbox: false disables firewall", func(t *testing.T) { - workflowsDir := t.TempDir() - - markdown := `--- -engine: copilot -sandbox: false -strict: false -network: - allowed: - - example.com -on: workflow_dispatch ---- - -Test workflow with network restrictions but sandbox disabled. -` - - workflowPath := filepath.Join(workflowsDir, "test-sandbox-disabled-firewall.md") - err := os.WriteFile(workflowPath, []byte(markdown), 0644) - require.NoError(t, err) - - compiler := NewCompiler() - compiler.SetStrictMode(false) - compiler.SetSkipValidation(true) - - err = compiler.CompileWorkflow(workflowPath) - require.NoError(t, err) - - // Read the compiled workflow - lockPath := filepath.Join(workflowsDir, "test-sandbox-disabled-firewall.lock.yml") - lockContent, err := os.ReadFile(lockPath) - require.NoError(t, err) - result := string(lockContent) - - // The compiled workflow should NOT contain AWF commands - assert.NotContains(t, result, "sudo -E awf", "Workflow should not contain AWF command when sandbox is disabled") - assert.NotContains(t, result, "awf --", "Workflow should not contain AWF wrapper when sandbox is disabled") - - // Should contain direct copilot command instead - assert.Contains(t, result, "copilot", "Workflow should contain direct copilot command") - }) - - t.Run("sandbox: false skips MCP gateway configuration", func(t *testing.T) { - workflowsDir := t.TempDir() - - markdown := `--- -engine: copilot -sandbox: false -strict: false -tools: - github: - mode: local -on: workflow_dispatch ---- - -Test workflow with tools but sandbox disabled. -` - - workflowPath := filepath.Join(workflowsDir, "test-sandbox-disabled-mcp.md") - err := os.WriteFile(workflowPath, []byte(markdown), 0644) - require.NoError(t, err) - - compiler := NewCompiler() - compiler.SetStrictMode(false) - compiler.SetSkipValidation(true) - - err = compiler.CompileWorkflow(workflowPath) - require.NoError(t, err) - - // Read the compiled workflow - lockPath := filepath.Join(workflowsDir, "test-sandbox-disabled-mcp.lock.yml") - lockContent, err := os.ReadFile(lockPath) - require.NoError(t, err) - result := string(lockContent) - - // The MCP config should NOT contain gateway section when sandbox is disabled - // Check that MCP config is generated but without gateway - assert.Contains(t, result, "mcp-config.json", "MCP config should still be generated") - // Gateway-specific variables should not be present - assert.NotContains(t, result, "MCP_GATEWAY_PORT", "Gateway port should not be set when sandbox is disabled") - assert.NotContains(t, result, "MCP_GATEWAY_API_KEY", "Gateway API key should not be set when sandbox is disabled") - }) - - t.Run("sandbox: false shows warning at compile time", func(t *testing.T) { - workflowsDir := t.TempDir() - - markdown := `--- -engine: copilot -sandbox: false -strict: false -on: workflow_dispatch ---- - -Test workflow. -` - - workflowPath := filepath.Join(workflowsDir, "test-sandbox-disabled-warning.md") - err := os.WriteFile(workflowPath, []byte(markdown), 0644) - require.NoError(t, err) - - compiler := NewCompiler() - compiler.SetStrictMode(false) - compiler.SetSkipValidation(true) - - // Capture warning count before compilation - initialWarnings := compiler.GetWarningCount() - - err = compiler.CompileWorkflow(workflowPath) - require.NoError(t, err) - - // Should have incremented warning count - finalWarnings := compiler.GetWarningCount() - assert.Greater(t, finalWarnings, initialWarnings, "Expected warning to be emitted for sandbox: false") + assert.Contains(t, err.Error(), "disabling the sandbox is no longer supported") }) t.Run("sandbox: true is treated as unconfigured", func(t *testing.T) { @@ -216,82 +100,9 @@ Test workflow with sandbox: true. // This means AWF should be enabled by default assert.Contains(t, result, "sudo -E awf", "Workflow should contain AWF command by default when sandbox: true") }) - - t.Run("sandbox: false applies defaults correctly", func(t *testing.T) { - workflowData := &WorkflowData{ - Name: "test", - SandboxConfig: &SandboxConfig{ - Agent: &AgentSandboxConfig{ - Disabled: true, - }, - }, - } - - // Apply defaults - sandboxConfig := applySandboxDefaults(workflowData.SandboxConfig, nil) - - // Should preserve the disabled state - assert.NotNil(t, sandboxConfig) - assert.NotNil(t, sandboxConfig.Agent) - assert.True(t, sandboxConfig.Agent.Disabled, "Disabled state should be preserved") - }) - - t.Run("isSandboxDisabled helper function", func(t *testing.T) { - // Test nil workflow data - assert.False(t, isSandboxDisabled(nil)) - - // Test nil sandbox config - workflowData := &WorkflowData{Name: "test"} - assert.False(t, isSandboxDisabled(workflowData)) - - // Test enabled sandbox - workflowData.SandboxConfig = &SandboxConfig{ - Agent: &AgentSandboxConfig{ - Type: SandboxTypeAWF, - }, - } - assert.False(t, isSandboxDisabled(workflowData)) - - // Test disabled sandbox - workflowData.SandboxConfig = &SandboxConfig{ - Agent: &AgentSandboxConfig{ - Disabled: true, - }, - } - assert.True(t, isSandboxDisabled(workflowData)) - }) - - t.Run("MCP gateway config is nil when sandbox disabled", func(t *testing.T) { - workflowData := &WorkflowData{ - Name: "test", - SandboxConfig: &SandboxConfig{ - Agent: &AgentSandboxConfig{ - Disabled: true, - }, - }, - } - - gatewayConfig := buildMCPGatewayConfig(workflowData) - assert.Nil(t, gatewayConfig, "Gateway config should be nil when sandbox is disabled") - }) - - t.Run("MCP gateway config is not nil when sandbox enabled", func(t *testing.T) { - workflowData := &WorkflowData{ - Name: "test", - SandboxConfig: &SandboxConfig{ - Agent: &AgentSandboxConfig{ - Type: SandboxTypeAWF, - }, - }, - } - - gatewayConfig := buildMCPGatewayConfig(workflowData) - assert.NotNil(t, gatewayConfig, "Gateway config should not be nil when sandbox is enabled") - assert.Equal(t, "${MCP_GATEWAY_API_KEY}", gatewayConfig.APIKey) - }) } -// TestSandboxDisabledWithToolsConfiguration tests that MCP servers work without gateway when sandbox is disabled +// TestSandboxDisabledWithToolsConfiguration tests that sandbox: false is rejected even with tools configured func TestSandboxDisabledWithToolsConfiguration(t *testing.T) { workflowsDir := t.TempDir() @@ -315,30 +126,13 @@ Test workflow with tools and sandbox disabled. compiler := NewCompiler() compiler.SetStrictMode(false) - compiler.SetSkipValidation(true) err = compiler.CompileWorkflow(workflowPath) - require.NoError(t, err, "Compilation should succeed with tools and sandbox: false") - - // Read the compiled workflow - lockPath := filepath.Join(workflowsDir, "test-sandbox-disabled-tools.lock.yml") - lockContent, err := os.ReadFile(lockPath) - require.NoError(t, err) - result := string(lockContent) - - // Verify MCP config is generated - assert.Contains(t, result, "mcp-config.json", "MCP config should be generated") - - // Verify tools are configured in MCP config - assert.Contains(t, result, "github", "GitHub MCP server should be configured") - - // Verify no gateway configuration - assert.NotContains(t, result, "MCP_GATEWAY_PORT", "Gateway port should not be present") - assert.NotContains(t, result, "MCP_GATEWAY_API_KEY", "Gateway API key should not be present") - assert.NotContains(t, result, "MCP_GATEWAY_DOMAIN", "Gateway domain should not be present") + require.Error(t, err, "Compilation should fail with sandbox: false") + assert.Contains(t, err.Error(), "disabling the sandbox is no longer supported") } -// TestSandboxDisabledCopilotExecution tests that copilot execution is direct (not wrapped with AWF) when sandbox is disabled +// TestSandboxDisabledCopilotExecution tests that sandbox: false is rejected func TestSandboxDisabledCopilotExecution(t *testing.T) { workflowsDir := t.TempDir() @@ -361,32 +155,8 @@ Test workflow with direct copilot execution. compiler := NewCompiler() compiler.SetStrictMode(false) - compiler.SetSkipValidation(true) err = compiler.CompileWorkflow(workflowPath) - require.NoError(t, err) - - // Read the compiled workflow - lockPath := filepath.Join(workflowsDir, "test-sandbox-disabled-execution.lock.yml") - lockContent, err := os.ReadFile(lockPath) - require.NoError(t, err) - result := string(lockContent) - - // The copilot command should be executed directly, not wrapped with AWF - // Look for direct copilot invocation without AWF - lines := strings.Split(result, "\n") - foundDirectCopilot := false - foundAWF := false - - for _, line := range lines { - if strings.Contains(line, "copilot ") && !strings.Contains(line, "#") { // Not a comment - foundDirectCopilot = true - } - if strings.Contains(line, "sudo -E awf") || strings.Contains(line, "awf --") { - foundAWF = true - } - } - - assert.True(t, foundDirectCopilot, "Should find direct copilot command") - assert.False(t, foundAWF, "Should not find AWF wrapper when sandbox is disabled") + require.Error(t, err, "Compilation should fail with sandbox: false") + assert.Contains(t, err.Error(), "disabling the sandbox is no longer supported") } diff --git a/pkg/workflow/sandbox_validation.go b/pkg/workflow/sandbox_validation.go index 00f26ccc05..e95242ae07 100644 --- a/pkg/workflow/sandbox_validation.go +++ b/pkg/workflow/sandbox_validation.go @@ -88,13 +88,15 @@ func validateSandboxConfig(workflowData *WorkflowData) error { sandboxConfig := workflowData.SandboxConfig - // Check if sandbox: false or sandbox.agent: false was specified - // In non-strict mode, this is allowed (with a warning shown at compile time) - // The strict mode check happens in validateStrictFirewall() + // Reject if sandbox was explicitly disabled + // sandbox: false is no longer supported if sandboxConfig.Agent != nil && sandboxConfig.Agent.Disabled { - // sandbox: false is allowed in non-strict mode, so we don't error here - // The warning is emitted in compiler.go - sandboxValidationLog.Print("sandbox: false detected, will be validated by strict mode check") + return NewConfigurationError( + "sandbox", + "false", + "disabling the sandbox is no longer supported. The sandbox and MCP gateway provide important security protections.", + "Remove 'sandbox: false' from your workflow to use the default sandbox configuration.", + ) } // Validate mounts syntax if specified in agent config @@ -177,7 +179,7 @@ func validateSandboxConfig(workflowData *WorkflowData) error { "sandbox", "enabled without MCP servers", "agent sandbox requires MCP servers to be configured", - "Add MCP tools to your workflow:\n\ntools:\n github:\n mode: remote\n playwright:\n allowed_domains: [\"example.com\"]\n\nOr disable the sandbox:\nsandbox: false", + "Add MCP tools to your workflow:\n\ntools:\n github:\n mode: remote\n playwright:\n allowed_domains: [\"example.com\"]", ) } if hasExplicitSandboxConfig {