From fa8b5bdf5430aab08974b0d152f663c36fa2995b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 06:38:12 +0000 Subject: [PATCH 1/8] chore: start threat detection engine error template improvements Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/daily-byok-ollama-test.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/daily-byok-ollama-test.lock.yml b/.github/workflows/daily-byok-ollama-test.lock.yml index 857f15a3b9a..38ffad98ff2 100644 --- a/.github/workflows/daily-byok-ollama-test.lock.yml +++ b/.github/workflows/daily-byok-ollama-test.lock.yml @@ -1451,7 +1451,7 @@ jobs: printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.43/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"targets\":{\"copilot\":{\"host\":\"host.docker.internal:11434\"}}},\"container\":{\"imageTag\":\"0.27.43,squid=sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d,agent=sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6,api-proxy=sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1,cli-proxy=sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.43/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"defaultAiCreditsPricing\":{\"input\":0.000001,\"output\":0.000001},\"targets\":{\"copilot\":{\"host\":\"host.docker.internal:11434\"}},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.43,squid=sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d,agent=sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6,api-proxy=sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1,cli-proxy=sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST="" From cc448118f87f9ad7a74c151aa69fff52d5e7d18b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 06:43:58 +0000 Subject: [PATCH 2/8] improve: threat detection engine error template with progressive disclosure Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/generate_footer.cjs | 2 +- actions/setup/js/generate_footer.test.cjs | 4 ++-- actions/setup/js/messages.test.cjs | 2 +- actions/setup/js/messages_run_status.cjs | 2 +- actions/setup/md/detection_runs_comment.md | 7 +++++++ 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/actions/setup/js/generate_footer.cjs b/actions/setup/js/generate_footer.cjs index 77e5af6340f..e7adc3a020d 100644 --- a/actions/setup/js/generate_footer.cjs +++ b/actions/setup/js/generate_footer.cjs @@ -133,7 +133,7 @@ function getExpiredEntityCautionAlert(workflowName, runUrl) { const detectionReason = process.env.GH_AW_DETECTION_REASON || ""; const reasonText = getDetectionReasonText(detectionReason); if (isToolingFailureReason(detectionReason)) { - return `> [!WARNING]\n> threat detection engine error\n> The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.\n> ${getThreatDetectedMarker(detectionReason)}\n>\n>
\n> Details\n>\n> ${reasonText}\n>\n> Review the [workflow run logs](${runUrl}) for details.\n>
`; + return `> [!WARNING]\n> **Threat Detection Engine Failure** — The analysis engine could not complete. This is a tooling failure, not a security finding.\n> ${getThreatDetectedMarker(detectionReason)}\n>\n>
\n> What happened\n>\n> ${reasonText}\n>\n> Review the [workflow run logs](${runUrl}) for details.\n>
`; } return `> [!CAUTION]\n> agentic threat detected\n> Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation.\n> ${getThreatDetectedMarker(detectionReason)}\n>\n>
\n> Details\n>\n> ${reasonText}\n>\n> Review the [workflow run logs](${runUrl}) for details.\n>
`; } diff --git a/actions/setup/js/generate_footer.test.cjs b/actions/setup/js/generate_footer.test.cjs index e150ea86133..a47d22bed63 100644 --- a/actions/setup/js/generate_footer.test.cjs +++ b/actions/setup/js/generate_footer.test.cjs @@ -486,7 +486,7 @@ describe("generate_footer.cjs", () => { const result = getExpiredEntityCautionAlert("Test Workflow", "https://github.com/test/repo/actions/runs/123"); expect(result).toContain("> [!WARNING]"); - expect(result).toContain("threat detection engine error"); + expect(result).toContain("Threat Detection Engine Failure"); expect(result).toContain(""); expect(result).not.toContain("> [!CAUTION]"); expect(result).not.toContain("agentic threat detected"); @@ -500,7 +500,7 @@ describe("generate_footer.cjs", () => { const result = getExpiredEntityCautionAlert("Test Workflow", "https://github.com/test/repo/actions/runs/123"); expect(result).toContain("> [!WARNING]"); - expect(result).toContain("threat detection engine error"); + expect(result).toContain("Threat Detection Engine Failure"); expect(result).not.toContain("> [!CAUTION]"); expect(result).not.toContain("agentic threat detected"); expect(result).toContain("could not be parsed"); diff --git a/actions/setup/js/messages.test.cjs b/actions/setup/js/messages.test.cjs index 2f0e8283df7..bcc481b79b6 100644 --- a/actions/setup/js/messages.test.cjs +++ b/actions/setup/js/messages.test.cjs @@ -1411,7 +1411,7 @@ describe("messages.cjs", () => { const result = getDetectionCautionAlert("Test Workflow", "https://github.com/test/repo/actions/runs/123"); expect(result).toContain("> [!WARNING]"); - expect(result).toContain("threat detection engine error"); + expect(result).toContain("Threat Detection Engine Failure"); expect(result).not.toContain("> [!CAUTION]"); expect(result).not.toContain("agentic threat detected"); expect(result).toContain("threat detection engine failed"); diff --git a/actions/setup/js/messages_run_status.cjs b/actions/setup/js/messages_run_status.cjs index 445e7cfc925..82935f240e0 100644 --- a/actions/setup/js/messages_run_status.cjs +++ b/actions/setup/js/messages_run_status.cjs @@ -154,7 +154,7 @@ function getDetectionWarningMessage(ctx) { const reasonText = getDetectionReasonText(ctx.reason); const isEngineError = isToolingFailureReason(ctx.reason); if (isEngineError) { - const defaultTemplate = `> [!WARNING]\n> threat detection engine error\n> The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.\n> ${getThreatDetectedMarkerTemplate()}\n>\n>
\n> Details\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n>
`; + const defaultTemplate = `> [!WARNING]\n> **Threat Detection Engine Failure** — The analysis engine could not complete. This is a tooling failure, not a security finding.\n> ${getThreatDetectedMarkerTemplate()}\n>\n>
\n> What happened\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n>
`; return renderConfiguredMessage("detectionEngineError", defaultTemplate, { ...ctx, reasonText, threatKinds: normalizeThreatKinds(ctx.reason) }); } const defaultTemplate = `> [!CAUTION]\n> agentic threat detected\n> Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation.\n> ${getThreatDetectedMarkerTemplate()}\n>\n>
\n> Details\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n>
`; diff --git a/actions/setup/md/detection_runs_comment.md b/actions/setup/md/detection_runs_comment.md index 68cf428f937..528339e2cf2 100644 --- a/actions/setup/md/detection_runs_comment.md +++ b/actions/setup/md/detection_runs_comment.md @@ -1,7 +1,14 @@ ### {workflow_name} +The threat detection engine could not complete analysis for this run. This is a tooling failure, not a security finding. + +
+Run details + | Field | Value | |---|---| | Conclusion | `{conclusion}` | | Reason | `{reason}` | | Run | [View run]({run_url}) | + +
From 55a994480aecd0524febd34de03120a04b0bcd3d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 07:06:39 +0000 Subject: [PATCH 3/8] Apply remaining changes Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/generate_footer.cjs | 56 +++++++++++++++++-- actions/setup/js/messages_run_status.cjs | 14 +++-- actions/setup/md/threat_detection_caution.md | 12 ++++ .../setup/md/threat_detection_engine_error.md | 11 ++++ 4 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 actions/setup/md/threat_detection_caution.md create mode 100644 actions/setup/md/threat_detection_engine_error.md diff --git a/actions/setup/js/generate_footer.cjs b/actions/setup/js/generate_footer.cjs index e7adc3a020d..b4ed3fd45ac 100644 --- a/actions/setup/js/generate_footer.cjs +++ b/actions/setup/js/generate_footer.cjs @@ -1,8 +1,50 @@ // @ts-check /// +const fs = require("fs"); +const path = require("path"); const { getDetectionReasonText, getThreatDetectedMarker, isToolingFailureReason } = require("./threat_detection_warning.cjs"); +/** + * Resolves the path to a named template file in the prompts directory. + * Uses GH_AW_PROMPTS_DIR if set, otherwise falls back to the source md/ directory. + * Intentionally does not import from messages_core.cjs — see getExpiredEntityCautionAlert note. + * @param {string} filename - Template filename (e.g. "threat_detection_engine_error.md") + * @returns {string} Absolute path to the template file + */ +function resolveLocalTemplatePath(filename) { + const promptsDir = process.env.GH_AW_PROMPTS_DIR; + if (promptsDir) return `${promptsDir}/${filename}`; + return path.join(__dirname, "../md", filename); +} + +/** + * Renders a template string by replacing {key} placeholders with context values. + * Intentionally does not import renderTemplate from messages_core.cjs — see getExpiredEntityCautionAlert note. + * @param {string} template - Template string with {key} placeholders + * @param {Record} context - Key-value pairs for substitution + * @returns {string} Rendered string + */ +function renderLocalTemplate(template, context) { + return template.replace(/\{(\w+)\}/g, (match, key) => { + const value = context[key]; + return value !== undefined && value !== null ? String(value) : match; + }); +} + +/** + * Reads a template file and renders it with the given context. + * Intentionally does not import renderTemplateFromFile from messages_core.cjs — see getExpiredEntityCautionAlert note. + * @param {string} filename - Template filename (e.g. "threat_detection_engine_error.md") + * @param {Record} context - Key-value pairs for substitution + * @returns {string} Rendered template content (trailing newline trimmed) + */ +function renderLocalTemplateFile(filename, context) { + const filePath = resolveLocalTemplatePath(filename); + const template = fs.readFileSync(filePath, "utf8"); + return renderLocalTemplate(template.trimEnd(), context); +} + /** * Generates a standalone workflow-id XML comment marker for searchability. * This marker enables finding all items (issues, discussions, PRs, comments) @@ -113,10 +155,11 @@ function generateXMLMarker(workflowName, runUrl) { * admonition is used so reviewers can distinguish "detection engine crashed" from "detection * engine found something". Actual threat findings (threat_detected) keep [!CAUTION]. * - * Note: This function is intentionally kept inline (not imported from messages_footer.cjs) - * because importing messages_footer.cjs here would cause the bundler to inline - * messages_core.cjs which contains 'GH_AW_SAFE_OUTPUT_MESSAGES:' in a warning message, - * breaking tests that check for env var declarations. + * Note: Template rendering uses local helpers (resolveLocalTemplatePath / renderLocalTemplateFile) + * instead of importing from messages_core.cjs, because importing messages_core.cjs (directly + * or transitively via messages_footer.cjs) would cause the bundler to inline + * 'GH_AW_SAFE_OUTPUT_MESSAGES:' in a warning message, breaking tests that check for env var + * declarations. * * Warning reason text and threat marker formatting are centralized in * threat_detection_warning.cjs to keep warning-mode messaging consistent. @@ -132,10 +175,11 @@ function getExpiredEntityCautionAlert(workflowName, runUrl) { } const detectionReason = process.env.GH_AW_DETECTION_REASON || ""; const reasonText = getDetectionReasonText(detectionReason); + const context = { threat_detected_marker: getThreatDetectedMarker(detectionReason), reason_text: reasonText, run_url: runUrl }; if (isToolingFailureReason(detectionReason)) { - return `> [!WARNING]\n> **Threat Detection Engine Failure** — The analysis engine could not complete. This is a tooling failure, not a security finding.\n> ${getThreatDetectedMarker(detectionReason)}\n>\n>
\n> What happened\n>\n> ${reasonText}\n>\n> Review the [workflow run logs](${runUrl}) for details.\n>
`; + return renderLocalTemplateFile("threat_detection_engine_error.md", context); } - return `> [!CAUTION]\n> agentic threat detected\n> Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation.\n> ${getThreatDetectedMarker(detectionReason)}\n>\n>
\n> Details\n>\n> ${reasonText}\n>\n> Review the [workflow run logs](${runUrl}) for details.\n>
`; + return renderLocalTemplateFile("threat_detection_caution.md", context); } /** diff --git a/actions/setup/js/messages_run_status.cjs b/actions/setup/js/messages_run_status.cjs index 82935f240e0..66056b73724 100644 --- a/actions/setup/js/messages_run_status.cjs +++ b/actions/setup/js/messages_run_status.cjs @@ -7,7 +7,7 @@ * for workflow execution notifications. */ -const { getMessages, renderTemplate, toSnakeCase } = require("./messages_core.cjs"); +const { getMessages, renderTemplate, renderTemplateFromFile, toSnakeCase, getPromptPath } = require("./messages_core.cjs"); const { getDetectionReasonText, getThreatDetectedMarkerTemplate, normalizeThreatKinds, isToolingFailureReason } = require("./threat_detection_warning.cjs"); /** @@ -153,12 +153,14 @@ function getCommitPushedMessage(ctx) { function getDetectionWarningMessage(ctx) { const reasonText = getDetectionReasonText(ctx.reason); const isEngineError = isToolingFailureReason(ctx.reason); - if (isEngineError) { - const defaultTemplate = `> [!WARNING]\n> **Threat Detection Engine Failure** — The analysis engine could not complete. This is a tooling failure, not a security finding.\n> ${getThreatDetectedMarkerTemplate()}\n>\n>
\n> What happened\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n>
`; - return renderConfiguredMessage("detectionEngineError", defaultTemplate, { ...ctx, reasonText, threatKinds: normalizeThreatKinds(ctx.reason) }); + const templateFile = isEngineError ? "threat_detection_engine_error.md" : "threat_detection_caution.md"; + const messageKey = isEngineError ? "detectionEngineError" : "detectionWarning"; + const messages = getMessages(); + const configTemplate = messages?.[messageKey]; + if (configTemplate) { + return renderTemplate(configTemplate, toSnakeCase({ ...ctx, reasonText, threat_detected_marker: getThreatDetectedMarkerTemplate(), threatKinds: normalizeThreatKinds(ctx.reason) })); } - const defaultTemplate = `> [!CAUTION]\n> agentic threat detected\n> Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation.\n> ${getThreatDetectedMarkerTemplate()}\n>\n>
\n> Details\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n>
`; - return renderConfiguredMessage("detectionWarning", defaultTemplate, { ...ctx, reasonText, threatKinds: normalizeThreatKinds(ctx.reason) }); + return renderTemplateFromFile(getPromptPath(templateFile), toSnakeCase({ ...ctx, reasonText, threat_detected_marker: getThreatDetectedMarkerTemplate(), threatKinds: normalizeThreatKinds(ctx.reason) })).trimEnd(); } module.exports = { diff --git a/actions/setup/md/threat_detection_caution.md b/actions/setup/md/threat_detection_caution.md new file mode 100644 index 00000000000..b3577a7703b --- /dev/null +++ b/actions/setup/md/threat_detection_caution.md @@ -0,0 +1,12 @@ +> [!CAUTION] +> agentic threat detected +> Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation. +> {threat_detected_marker} +> +>
+> Details +> +> {reason_text} +> +> Review the [workflow run logs]({run_url}) for details. +>
diff --git a/actions/setup/md/threat_detection_engine_error.md b/actions/setup/md/threat_detection_engine_error.md new file mode 100644 index 00000000000..d910df5df87 --- /dev/null +++ b/actions/setup/md/threat_detection_engine_error.md @@ -0,0 +1,11 @@ +> [!WARNING] +> **Threat Detection Engine Failure** — The analysis engine could not complete. This is a tooling failure, not a security finding. +> {threat_detected_marker} +> +>
+> What happened +> +> {reason_text} +> +> Review the [workflow run logs]({run_url}) for details. +>
From bda0e8dc89a20d78fe063e0f3c7806bcf2d5824c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 07:59:24 +0000 Subject: [PATCH 4/8] refactor: extract template helpers to prompt_file_helpers.cjs and use from generate_footer.cjs - Create prompt_file_helpers.cjs with getPromptPath (with ../md fallback), renderTemplate, and renderTemplateFromFile - Update messages_core.cjs to import and re-export these from prompt_file_helpers.cjs instead of defining them inline - Update generate_footer.cjs to remove the three inline local helpers (resolveLocalTemplatePath, renderLocalTemplate, renderLocalTemplateFile) and import from prompt_file_helpers.cjs - Update generate_footer.test.cjs to set GH_AW_PROMPTS_DIR to the source md/ directory (consistent with other test files) so template rendering tests work when RUNNER_TEMP is set but runtime prompts aren't populated - Update messages_core.test.cjs: the getPromptPath fallback now returns a ../md path instead of throwing Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/generate_footer.cjs | 56 +++------------------ actions/setup/js/generate_footer.test.cjs | 18 ++++++- actions/setup/js/messages_core.cjs | 52 +------------------- actions/setup/js/messages_core.test.cjs | 5 +- actions/setup/js/prompt_file_helpers.cjs | 59 +++++++++++++++++++++++ 5 files changed, 87 insertions(+), 103 deletions(-) create mode 100644 actions/setup/js/prompt_file_helpers.cjs diff --git a/actions/setup/js/generate_footer.cjs b/actions/setup/js/generate_footer.cjs index b4ed3fd45ac..11657ed1cff 100644 --- a/actions/setup/js/generate_footer.cjs +++ b/actions/setup/js/generate_footer.cjs @@ -1,49 +1,8 @@ // @ts-check /// -const fs = require("fs"); -const path = require("path"); const { getDetectionReasonText, getThreatDetectedMarker, isToolingFailureReason } = require("./threat_detection_warning.cjs"); - -/** - * Resolves the path to a named template file in the prompts directory. - * Uses GH_AW_PROMPTS_DIR if set, otherwise falls back to the source md/ directory. - * Intentionally does not import from messages_core.cjs — see getExpiredEntityCautionAlert note. - * @param {string} filename - Template filename (e.g. "threat_detection_engine_error.md") - * @returns {string} Absolute path to the template file - */ -function resolveLocalTemplatePath(filename) { - const promptsDir = process.env.GH_AW_PROMPTS_DIR; - if (promptsDir) return `${promptsDir}/${filename}`; - return path.join(__dirname, "../md", filename); -} - -/** - * Renders a template string by replacing {key} placeholders with context values. - * Intentionally does not import renderTemplate from messages_core.cjs — see getExpiredEntityCautionAlert note. - * @param {string} template - Template string with {key} placeholders - * @param {Record} context - Key-value pairs for substitution - * @returns {string} Rendered string - */ -function renderLocalTemplate(template, context) { - return template.replace(/\{(\w+)\}/g, (match, key) => { - const value = context[key]; - return value !== undefined && value !== null ? String(value) : match; - }); -} - -/** - * Reads a template file and renders it with the given context. - * Intentionally does not import renderTemplateFromFile from messages_core.cjs — see getExpiredEntityCautionAlert note. - * @param {string} filename - Template filename (e.g. "threat_detection_engine_error.md") - * @param {Record} context - Key-value pairs for substitution - * @returns {string} Rendered template content (trailing newline trimmed) - */ -function renderLocalTemplateFile(filename, context) { - const filePath = resolveLocalTemplatePath(filename); - const template = fs.readFileSync(filePath, "utf8"); - return renderLocalTemplate(template.trimEnd(), context); -} +const { getPromptPath, renderTemplateFromFile } = require("./prompt_file_helpers.cjs"); /** * Generates a standalone workflow-id XML comment marker for searchability. @@ -155,11 +114,10 @@ function generateXMLMarker(workflowName, runUrl) { * admonition is used so reviewers can distinguish "detection engine crashed" from "detection * engine found something". Actual threat findings (threat_detected) keep [!CAUTION]. * - * Note: Template rendering uses local helpers (resolveLocalTemplatePath / renderLocalTemplateFile) - * instead of importing from messages_core.cjs, because importing messages_core.cjs (directly - * or transitively via messages_footer.cjs) would cause the bundler to inline - * 'GH_AW_SAFE_OUTPUT_MESSAGES:' in a warning message, breaking tests that check for env var - * declarations. + * Note: Template rendering uses prompt_file_helpers.cjs instead of messages_core.cjs, + * because importing messages_core.cjs (directly or transitively via messages_footer.cjs) + * would cause the bundler to inline 'GH_AW_SAFE_OUTPUT_MESSAGES:' in a warning message, + * breaking tests that check for env var declarations. * * Warning reason text and threat marker formatting are centralized in * threat_detection_warning.cjs to keep warning-mode messaging consistent. @@ -177,9 +135,9 @@ function getExpiredEntityCautionAlert(workflowName, runUrl) { const reasonText = getDetectionReasonText(detectionReason); const context = { threat_detected_marker: getThreatDetectedMarker(detectionReason), reason_text: reasonText, run_url: runUrl }; if (isToolingFailureReason(detectionReason)) { - return renderLocalTemplateFile("threat_detection_engine_error.md", context); + return renderTemplateFromFile(getPromptPath("threat_detection_engine_error.md"), context).trimEnd(); } - return renderLocalTemplateFile("threat_detection_caution.md", context); + return renderTemplateFromFile(getPromptPath("threat_detection_caution.md"), context).trimEnd(); } /** diff --git a/actions/setup/js/generate_footer.test.cjs b/actions/setup/js/generate_footer.test.cjs index a47d22bed63..129706c2cbe 100644 --- a/actions/setup/js/generate_footer.test.cjs +++ b/actions/setup/js/generate_footer.test.cjs @@ -1,4 +1,6 @@ -import { describe, it, expect, beforeEach, vi } from "vitest"; +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { fileURLToPath } from "url"; +import path from "path"; // Mock the global objects that GitHub Actions provides const mockCore = { @@ -337,8 +339,14 @@ describe("generate_footer.cjs", () => { describe("generateExpiredEntityFooter", () => { let generateExpiredEntityFooter; let getExpiredEntityCautionAlert; + let originalPromptsDir; beforeEach(async () => { + // Point GH_AW_PROMPTS_DIR to the source md/ directory so getPromptPath() + // resolves to the local templates. This is needed because RUNNER_TEMP may be + // set but the runtime prompts directory is not populated in the test environment. + originalPromptsDir = process.env.GH_AW_PROMPTS_DIR; + process.env.GH_AW_PROMPTS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "../md"); // Reset modules and import fresh vi.resetModules(); const freshModule = await import("./generate_footer.cjs"); @@ -346,6 +354,14 @@ describe("generate_footer.cjs", () => { getExpiredEntityCautionAlert = freshModule.getExpiredEntityCautionAlert; }); + afterEach(() => { + if (originalPromptsDir !== undefined) { + process.env.GH_AW_PROMPTS_DIR = originalPromptsDir; + } else { + delete process.env.GH_AW_PROMPTS_DIR; + } + }); + it("should generate footer with 'Closed by' wording and workflow link", () => { const result = generateExpiredEntityFooter("Test Workflow", "https://github.com/test/repo/actions/runs/123", "test-workflow"); diff --git a/actions/setup/js/messages_core.cjs b/actions/setup/js/messages_core.cjs index 1203f513713..3b797957765 100644 --- a/actions/setup/js/messages_core.cjs +++ b/actions/setup/js/messages_core.cjs @@ -26,7 +26,7 @@ */ const { getErrorMessage } = require("./error_helpers.cjs"); -const fs = require("fs"); +const { getPromptPath, renderTemplate, renderTemplateFromFile } = require("./prompt_file_helpers.cjs"); /** * @typedef {Object} SafeOutputMessages @@ -72,23 +72,6 @@ function getMessages() { } } -/** - * Replace placeholders in a template string with values from context. - * Supports {key} syntax for placeholder replacement. - * @param {string} template - Template string with {key} placeholders - * @param {Record} context - Key-value pairs for replacement - * @returns {string} Template with placeholders replaced - */ -function renderTemplate(template, context) { - return template.replace(/\{(\w+)\}/g, (match, key) => { - const value = context[key]; - if (value === undefined || value === null) { - return match; - } - return String(value); - }); -} - /** * Render a comma-separated files list into markdown inline code spans. * - Trims each entry and drops empty segments @@ -115,39 +98,6 @@ function renderFilesList(value) { .join(", "); } -/** - * Resolve the absolute path to a prompt template file. - * Prefers GH_AW_PROMPTS_DIR when set, otherwise falls back to - * ${RUNNER_TEMP}/gh-aw/prompts (the runtime location used in production). - * Throws if neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set. - * @param {string} name - Template filename (e.g. "agent_timeout.md") - * @returns {string} Absolute path to the prompt template file - */ -function getPromptPath(name) { - const promptsDir = process.env.GH_AW_PROMPTS_DIR || (process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts` : null); - if (!promptsDir) { - throw new Error("Cannot resolve prompt path: neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set"); - } - return `${promptsDir}/${name}`; -} - -/** - * Read a template file and render it with the given context. - * Combines file loading and template rendering into a single helper. - * @param {string} templatePath - Absolute path to the template file - * @param {Record} context - Key-value pairs for replacement - * @returns {string} Rendered template with placeholders replaced - */ -function renderTemplateFromFile(templatePath, context) { - let template; - try { - template = fs.readFileSync(templatePath, "utf8"); - } catch (err) { - throw new Error(`Failed to read file ${templatePath}: ${String(err)}`, { cause: err }); - } - return renderTemplate(template, context); -} - /** * Convert context object keys to snake_case for template rendering. * Also keeps original camelCase keys for backwards compatibility. diff --git a/actions/setup/js/messages_core.test.cjs b/actions/setup/js/messages_core.test.cjs index 683ec3f53b6..9c5895d7747 100644 --- a/actions/setup/js/messages_core.test.cjs +++ b/actions/setup/js/messages_core.test.cjs @@ -269,9 +269,10 @@ describe("messages_core.cjs", () => { expect(getPromptPath("bar.md")).toBe("/tmp/runner/gh-aw/prompts/bar.md"); }); - it("should throw when neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set", async () => { + it("should fall back to md/ directory when neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set", async () => { const { getPromptPath } = await import("./messages_core.cjs?" + Date.now()); - expect(() => getPromptPath("any.md")).toThrow("Cannot resolve prompt path: neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set"); + const result = getPromptPath("any.md"); + expect(result).toMatch(/actions\/setup\/md\/any\.md$/); }); }); diff --git a/actions/setup/js/prompt_file_helpers.cjs b/actions/setup/js/prompt_file_helpers.cjs new file mode 100644 index 00000000000..20493f8d053 --- /dev/null +++ b/actions/setup/js/prompt_file_helpers.cjs @@ -0,0 +1,59 @@ +// @ts-check +/// + +const fs = require("fs"); +const path = require("path"); + +/** + * Resolve the absolute path to a prompt template file. + * Prefers GH_AW_PROMPTS_DIR when set, otherwise falls back to + * ${RUNNER_TEMP}/gh-aw/prompts (the runtime location used in production). + * When neither is set, falls back to the source md/ directory relative to + * this file (used in local development and unit tests). + * @param {string} name - Template filename (e.g. "agent_timeout.md") + * @returns {string} Absolute path to the prompt template file + */ +function getPromptPath(name) { + const promptsDir = process.env.GH_AW_PROMPTS_DIR || (process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts` : null) || path.join(__dirname, "../md"); + return `${promptsDir}/${name}`; +} + +/** + * Replace placeholders in a template string with values from context. + * Supports {key} syntax for placeholder replacement. + * @param {string} template - Template string with {key} placeholders + * @param {Record} context - Key-value pairs for replacement + * @returns {string} Template with placeholders replaced + */ +function renderTemplate(template, context) { + return template.replace(/\{(\w+)\}/g, (match, key) => { + const value = context[key]; + if (value === undefined || value === null) { + return match; + } + return String(value); + }); +} + +/** + * Read a template file and render it with the given context. + * Combines file loading and template rendering into a single helper. + * @param {string} templatePath - Absolute path to the template file + * @param {Record} context - Key-value pairs for replacement + * @returns {string} Rendered template with placeholders replaced + */ +function renderTemplateFromFile(templatePath, context) { + let template; + try { + template = fs.readFileSync(templatePath, "utf8"); + } catch (err) { + throw new Error(`Failed to read file ${templatePath}: ${String(err)}`, { cause: err }); + } + return renderTemplate(template, context); +} + +module.exports = { + getPromptPath, + renderTemplate, + renderTemplateFromFile, +}; From 8f769c1edb31f19dfef47daa4ae4e89510cf2e16 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 08:14:53 +0000 Subject: [PATCH 5/8] refactor: remove prompt_file_helpers.cjs and use existing helpers from messages_core.cjs Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/generate_footer.cjs | 9 +--- actions/setup/js/messages_core.cjs | 52 ++++++++++++++++++++- actions/setup/js/messages_core.test.cjs | 5 +- actions/setup/js/prompt_file_helpers.cjs | 59 ------------------------ 4 files changed, 55 insertions(+), 70 deletions(-) delete mode 100644 actions/setup/js/prompt_file_helpers.cjs diff --git a/actions/setup/js/generate_footer.cjs b/actions/setup/js/generate_footer.cjs index 11657ed1cff..7c1413d7c64 100644 --- a/actions/setup/js/generate_footer.cjs +++ b/actions/setup/js/generate_footer.cjs @@ -2,7 +2,7 @@ /// const { getDetectionReasonText, getThreatDetectedMarker, isToolingFailureReason } = require("./threat_detection_warning.cjs"); -const { getPromptPath, renderTemplateFromFile } = require("./prompt_file_helpers.cjs"); +const { getPromptPath, renderTemplateFromFile } = require("./messages_core.cjs"); /** * Generates a standalone workflow-id XML comment marker for searchability. @@ -114,12 +114,7 @@ function generateXMLMarker(workflowName, runUrl) { * admonition is used so reviewers can distinguish "detection engine crashed" from "detection * engine found something". Actual threat findings (threat_detected) keep [!CAUTION]. * - * Note: Template rendering uses prompt_file_helpers.cjs instead of messages_core.cjs, - * because importing messages_core.cjs (directly or transitively via messages_footer.cjs) - * would cause the bundler to inline 'GH_AW_SAFE_OUTPUT_MESSAGES:' in a warning message, - * breaking tests that check for env var declarations. - * - * Warning reason text and threat marker formatting are centralized in + * Note: Warning reason text and threat marker formatting are centralized in * threat_detection_warning.cjs to keep warning-mode messaging consistent. * * @param {string} workflowName - Name of the workflow diff --git a/actions/setup/js/messages_core.cjs b/actions/setup/js/messages_core.cjs index 3b797957765..1203f513713 100644 --- a/actions/setup/js/messages_core.cjs +++ b/actions/setup/js/messages_core.cjs @@ -26,7 +26,7 @@ */ const { getErrorMessage } = require("./error_helpers.cjs"); -const { getPromptPath, renderTemplate, renderTemplateFromFile } = require("./prompt_file_helpers.cjs"); +const fs = require("fs"); /** * @typedef {Object} SafeOutputMessages @@ -72,6 +72,23 @@ function getMessages() { } } +/** + * Replace placeholders in a template string with values from context. + * Supports {key} syntax for placeholder replacement. + * @param {string} template - Template string with {key} placeholders + * @param {Record} context - Key-value pairs for replacement + * @returns {string} Template with placeholders replaced + */ +function renderTemplate(template, context) { + return template.replace(/\{(\w+)\}/g, (match, key) => { + const value = context[key]; + if (value === undefined || value === null) { + return match; + } + return String(value); + }); +} + /** * Render a comma-separated files list into markdown inline code spans. * - Trims each entry and drops empty segments @@ -98,6 +115,39 @@ function renderFilesList(value) { .join(", "); } +/** + * Resolve the absolute path to a prompt template file. + * Prefers GH_AW_PROMPTS_DIR when set, otherwise falls back to + * ${RUNNER_TEMP}/gh-aw/prompts (the runtime location used in production). + * Throws if neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set. + * @param {string} name - Template filename (e.g. "agent_timeout.md") + * @returns {string} Absolute path to the prompt template file + */ +function getPromptPath(name) { + const promptsDir = process.env.GH_AW_PROMPTS_DIR || (process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts` : null); + if (!promptsDir) { + throw new Error("Cannot resolve prompt path: neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set"); + } + return `${promptsDir}/${name}`; +} + +/** + * Read a template file and render it with the given context. + * Combines file loading and template rendering into a single helper. + * @param {string} templatePath - Absolute path to the template file + * @param {Record} context - Key-value pairs for replacement + * @returns {string} Rendered template with placeholders replaced + */ +function renderTemplateFromFile(templatePath, context) { + let template; + try { + template = fs.readFileSync(templatePath, "utf8"); + } catch (err) { + throw new Error(`Failed to read file ${templatePath}: ${String(err)}`, { cause: err }); + } + return renderTemplate(template, context); +} + /** * Convert context object keys to snake_case for template rendering. * Also keeps original camelCase keys for backwards compatibility. diff --git a/actions/setup/js/messages_core.test.cjs b/actions/setup/js/messages_core.test.cjs index 9c5895d7747..683ec3f53b6 100644 --- a/actions/setup/js/messages_core.test.cjs +++ b/actions/setup/js/messages_core.test.cjs @@ -269,10 +269,9 @@ describe("messages_core.cjs", () => { expect(getPromptPath("bar.md")).toBe("/tmp/runner/gh-aw/prompts/bar.md"); }); - it("should fall back to md/ directory when neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set", async () => { + it("should throw when neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set", async () => { const { getPromptPath } = await import("./messages_core.cjs?" + Date.now()); - const result = getPromptPath("any.md"); - expect(result).toMatch(/actions\/setup\/md\/any\.md$/); + expect(() => getPromptPath("any.md")).toThrow("Cannot resolve prompt path: neither GH_AW_PROMPTS_DIR nor RUNNER_TEMP is set"); }); }); diff --git a/actions/setup/js/prompt_file_helpers.cjs b/actions/setup/js/prompt_file_helpers.cjs deleted file mode 100644 index 20493f8d053..00000000000 --- a/actions/setup/js/prompt_file_helpers.cjs +++ /dev/null @@ -1,59 +0,0 @@ -// @ts-check -/// - -const fs = require("fs"); -const path = require("path"); - -/** - * Resolve the absolute path to a prompt template file. - * Prefers GH_AW_PROMPTS_DIR when set, otherwise falls back to - * ${RUNNER_TEMP}/gh-aw/prompts (the runtime location used in production). - * When neither is set, falls back to the source md/ directory relative to - * this file (used in local development and unit tests). - * @param {string} name - Template filename (e.g. "agent_timeout.md") - * @returns {string} Absolute path to the prompt template file - */ -function getPromptPath(name) { - const promptsDir = process.env.GH_AW_PROMPTS_DIR || (process.env.RUNNER_TEMP ? `${process.env.RUNNER_TEMP}/gh-aw/prompts` : null) || path.join(__dirname, "../md"); - return `${promptsDir}/${name}`; -} - -/** - * Replace placeholders in a template string with values from context. - * Supports {key} syntax for placeholder replacement. - * @param {string} template - Template string with {key} placeholders - * @param {Record} context - Key-value pairs for replacement - * @returns {string} Template with placeholders replaced - */ -function renderTemplate(template, context) { - return template.replace(/\{(\w+)\}/g, (match, key) => { - const value = context[key]; - if (value === undefined || value === null) { - return match; - } - return String(value); - }); -} - -/** - * Read a template file and render it with the given context. - * Combines file loading and template rendering into a single helper. - * @param {string} templatePath - Absolute path to the template file - * @param {Record} context - Key-value pairs for replacement - * @returns {string} Rendered template with placeholders replaced - */ -function renderTemplateFromFile(templatePath, context) { - let template; - try { - template = fs.readFileSync(templatePath, "utf8"); - } catch (err) { - throw new Error(`Failed to read file ${templatePath}: ${String(err)}`, { cause: err }); - } - return renderTemplate(template, context); -} - -module.exports = { - getPromptPath, - renderTemplate, - renderTemplateFromFile, -}; From 7a731b9e241089f9cd21f10c3bfa5ae3a5c5622b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 09:14:18 +0000 Subject: [PATCH 6/8] fix: add missing threat detection template copies to create_pull_request test setup Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/create_pull_request.test.cjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actions/setup/js/create_pull_request.test.cjs b/actions/setup/js/create_pull_request.test.cjs index 58f459c6277..e17d52ec36d 100644 --- a/actions/setup/js/create_pull_request.test.cjs +++ b/actions/setup/js/create_pull_request.test.cjs @@ -3593,6 +3593,8 @@ describe("create_pull_request - threat detection caution", () => { copyPromptTemplate(promptsDir, "manifest_protection_request_review.md"); copyPromptTemplate(promptsDir, "manifest_protection_request_changes_review.md"); copyPromptTemplate(promptsDir, "threat_warning_request_changes_review.md"); + copyPromptTemplate(promptsDir, "threat_detection_caution.md"); + copyPromptTemplate(promptsDir, "threat_detection_engine_error.md"); copyPromptTemplate(promptsDir, "safe_outputs_disclosure_header.md"); process.env.GH_AW_PROMPTS_DIR = promptsDir; From 7245cdaacc221e7a1bcf8434ec743b9dea7fe52d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:04:41 +0000 Subject: [PATCH 7/8] fix: neutral detection_runs_comment summary and stronger test assertions for progressive disclosure Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/generate_footer.test.cjs | 6 ++++-- actions/setup/js/messages.test.cjs | 3 ++- actions/setup/md/detection_runs_comment.md | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/actions/setup/js/generate_footer.test.cjs b/actions/setup/js/generate_footer.test.cjs index 129706c2cbe..2db01ca37d7 100644 --- a/actions/setup/js/generate_footer.test.cjs +++ b/actions/setup/js/generate_footer.test.cjs @@ -502,7 +502,8 @@ describe("generate_footer.cjs", () => { const result = getExpiredEntityCautionAlert("Test Workflow", "https://github.com/test/repo/actions/runs/123"); expect(result).toContain("> [!WARNING]"); - expect(result).toContain("Threat Detection Engine Failure"); + expect(result).toContain("**Threat Detection Engine Failure**"); + expect(result).toContain("What happened"); expect(result).toContain(""); expect(result).not.toContain("> [!CAUTION]"); expect(result).not.toContain("agentic threat detected"); @@ -516,7 +517,8 @@ describe("generate_footer.cjs", () => { const result = getExpiredEntityCautionAlert("Test Workflow", "https://github.com/test/repo/actions/runs/123"); expect(result).toContain("> [!WARNING]"); - expect(result).toContain("Threat Detection Engine Failure"); + expect(result).toContain("**Threat Detection Engine Failure**"); + expect(result).toContain("What happened"); expect(result).not.toContain("> [!CAUTION]"); expect(result).not.toContain("agentic threat detected"); expect(result).toContain("could not be parsed"); diff --git a/actions/setup/js/messages.test.cjs b/actions/setup/js/messages.test.cjs index bcc481b79b6..2019c3c8624 100644 --- a/actions/setup/js/messages.test.cjs +++ b/actions/setup/js/messages.test.cjs @@ -1411,7 +1411,8 @@ describe("messages.cjs", () => { const result = getDetectionCautionAlert("Test Workflow", "https://github.com/test/repo/actions/runs/123"); expect(result).toContain("> [!WARNING]"); - expect(result).toContain("Threat Detection Engine Failure"); + expect(result).toContain("**Threat Detection Engine Failure**"); + expect(result).toContain("What happened"); expect(result).not.toContain("> [!CAUTION]"); expect(result).not.toContain("agentic threat detected"); expect(result).toContain("threat detection engine failed"); diff --git a/actions/setup/md/detection_runs_comment.md b/actions/setup/md/detection_runs_comment.md index 528339e2cf2..edb408ab6ac 100644 --- a/actions/setup/md/detection_runs_comment.md +++ b/actions/setup/md/detection_runs_comment.md @@ -1,6 +1,6 @@ ### {workflow_name} -The threat detection engine could not complete analysis for this run. This is a tooling failure, not a security finding. +Threat detection produced a **{conclusion}** result for this run.
Run details From 8f8b3b9ed39f43eac460f0ebf482eec95181cddc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:38:48 +0000 Subject: [PATCH 8/8] fix: add missing threat detection template files to handle_agent_failure test setup Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/handle_agent_failure.test.cjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/actions/setup/js/handle_agent_failure.test.cjs b/actions/setup/js/handle_agent_failure.test.cjs index 31ec2878f7d..ecc666b34cb 100644 --- a/actions/setup/js/handle_agent_failure.test.cjs +++ b/actions/setup/js/handle_agent_failure.test.cjs @@ -184,6 +184,14 @@ describe("handle_agent_failure", () => { fs.writeFileSync(path.join(promptsDir, "daily_cap_rollup_issue.md"), "Daily cap rollup issue body cap={cap} window={window_hours}"); fs.writeFileSync(path.join(promptsDir, "daily_cap_rollup_comment.md"), "Failure suppressed workflow={workflow_name} run={run_url} categories={summary} cap={cap} window={window_hours}h"); fs.writeFileSync(path.join(promptsDir, "optimize_token_consumption_context.md"), "OPTIMIZE CONTEXT guardrail={guardrail_name} run={run_url}"); + fs.writeFileSync( + path.join(promptsDir, "threat_detection_caution.md"), + "> [!CAUTION]\n> agentic threat detected\n> Threat detection flagged this output in warn mode. Manual review is REQUIRED before any follow-up automation.\n> {threat_detected_marker}\n>\n>
\n> Details\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n>
" + ); + fs.writeFileSync( + path.join(promptsDir, "threat_detection_engine_error.md"), + "> [!WARNING]\n> **Threat Detection Engine Failure** — The analysis engine could not complete. This is a tooling failure, not a security finding.\n> {threat_detected_marker}\n>\n>
\n> What happened\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n>
" + ); process.env.RUNNER_TEMP = tmpDir; process.env.GH_AW_WORKFLOW_NAME = "Test Workflow";