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
4 changes: 2 additions & 2 deletions .github/workflows/smoke-checkout-pr-dispatch.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions actions/setup/js/create_pull_request.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
13 changes: 5 additions & 8 deletions actions/setup/js/generate_footer.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/// <reference types="@actions/github-script" />

const { getDetectionReasonText, getThreatDetectedMarker, isToolingFailureReason } = require("./threat_detection_warning.cjs");
const { getPromptPath, renderTemplateFromFile } = require("./messages_core.cjs");

/**
* Generates a standalone workflow-id XML comment marker for searchability.
Expand Down Expand Up @@ -113,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: 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.
*
* 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
Expand All @@ -132,10 +128,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 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> <details>\n> <summary>Details</summary>\n>\n> ${reasonText}\n>\n> Review the [workflow run logs](${runUrl}) for details.\n> </details>`;
return renderTemplateFromFile(getPromptPath("threat_detection_engine_error.md"), context).trimEnd();
}
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> <details>\n> <summary>Details</summary>\n>\n> ${reasonText}\n>\n> Review the [workflow run logs](${runUrl}) for details.\n> </details>`;
return renderTemplateFromFile(getPromptPath("threat_detection_caution.md"), context).trimEnd();
}

/**
Expand Down
24 changes: 21 additions & 3 deletions actions/setup/js/generate_footer.test.cjs
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -337,15 +339,29 @@ 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");
generateExpiredEntityFooter = freshModule.generateExpiredEntityFooter;
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");

Expand Down Expand Up @@ -486,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 error");
expect(result).toContain("**Threat Detection Engine Failure**");
expect(result).toContain("What happened");
expect(result).toContain("<!-- gh-aw-threat-engine-error -->");
expect(result).not.toContain("<!-- gh-aw-threat-detected -->");
expect(result).not.toContain("> [!CAUTION]");
Expand All @@ -501,7 +518,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 error");
expect(result).toContain("**Threat Detection Engine Failure**");
expect(result).toContain("What happened");
expect(result).toContain("<!-- gh-aw-threat-engine-error -->");
expect(result).not.toContain("<!-- gh-aw-threat-detected -->");
expect(result).not.toContain("> [!CAUTION]");
Expand Down
8 changes: 8 additions & 0 deletions actions/setup/js/handle_agent_failure.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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> <details>\n> <summary>Details</summary>\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n> </details>"
);
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> <details>\n> <summary>What happened</summary>\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n> </details>"
);

process.env.RUNNER_TEMP = tmpDir;
process.env.GH_AW_WORKFLOW_NAME = "Test Workflow";
Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/messages.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 error");
expect(result).toContain("**Threat Detection Engine Failure**");
expect(result).toContain("What happened");
expect(result).toContain("<!-- gh-aw-threat-engine-error -->");
expect(result).not.toContain("<!-- gh-aw-threat-detected -->");
expect(result).not.toContain("> [!CAUTION]");
Expand Down
18 changes: 10 additions & 8 deletions actions/setup/js/messages_run_status.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
* for workflow execution notifications.
*/

const { getMessages, renderTemplate, toSnakeCase } = require("./messages_core.cjs");
const { getDetectionReasonText, getThreatWarningPresentation, normalizeThreatKinds, isToolingFailureReason } = require("./threat_detection_warning.cjs");
const { getMessages, renderTemplate, renderTemplateFromFile, toSnakeCase, getPromptPath } = require("./messages_core.cjs");
const { getDetectionReasonText, getThreatDetectedMarkerTemplate, getThreatEngineErrorMarkerTemplate, normalizeThreatKinds, isToolingFailureReason } = require("./threat_detection_warning.cjs");

/**
* Renders a message using a custom template from config or a default template.
Expand Down Expand Up @@ -152,14 +152,16 @@ function getCommitPushedMessage(ctx) {
*/
function getDetectionWarningMessage(ctx) {
const reasonText = getDetectionReasonText(ctx.reason);
const presentation = getThreatWarningPresentation(ctx.reason);
const isEngineError = isToolingFailureReason(ctx.reason);
if (isEngineError) {
const defaultTemplate = `> [!${presentation.admonition}]\n> ${presentation.title}\n> ${presentation.summary}\n> ${presentation.marker}\n>\n> <details>\n> <summary>Details</summary>\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n> </details>`;
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 markerTemplate = isEngineError ? getThreatEngineErrorMarkerTemplate() : getThreatDetectedMarkerTemplate();
const messages = getMessages();
const configTemplate = messages?.[messageKey];
if (configTemplate) {
return renderTemplate(configTemplate, toSnakeCase({ ...ctx, reasonText, threat_detected_marker: markerTemplate, threatKinds: normalizeThreatKinds(ctx.reason) }));
}
const defaultTemplate = `> [!${presentation.admonition}]\n> ${presentation.title}\n> ${presentation.summary}\n> ${presentation.marker}\n>\n> <details>\n> <summary>Details</summary>\n>\n> {reason_text}\n>\n> Review the [workflow run logs]({run_url}) for details.\n> </details>`;
return renderConfiguredMessage("detectionWarning", defaultTemplate, { ...ctx, reasonText, threatKinds: normalizeThreatKinds(ctx.reason) });
return renderTemplateFromFile(getPromptPath(templateFile), toSnakeCase({ ...ctx, reasonText, threat_detected_marker: markerTemplate, threatKinds: normalizeThreatKinds(ctx.reason) })).trimEnd();
}

module.exports = {
Expand Down
7 changes: 7 additions & 0 deletions actions/setup/md/detection_runs_comment.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
### {workflow_name}

Threat detection produced a **{conclusion}** result for this run.

<details>
<summary>Run details</summary>

| Field | Value |
|---|---|
| Conclusion | `{conclusion}` |
| Reason | `{reason}` |
| Run | [View run]({run_url}) |

</details>
12 changes: 12 additions & 0 deletions actions/setup/md/threat_detection_caution.md
Original file line number Diff line number Diff line change
@@ -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>
> <summary>Details</summary>
>
> {reason_text}
>
> Review the [workflow run logs]({run_url}) for details.
> </details>
11 changes: 11 additions & 0 deletions actions/setup/md/threat_detection_engine_error.md
Original file line number Diff line number Diff line change
@@ -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}
>
> <details>
> <summary>What happened</summary>
>
> {reason_text}
>
> Review the [workflow run logs]({run_url}) for details.
> </details>
Loading