diff --git a/.github/workflows/smoke-agent-all-merged.lock.yml b/.github/workflows/smoke-agent-all-merged.lock.yml index 5e02a44cfc..52d0767709 100644 --- a/.github/workflows/smoke-agent-all-merged.lock.yml +++ b/.github/workflows/smoke-agent-all-merged.lock.yml @@ -23,7 +23,7 @@ # # Guard policy smoke test: repos=all, min-integrity=merged (most restrictive) # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"4b15096b81758a5494492456bb53b83ac7891ad338b4a0be6eb340f1be5f1b18","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"e3acfb90c523ed41aff2574700873d4ff6fca754fce908743a5b8157de83596a","strict":true} name: "Smoke Agent: all/merged" "on": diff --git a/.github/workflows/smoke-agent-all-merged.md b/.github/workflows/smoke-agent-all-merged.md index a14842d463..f9993df760 100644 --- a/.github/workflows/smoke-agent-all-merged.md +++ b/.github/workflows/smoke-agent-all-merged.md @@ -23,7 +23,7 @@ network: - defaults - github safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: hide-older-comments: true max: 2 diff --git a/.github/workflows/smoke-agent-all-none.lock.yml b/.github/workflows/smoke-agent-all-none.lock.yml index 53941d5bb0..30e79b178d 100644 --- a/.github/workflows/smoke-agent-all-none.lock.yml +++ b/.github/workflows/smoke-agent-all-none.lock.yml @@ -23,7 +23,7 @@ # # Guard policy smoke test: repos=all, min-integrity=none (most permissive) # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"c5cb0ff0be1c9ba981c37d92deb45be8f6e2390a2fc0c666c54615388a0f14d6","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"8456baa1448783534cbadceb33b0a2573cad268e2f9770dbd94a5f8ffa6c5359","strict":true} name: "Smoke Agent: all/none" "on": diff --git a/.github/workflows/smoke-agent-all-none.md b/.github/workflows/smoke-agent-all-none.md index 182540f8ff..a0cc9ae773 100644 --- a/.github/workflows/smoke-agent-all-none.md +++ b/.github/workflows/smoke-agent-all-none.md @@ -23,7 +23,7 @@ network: - defaults - github safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: hide-older-comments: true max: 2 diff --git a/.github/workflows/smoke-agent-public-approved.lock.yml b/.github/workflows/smoke-agent-public-approved.lock.yml index 36fe932340..fb916895f3 100644 --- a/.github/workflows/smoke-agent-public-approved.lock.yml +++ b/.github/workflows/smoke-agent-public-approved.lock.yml @@ -23,7 +23,7 @@ # # Smoke test that validates assign-to-agent with the agentic-workflows custom agent # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"3c582fe0f4bd859be6ae9f91cdda2ebaafa21934283ac26646271a50c05ba1ea","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"3c97796557487cb3f9dfd02804b2c10932fddb5dd95d7c884c33a25e1a9c75d0","strict":true} name: "Smoke Agent: public/approved" "on": diff --git a/.github/workflows/smoke-agent-public-approved.md b/.github/workflows/smoke-agent-public-approved.md index 16f62abb09..3698a4bb6b 100644 --- a/.github/workflows/smoke-agent-public-approved.md +++ b/.github/workflows/smoke-agent-public-approved.md @@ -23,7 +23,7 @@ network: - defaults - github safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] assign-to-agent: target: "*" max: 1 diff --git a/.github/workflows/smoke-agent-public-none.lock.yml b/.github/workflows/smoke-agent-public-none.lock.yml index 346c1d1ef1..cae11bd77b 100644 --- a/.github/workflows/smoke-agent-public-none.lock.yml +++ b/.github/workflows/smoke-agent-public-none.lock.yml @@ -23,7 +23,7 @@ # # Guard policy smoke test: repos=public, min-integrity=none # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"3318acca97bbfd5d5e552976f57edd2f789657b40f07b73fd961bf014739864c","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"71e286872bc099c4788ba9b318a7ca0a6dd7ce4b1388d03cb2e864cb6f648740","strict":true} name: "Smoke Agent: public/none" "on": diff --git a/.github/workflows/smoke-agent-public-none.md b/.github/workflows/smoke-agent-public-none.md index a524e9ece8..93165819da 100644 --- a/.github/workflows/smoke-agent-public-none.md +++ b/.github/workflows/smoke-agent-public-none.md @@ -23,7 +23,7 @@ network: - defaults - github safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: hide-older-comments: true max: 2 diff --git a/.github/workflows/smoke-agent-scoped-approved.lock.yml b/.github/workflows/smoke-agent-scoped-approved.lock.yml index 5fc089a6c7..6aea392251 100644 --- a/.github/workflows/smoke-agent-scoped-approved.lock.yml +++ b/.github/workflows/smoke-agent-scoped-approved.lock.yml @@ -23,7 +23,7 @@ # # Guard policy smoke test: repos=[github/gh-aw, github/*], min-integrity=approved (scoped patterns) # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"b48c91de1358c6935ee29e21826320d018a9360ab53d2bad9a45ca99939e3ca8","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"8f173dceb0dfef5029dd1b3c2e82e0d19fd91482af0ea15994137ec59dd51a4c","strict":true} name: "Smoke Agent: scoped/approved" "on": diff --git a/.github/workflows/smoke-agent-scoped-approved.md b/.github/workflows/smoke-agent-scoped-approved.md index edfdce6c19..73332c3b71 100644 --- a/.github/workflows/smoke-agent-scoped-approved.md +++ b/.github/workflows/smoke-agent-scoped-approved.md @@ -25,7 +25,7 @@ network: - defaults - github safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: hide-older-comments: true max: 2 diff --git a/.github/workflows/smoke-call-workflow.lock.yml b/.github/workflows/smoke-call-workflow.lock.yml index 46c0f5eaf8..9d867686cc 100644 --- a/.github/workflows/smoke-call-workflow.lock.yml +++ b/.github/workflows/smoke-call-workflow.lock.yml @@ -23,7 +23,7 @@ # # Smoke test for the call-workflow safe output - orchestrator that calls a worker via workflow_call at compile-time fan-out # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"40e319124ecbe26f1e2e8e56c7e02788569d18eeff79c748c7b28b33304b4a50","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"61544e9548c9d83d99c2874329b16fde38e5eed99331c8de79e5eac9093ce3fa","strict":true} name: "Smoke Call Workflow" "on": diff --git a/.github/workflows/smoke-call-workflow.md b/.github/workflows/smoke-call-workflow.md index e56c66d4b4..3cb4fbf500 100644 --- a/.github/workflows/smoke-call-workflow.md +++ b/.github/workflows/smoke-call-workflow.md @@ -17,7 +17,7 @@ network: allowed: - defaults safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] call-workflow: workflows: - smoke-workflow-call diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 9cd37d1b53..555be6ca39 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -35,7 +35,7 @@ # # inlined-imports: true # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"ca7cebdbef133f4a3a2f6c0cddfbdf45fdfd2dc3d9fb2da2a1c9bbba7051f614","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"824ddfaa1c06335001cd4434a5ac6af2749652cef82c56645faa411a4b1c2711","strict":true} name: "Smoke Claude" "on": diff --git a/.github/workflows/smoke-claude.md b/.github/workflows/smoke-claude.md index 611dd0a967..6520cea1dd 100644 --- a/.github/workflows/smoke-claude.md +++ b/.github/workflows/smoke-claude.md @@ -55,7 +55,7 @@ runtimes: go: version: "1.25" safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: hide-older-comments: true max: 2 diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 598f9527f7..840e7898c5 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -28,7 +28,7 @@ # - shared/gh.md # - shared/reporting.md # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"ad43d1661db52914b927f65775b81caeac5038cd670ff101502b28c508987f20","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"57064b51acff99a53ad08aada3ddf9626cdf25a44c87298d5df01726d62273e5","strict":true} name: "Smoke Codex" "on": diff --git a/.github/workflows/smoke-codex.md b/.github/workflows/smoke-codex.md index bf299ecc8f..2b8f27305b 100644 --- a/.github/workflows/smoke-codex.md +++ b/.github/workflows/smoke-codex.md @@ -41,7 +41,7 @@ sandbox: mcp: container: "ghcr.io/github/gh-aw-mcpg" safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: hide-older-comments: true max: 2 diff --git a/.github/workflows/smoke-copilot-arm.lock.yml b/.github/workflows/smoke-copilot-arm.lock.yml index 33d6697868..a86411f45e 100644 --- a/.github/workflows/smoke-copilot-arm.lock.yml +++ b/.github/workflows/smoke-copilot-arm.lock.yml @@ -29,7 +29,7 @@ # - shared/github-queries-mcp-script.md # - shared/reporting.md # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"efc63d49a71c555776c3f68123c684fdaaf063a0fdd43f594f267fc3fc4be53e","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"7d98a942c43c77f4d9757066e1492cdd3c197fb6272337d072c67412dfa07e95","strict":true} name: "Smoke Copilot ARM64" "on": diff --git a/.github/workflows/smoke-copilot-arm.md b/.github/workflows/smoke-copilot-arm.md index 0d3cb2102c..f54a95bd6a 100644 --- a/.github/workflows/smoke-copilot-arm.md +++ b/.github/workflows/smoke-copilot-arm.md @@ -45,7 +45,7 @@ sandbox: mcp: container: "ghcr.io/github/gh-aw-mcpg" safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: allowed-repos: ["github/gh-aw"] hide-older-comments: true diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 2fd336ceb0..ab5f23db88 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -29,7 +29,7 @@ # - shared/github-queries-mcp-script.md # - shared/reporting.md # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"00655b0e7e36f5b9cd4f2463f6d1fa4626f10db1abd0ed26dae87c1fa244d4dd","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"1a7fa2c830c582eef2c1ca858d020772b9efc949615721d692484047ab127162","strict":true} name: "Smoke Copilot" "on": diff --git a/.github/workflows/smoke-copilot.md b/.github/workflows/smoke-copilot.md index 3c1753302d..8ea8a0d1fc 100644 --- a/.github/workflows/smoke-copilot.md +++ b/.github/workflows/smoke-copilot.md @@ -48,7 +48,7 @@ sandbox: mcp: container: "ghcr.io/github/gh-aw-mcpg" safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: allowed-repos: ["github/gh-aw"] hide-older-comments: true diff --git a/.github/workflows/smoke-create-cross-repo-pr.lock.yml b/.github/workflows/smoke-create-cross-repo-pr.lock.yml index f4196223e4..14e3669717 100644 --- a/.github/workflows/smoke-create-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-create-cross-repo-pr.lock.yml @@ -23,7 +23,7 @@ # # Smoke test validating cross-repo pull request creation in githubnext/gh-aw-side-repo # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"e2a2075f1ad09ed0c032a09df186a30cdb7c67df6824cac503ae642da6b1d7a7","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"23144cc5cfaff8c43f78aeac9193fc954d2391a4d6fc0207772ebb4127c0ad56","strict":true} name: "Smoke Create Cross-Repo PR" "on": diff --git a/.github/workflows/smoke-create-cross-repo-pr.md b/.github/workflows/smoke-create-cross-repo-pr.md index 416f450cdd..b5df9fba6a 100644 --- a/.github/workflows/smoke-create-cross-repo-pr.md +++ b/.github/workflows/smoke-create-cross-repo-pr.md @@ -32,7 +32,7 @@ tools: github-token: ${{ secrets.GH_AW_SIDE_REPO_PAT }} safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] create-pull-request: target-repo: "githubnext/gh-aw-side-repo" github-token: ${{ secrets.GH_AW_SIDE_REPO_PAT }} diff --git a/.github/workflows/smoke-gemini.lock.yml b/.github/workflows/smoke-gemini.lock.yml index 5959725083..d892866f00 100644 --- a/.github/workflows/smoke-gemini.lock.yml +++ b/.github/workflows/smoke-gemini.lock.yml @@ -28,7 +28,7 @@ # - shared/gh.md # - shared/reporting.md # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"ac2dc0e8d85ce6faa57b8161338bf3abd8de98b22265eb833db40965b55bda3a","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"130f0cf7df1822b203c39619f6272bb21a12a2e894d0d01a65648278159fa51d","strict":true} name: "Smoke Gemini" "on": diff --git a/.github/workflows/smoke-gemini.md b/.github/workflows/smoke-gemini.md index 532a87b035..02a19abf31 100644 --- a/.github/workflows/smoke-gemini.md +++ b/.github/workflows/smoke-gemini.md @@ -32,7 +32,7 @@ tools: - "*" web-fetch: safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: hide-older-comments: true max: 2 diff --git a/.github/workflows/smoke-multi-pr.lock.yml b/.github/workflows/smoke-multi-pr.lock.yml index a6dd807512..513d4e479c 100644 --- a/.github/workflows/smoke-multi-pr.lock.yml +++ b/.github/workflows/smoke-multi-pr.lock.yml @@ -23,7 +23,7 @@ # # Test creating multiple pull requests in a single workflow run # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"e4b831bcb1c19ca8689f6094b8a56ee28b6300cbfb857297cb03c2cd051ad6c1","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"ee31e52ea131e5257cdee9983e2401eca1666057c51720c5e667f48b6a4de359","strict":true} name: "Smoke Multi PR" "on": diff --git a/.github/workflows/smoke-multi-pr.md b/.github/workflows/smoke-multi-pr.md index be494d3520..af689d422f 100644 --- a/.github/workflows/smoke-multi-pr.md +++ b/.github/workflows/smoke-multi-pr.md @@ -25,7 +25,7 @@ tools: - "echo *" - "printf *" safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] create-pull-request: title-prefix: "[smoke-multi-pr] " if-no-changes: "warn" diff --git a/.github/workflows/smoke-project.lock.yml b/.github/workflows/smoke-project.lock.yml index f282a80b4a..3ff2361448 100644 --- a/.github/workflows/smoke-project.lock.yml +++ b/.github/workflows/smoke-project.lock.yml @@ -23,7 +23,7 @@ # # Smoke Project - Test project operations # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"537682c51777e746059922e9d8efd9595b4135c3f9ede3270cdf3eb53ff0029d","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"710ec62380b7f4046fc0e531419bf0be666859f6668744e95e249cc359b3d6c8","strict":true} name: "Smoke Project" "on": diff --git a/.github/workflows/smoke-project.md b/.github/workflows/smoke-project.md index e0e9ba6fe3..a9918c53f0 100644 --- a/.github/workflows/smoke-project.md +++ b/.github/workflows/smoke-project.md @@ -24,7 +24,7 @@ tools: bash: - "*" safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: hide-older-comments: true max: 2 diff --git a/.github/workflows/smoke-temporary-id.lock.yml b/.github/workflows/smoke-temporary-id.lock.yml index 3c1a933e3f..01df6da921 100644 --- a/.github/workflows/smoke-temporary-id.lock.yml +++ b/.github/workflows/smoke-temporary-id.lock.yml @@ -23,7 +23,7 @@ # # Test temporary ID functionality for issue chaining and cross-references # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"1407ac440e5791bff6b9fcb37b2df5a8e39ad80c22b3eb9143bfc8a74da7d60a","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"12576301d22af2259a94778128a412fb13a9fa458278f35ffd57e10d58921542","strict":true} name: "Smoke Temporary ID" "on": diff --git a/.github/workflows/smoke-temporary-id.md b/.github/workflows/smoke-temporary-id.md index aed16b05d3..35986171af 100644 --- a/.github/workflows/smoke-temporary-id.md +++ b/.github/workflows/smoke-temporary-id.md @@ -20,7 +20,7 @@ network: - defaults - node safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] create-issue: expires: 2h title-prefix: "[smoke-temporary-id] " diff --git a/.github/workflows/smoke-test-tools.lock.yml b/.github/workflows/smoke-test-tools.lock.yml index 8019892ebc..0eba0a938d 100644 --- a/.github/workflows/smoke-test-tools.lock.yml +++ b/.github/workflows/smoke-test-tools.lock.yml @@ -23,7 +23,7 @@ # # Smoke test to validate common development tools are available in the agent container # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"ffb326aef953a4e2ab9774e12e0982b5134333724bd729957a90ef83db7079a9","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"5f0e65a120ba50edaf8801625d4f570c66f3428d29b3a6a82a32610833d8d1a5","strict":true} name: "Agent Container Smoke Test" "on": diff --git a/.github/workflows/smoke-test-tools.md b/.github/workflows/smoke-test-tools.md index a9567e03f9..5c72f9614f 100644 --- a/.github/workflows/smoke-test-tools.md +++ b/.github/workflows/smoke-test-tools.md @@ -34,7 +34,7 @@ tools: bash: - "*" safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: hide-older-comments: true max: 2 diff --git a/.github/workflows/smoke-update-cross-repo-pr.lock.yml b/.github/workflows/smoke-update-cross-repo-pr.lock.yml index 05b7dc6f07..1a8393b70c 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-update-cross-repo-pr.lock.yml @@ -23,7 +23,7 @@ # # Smoke test validating cross-repo pull request updates in githubnext/gh-aw-side-repo by adding lines from Homer's Odyssey to the README # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"4077d1a1b4e99803242a555d077279d3cc4448ee14b1548761e0af73c6dcaa9f","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"47ff8252b0357a39438ef0a444222b2ec18a4e2dbfac8706b651d78505b115d9","strict":true} name: "Smoke Update Cross-Repo PR" "on": diff --git a/.github/workflows/smoke-update-cross-repo-pr.md b/.github/workflows/smoke-update-cross-repo-pr.md index 72f7308c9f..b2675b17b6 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.md +++ b/.github/workflows/smoke-update-cross-repo-pr.md @@ -36,7 +36,7 @@ tools: github-token: ${{ secrets.GH_AW_SIDE_REPO_PAT }} safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] create-issue: expires: 2h close-older-issues: true diff --git a/.github/workflows/smoke-workflow-call-with-inputs.lock.yml b/.github/workflows/smoke-workflow-call-with-inputs.lock.yml index 0597c00d2d..386c63808c 100644 --- a/.github/workflows/smoke-workflow-call-with-inputs.lock.yml +++ b/.github/workflows/smoke-workflow-call-with-inputs.lock.yml @@ -23,7 +23,7 @@ # # Reusable workflow with inputs - used to test that multiple callers don't clash on artifact names # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"771740f24e223b8d34da36ea8e78936a7e124c69e9b98c1e4c8ff4b5ddaebe00","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"70659d13c51a9ae641724fcb962c32d078d6734c237660a62fdc5ccc0e7c999d","strict":true} name: "Smoke Workflow Call with Inputs" "on": diff --git a/.github/workflows/smoke-workflow-call-with-inputs.md b/.github/workflows/smoke-workflow-call-with-inputs.md index dc97e41c7f..b508cdd2b6 100644 --- a/.github/workflows/smoke-workflow-call-with-inputs.md +++ b/.github/workflows/smoke-workflow-call-with-inputs.md @@ -30,7 +30,7 @@ tools: - "echo *" - "date" safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] noop: timeout-minutes: 5 --- diff --git a/.github/workflows/smoke-workflow-call.lock.yml b/.github/workflows/smoke-workflow-call.lock.yml index 2aeeea337b..de8f1f45c3 100644 --- a/.github/workflows/smoke-workflow-call.lock.yml +++ b/.github/workflows/smoke-workflow-call.lock.yml @@ -23,7 +23,7 @@ # # Reusable workflow to validate checkout from fork works correctly in workflow_call context # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"972c331610f4c5939b8d9a0ab62a3a7f666c2882b2207acbf7e49f55c2b3a980","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"ec8e06e54f77696dc841d99cf0dbe8391ab81ac495ec1ebb806afab7287c77f1","strict":true} name: "Smoke Workflow Call" "on": diff --git a/.github/workflows/smoke-workflow-call.md b/.github/workflows/smoke-workflow-call.md index 412ef02a3f..0d0029b44a 100644 --- a/.github/workflows/smoke-workflow-call.md +++ b/.github/workflows/smoke-workflow-call.md @@ -35,7 +35,7 @@ tools: - "git remote *" - "echo *" safe-outputs: - allowed-url-domains: [default-redaction] + allowed-domains: [default-safe-outputs] add-comment: hide-older-comments: true max: 1 diff --git a/pkg/cli/add_integration_test.go b/pkg/cli/add_integration_test.go index 53b28a7e29..915e433b38 100644 --- a/pkg/cli/add_integration_test.go +++ b/pkg/cli/add_integration_test.go @@ -978,7 +978,11 @@ func TestAddWorkflowWithDispatchWorkflowFromSharedImport(t *testing.T) { // frontmatter. haiku-printer lives as haiku-printer.yml (a plain GitHub Actions // workflow). The fetcher falls back to .yml when .md is 404, so both the main // workflow and the dispatch-workflow dependency are written to disk. - workflowSpec := "github/gh-aw/.github/workflows/smoke-copilot.md@main" + // + // Note: pinned to a specific commit SHA from the branch that renamed + // allowed-url-domains → allowed-domains (schema change). Update to @main once + // that change has been merged. + workflowSpec := "github/gh-aw/.github/workflows/smoke-copilot.md@c93eec8" cmd := exec.Command(setup.binaryPath, "add", workflowSpec, "--verbose") cmd.Dir = setup.tempDir diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index a8e81ee955..9b69f7cedd 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -4136,7 +4136,7 @@ "properties": { "allowed-domains": { "type": "array", - "description": "List of allowed domains for URI filtering in AI workflow output. URLs from other domains will be replaced with '(redacted)' for security.", + "description": "List of allowed domains for URL redaction in safe output handlers. Supports ecosystem identifiers (e.g., \"python\", \"node\", \"default-safe-outputs\") like network.allowed. These domains are unioned with the engine defaults and network.allowed when computing the final allowed domain set. localhost and github.com are always included.", "items": { "type": "string" } @@ -7411,13 +7411,6 @@ } ] ] - }, - "allowed-url-domains": { - "type": "array", - "description": "Additional allowed domains for URL redaction in safe output handlers. Supports the same ecosystem identifiers as network.allowed (e.g., \"python\", \"node\", \"dev-tools\"). These domains are unioned with the engine defaults and network.allowed when computing the final allowed domain set. localhost and github.com are always included.", - "items": { - "type": "string" - } } }, "additionalProperties": false diff --git a/pkg/workflow/allowed_domains_sanitization_test.go b/pkg/workflow/allowed_domains_sanitization_test.go index ce53617eb3..98e0bbe5c7 100644 --- a/pkg/workflow/allowed_domains_sanitization_test.go +++ b/pkg/workflow/allowed_domains_sanitization_test.go @@ -238,9 +238,9 @@ Test workflow with ecosystem identifiers. } } -// TestManualAllowedDomainsHasPriority tests that manually configured allowed-domains -// takes precedence over network configuration -func TestManualAllowedDomainsHasPriority(t *testing.T) { +// TestManualAllowedDomainsUnionWithNetworkConfig tests that manually configured allowed-domains +// unions with network configuration (not overrides it) +func TestManualAllowedDomainsUnionWithNetworkConfig(t *testing.T) { tests := []struct { name string workflow string @@ -248,7 +248,7 @@ func TestManualAllowedDomainsHasPriority(t *testing.T) { unexpectedDomain string }{ { - name: "Manual allowed-domains overrides network config", + name: "Manual allowed-domains unions with network config", workflow: `--- on: push permissions: @@ -270,14 +270,15 @@ safe-outputs: # Test Workflow -Test that manual allowed-domains takes precedence. +Test that manual allowed-domains unions with network config. `, expectedDomains: []string{ "manual-domain.com", "override.org", + "example.com", // from network.allowed - still present (union) }, - // Network domains and Copilot defaults should NOT be included - unexpectedDomain: "example.com", + // No domain should be absent + unexpectedDomain: "", }, { name: "Empty allowed-domains uses network config", @@ -459,16 +460,16 @@ func TestComputeAllowedDomainsForSanitization(t *testing.T) { } } -// TestAllowedURLDomainsUnionWithNetworkConfig tests that safe-outputs.allowed-url-domains +// TestAllowedDomainsUnionWithNetworkConfig tests that safe-outputs.allowed-domains // is unioned with network.allowed and always includes localhost and github.com -func TestAllowedURLDomainsUnionWithNetworkConfig(t *testing.T) { +func TestAllowedDomainsUnionWithNetworkConfig(t *testing.T) { tests := []struct { name string workflow string expectedDomains []string }{ { - name: "allowed-url-domains unioned with Copilot defaults and network config", + name: "allowed-domains unioned with Copilot defaults and network config", workflow: `--- on: push permissions: @@ -481,16 +482,16 @@ network: - example.com safe-outputs: create-issue: - allowed-url-domains: + allowed-domains: - extra-domain.com --- # Test Workflow -Test allowed-url-domains union with network config. +Test allowed-domains union with network config. `, expectedDomains: []string{ - "extra-domain.com", // from allowed-url-domains + "extra-domain.com", // from allowed-domains "example.com", // from network.allowed "api.github.com", // Copilot default "localhost", // always included @@ -498,7 +499,7 @@ Test allowed-url-domains union with network config. }, }, { - name: "allowed-url-domains supports ecosystem identifiers", + name: "allowed-domains supports ecosystem identifiers", workflow: `--- on: push permissions: @@ -508,14 +509,14 @@ engine: copilot strict: false safe-outputs: create-issue: - allowed-url-domains: + allowed-domains: - dev-tools - python --- # Test Workflow -Test allowed-url-domains with ecosystem identifiers. +Test allowed-domains with ecosystem identifiers. `, expectedDomains: []string{ "codecov.io", // from dev-tools ecosystem @@ -526,7 +527,7 @@ Test allowed-url-domains with ecosystem identifiers. }, }, { - name: "allowed-url-domains does not override network config", + name: "allowed-domains does not override network config", workflow: `--- on: push permissions: @@ -539,16 +540,16 @@ network: - network-domain.com safe-outputs: create-issue: - allowed-url-domains: + allowed-domains: - url-domain.com --- # Test Workflow -Test that allowed-url-domains does not override network config. +Test that allowed-domains does not override network config. `, expectedDomains: []string{ - "url-domain.com", // from allowed-url-domains + "url-domain.com", // from allowed-domains "network-domain.com", // from network.allowed - still present (union) "api.github.com", // Copilot default "localhost", // always included @@ -558,7 +559,7 @@ Test that allowed-url-domains does not override network config. for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tmpDir := testutil.TempDir(t, "allowed-url-domains-test") + tmpDir := testutil.TempDir(t, "allowed-domains-test") testFile := filepath.Join(tmpDir, "test-workflow.md") if err := os.WriteFile(testFile, []byte(tt.workflow), 0644); err != nil { t.Fatal(err) diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index 38210f900d..7e3723dc35 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -155,12 +155,6 @@ func (c *Compiler) validateWorkflowData(workflowData *WorkflowData, markdownPath return formatCompilerError(markdownPath, "error", err.Error(), err) } - // Validate safe-outputs allowed-url-domains configuration - log.Printf("Validating safe-outputs allowed-url-domains") - if err := c.validateSafeOutputsAllowedURLDomains(workflowData.SafeOutputs); err != nil { - return formatCompilerError(markdownPath, "error", err.Error(), err) - } - // Emit warnings for push-to-pull-request-branch misconfiguration log.Printf("Validating push-to-pull-request-branch configuration") c.validatePushToPullRequestBranchWarnings(workflowData.SafeOutputs, workflowData.CheckoutConfigs) diff --git a/pkg/workflow/compiler_safe_outputs_steps.go b/pkg/workflow/compiler_safe_outputs_steps.go index 64c6c42936..a218fab6b3 100644 --- a/pkg/workflow/compiler_safe_outputs_steps.go +++ b/pkg/workflow/compiler_safe_outputs_steps.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "sort" - "strings" "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/stringutil" @@ -320,10 +319,8 @@ func (c *Compiler) buildHandlerManagerStep(data *WorkflowData) []string { // default GitHub domains, causing user-configured allowed domains to be redacted. var domainsStr string if data.SafeOutputs != nil && len(data.SafeOutputs.AllowedDomains) > 0 { - domainsStr = strings.Join(data.SafeOutputs.AllowedDomains, ",") - } else if data.SafeOutputs != nil && len(data.SafeOutputs.AllowedURLDomains) > 0 { - // allowed-url-domains: additional domains unioned with engine/network base set - domainsStr = c.computeAllowedURLDomainsForSanitization(data) + // allowed-domains: additional domains unioned with engine/network base set; supports ecosystem identifiers + domainsStr = c.computeExpandedAllowedDomainsForSanitization(data) } else { domainsStr = c.computeAllowedDomainsForSanitization(data) } diff --git a/pkg/workflow/compiler_safe_outputs_steps_test.go b/pkg/workflow/compiler_safe_outputs_steps_test.go index 3e70e9d29d..f77b24f2cc 100644 --- a/pkg/workflow/compiler_safe_outputs_steps_test.go +++ b/pkg/workflow/compiler_safe_outputs_steps_test.go @@ -508,7 +508,9 @@ func TestBuildHandlerManagerStep(t *testing.T) { AddComments: &AddCommentsConfig{}, }, checkContains: []string{ - "GH_AW_ALLOWED_DOMAINS: \"docs.example.com,api.example.com\"", + "GH_AW_ALLOWED_DOMAINS:", + "docs.example.com", + "api.example.com", "GITHUB_SERVER_URL: ${{ github.server_url }}", "GITHUB_API_URL: ${{ github.api_url }}", }, diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index e249912592..0be848abe8 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -477,26 +477,25 @@ type SafeOutputsConfig struct { ThreatDetection *ThreatDetectionConfig `yaml:"threat-detection,omitempty"` // Threat detection configuration Jobs map[string]*SafeJobConfig `yaml:"jobs,omitempty"` // Safe-jobs configuration (moved from top-level) GitHubApp *GitHubAppConfig `yaml:"github-app,omitempty"` // GitHub App credentials for token minting - AllowedDomains []string `yaml:"allowed-domains,omitempty"` - AllowedURLDomains []string `yaml:"allowed-url-domains,omitempty"` // Additional allowed domains for URL redaction, unioned with network.allowed; supports ecosystem identifiers - AllowGitHubReferences []string `yaml:"allowed-github-references,omitempty"` // Allowed repositories for GitHub references (e.g., ["repo", "org/repo2"]) - Staged bool `yaml:"staged,omitempty"` // If true, emit step summary messages instead of making GitHub API calls - Env map[string]string `yaml:"env,omitempty"` // Environment variables to pass to safe output jobs - GitHubToken string `yaml:"github-token,omitempty"` // GitHub token for safe output jobs - MaximumPatchSize int `yaml:"max-patch-size,omitempty"` // Maximum allowed patch size in KB (defaults to 1024) - RunsOn string `yaml:"runs-on,omitempty"` // Runner configuration for safe-outputs jobs - Messages *SafeOutputMessagesConfig `yaml:"messages,omitempty"` // Custom message templates for footer and notifications - Mentions *MentionsConfig `yaml:"mentions,omitempty"` // Configuration for @mention filtering in safe outputs - Footer *bool `yaml:"footer,omitempty"` // Global footer control - when false, omits visible footer from all safe outputs (XML markers still included) - GroupReports bool `yaml:"group-reports,omitempty"` // If true, create parent "Failed runs" issue for agent failures (default: false) - ReportFailureAsIssue *bool `yaml:"report-failure-as-issue,omitempty"` // If false, disables creating failure tracking issues when workflows fail (default: true) - FailureIssueRepo string `yaml:"failure-issue-repo,omitempty"` // Repository to create failure issues in (format: "owner/repo"), defaults to current repo - MaxBotMentions *string `yaml:"max-bot-mentions,omitempty"` // Maximum bot trigger references (e.g. 'fixes #123') allowed before filtering. Default: 10. Supports integer or GitHub Actions expression. - Steps []any `yaml:"steps,omitempty"` // User-provided steps injected after setup/checkout and before safe-output code - IDToken *string `yaml:"id-token,omitempty"` // Override id-token permission: "write" to force-add, "none" to disable auto-detection - ConcurrencyGroup string `yaml:"concurrency-group,omitempty"` // Concurrency group for the safe-outputs job (cancel-in-progress is always false) - Environment string `yaml:"environment,omitempty"` // Override the GitHub deployment environment for the safe-outputs job (defaults to the top-level environment: field) - AutoInjectedCreateIssue bool `yaml:"-"` // Internal: true when create-issues was automatically injected by the compiler (not user-configured) + AllowedDomains []string `yaml:"allowed-domains,omitempty"` // Allowed domains for URL redaction, unioned with network.allowed; supports ecosystem identifiers + AllowGitHubReferences []string `yaml:"allowed-github-references,omitempty"` // Allowed repositories for GitHub references (e.g., ["repo", "org/repo2"]) + Staged bool `yaml:"staged,omitempty"` // If true, emit step summary messages instead of making GitHub API calls + Env map[string]string `yaml:"env,omitempty"` // Environment variables to pass to safe output jobs + GitHubToken string `yaml:"github-token,omitempty"` // GitHub token for safe output jobs + MaximumPatchSize int `yaml:"max-patch-size,omitempty"` // Maximum allowed patch size in KB (defaults to 1024) + RunsOn string `yaml:"runs-on,omitempty"` // Runner configuration for safe-outputs jobs + Messages *SafeOutputMessagesConfig `yaml:"messages,omitempty"` // Custom message templates for footer and notifications + Mentions *MentionsConfig `yaml:"mentions,omitempty"` // Configuration for @mention filtering in safe outputs + Footer *bool `yaml:"footer,omitempty"` // Global footer control - when false, omits visible footer from all safe outputs (XML markers still included) + GroupReports bool `yaml:"group-reports,omitempty"` // If true, create parent "Failed runs" issue for agent failures (default: false) + ReportFailureAsIssue *bool `yaml:"report-failure-as-issue,omitempty"` // If false, disables creating failure tracking issues when workflows fail (default: true) + FailureIssueRepo string `yaml:"failure-issue-repo,omitempty"` // Repository to create failure issues in (format: "owner/repo"), defaults to current repo + MaxBotMentions *string `yaml:"max-bot-mentions,omitempty"` // Maximum bot trigger references (e.g. 'fixes #123') allowed before filtering. Default: 10. Supports integer or GitHub Actions expression. + Steps []any `yaml:"steps,omitempty"` // User-provided steps injected after setup/checkout and before safe-output code + IDToken *string `yaml:"id-token,omitempty"` // Override id-token permission: "write" to force-add, "none" to disable auto-detection + ConcurrencyGroup string `yaml:"concurrency-group,omitempty"` // Concurrency group for the safe-outputs job (cancel-in-progress is always false) + Environment string `yaml:"environment,omitempty"` // Override the GitHub deployment environment for the safe-outputs job (defaults to the top-level environment: field) + AutoInjectedCreateIssue bool `yaml:"-"` // Internal: true when create-issues was automatically injected by the compiler (not user-configured) } // SafeOutputMessagesConfig holds custom message templates for safe-output footer and notification messages diff --git a/pkg/workflow/compiler_yaml.go b/pkg/workflow/compiler_yaml.go index 63a0f79829..33da8f9fa4 100644 --- a/pkg/workflow/compiler_yaml.go +++ b/pkg/workflow/compiler_yaml.go @@ -693,11 +693,8 @@ func (c *Compiler) generateOutputCollectionStep(yaml *strings.Builder, data *Wor // Use manually configured domains if available, otherwise compute from network configuration var domainsStr string if data.SafeOutputs != nil && len(data.SafeOutputs.AllowedDomains) > 0 { - // Use manually configured allowed domains (legacy override behavior) - domainsStr = strings.Join(data.SafeOutputs.AllowedDomains, ",") - } else if data.SafeOutputs != nil && len(data.SafeOutputs.AllowedURLDomains) > 0 { - // allowed-url-domains: additional domains unioned with engine/network base set - domainsStr = c.computeAllowedURLDomainsForSanitization(data) + // allowed-domains: additional domains unioned with engine/network base set; supports ecosystem identifiers + domainsStr = c.computeExpandedAllowedDomainsForSanitization(data) } else { // Fall back to computing from network configuration (same as firewall) domainsStr = c.computeAllowedDomainsForSanitization(data) diff --git a/pkg/workflow/domains.go b/pkg/workflow/domains.go index aab1702e3a..38f68a55fb 100644 --- a/pkg/workflow/domains.go +++ b/pkg/workflow/domains.go @@ -132,10 +132,10 @@ func init() { // component ecosystems. These are resolved at lookup time, so they stay in sync with // any future changes to the component ecosystems. var compoundEcosystems = map[string][]string{ - // default-redaction: the recommended baseline for URL redaction in safe-outputs. + // default-safe-outputs: the recommended baseline for URL redaction in safe-outputs. // Covers common infrastructure certificate/OCSP hosts (via "defaults") plus popular // developer-tool and CI/CD service domains (via "dev-tools"). - "default-redaction": {"defaults", "dev-tools"}, + "default-safe-outputs": {"defaults", "dev-tools"}, } // getEcosystemDomains returns the domains for a given ecosystem category. @@ -361,7 +361,7 @@ var ecosystemPriority = []string{ "swift", "terraform", "zig", - "default-redaction", // compound: defaults + dev-tools + "default-safe-outputs", // compound: defaults + dev-tools } // GetDomainEcosystem returns the ecosystem identifier for a given domain, or empty string if not found. @@ -669,10 +669,10 @@ func (c *Compiler) computeAllowedDomainsForSanitization(data *WorkflowData) stri } } -// expandAllowedURLDomains expands a list of domain entries (which may include ecosystem +// expandAllowedDomains expands a list of domain entries (which may include ecosystem // identifiers like "python", "node", "dev-tools") into a deduplicated, sorted list of // concrete domain strings. This uses the same expansion logic as network.allowed. -func expandAllowedURLDomains(entries []string) []string { +func expandAllowedDomains(entries []string) []string { domainMap := make(map[string]bool) for _, entry := range entries { ecosystemDomains := getEcosystemDomains(entry) @@ -692,11 +692,11 @@ func expandAllowedURLDomains(entries []string) []string { return result } -// computeAllowedURLDomainsForSanitization computes the allowed domains for URL sanitization, -// unioning the engine/network base set with the safe-outputs.allowed-url-domains entries. +// computeExpandedAllowedDomainsForSanitization computes the allowed domains for URL sanitization, +// unioning the engine/network base set with the safe-outputs.allowed-domains entries. // It always includes "localhost" and "github.com" in the result. -// The allowed-url-domains entries support ecosystem identifiers (same syntax as network.allowed). -func (c *Compiler) computeAllowedURLDomainsForSanitization(data *WorkflowData) string { +// The allowed-domains entries support ecosystem identifiers (same syntax as network.allowed). +func (c *Compiler) computeExpandedAllowedDomainsForSanitization(data *WorkflowData) string { // Start from the base set (engine defaults + network.allowed + tools + runtimes) base := c.computeAllowedDomainsForSanitization(data) @@ -712,9 +712,9 @@ func (c *Compiler) computeAllowedURLDomainsForSanitization(data *WorkflowData) s } } - // Union with allowed-url-domains (expanded) - if data.SafeOutputs != nil && len(data.SafeOutputs.AllowedURLDomains) > 0 { - for _, d := range expandAllowedURLDomains(data.SafeOutputs.AllowedURLDomains) { + // Union with allowed-domains (expanded) + if data.SafeOutputs != nil && len(data.SafeOutputs.AllowedDomains) > 0 { + for _, d := range expandAllowedDomains(data.SafeOutputs.AllowedDomains) { domainMap[d] = true } } diff --git a/pkg/workflow/domains_test.go b/pkg/workflow/domains_test.go index 2833f38b1e..f84bd2c8fc 100644 --- a/pkg/workflow/domains_test.go +++ b/pkg/workflow/domains_test.go @@ -893,10 +893,10 @@ func TestGetCodexAllowedDomainsWithToolsAndRuntimes(t *testing.T) { }) } -// TestExpandAllowedURLDomains tests the expandAllowedURLDomains function -func TestExpandAllowedURLDomains(t *testing.T) { +// TestExpandAllowedDomains tests the expandAllowedDomains function +func TestExpandAllowedDomains(t *testing.T) { t.Run("plain domains are returned as-is", func(t *testing.T) { - result := expandAllowedURLDomains([]string{"example.com", "test.org"}) + result := expandAllowedDomains([]string{"example.com", "test.org"}) if !strings.Contains(strings.Join(result, ","), "example.com") { t.Error("Expected example.com in result") } @@ -906,7 +906,7 @@ func TestExpandAllowedURLDomains(t *testing.T) { }) t.Run("ecosystem identifiers are expanded", func(t *testing.T) { - result := expandAllowedURLDomains([]string{"python"}) + result := expandAllowedDomains([]string{"python"}) joined := strings.Join(result, ",") if !strings.Contains(joined, "pypi.org") { t.Error("Expected pypi.org from python ecosystem in result") @@ -914,7 +914,7 @@ func TestExpandAllowedURLDomains(t *testing.T) { }) t.Run("dev-tools ecosystem is expanded", func(t *testing.T) { - result := expandAllowedURLDomains([]string{"dev-tools"}) + result := expandAllowedDomains([]string{"dev-tools"}) joined := strings.Join(result, ",") if !strings.Contains(joined, "codecov.io") { t.Error("Expected codecov.io from dev-tools ecosystem in result") @@ -925,7 +925,7 @@ func TestExpandAllowedURLDomains(t *testing.T) { }) t.Run("mixed plain domains and ecosystem identifiers", func(t *testing.T) { - result := expandAllowedURLDomains([]string{"example.com", "python"}) + result := expandAllowedDomains([]string{"example.com", "python"}) joined := strings.Join(result, ",") if !strings.Contains(joined, "example.com") { t.Error("Expected example.com in result") @@ -936,16 +936,16 @@ func TestExpandAllowedURLDomains(t *testing.T) { }) t.Run("empty input returns empty result", func(t *testing.T) { - result := expandAllowedURLDomains([]string{}) + result := expandAllowedDomains([]string{}) if len(result) != 0 { t.Errorf("Expected empty result, got %v", result) } }) } -// TestComputeAllowedURLDomainsForSanitization tests that allowed-url-domains are unioned with +// TestComputeExpandedAllowedDomainsForSanitization tests that allowed-domains are unioned with // the engine/network base set and always includes localhost and github.com -func TestComputeAllowedURLDomainsForSanitization(t *testing.T) { +func TestComputeExpandedAllowedDomainsForSanitization(t *testing.T) { compiler := NewCompiler() t.Run("unions with engine base set", func(t *testing.T) { @@ -955,10 +955,10 @@ func TestComputeAllowedURLDomainsForSanitization(t *testing.T) { Allowed: []string{"example.com"}, }, SafeOutputs: &SafeOutputsConfig{ - AllowedURLDomains: []string{"extra-domain.com"}, + AllowedDomains: []string{"extra-domain.com"}, }, } - result := compiler.computeAllowedURLDomainsForSanitization(data) + result := compiler.computeExpandedAllowedDomainsForSanitization(data) if !strings.Contains(result, "extra-domain.com") { t.Error("Expected extra-domain.com in result") } @@ -974,12 +974,12 @@ func TestComputeAllowedURLDomainsForSanitization(t *testing.T) { data := &WorkflowData{ EngineConfig: &EngineConfig{ID: "copilot"}, SafeOutputs: &SafeOutputsConfig{ - AllowedURLDomains: []string{"extra-domain.com"}, + AllowedDomains: []string{"extra-domain.com"}, }, } - result := compiler.computeAllowedURLDomainsForSanitization(data) + result := compiler.computeExpandedAllowedDomainsForSanitization(data) if !strings.Contains(result, "localhost") { - t.Error("Expected localhost to always be in allowed-url-domains result") + t.Error("Expected localhost to always be in allowed-domains result") } }) @@ -987,12 +987,12 @@ func TestComputeAllowedURLDomainsForSanitization(t *testing.T) { data := &WorkflowData{ EngineConfig: &EngineConfig{ID: "codex"}, SafeOutputs: &SafeOutputsConfig{ - AllowedURLDomains: []string{"extra-domain.com"}, + AllowedDomains: []string{"extra-domain.com"}, }, } - result := compiler.computeAllowedURLDomainsForSanitization(data) + result := compiler.computeExpandedAllowedDomainsForSanitization(data) if !strings.Contains(result, "github.com") { - t.Error("Expected github.com to always be in allowed-url-domains result") + t.Error("Expected github.com to always be in allowed-domains result") } }) @@ -1000,10 +1000,10 @@ func TestComputeAllowedURLDomainsForSanitization(t *testing.T) { data := &WorkflowData{ EngineConfig: &EngineConfig{ID: "copilot"}, SafeOutputs: &SafeOutputsConfig{ - AllowedURLDomains: []string{"python", "dev-tools"}, + AllowedDomains: []string{"python", "dev-tools"}, }, } - result := compiler.computeAllowedURLDomainsForSanitization(data) + result := compiler.computeExpandedAllowedDomainsForSanitization(data) if !strings.Contains(result, "pypi.org") { t.Error("Expected pypi.org from python ecosystem in result") } @@ -1013,10 +1013,10 @@ func TestComputeAllowedURLDomainsForSanitization(t *testing.T) { }) } -// TestDefaultRedactionEcosystem tests that the default-redaction compound ecosystem +// TestDefaultSafeOutputsEcosystem tests that the default-safe-outputs compound ecosystem // correctly expands to the union of defaults + dev-tools -func TestDefaultRedactionEcosystem(t *testing.T) { - result := expandAllowedURLDomains([]string{"default-redaction"}) +func TestDefaultSafeOutputsEcosystem(t *testing.T) { + result := expandAllowedDomains([]string{"default-safe-outputs"}) joined := strings.Join(result, ",") // From defaults ecosystem @@ -1026,12 +1026,12 @@ func TestDefaultRedactionEcosystem(t *testing.T) { for _, d := range append(defaultsSamples, devToolsSamples...) { if !strings.Contains(joined, d) { - t.Errorf("Expected domain %q from default-redaction ecosystem in result", d) + t.Errorf("Expected domain %q from default-safe-outputs ecosystem in result", d) } } // Should include both defaults and dev-tools (at least 34+21 = 55 domains) if len(result) < 50 { - t.Errorf("Expected at least 50 domains in default-redaction, got %d", len(result)) + t.Errorf("Expected at least 50 domains in default-safe-outputs, got %d", len(result)) } } diff --git a/pkg/workflow/safe_outputs_config.go b/pkg/workflow/safe_outputs_config.go index 560a6e6cb9..61ce26df1c 100644 --- a/pkg/workflow/safe_outputs_config.go +++ b/pkg/workflow/safe_outputs_config.go @@ -170,7 +170,7 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut config.AutofixCodeScanningAlert = autofixCodeScanningAlertConfig } - // Parse allowed-domains configuration + // Parse allowed-domains configuration (additional domains, unioned with network.allowed; supports ecosystem identifiers) if allowedDomains, exists := outputMap["allowed-domains"]; exists { if domainsArray, ok := allowedDomains.([]any); ok { var domainStrings []string @@ -184,20 +184,6 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut } } - // Parse allowed-url-domains configuration (additional domains, unioned with network.allowed) - if allowedURLDomains, exists := outputMap["allowed-url-domains"]; exists { - if domainsArray, ok := allowedURLDomains.([]any); ok { - var domainStrings []string - for _, domain := range domainsArray { - if domainStr, ok := domain.(string); ok { - domainStrings = append(domainStrings, domainStr) - } - } - config.AllowedURLDomains = domainStrings - safeOutputsConfigLog.Printf("Configured allowed-url-domains with %d domain(s)", len(domainStrings)) - } - } - // Parse allowed-github-references configuration if allowGitHubRefs, exists := outputMap["allowed-github-references"]; exists { if refsArray, ok := allowGitHubRefs.([]any); ok { diff --git a/pkg/workflow/safe_outputs_validation.go b/pkg/workflow/safe_outputs_validation.go index 9820777d7c..cb466de065 100644 --- a/pkg/workflow/safe_outputs_validation.go +++ b/pkg/workflow/safe_outputs_validation.go @@ -21,6 +21,12 @@ func (c *Compiler) validateNetworkAllowedDomains(network *NetworkPermissions) er collector := NewErrorCollector(c.failFast) for i, domain := range network.Allowed { + // "*" means allow all traffic - skip validation + if domain == "*" { + safeOutputsDomainsValidationLog.Print("Skipping allow-all wildcard '*'") + continue + } + // Skip ecosystem identifiers - they don't need domain pattern validation if isEcosystemIdentifier(domain) { safeOutputsDomainsValidationLog.Printf("Skipping ecosystem identifier: %s", domain) @@ -44,11 +50,15 @@ func (c *Compiler) validateNetworkAllowedDomains(network *NetworkPermissions) er return nil } +// isEcosystemIdentifierPattern matches valid ecosystem identifiers like "defaults", "node", "dev-tools" +var isEcosystemIdentifierPattern = regexp.MustCompile(`^[a-z][a-z0-9-]*$`) + // isEcosystemIdentifier checks if a domain string is actually an ecosystem identifier func isEcosystemIdentifier(domain string) bool { - // Ecosystem identifiers don't contain dots and don't have protocol prefixes - // They are simple identifiers like "defaults", "node", "python", etc. - return !strings.Contains(domain, ".") && !strings.Contains(domain, "://") + // Ecosystem identifiers are simple lowercase alphanumeric identifiers with optional hyphens + // like "defaults", "node", "python", "dev-tools", "default-safe-outputs". + // They don't contain dots, protocol prefixes, spaces, wildcards, or other special characters. + return isEcosystemIdentifierPattern.MatchString(domain) } // domainPattern validates domain patterns including wildcards @@ -61,7 +71,8 @@ func isEcosystemIdentifier(domain string) bool { // - Empty or malformed domains var domainPattern = regexp.MustCompile(`^(\*\.)?[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$`) -// validateSafeOutputsAllowedDomains validates the allowed-domains configuration in safe-outputs +// validateSafeOutputsAllowedDomains validates the allowed-domains configuration in safe-outputs. +// Supports ecosystem identifiers (e.g., "python", "node", "default-safe-outputs") like network.allowed. func (c *Compiler) validateSafeOutputsAllowedDomains(config *SafeOutputsConfig) error { if config == nil || len(config.AllowedDomains) == 0 { return nil @@ -72,35 +83,6 @@ func (c *Compiler) validateSafeOutputsAllowedDomains(config *SafeOutputsConfig) collector := NewErrorCollector(c.failFast) for i, domain := range config.AllowedDomains { - if err := validateDomainPattern(domain); err != nil { - wrappedErr := fmt.Errorf("safe-outputs.allowed-domains[%d]: %w", i, err) - if returnErr := collector.Add(wrappedErr); returnErr != nil { - return returnErr // Fail-fast mode - } - } - } - - if err := collector.Error(); err != nil { - safeOutputsDomainsValidationLog.Printf("Safe outputs allowed domains validation failed: %v", err) - return err - } - - safeOutputsDomainsValidationLog.Print("Safe outputs allowed domains validation passed") - return nil -} - -// validateSafeOutputsAllowedURLDomains validates the allowed-url-domains configuration in safe-outputs. -// Supports ecosystem identifiers (e.g., "python", "node") like network.allowed. -func (c *Compiler) validateSafeOutputsAllowedURLDomains(config *SafeOutputsConfig) error { - if config == nil || len(config.AllowedURLDomains) == 0 { - return nil - } - - safeOutputsDomainsValidationLog.Printf("Validating %d allowed-url-domains", len(config.AllowedURLDomains)) - - collector := NewErrorCollector(c.failFast) - - for i, domain := range config.AllowedURLDomains { // Skip ecosystem identifiers - they don't need domain pattern validation if isEcosystemIdentifier(domain) { safeOutputsDomainsValidationLog.Printf("Skipping ecosystem identifier: %s", domain) @@ -108,7 +90,7 @@ func (c *Compiler) validateSafeOutputsAllowedURLDomains(config *SafeOutputsConfi } if err := validateDomainPattern(domain); err != nil { - wrappedErr := fmt.Errorf("safe-outputs.allowed-url-domains[%d]: %w", i, err) + wrappedErr := fmt.Errorf("safe-outputs.allowed-domains[%d]: %w", i, err) if returnErr := collector.Add(wrappedErr); returnErr != nil { return returnErr // Fail-fast mode } @@ -116,11 +98,11 @@ func (c *Compiler) validateSafeOutputsAllowedURLDomains(config *SafeOutputsConfi } if err := collector.Error(); err != nil { - safeOutputsDomainsValidationLog.Printf("Safe outputs allowed-url-domains validation failed: %v", err) + safeOutputsDomainsValidationLog.Printf("Safe outputs allowed domains validation failed: %v", err) return err } - safeOutputsDomainsValidationLog.Print("Safe outputs allowed-url-domains validation passed") + safeOutputsDomainsValidationLog.Print("Safe outputs allowed domains validation passed") return nil }