From 37f984f4f876aa30a82a72c2580e84bf6ae79086 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Fri, 3 Apr 2026 22:42:25 +0000 Subject: [PATCH] fix autofix body leak, harden prompt against injection, trigger-aware comments (#512) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix autofix body leak, harden prompt against injection, trigger-aware comments never forward event bodies to the agent prompt — they are user-generated content and a prompt injection vector. the agent fetches bodies on demand via MCP tools (checkout_pr, get_issue, etc.). - always set event.body to null in dispatch(), add promptFromBody: false to autofix, strip body from nested pull_request object - replace buildEventTitleBody with buildEventTitle rendering inline references like PR #497 ("Title") instead of raw markdown headings - add LEAPING_REASON_MAP for trigger-aware progress comments (e.g. "CI failure detected. Leaping into action...") - thread type through buildLeapingIntoActionComment, createLeapingComment, and updateCommentToLeaping Made-with: Cursor * rename translateWorkflowRunType.ts to workflowRunTypes.ts Made-with: Cursor --- entry | 26 ++++++++++---------------- utils/instructions.ts | 31 +++++++++++-------------------- 2 files changed, 21 insertions(+), 36 deletions(-) diff --git a/entry b/entry index bd06704..1af6535 100755 --- a/entry +++ b/entry @@ -150317,17 +150317,11 @@ function buildRuntimeContext(ctx) { const filtered = Object.fromEntries(Object.entries(data).filter(([_2, v]) => v !== void 0)); return encode(filtered); } -function buildEventTitleBody(event) { - const sections = []; +function buildEventTitle(event) { const trimmedTitle = typeof event.title === "string" ? event.title.trim() : ""; - const trimmedBody = typeof event.body === "string" ? event.body.trim() : ""; - if (trimmedTitle) { - sections.push(`# ${trimmedTitle}`); - } - if (trimmedBody) { - sections.push(trimmedBody); - } - return sections.join("\n\n"); + if (!trimmedTitle) return ""; + const prefix = event.issue_number ? `${event.is_pr ? "PR" : "Issue"} #${event.issue_number}` : ""; + return prefix ? `${prefix} ("${trimmedTitle}")` : `("${trimmedTitle}")`; } function buildEventMetadata(event) { const { title: _t, body: _b, trigger, ...rest } = event; @@ -150485,9 +150479,9 @@ function buildContextSections(ctx) { const eventInstructionsSection = ctx.eventInstructions ? `************* EVENT-LEVEL INSTRUCTIONS ************* ${ctx.eventInstructions}` : ""; - const titleBodySection = ctx.eventTitleBody ? `${relatedLabel} + const titleBodySection = ctx.eventTitle ? `${relatedLabel} -${ctx.eventTitleBody}` : ""; +${ctx.eventTitle}` : ""; const metadataSection = ctx.eventMetadata ? `--- event context --- ${ctx.eventMetadata}` : ""; @@ -150505,15 +150499,15 @@ ${metadataSection}`; return [eventInstructionsSection, userSection].filter(Boolean).join("\n\n"); } function buildCommonInputs(ctx) { - const eventTitleBody = buildEventTitleBody(ctx.payload.event); + const eventTitle = buildEventTitle(ctx.payload.event); const eventMetadata = buildEventMetadata(ctx.payload.event); const runtime = buildRuntimeContext(ctx); const user = ctx.payload.prompt; const eventInstructions = ctx.payload.eventInstructions ?? ""; - const event = [eventTitleBody, eventMetadata].filter(Boolean).join("\n\n---\n\n"); + const event = [eventTitle, eventMetadata].filter(Boolean).join("\n\n---\n\n"); const userQuoted = user ? user.split("\n").map((line) => `> ${line}`).join("\n") : ""; return { - eventTitleBody, + eventTitle, eventMetadata, runtime, user, @@ -150567,7 +150561,7 @@ If the task clearly requires no work, call \`${ghPullfrogMcpName}/report_progres const contextSections = buildContextSections({ payload: ctx.payload, eventInstructions: inputs.eventInstructions, - eventTitleBody: inputs.eventTitleBody, + eventTitle: inputs.eventTitle, eventMetadata: inputs.eventMetadata, userQuoted: inputs.userQuoted }); diff --git a/utils/instructions.ts b/utils/instructions.ts index f264501..bc15b04 100644 --- a/utils/instructions.ts +++ b/utils/instructions.ts @@ -53,22 +53,13 @@ function buildRuntimeContext(ctx: InstructionsContext): string { return toonEncode(filtered); } -function buildEventTitleBody(event: PayloadEvent): string { - const sections: string[] = []; - - // render title + body as markdown +function buildEventTitle(event: PayloadEvent): string { const trimmedTitle = typeof event.title === "string" ? event.title.trim() : ""; - const trimmedBody = typeof event.body === "string" ? event.body.trim() : ""; + if (!trimmedTitle) return ""; - if (trimmedTitle) { - sections.push(`# ${trimmedTitle}`); - } + const prefix = event.issue_number ? `${event.is_pr ? "PR" : "Issue"} #${event.issue_number}` : ""; - if (trimmedBody) { - sections.push(trimmedBody); - } - - return sections.join("\n\n"); + return prefix ? `${prefix} ("${trimmedTitle}")` : `("${trimmedTitle}")`; } function buildEventMetadata(event: PayloadEvent): string { @@ -261,7 +252,7 @@ export interface ResolvedInstructions { interface ContextSectionsInput { payload: ResolvedPayload; eventInstructions: string; - eventTitleBody: string; + eventTitle: string; eventMetadata: string; userQuoted: string; } @@ -276,7 +267,7 @@ function buildContextSections(ctx: ContextSectionsInput): string { ${ctx.eventInstructions}` : ""; - const titleBodySection = ctx.eventTitleBody ? `${relatedLabel}\n\n${ctx.eventTitleBody}` : ""; + const titleBodySection = ctx.eventTitle ? `${relatedLabel}\n\n${ctx.eventTitle}` : ""; const metadataSection = ctx.eventMetadata ? `--- event context ---\n\n${ctx.eventMetadata}` : ""; const userSection = ctx.userQuoted @@ -298,7 +289,7 @@ ${metadataSection}`; // shared computation for all instruction builders interface CommonInputs { - eventTitleBody: string; + eventTitle: string; eventMetadata: string; runtime: string; user: string; @@ -308,12 +299,12 @@ interface CommonInputs { } function buildCommonInputs(ctx: InstructionsContext): CommonInputs { - const eventTitleBody = buildEventTitleBody(ctx.payload.event); + const eventTitle = buildEventTitle(ctx.payload.event); const eventMetadata = buildEventMetadata(ctx.payload.event); const runtime = buildRuntimeContext(ctx); const user = ctx.payload.prompt; const eventInstructions = ctx.payload.eventInstructions ?? ""; - const event = [eventTitleBody, eventMetadata].filter(Boolean).join("\n\n---\n\n"); + const event = [eventTitle, eventMetadata].filter(Boolean).join("\n\n---\n\n"); const userQuoted = user ? user .split("\n") @@ -321,7 +312,7 @@ function buildCommonInputs(ctx: InstructionsContext): CommonInputs { .join("\n") : ""; return { - eventTitleBody, + eventTitle, eventMetadata, runtime, user, @@ -388,7 +379,7 @@ If the task clearly requires no work, call \`${ghPullfrogMcpName}/report_progres const contextSections = buildContextSections({ payload: ctx.payload, eventInstructions: inputs.eventInstructions, - eventTitleBody: inputs.eventTitleBody, + eventTitle: inputs.eventTitle, eventMetadata: inputs.eventMetadata, userQuoted: inputs.userQuoted, });