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
60 changes: 15 additions & 45 deletions actions/setup/js/validate_lockdown_requirements.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// @ts-check

const { renderLockdownTokenErrorMessage, renderPublicStrictModeErrorMessage, renderPullRequestTargetErrorMessage } = require("./validate_lockdown_requirements_templates.cjs");

/**
* Validates that lockdown mode requirements are met at runtime.
*
Expand All @@ -22,8 +24,17 @@
* @param {any} core - GitHub Actions core library
* @returns {void}
*/
const { ERR_VALIDATION } = require("./error_codes.cjs");
function validateLockdownRequirements(core) {
/**
* @param {string} message
* @returns {never}
*/
function failWithError(message) {
core.setOutput("lockdown_check_failed", "true");
core.setFailed(message);
throw new Error(message);
}

// Check if lockdown mode is explicitly enabled (set to "true" in frontmatter)
const lockdownEnabled = process.env.GITHUB_MCP_LOCKDOWN_EXPLICIT === "true";

Expand All @@ -46,22 +57,7 @@ function validateLockdownRequirements(core) {
core.info(`Custom github-token configured: ${hasCustomToken}`);

if (!hasAnyCustomToken) {
const errorMessage =
"Lockdown mode is enabled (lockdown: true) but no custom GitHub token is configured.\\n" +
"\\n" +
"Please configure one of the following as a repository secret:\\n" +
" - GH_AW_GITHUB_TOKEN (recommended)\\n" +
" - GH_AW_GITHUB_MCP_SERVER_TOKEN (alternative)\\n" +
" - Custom github-token in your workflow frontmatter\\n" +
"\\n" +
"See: https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/auth.mdx\\n" +
"\\n" +
"To set a token:\\n" +
' gh aw secrets set GH_AW_GITHUB_TOKEN --value "YOUR_FINE_GRAINED_PAT"';

core.setOutput("lockdown_check_failed", "true");
core.setFailed(errorMessage);
throw new Error(errorMessage);
failWithError(renderLockdownTokenErrorMessage());
}

core.info("✓ Lockdown mode requirements validated: Custom GitHub token is configured");
Expand All @@ -77,20 +73,7 @@ function validateLockdownRequirements(core) {
core.info(`Compiled with strict mode: ${isStrict}`);

if (isPublic && !isStrict) {
const errorMessage =
"This workflow is running on a public repository but was not compiled with strict mode.\\n" +
"\\n" +
"Public repository workflows must be compiled with strict mode enabled to meet\\n" +
"the security requirements for public exposure.\\n" +
"\\n" +
"To fix this, recompile the workflow with strict mode:\\n" +
" gh aw compile --strict\\n" +
"\\n" +
"See: https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/security.mdx";

core.setOutput("lockdown_check_failed", "true");
core.setFailed(errorMessage);
throw new Error(errorMessage);
failWithError(renderPublicStrictModeErrorMessage());
}

if (isPublic && isStrict) {
Expand All @@ -104,20 +87,7 @@ function validateLockdownRequirements(core) {
// and potentially exfiltrate secrets or cause unintended side effects.
const eventName = process.env.GITHUB_EVENT_NAME;
if (isPublic && eventName === "pull_request_target") {
const errorMessage =
"This workflow is triggered by the pull_request_target event on a public repository.\\n" +
"\\n" +
"The pull_request_target event is not allowed on public repositories because it runs\\n" +
"workflows with access to repository secrets even when triggered from a fork, which\\n" +
'creates a significant security risk (known as a "pwn request").\\n' +
"\\n" +
"To fix this, use the pull_request event instead, or migrate to a private repository.\\n" +
"\\n" +
"See: https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/security.mdx";

core.setOutput("lockdown_check_failed", "true");
core.setFailed(errorMessage);
throw new Error(errorMessage);
failWithError(renderPullRequestTargetErrorMessage());
}
}

Expand Down
76 changes: 76 additions & 0 deletions actions/setup/js/validate_lockdown_requirements.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,80 @@ describe("validate_lockdown_requirements", () => {
expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("not compiled with strict mode"));
});
});

describe("setOutput side effects", () => {
it("should not set lockdown_check_failed output on a fully successful run", () => {
// All conditions pass: no lockdown, private repo, no special event
process.env.GITHUB_REPOSITORY_VISIBILITY = "private";
process.env.GITHUB_EVENT_NAME = "push";

validateLockdownRequirements(mockCore);

expect(mockCore.setOutput).not.toHaveBeenCalled();
expect(mockCore.setFailed).not.toHaveBeenCalled();
});

it("should not set lockdown_check_failed when lockdown is enabled with token and repo is private", () => {
process.env.GITHUB_MCP_LOCKDOWN_EXPLICIT = "true";
process.env.GH_AW_GITHUB_TOKEN = "ghp_test";
process.env.GITHUB_REPOSITORY_VISIBILITY = "private";

validateLockdownRequirements(mockCore);

expect(mockCore.setOutput).not.toHaveBeenCalled();
});
});

describe("multiple tokens", () => {
it("should pass when all three tokens are configured simultaneously", () => {
process.env.GITHUB_MCP_LOCKDOWN_EXPLICIT = "true";
process.env.GH_AW_GITHUB_TOKEN = "ghp_token1";
process.env.GH_AW_GITHUB_MCP_SERVER_TOKEN = "ghp_token2";
process.env.CUSTOM_GITHUB_TOKEN = "ghp_token3";

validateLockdownRequirements(mockCore);

expect(mockCore.setFailed).not.toHaveBeenCalled();
expect(mockCore.info).toHaveBeenCalledWith("GH_AW_GITHUB_TOKEN configured: true");
expect(mockCore.info).toHaveBeenCalledWith("GH_AW_GITHUB_MCP_SERVER_TOKEN configured: true");
expect(mockCore.info).toHaveBeenCalledWith("Custom github-token configured: true");
expect(mockCore.info).toHaveBeenCalledWith("✓ Lockdown mode requirements validated: Custom GitHub token is configured");
});
});

describe("error messages", () => {
it("should include token guidance in lockdown error message", () => {
process.env.GITHUB_MCP_LOCKDOWN_EXPLICIT = "true";

expect(() => {
validateLockdownRequirements(mockCore);
}).toThrow("no custom GitHub token is configured");

const errorMsg = mockCore.setFailed.mock.calls[0][0];
expect(errorMsg).toContain("GH_AW_GITHUB_TOKEN (recommended)");
expect(errorMsg).toContain("GH_AW_GITHUB_MCP_SERVER_TOKEN (alternative)");
});

it("should include compile guidance in strict mode error message", () => {
process.env.GITHUB_REPOSITORY_VISIBILITY = "public";
process.env.GH_AW_COMPILED_STRICT = "false";

expect(() => {
validateLockdownRequirements(mockCore);
}).toThrow("not compiled with strict mode");

const errorMsg = mockCore.setFailed.mock.calls[0][0];
expect(errorMsg).toContain("gh aw compile --strict");
});

it("should include pwn request warning in pull_request_target error message", () => {
process.env.GITHUB_REPOSITORY_VISIBILITY = "public";
process.env.GH_AW_COMPILED_STRICT = "true";
process.env.GITHUB_EVENT_NAME = "pull_request_target";

expect(() => {
validateLockdownRequirements(mockCore);
}).toThrow("pwn request");
});
});
});
59 changes: 59 additions & 0 deletions actions/setup/js/validate_lockdown_requirements_templates.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// @ts-check

const { renderTemplate } = require("./messages_core.cjs");

const LOCKDOWN_TOKEN_ERROR_TEMPLATE = `Lockdown mode is enabled (lockdown: true) but no custom GitHub token is configured.

Please configure one of the following as a repository secret:
- GH_AW_GITHUB_TOKEN (recommended)
- GH_AW_GITHUB_MCP_SERVER_TOKEN (alternative)
- Custom github-token in your workflow frontmatter

See: {auth_docs_url}

To set a token:
gh aw secrets set GH_AW_GITHUB_TOKEN --value "YOUR_FINE_GRAINED_PAT"`;

const PUBLIC_STRICT_MODE_ERROR_TEMPLATE = `This workflow is running on a public repository but was not compiled with strict mode.

Public repository workflows must be compiled with strict mode enabled to meet
the security requirements for public exposure.

To fix this, recompile the workflow with strict mode:
{strict_compile_command}

See: {security_docs_url}`;

const PULL_REQUEST_TARGET_ERROR_TEMPLATE = `This workflow is triggered by the pull_request_target event on a public repository.

The pull_request_target event is not allowed on public repositories because it runs
workflows with access to repository secrets even when triggered from a fork, which
creates a significant security risk (known as a "pwn request").

To fix this, use the pull_request event instead, or migrate to a private repository.

See: {security_docs_url}`;

const TEMPLATE_CONTEXT = {
auth_docs_url: "https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/auth.mdx",
security_docs_url: "https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/security.mdx",
strict_compile_command: "gh aw compile --strict",
};

function renderLockdownTokenErrorMessage() {
return renderTemplate(LOCKDOWN_TOKEN_ERROR_TEMPLATE, TEMPLATE_CONTEXT);
}

function renderPublicStrictModeErrorMessage() {
return renderTemplate(PUBLIC_STRICT_MODE_ERROR_TEMPLATE, TEMPLATE_CONTEXT);
}

function renderPullRequestTargetErrorMessage() {
return renderTemplate(PULL_REQUEST_TARGET_ERROR_TEMPLATE, TEMPLATE_CONTEXT);
}

module.exports = {
renderLockdownTokenErrorMessage,
renderPublicStrictModeErrorMessage,
renderPullRequestTargetErrorMessage,
};
Loading