Add cross-repository support to dispatch-workflow safe-output#16620
Add cross-repository support to dispatch-workflow safe-output#16620
Conversation
Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This pull request adds cross-repository support to the dispatch-workflow safe-output feature, enabling workflows to orchestrate actions across multiple repositories with deny-by-default security. The implementation follows established patterns from create-issue and add-comment handlers.
Changes:
- Added
target-repoandallowed-reposconfiguration fields with deny-by-default security model - Integrated repository validation using existing
repo_helpers.cjsutilities - Updated schema, documentation, and example workflow to demonstrate cross-repository dispatch
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/dispatch_workflow.go | Added parsing for target-repo and allowed-repos YAML fields |
| pkg/workflow/dispatch_workflow_test.go | Added test coverage for cross-repo configuration parsing |
| pkg/workflow/compiler_safe_outputs_config.go | Updated handler config builder to include cross-repo fields |
| pkg/parser/schemas/main_workflow_schema.json | Added JSON schema validation for repository slug patterns |
| actions/setup/js/dispatch_workflow.cjs | Implemented cross-repo validation and auto-qualification logic |
| actions/setup/js/dispatch_workflow.test.cjs | Added comprehensive test suite for cross-repo scenarios |
| docs/src/content/docs/reference/safe-outputs.md | Updated documentation with cross-repo examples and security model |
| .github/workflows/dispatch-cross-repo-example.md | Added example workflow demonstrating cross-repo dispatch |
| .github/workflows/dispatch-cross-repo-example.lock.yml | Compiled workflow output with cross-repo configuration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Determine target repository for this dispatch (default to current repo) | ||
| const itemRepo = item.repo ? String(item.repo).trim() : defaultTargetRepo; | ||
|
|
||
| // Validate the repository is allowed | ||
| const repoValidation = validateRepo(itemRepo, defaultTargetRepo, allowedRepos); | ||
| if (!repoValidation.valid) { | ||
| const errorMessage = repoValidation.error || `Repository '${itemRepo}' validation failed`; | ||
| core.warning(errorMessage); | ||
| return { | ||
| success: false, | ||
| error: errorMessage, | ||
| }; | ||
| } | ||
|
|
||
| // Use the qualified repo from validation (handles bare names) | ||
| const qualifiedItemRepo = repoValidation.qualifiedRepo; | ||
|
|
||
| // Parse the repository slug | ||
| const repoParts = parseRepoSlug(qualifiedItemRepo); | ||
| if (!repoParts) { | ||
| const error = `Invalid repository format '${itemRepo}'. Expected 'owner/repo'.`; | ||
| core.warning(error); | ||
| return { | ||
| success: false, | ||
| error: error, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Consider using the newer resolveAndValidateRepo helper function instead of manually calling validateRepo and parseRepoSlug separately. This would make the code more consistent with other handlers like add_comment, create_pull_request, and reply_to_pr_review_comment.
The resolveAndValidateRepo function combines both steps and returns a result with success, error, repo, and repoParts fields, simplifying the logic:
const repoResult = resolveAndValidateRepo(item, defaultTargetRepo, allowedRepos, "workflow dispatch");
if (!repoResult.success) {
core.warning(repoResult.error);
return {
success: false,
error: repoResult.error,
};
}
const { repo: qualifiedItemRepo, repoParts } = repoResult;This would replace lines 123-149 and make the code more maintainable and consistent with the established patterns in the codebase.
|
dispatch-workflowpreviously only supported same-repository orchestration. This adds cross-repository dispatch with deny-by-default security, following the existing patterns fromcreate-issueandadd-comment.Implementation
Schema & Configuration
target-repoandallowed-reposfields toDispatchWorkflowConfigCompiler
target_repoandallowed_reposin safe-outputs confighandlerConfigBuilderfor consistent JavaScript config formatRuntime Handler
repo_helpers.cjsfor repository validationDocumentation
Example
Same-repository (unchanged):
Cross-repository:
Agent output with cross-repo target:
{ "type": "dispatch_workflow", "workflow_name": "deploy-app", "repo": "staging-repo", // Auto-qualified to "github/staging-repo" "inputs": { "environment": "staging" } }Security Model
allowed-reposconfigurationvalidateRepo()fromrepo_helpers.cjsused by other safe-outputsWarning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
https://api.github.com/graphql/usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw GO111MODULE 64/bin/go git rev-�� --show-toplevel V4okxC0o4tfu /usr/bin/git -json GO111MODULE 64/bin/go git(http block)/usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw --write 64/bin/go git rev-�� --show-toplevel sh /usr/bin/git npx prettier --wgit git 64/bin/go git(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v3/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha 6106/001/stability-test.md GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE .noreply.github.rev-parse GOMODCACHE go env -json GO111MODULE cfg GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha g_.a GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go env EbtuGYcAs GO111MODULE ache/node/24.13.0/x64/bin/node GOINSECURE GOMOD GOMODCACHE ortcfg(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v4 --jq .object.sha _.a GO111MODULE /opt/hostedtoolcache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go env xbEtErTH5 GO111MODULE /usr/bin/grep GOINSECURE GOMOD GOMODCACHE grep(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v4 --jq .object.sha vaScript3413448409/001/test-frontmatter-with-env-template-expressions.md GO111MODULE /opt/hostedtoolcache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 815771/b273/vet.cfg GOINSECURE GOMOD GOMODCACHE iptables(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v4 --jq .object.sha --show-toplevel 5603496/b394/importcfg /usr/lib/git-core/git k/gh-aw/gh-aw/pkgit k/gh-aw/gh-aw/pkrev-parse 64/bin/go /usr/lib/git-core/git main�� run --auto /usr/bin/git --detach -trimpath 64/bin/go git(http block)https://api.github.com/repos/actions/checkout/git/ref/tags/v5/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/pkg/tool/linux_amd64/compile GOINSECURE GOMOD GOMODCACHE 64/pkg/tool/linuf() { test "$1" = get && echo "******"; }; f get(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha -m l 0/x64/bin/node -json GO111MODULE 64/bin/go 0/x64/bin/node(http block)/usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha user.email test@example.com /opt/hostedtoolcache/node/24.13.0/x64/bin/node(http block)https://api.github.com/repos/actions/github-script/git/ref/tags/v8/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE sh(http block)/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE sh -c npx prettier --check '**/*.cjs' GOINSECURE GOPROXY 64/bin/go GOSUMDB GOWORK 64/bin/go /bin/sh(http block)/usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE sh -c npx prettier --check '**/*.cjs' GOINSECURE GOPROXY 64/bin/go GOSUMDB GOWORK 64/bin/go git(http block)https://api.github.com/repos/actions/setup-go/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha rt GO111MODULE /opt/hostedtoolcache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE /usr/local/sbin/iptables GOINSECURE GOMOD GOMODCACHE iptables(http block)/usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha blog-auditor.md GO111MODULE 0/x64/bin/node GOINSECURE GOMOD GOMODCACHE go t-ha�� SameOutput3049068406/001/stability-test.md GO111MODULE /home/REDACTED/work/node_modules/.bin/sh GOINSECURE GOMOD GOMODCACHE sh(http block)https://api.github.com/repos/actions/setup-node/git/ref/tags/v4/usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha vaScript3413448409/001/test-complex-frontmatter-with-tools.md GO111MODULE ache/go/1.25.0/x64/pkg/tool/linux_amd64/compile GOINSECURE GOMOD GOMODCACHE ache/go/1.25.0/x64/pkg/tool/linux_amd64/compile env 815771/b404/_pkg_.a GO111MODULE 815771/b404=> GOINSECURE GOMOD GOMODCACHE iptables(http block)/usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha auto-triage-issues.md GO111MODULE /opt/hostedtoolcache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE ache/go/1.25.0/x64/pkg/tool/linux_amd64/link GOINSECURE GOMOD GOMODCACHE ache/go/1.25.0/x64/pkg/tool/linux_amd64/link(http block)https://api.github.com/repos/github/gh-aw/actions/runs/1/artifacts/usr/bin/gh gh run download 1 --dir test-logs/run-1 GO111MODULE x_amd64/link GOINSECURE GOMOD GOMODCACHE x_amd64/link env -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE L1/BH39Jih_oyS2LvJ_3yZB/SkE-TZwGremote.origin.url(http block)/usr/bin/gh gh run download 1 --dir test-logs/run-1 GO111MODULE x_amd64/compile GOINSECURE GOMOD GOMODCACHE x_amd64/compile env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/12345/artifacts/usr/bin/gh gh run download 12345 --dir test-logs/run-12345 patch-workflow Co-authored-by: -ifaceassert x_amd64/compile GOINSECURE GOMOD GOMODCACHE x_amd64/compile env -json GO111MODULE de/node/bin/bash GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh run download 12345 --dir test-logs/run-12345 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/12346/artifacts/usr/bin/gh gh run download 12346 --dir test-logs/run-12346 patch-workflow -w 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh run download 12346 --dir test-logs/run-12346 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/2/artifacts/usr/bin/gh gh run download 2 --dir test-logs/run-2 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh run download 2 --dir test-logs/run-2 GO111MODULE x_amd64/compile GOINSECURE GOMOD GOMODCACHE x_amd64/compile env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/3/artifacts/usr/bin/gh gh run download 3 --dir test-logs/run-3 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh run download 3 --dir test-logs/run-3 GO111MODULE x_amd64/link GOINSECURE GOMOD GOMODCACHE x_amd64/link env -json GO111MODULE 64/bin/go GOINSECURE 31/j0WE1ayJd61Of/tmp/js-hash-test-697798607/test-hash.js GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/4/artifacts/usr/bin/gh gh run download 4 --dir test-logs/run-4 GO111MODULE x_amd64/link GOINSECURE GOMOD GOMODCACHE x_amd64/link env -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE K0/Z2-ZXw9FhOpopzJMpcsk/dLpXjwavHaHtRvkD5Mds(http block)/usr/bin/gh gh run download 4 --dir test-logs/run-4 GO111MODULE x_amd64/link GOINSECURE GOMOD GOMODCACHE x_amd64/link env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/runs/5/artifacts/usr/bin/gh gh run download 5 --dir test-logs/run-5 GO111MODULE x_amd64/vet GOINSECURE GOMOD GOMODCACHE x_amd64/vet env -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh run download 5 --dir test-logs/run-5 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/actions/workflows/usr/bin/gh gh workflow list --json name,state,path 5603496/b387/_pkGOINSECURE GO111MODULE 64/bin/go GOINSECURE b/gh-aw/pkg/pars-5 GOMODCACHE go env ohS8/nmCJFAHnL_WGOSUMDB GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE 5603496/b387/importcfg(http block)/usr/bin/gh gh run list --json databaseId,number,url,status,conclusion,workflowName,createdAt,startedAt,updatedAt,event,headBranch,headSha,displayTitle --workflow nonexistent-workflow-12345 --limit 100 GOMOD GOMODCACHE node /hom�� --check **/*.cjs 64/bin/go **/*.json --ignore-path ../../../.pretti--show-toplevel go(http block)/usr/bin/gh gh run list --json databaseId,number,url,status,conclusion,workflowName,createdAt,startedAt,updatedAt,event,headBranch,headSha,displayTitle --workflow nonexistent-workflow-12345 --limit 6 GOMOD GOMODCACHE x_amd64/vet env -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE com> GOMODCACHE s9ZXZGY/X4XoDkfiiEtxJ64HjgrP env 1983624750/.github/workflows GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE tions/setup/js/node_modules/.bin/sh GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999/usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE com> GOMODCACHE F5/4qLQS-oeomrMmfGrEiad/6CmI5DOkrev-parse env -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env 1989517617/.github/workflows GO111MODULE tions/setup/node_modules/.bin/sh GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/nonexistent/repo/actions/runs/12345/usr/bin/gh gh run view 12345 --repo nonexistent/repo --json status,conclusion GOINSECURE GOMOD GOMODCACHE x_amd64/compile env -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh run view 12345 --repo nonexistent/repo --json status,conclusion GOINSECURE GOMOD GOMODCACHE x_amd64/vet env -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)https://api.github.com/repos/owner/repo/actions/workflows/usr/bin/gh gh workflow list --json name,state,path --repo owner/repo 64/bin/go GOINSECURE b/gh-aw/pkg/stri--norc GOMODCACHE go env H-p0/9qNQayvBt5HGOSUMDB GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE 5603496/b394/importcfg(http block)/usr/bin/gh gh workflow list --json name,state,path --repo owner/repo 64/bin/go GOINSECURE b/gh-aw/pkg/test--norc GOMODCACHE go env zeqw/XyZhZXSTEgcGOSUMDB GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE 5603496/b398/importcfg(http block)/usr/bin/gh gh workflow list --json name,state,path --repo owner/repo 64/bin/go **/*.ts **/*.json --ignore-path sh -c npx prettier --w**/*.ts git 64/bin/go --show-toplevel 731ec18a..HEAD(http block)https://api.github.com/repos/owner/repo/contents/file.md/tmp/go-build341815771/b368/cli.test /tmp/go-build341815771/b368/cli.test -test.testlogfile=/tmp/go-build341815771/b368/testlog.txt -test.paniconexit0 -test.v=true -test.parallel=4 -test.timeout=10m0s -test.run=^Test -test.short=true GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE sh(http block)/tmp/go-build1005566950/b368/cli.test /tmp/go-build1005566950/b368/cli.test -test.testlogfile=/tmp/go-build1005566950/b368/testlog.txt -test.paniconexit0 -test.v=true -test.parallel=4 -test.timeout=10m0s -test.run=^Test -test.short=true -path /dev/null x_amd64/compile /usr/bin/git sh -c "prettier" --wriGOINSECURE git modules/@npmcli/run-script/lib/node-gyp-bin/node tierignore go /usr/bin/git go(http block)https://api.github.com/repos/test-owner/test-repo/actions/secrets/usr/bin/gh gh api /repos/test-owner/test-repo/actions/secrets --jq .secrets[].name 5603496/b378/_pkGOINSECURE GO111MODULE 64/bin/go GOINSECURE b/gh-aw/pkg/envu--norc GOMODCACHE go env 1zH8/K9m-5ZMr7YuGOSUMDB GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE 5603496/b378/imporigin(http block)/usr/bin/gh gh api /repos/test-owner/test-repo/actions/secrets --jq .secrets[].name pkg/workflow/data/action_pins.json; \ echo "��� Action pins synced successfully"; \ else \ echo "��� Warning: .github/aw/actions-lock.json does not exist yet"; \ fi --write 64/bin/go **/*.ts **/*.json --ignore-path sh -c npx prettier --wGOSUMDB git 64/bin/go tierignore go(http block)https://api.github.com/user/usr/bin/gh gh api user --jq .login GOSUMDB GOWORK 64/bin/go GOINSECURE GOMOD GOMODCACHE go env heck '**/*.cjs' GOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go(http block)/usr/bin/gh gh api user --jq .login ck 'scripts/**/*GOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE /bin/sh(http block)/usr/bin/gh gh api user --jq .login ck 'scripts/**/*GOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE npm(http block)If you need me to access, download, or install something from one of these locations, you can either:
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.