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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions actions/setup/js/invocation_context_helpers.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,33 @@ function checkAllowedRepo(workflowRepo, targetRepo) {
const defaultRepo = `${workflowRepo.owner}/${workflowRepo.repo}`;
const targetRepoSlug = `${targetRepo.owner}/${targetRepo.repo}`;
const allowedRepos = parseAllowedRepos(process.env.GH_AW_ALLOWED_REPOS);

// Fall back to per-handler safe-output allowlists when a global allowlist is
// not provided. This prevents post-action context resolution from rejecting
// a repository that was already allowed by the active safe-output handler.
if (allowedRepos.size === 0) {
const handlerConfigRaw = process.env.GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG;
if (typeof handlerConfigRaw === "string" && handlerConfigRaw.trim() !== "") {
try {
const handlerConfig = JSON.parse(handlerConfigRaw);
if (handlerConfig && typeof handlerConfig === "object" && !Array.isArray(handlerConfig)) {
for (const value of Object.values(handlerConfig)) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
continue;
}
const parsed = parseAllowedRepos(value.allowed_repos);
for (const repo of parsed) {
allowedRepos.add(repo);
}
}
}
} catch (_error) {
// Best-effort only. If the handler config cannot be parsed, continue
// with the global allowlist (if any).
}
}
}

const validation = validateTargetRepo(targetRepoSlug, defaultRepo, allowedRepos);
if (!validation.valid) {
throw new Error(`${ERR_VALIDATION}: ${validation.error}`);
Expand Down
36 changes: 36 additions & 0 deletions actions/setup/js/invocation_context_helpers.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,42 @@ describe("invocation_context_helpers", () => {
}
});

it("allows workflow_dispatch target_repo when handler allowlist includes it", () => {
const originalAllowedRepos = process.env.GH_AW_ALLOWED_REPOS;
const originalHandlerConfig = process.env.GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG;
try {
delete process.env.GH_AW_ALLOWED_REPOS;
process.env.GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG = JSON.stringify({
create_pull_request: {
allowed_repos: ["target-owner/target-repo"],
},
});

const resolved = resolveInvocationContext({
eventName: "workflow_dispatch",
repo: { owner: "side-owner", repo: "side-repo" },
payload: {
inputs: {
target_repo: "target-owner/target-repo",
},
},
});

expect(resolved.eventRepo).toEqual({ owner: "target-owner", repo: "target-repo" });
} finally {
if (originalAllowedRepos === undefined) {
delete process.env.GH_AW_ALLOWED_REPOS;
} else {
process.env.GH_AW_ALLOWED_REPOS = originalAllowedRepos;
}
if (originalHandlerConfig === undefined) {
delete process.env.GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG;
} else {
process.env.GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG = originalHandlerConfig;
}
}
});

it("allows workflow_dispatch without target_repo inputs", () => {
const resolved = resolveInvocationContext({
eventName: "workflow_dispatch",
Expand Down
Loading