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
57 changes: 33 additions & 24 deletions actions/setup/js/firewall_blocked_domains.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
const fs = require("fs");
const path = require("path");
const { sanitizeDomainName } = require("./sanitize_content_core.cjs");
const { renderTemplateFromFile } = require("./messages_core.cjs");
const { renderMarkdownTemplate } = require("./render_template.cjs");

/**
* Parses a single firewall log line
Expand Down Expand Up @@ -184,43 +186,50 @@ function getBlockedDomains(logsDir) {

/**
* Generates HTML details/summary section for blocked domains wrapped in a GitHub warning alert
* @param {string[]} blockedDomains - Array of blocked domain names
* @param {string[]} blockedDomains - Array of blocked domain names (expected to be pre-sanitized via getBlockedDomains)
* @param {string} [templatePath] - Optional path to template file (defaults to RUNNER_TEMP/gh-aw/prompts/firewall_blocked_domains.md)
Comment on lines 188 to +190
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc still says this function generates an “HTML details/summary section”, but the implementation now renders a Markdown warning alert from a template. Update the description to match the actual output to avoid confusing future maintainers.

See below for a potential fix:

 * Renders a Markdown GitHub warning alert for blocked domains from a template
 * @param {string[]} blockedDomains - Array of blocked domain names (expected to be pre-sanitized via getBlockedDomains)
 * @param {string} [templatePath] - Optional path to template file (defaults to RUNNER_TEMP/gh-aw/prompts/firewall_blocked_domains.md)
 * @returns {string} Rendered Markdown warning alert, or empty string if no blocked domains

Copilot uses AI. Check for mistakes.
* @returns {string} GitHub warning alert with details section, or empty string if no blocked domains
*/
function generateBlockedDomainsSection(blockedDomains) {
function generateBlockedDomainsSection(blockedDomains, templatePath) {
if (!blockedDomains || blockedDomains.length === 0) {
return "";
}

const domainCount = blockedDomains.length;
const domainWord = domainCount === 1 ? "domain" : "domains";
const verb = domainCount === 1 ? "was" : "were";

let section = "\n\n> [!WARNING]\n";
section += `> **⚠️ Firewall blocked ${domainCount} ${domainWord}**\n`;
section += `>\n`;
section += `> The following ${domainWord} ${domainCount === 1 ? "was" : "were"} blocked by the firewall during workflow execution:\n`;
section += `>\n`;
// Build domain bullet list lines
const domainList = blockedDomains.map(domain => `> - \`${domain}\`\n`).join("");

// List domains as bullet points (within the alert)
for (const domain of blockedDomains) {
section += `> - \`${domain}\`\n`;
}
// Build YAML network.allowed list lines
const yamlNetworkList = blockedDomains.map(domain => `> - "${domain}"\n`).join("");

const hasGitHubApiBlocked = blockedDomains.includes("api.github.com");

section += `>\n`;
section += `> To allow these domains, add them to the \`network.allowed\` list in your workflow frontmatter:\n`;
section += `>\n`;
section += `> \`\`\`yaml\n`;
section += `> network:\n`;
section += `> allowed:\n`;
section += `> - defaults\n`;
for (const domain of blockedDomains) {
section += `> - "${domain}"\n`;
// Resolve template path: explicit > RUNNER_TEMP (production) > source tree (local dev/test)
let resolvedTemplatePath = templatePath;
if (!resolvedTemplatePath) {
resolvedTemplatePath = process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts/firewall_blocked_domains.md` : path.join(__dirname, "../md/firewall_blocked_domains.md");
Comment on lines +210 to +213
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Template path resolution here doesn’t honor the existing GH_AW_PROMPTS_DIR override that other prompt-rendering code uses (e.g., actions/setup/js/comment_memory.cjs:24 and actions/setup/js/gateway_difc_filtered.cjs:128). To keep behavior consistent (and make testing/overrides work uniformly), consider resolving promptsDir via process.env.GH_AW_PROMPTS_DIR || ${process.env.RUNNER_TEMP}/gh-aw/prompts`` when templatePath isn’t provided.

Suggested change
// Resolve template path: explicit > RUNNER_TEMP (production) > source tree (local dev/test)
let resolvedTemplatePath = templatePath;
if (!resolvedTemplatePath) {
resolvedTemplatePath = process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts/firewall_blocked_domains.md` : path.join(__dirname, "../md/firewall_blocked_domains.md");
// Resolve template path: explicit > GH_AW_PROMPTS_DIR override > RUNNER_TEMP (production) > source tree (local dev/test)
let resolvedTemplatePath = templatePath;
if (!resolvedTemplatePath) {
const promptsDir =
process.env.GH_AW_PROMPTS_DIR ||
(process.env.RUNNER_TEMP && `${process.env.RUNNER_TEMP}/gh-aw/prompts`);
resolvedTemplatePath = promptsDir
? path.join(promptsDir, "firewall_blocked_domains.md")
: path.join(__dirname, "../md/firewall_blocked_domains.md");

Copilot uses AI. Check for mistakes.
}
section += `> \`\`\`\n`;
section += `>\n`;
section += `> See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information.\n`;

return section;
// First pass: substitute {key} placeholders.
// has_github_api_blocked is set to the string "true" or "false" so that
// renderMarkdownTemplate's isTruthy() correctly evaluates the
// {{#if {has_github_api_blocked}}} conditional in the template
// (isTruthy("false") === false per the template engine's explicit check).
const rendered = renderTemplateFromFile(resolvedTemplatePath, {
domain_count: domainCount,
domain_word: domainWord,
verb,
domain_list: domainList,
yaml_network_list: yamlNetworkList,
has_github_api_blocked: hasGitHubApiBlocked ? "true" : "false",
});

// Second pass: evaluate {{#if ...}} conditional blocks (e.g. the gh-proxy tip section)
// Template starts without leading newlines; prepend separator expected by callers
return "\n\n" + renderMarkdownTemplate(rendered);
}

module.exports = {
Expand Down
51 changes: 45 additions & 6 deletions actions/setup/js/firewall_blocked_domains.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
import fs from "fs";
import path from "path";
import os from "os";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Path to the template file in the source tree (used in tests instead of RUNNER_TEMP)
const TEMPLATE_PATH = path.join(__dirname, "../md/firewall_blocked_domains.md");

describe("firewall_blocked_domains.cjs", () => {
let parseFirewallLogLine;
Expand Down Expand Up @@ -306,7 +313,7 @@ describe("firewall_blocked_domains.cjs", () => {
});

it("should generate warning section for single blocked domain", () => {
const result = generateBlockedDomainsSection(["blocked.example.com"]);
const result = generateBlockedDomainsSection(["blocked.example.com"], TEMPLATE_PATH);

expect(result).toContain("> [!WARNING]");
expect(result).toContain("> **⚠️ Firewall blocked 1 domain**");
Expand All @@ -318,7 +325,7 @@ describe("firewall_blocked_domains.cjs", () => {

it("should generate warning section for multiple blocked domains", () => {
const domains = ["alpha.example.com", "beta.example.com", "gamma.example.com"];
const result = generateBlockedDomainsSection(domains);
const result = generateBlockedDomainsSection(domains, TEMPLATE_PATH);

expect(result).toContain("> [!WARNING]");
expect(result).toContain("> **⚠️ Firewall blocked 3 domains**");
Expand All @@ -330,23 +337,55 @@ describe("firewall_blocked_domains.cjs", () => {
});

it("should use correct singular/plural form", () => {
const singleResult = generateBlockedDomainsSection(["single.com"]);
const singleResult = generateBlockedDomainsSection(["single.com"], TEMPLATE_PATH);
expect(singleResult).toContain("1 domain");
expect(singleResult).toContain("domain was blocked");

const multiResult = generateBlockedDomainsSection(["one.com", "two.com"]);
const multiResult = generateBlockedDomainsSection(["one.com", "two.com"], TEMPLATE_PATH);
expect(multiResult).toContain("2 domains");
expect(multiResult).toContain("domains were blocked");
});

it("should format domains with backticks", () => {
const result = generateBlockedDomainsSection(["example.com"]);
const result = generateBlockedDomainsSection(["example.com"], TEMPLATE_PATH);
expect(result).toMatch(/> - `example\.com`/);
});

it("should start with double newline and warning alert", () => {
const result = generateBlockedDomainsSection(["example.com"]);
const result = generateBlockedDomainsSection(["example.com"], TEMPLATE_PATH);
expect(result).toMatch(/^\n\n> \[!WARNING\]/);
});

it("should suggest gh-proxy mode when api.github.com is blocked", () => {
const result = generateBlockedDomainsSection(["api.github.com"], TEMPLATE_PATH);

expect(result).toContain("> [!WARNING]");
expect(result).toContain("> **⚠️ Firewall blocked 1 domain**");
expect(result).toContain("> - `api.github.com`");
expect(result).toContain("`tools.github.mode: gh-proxy`");
expect(result).toContain("> ```yaml\n> tools:\n> github:\n> mode: gh-proxy\n> ```");
expect(result).toContain("> See [GitHub Tools](https://github.github.com/gh-aw/reference/github-tools/) for more information on `gh-proxy` mode.");
expect(result).toContain("> See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information.");
});

it("should suggest gh-proxy mode when api.github.com is among other blocked domains", () => {
const domains = ["api.github.com", "other.example.com"];
const result = generateBlockedDomainsSection(domains, TEMPLATE_PATH);

expect(result).toContain("> [!WARNING]");
expect(result).toContain("> **⚠️ Firewall blocked 2 domains**");
expect(result).toContain("> - `api.github.com`");
expect(result).toContain("> - `other.example.com`");
expect(result).toContain("> ```yaml\n> tools:\n> github:\n> mode: gh-proxy\n> ```");
expect(result).toContain("> See [GitHub Tools](https://github.github.com/gh-aw/reference/github-tools/) for more information on `gh-proxy` mode.");
});

it("should not suggest gh-proxy mode when api.github.com is not blocked", () => {
const result = generateBlockedDomainsSection(["other.example.com"], TEMPLATE_PATH);

expect(result).not.toContain("gh-proxy");
expect(result).not.toContain("GitHub Tools");
expect(result).toContain("> See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information.");
});
});
});
27 changes: 27 additions & 0 deletions actions/setup/md/firewall_blocked_domains.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
> [!WARNING]
> **⚠️ Firewall blocked {domain_count} {domain_word}**
>
> The following {domain_word} {verb} blocked by the firewall during workflow execution:
>
{domain_list}>
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The {domain_list} placeholder line ends with an extra > ({domain_list}>), which will render an unintended standalone blockquote marker after the bullet list. Remove the trailing > so the rendered warning block doesn’t include a spurious > line.

Suggested change
{domain_list}>
{domain_list}

Copilot uses AI. Check for mistakes.
{{#if {has_github_api_blocked}}}
> **💡 Tip:** `api.github.com` is blocked because GitHub API access uses the built-in GitHub tools by default. Instead of adding `api.github.com` to `network.allowed`, use `tools.github.mode: gh-proxy` for direct pre-authenticated GitHub CLI access without requiring network access to `api.github.com`:
>
> ```yaml
> tools:
> github:
> mode: gh-proxy
> ```
>
> See [GitHub Tools](https://github.github.com/gh-aw/reference/github-tools/) for more information on `gh-proxy` mode.
>
{{/if}}
> To allow these domains, add them to the `network.allowed` list in your workflow frontmatter:
>
> ```yaml
> network:
> allowed:
> - defaults
{yaml_network_list}> ```
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The YAML list placeholder and the closing code fence are currently on the same template line ({yaml_network_list}> ````). This is fragile and can produce malformed Markdown depending on whether the placeholder expansion ends with a newline. Put {yaml_network_list}on its own line and keep the closing> ``` on the next line (and remove any stray>` after the placeholder) so the fenced block is always well-formed.

Suggested change
{yaml_network_list}> ```
{yaml_network_list}
> ```

Copilot uses AI. Check for mistakes.
>
> See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information.
Loading