diff --git a/actions/setup/js/determine_automatic_lockdown.cjs b/actions/setup/js/determine_automatic_lockdown.cjs
index 0af3452b3d..be17474d77 100644
--- a/actions/setup/js/determine_automatic_lockdown.cjs
+++ b/actions/setup/js/determine_automatic_lockdown.cjs
@@ -87,18 +87,25 @@ async function determineAutomaticLockdown(github, context, core) {
// Write resolved guard policy values to the step summary
const autoLabel = isPrivate ? "automatic (private repo)" : "automatic (public repo)";
- await core.summary
- .addHeading("GitHub MCP Guard Policy", 3)
- .addTable([
- [
- { data: "Field", header: true },
- { data: "Value", header: true },
- { data: "Source", header: true },
- ],
- ["min-integrity", resolvedMinIntegrity, configuredMinIntegrity ? "workflow config" : autoLabel],
- ["repos", resolvedRepos, configuredRepos ? "workflow config" : autoLabel],
- ])
- .write();
+ const minIntegritySource = configuredMinIntegrity ? "workflow config" : autoLabel;
+ const reposSource = configuredRepos ? "workflow config" : autoLabel;
+
+ /**
+ * Escapes a value for safe embedding in a markdown table cell.
+ * Replaces HTML-special characters and pipe characters that would break the table.
+ * @param {string} value
+ * @returns {string}
+ */
+ const escapeCell = value => value.replace(/&/g, "&").replace(//g, ">").replace(/\|/g, "\\|").replace(/\n/g, " ");
+
+ const tableRows = [
+ "| Field | Value | Source |",
+ "|-------|-------|--------|",
+ `| min-integrity | ${escapeCell(resolvedMinIntegrity)} | ${escapeCell(minIntegritySource)} |`,
+ `| repos | ${escapeCell(resolvedRepos)} | ${escapeCell(reposSource)} |`,
+ ].join("\n");
+ const details = `\nGitHub MCP Guard Policy
\n\n${tableRows}\n\n \n`;
+ await core.summary.addRaw(details).write();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
core.error(`Failed to determine automatic guard policy: ${errorMessage}`);
diff --git a/actions/setup/js/determine_automatic_lockdown.test.cjs b/actions/setup/js/determine_automatic_lockdown.test.cjs
index e34e0f17ce..54c4caba78 100644
--- a/actions/setup/js/determine_automatic_lockdown.test.cjs
+++ b/actions/setup/js/determine_automatic_lockdown.test.cjs
@@ -33,8 +33,7 @@ describe("determine_automatic_lockdown", () => {
error: vi.fn(),
setOutput: vi.fn(),
summary: {
- addHeading: vi.fn().mockReturnThis(),
- addTable: vi.fn().mockReturnThis(),
+ addRaw: vi.fn().mockReturnThis(),
write: vi.fn().mockResolvedValue(undefined),
},
};
@@ -218,16 +217,21 @@ describe("determine_automatic_lockdown", () => {
await determineAutomaticLockdown(mockGithub, mockContext, mockCore);
- expect(mockCore.summary.addHeading).toHaveBeenCalledWith("GitHub MCP Guard Policy", 3);
- expect(mockCore.summary.addTable).toHaveBeenCalledWith([
- [
- { data: "Field", header: true },
- { data: "Value", header: true },
- { data: "Source", header: true },
- ],
- ["min-integrity", "approved", "automatic (public repo)"],
- ["repos", "all", "automatic (public repo)"],
- ]);
+ expect(mockCore.summary.addRaw).toHaveBeenCalledTimes(1);
+ const publicSummaryArg = mockCore.summary.addRaw.mock.calls[0][0];
+ expect(publicSummaryArg).toContain("");
+ expect(publicSummaryArg).toContain("GitHub MCP Guard Policy");
+ // Ensure we have a well-formed block with a and closing
+ expect(publicSummaryArg).toMatch(/[\s\S]*<\/details>/);
+ expect(publicSummaryArg).toMatch(/[\s\S]*GitHub MCP Guard Policy[\s\S]*<\/summary>/);
+ // Ensure the markdown table header and separator are present
+ expect(publicSummaryArg).toMatch(/\| *Field *\| *Value *\| *Source *\|/);
+ expect(publicSummaryArg).toMatch(/\|[- ]+\|[- ]+\|[- ]+\|/);
+ expect(publicSummaryArg).toContain("min-integrity");
+ expect(publicSummaryArg).toContain("approved");
+ expect(publicSummaryArg).toContain("automatic (public repo)");
+ expect(publicSummaryArg).toContain("repos");
+ expect(publicSummaryArg).toContain("all");
expect(mockCore.summary.write).toHaveBeenCalled();
});
@@ -244,15 +248,18 @@ describe("determine_automatic_lockdown", () => {
await determineAutomaticLockdown(mockGithub, mockContext, mockCore);
- expect(mockCore.summary.addTable).toHaveBeenCalledWith([
- [
- { data: "Field", header: true },
- { data: "Value", header: true },
- { data: "Source", header: true },
- ],
- ["min-integrity", "merged", "workflow config"],
- ["repos", "public", "workflow config"],
- ]);
+ expect(mockCore.summary.addRaw).toHaveBeenCalledTimes(1);
+ const configuredSummaryArg = mockCore.summary.addRaw.mock.calls[0][0];
+ // Ensure we have a well-formed block with closing
+ expect(configuredSummaryArg).toMatch(/[\s\S]*<\/details>/);
+ // Ensure the markdown table header and separator are present
+ expect(configuredSummaryArg).toMatch(/\| *Field *\| *Value *\| *Source *\|/);
+ expect(configuredSummaryArg).toMatch(/\|[- ]+\|[- ]+\|[- ]+\|/);
+ expect(configuredSummaryArg).toContain("min-integrity");
+ expect(configuredSummaryArg).toContain("merged");
+ expect(configuredSummaryArg).toContain("workflow config");
+ expect(configuredSummaryArg).toContain("repos");
+ expect(configuredSummaryArg).toContain("public");
expect(mockCore.summary.write).toHaveBeenCalled();
});
@@ -266,15 +273,18 @@ describe("determine_automatic_lockdown", () => {
await determineAutomaticLockdown(mockGithub, mockContext, mockCore);
- expect(mockCore.summary.addTable).toHaveBeenCalledWith([
- [
- { data: "Field", header: true },
- { data: "Value", header: true },
- { data: "Source", header: true },
- ],
- ["min-integrity", "none", "automatic (private repo)"],
- ["repos", "all", "automatic (private repo)"],
- ]);
+ expect(mockCore.summary.addRaw).toHaveBeenCalledTimes(1);
+ const privateSummaryArg = mockCore.summary.addRaw.mock.calls[0][0];
+ // Ensure we have a well-formed block with closing
+ expect(privateSummaryArg).toMatch(/[\s\S]*<\/details>/);
+ // Ensure the markdown table header and separator are present
+ expect(privateSummaryArg).toMatch(/\| *Field *\| *Value *\| *Source *\|/);
+ expect(privateSummaryArg).toMatch(/\|[- ]+\|[- ]+\|[- ]+\|/);
+ expect(privateSummaryArg).toContain("min-integrity");
+ expect(privateSummaryArg).toContain("none");
+ expect(privateSummaryArg).toContain("automatic (private repo)");
+ expect(privateSummaryArg).toContain("repos");
+ expect(privateSummaryArg).toContain("all");
expect(mockCore.summary.write).toHaveBeenCalled();
});
});