Skip to content

Commit 94f9b68

Browse files
authored
Suggest tools.github.mode: gh-proxy when api.github.com is firewall-blocked (#28293)
1 parent 3de7b98 commit 94f9b68

3 files changed

Lines changed: 105 additions & 30 deletions

File tree

actions/setup/js/firewall_blocked_domains.cjs

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
const fs = require("fs");
1212
const path = require("path");
1313
const { sanitizeDomainName } = require("./sanitize_content_core.cjs");
14+
const { renderTemplateFromFile } = require("./messages_core.cjs");
15+
const { renderMarkdownTemplate } = require("./render_template.cjs");
1416

1517
/**
1618
* Parses a single firewall log line
@@ -184,43 +186,50 @@ function getBlockedDomains(logsDir) {
184186

185187
/**
186188
* Generates HTML details/summary section for blocked domains wrapped in a GitHub warning alert
187-
* @param {string[]} blockedDomains - Array of blocked domain names
189+
* @param {string[]} blockedDomains - Array of blocked domain names (expected to be pre-sanitized via getBlockedDomains)
190+
* @param {string} [templatePath] - Optional path to template file (defaults to RUNNER_TEMP/gh-aw/prompts/firewall_blocked_domains.md)
188191
* @returns {string} GitHub warning alert with details section, or empty string if no blocked domains
189192
*/
190-
function generateBlockedDomainsSection(blockedDomains) {
193+
function generateBlockedDomainsSection(blockedDomains, templatePath) {
191194
if (!blockedDomains || blockedDomains.length === 0) {
192195
return "";
193196
}
194197

195198
const domainCount = blockedDomains.length;
196199
const domainWord = domainCount === 1 ? "domain" : "domains";
200+
const verb = domainCount === 1 ? "was" : "were";
197201

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

204-
// List domains as bullet points (within the alert)
205-
for (const domain of blockedDomains) {
206-
section += `> - \`${domain}\`\n`;
207-
}
205+
// Build YAML network.allowed list lines
206+
const yamlNetworkList = blockedDomains.map(domain => `> - "${domain}"\n`).join("");
207+
208+
const hasGitHubApiBlocked = blockedDomains.includes("api.github.com");
208209

209-
section += `>\n`;
210-
section += `> To allow these domains, add them to the \`network.allowed\` list in your workflow frontmatter:\n`;
211-
section += `>\n`;
212-
section += `> \`\`\`yaml\n`;
213-
section += `> network:\n`;
214-
section += `> allowed:\n`;
215-
section += `> - defaults\n`;
216-
for (const domain of blockedDomains) {
217-
section += `> - "${domain}"\n`;
210+
// Resolve template path: explicit > RUNNER_TEMP (production) > source tree (local dev/test)
211+
let resolvedTemplatePath = templatePath;
212+
if (!resolvedTemplatePath) {
213+
resolvedTemplatePath = process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts/firewall_blocked_domains.md` : path.join(__dirname, "../md/firewall_blocked_domains.md");
218214
}
219-
section += `> \`\`\`\n`;
220-
section += `>\n`;
221-
section += `> See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information.\n`;
222215

223-
return section;
216+
// First pass: substitute {key} placeholders.
217+
// has_github_api_blocked is set to the string "true" or "false" so that
218+
// renderMarkdownTemplate's isTruthy() correctly evaluates the
219+
// {{#if {has_github_api_blocked}}} conditional in the template
220+
// (isTruthy("false") === false per the template engine's explicit check).
221+
const rendered = renderTemplateFromFile(resolvedTemplatePath, {
222+
domain_count: domainCount,
223+
domain_word: domainWord,
224+
verb,
225+
domain_list: domainList,
226+
yaml_network_list: yamlNetworkList,
227+
has_github_api_blocked: hasGitHubApiBlocked ? "true" : "false",
228+
});
229+
230+
// Second pass: evaluate {{#if ...}} conditional blocks (e.g. the gh-proxy tip section)
231+
// Template starts without leading newlines; prepend separator expected by callers
232+
return "\n\n" + renderMarkdownTemplate(rendered);
224233
}
225234

226235
module.exports = {

actions/setup/js/firewall_blocked_domains.test.cjs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
22
import fs from "fs";
33
import path from "path";
44
import os from "os";
5+
import { fileURLToPath } from "url";
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
10+
// Path to the template file in the source tree (used in tests instead of RUNNER_TEMP)
11+
const TEMPLATE_PATH = path.join(__dirname, "../md/firewall_blocked_domains.md");
512

613
describe("firewall_blocked_domains.cjs", () => {
714
let parseFirewallLogLine;
@@ -306,7 +313,7 @@ describe("firewall_blocked_domains.cjs", () => {
306313
});
307314

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

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

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

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

332339
it("should use correct singular/plural form", () => {
333-
const singleResult = generateBlockedDomainsSection(["single.com"]);
340+
const singleResult = generateBlockedDomainsSection(["single.com"], TEMPLATE_PATH);
334341
expect(singleResult).toContain("1 domain");
335342
expect(singleResult).toContain("domain was blocked");
336343

337-
const multiResult = generateBlockedDomainsSection(["one.com", "two.com"]);
344+
const multiResult = generateBlockedDomainsSection(["one.com", "two.com"], TEMPLATE_PATH);
338345
expect(multiResult).toContain("2 domains");
339346
expect(multiResult).toContain("domains were blocked");
340347
});
341348

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

347354
it("should start with double newline and warning alert", () => {
348-
const result = generateBlockedDomainsSection(["example.com"]);
355+
const result = generateBlockedDomainsSection(["example.com"], TEMPLATE_PATH);
349356
expect(result).toMatch(/^\n\n> \[!WARNING\]/);
350357
});
358+
359+
it("should suggest gh-proxy mode when api.github.com is blocked", () => {
360+
const result = generateBlockedDomainsSection(["api.github.com"], TEMPLATE_PATH);
361+
362+
expect(result).toContain("> [!WARNING]");
363+
expect(result).toContain("> **⚠️ Firewall blocked 1 domain**");
364+
expect(result).toContain("> - `api.github.com`");
365+
expect(result).toContain("`tools.github.mode: gh-proxy`");
366+
expect(result).toContain("> ```yaml\n> tools:\n> github:\n> mode: gh-proxy\n> ```");
367+
expect(result).toContain("> See [GitHub Tools](https://github.github.com/gh-aw/reference/github-tools/) for more information on `gh-proxy` mode.");
368+
expect(result).toContain("> See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information.");
369+
});
370+
371+
it("should suggest gh-proxy mode when api.github.com is among other blocked domains", () => {
372+
const domains = ["api.github.com", "other.example.com"];
373+
const result = generateBlockedDomainsSection(domains, TEMPLATE_PATH);
374+
375+
expect(result).toContain("> [!WARNING]");
376+
expect(result).toContain("> **⚠️ Firewall blocked 2 domains**");
377+
expect(result).toContain("> - `api.github.com`");
378+
expect(result).toContain("> - `other.example.com`");
379+
expect(result).toContain("> ```yaml\n> tools:\n> github:\n> mode: gh-proxy\n> ```");
380+
expect(result).toContain("> See [GitHub Tools](https://github.github.com/gh-aw/reference/github-tools/) for more information on `gh-proxy` mode.");
381+
});
382+
383+
it("should not suggest gh-proxy mode when api.github.com is not blocked", () => {
384+
const result = generateBlockedDomainsSection(["other.example.com"], TEMPLATE_PATH);
385+
386+
expect(result).not.toContain("gh-proxy");
387+
expect(result).not.toContain("GitHub Tools");
388+
expect(result).toContain("> See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information.");
389+
});
351390
});
352391
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
> [!WARNING]
2+
> **⚠️ Firewall blocked {domain_count} {domain_word}**
3+
>
4+
> The following {domain_word} {verb} blocked by the firewall during workflow execution:
5+
>
6+
{domain_list}>
7+
{{#if {has_github_api_blocked}}}
8+
> **💡 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`:
9+
>
10+
> ```yaml
11+
> tools:
12+
> github:
13+
> mode: gh-proxy
14+
> ```
15+
>
16+
> See [GitHub Tools](https://github.github.com/gh-aw/reference/github-tools/) for more information on `gh-proxy` mode.
17+
>
18+
{{/if}}
19+
> To allow these domains, add them to the `network.allowed` list in your workflow frontmatter:
20+
>
21+
> ```yaml
22+
> network:
23+
> allowed:
24+
> - defaults
25+
{yaml_network_list}> ```
26+
>
27+
> See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information.

0 commit comments

Comments
 (0)