fix autofix body leak, harden prompt against injection, trigger-aware comments (#512)

* 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
This commit is contained in:
Colin McDonnell
2026-04-03 22:42:25 +00:00
committed by pullfrog[bot]
parent d525fc21be
commit 37f984f4f8
2 changed files with 21 additions and 36 deletions
+10 -16
View File
@@ -150317,17 +150317,11 @@ function buildRuntimeContext(ctx) {
const filtered = Object.fromEntries(Object.entries(data).filter(([_2, v]) => v !== void 0)); const filtered = Object.fromEntries(Object.entries(data).filter(([_2, v]) => v !== void 0));
return encode(filtered); return encode(filtered);
} }
function buildEventTitleBody(event) { function buildEventTitle(event) {
const sections = [];
const trimmedTitle = typeof event.title === "string" ? event.title.trim() : ""; const trimmedTitle = typeof event.title === "string" ? event.title.trim() : "";
const trimmedBody = typeof event.body === "string" ? event.body.trim() : ""; if (!trimmedTitle) return "";
if (trimmedTitle) { const prefix = event.issue_number ? `${event.is_pr ? "PR" : "Issue"} #${event.issue_number}` : "";
sections.push(`# ${trimmedTitle}`); return prefix ? `${prefix} ("${trimmedTitle}")` : `("${trimmedTitle}")`;
}
if (trimmedBody) {
sections.push(trimmedBody);
}
return sections.join("\n\n");
} }
function buildEventMetadata(event) { function buildEventMetadata(event) {
const { title: _t, body: _b, trigger, ...rest } = event; const { title: _t, body: _b, trigger, ...rest } = event;
@@ -150485,9 +150479,9 @@ function buildContextSections(ctx) {
const eventInstructionsSection = ctx.eventInstructions ? `************* EVENT-LEVEL INSTRUCTIONS ************* const eventInstructionsSection = ctx.eventInstructions ? `************* EVENT-LEVEL INSTRUCTIONS *************
${ctx.eventInstructions}` : ""; ${ctx.eventInstructions}` : "";
const titleBodySection = ctx.eventTitleBody ? `${relatedLabel} const titleBodySection = ctx.eventTitle ? `${relatedLabel}
${ctx.eventTitleBody}` : ""; ${ctx.eventTitle}` : "";
const metadataSection = ctx.eventMetadata ? `--- event context --- const metadataSection = ctx.eventMetadata ? `--- event context ---
${ctx.eventMetadata}` : ""; ${ctx.eventMetadata}` : "";
@@ -150505,15 +150499,15 @@ ${metadataSection}`;
return [eventInstructionsSection, userSection].filter(Boolean).join("\n\n"); return [eventInstructionsSection, userSection].filter(Boolean).join("\n\n");
} }
function buildCommonInputs(ctx) { function buildCommonInputs(ctx) {
const eventTitleBody = buildEventTitleBody(ctx.payload.event); const eventTitle = buildEventTitle(ctx.payload.event);
const eventMetadata = buildEventMetadata(ctx.payload.event); const eventMetadata = buildEventMetadata(ctx.payload.event);
const runtime = buildRuntimeContext(ctx); const runtime = buildRuntimeContext(ctx);
const user = ctx.payload.prompt; const user = ctx.payload.prompt;
const eventInstructions = ctx.payload.eventInstructions ?? ""; 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") : ""; const userQuoted = user ? user.split("\n").map((line) => `> ${line}`).join("\n") : "";
return { return {
eventTitleBody, eventTitle,
eventMetadata, eventMetadata,
runtime, runtime,
user, user,
@@ -150567,7 +150561,7 @@ If the task clearly requires no work, call \`${ghPullfrogMcpName}/report_progres
const contextSections = buildContextSections({ const contextSections = buildContextSections({
payload: ctx.payload, payload: ctx.payload,
eventInstructions: inputs.eventInstructions, eventInstructions: inputs.eventInstructions,
eventTitleBody: inputs.eventTitleBody, eventTitle: inputs.eventTitle,
eventMetadata: inputs.eventMetadata, eventMetadata: inputs.eventMetadata,
userQuoted: inputs.userQuoted userQuoted: inputs.userQuoted
}); });
+11 -20
View File
@@ -53,22 +53,13 @@ function buildRuntimeContext(ctx: InstructionsContext): string {
return toonEncode(filtered); return toonEncode(filtered);
} }
function buildEventTitleBody(event: PayloadEvent): string { function buildEventTitle(event: PayloadEvent): string {
const sections: string[] = [];
// render title + body as markdown
const trimmedTitle = typeof event.title === "string" ? event.title.trim() : ""; const trimmedTitle = typeof event.title === "string" ? event.title.trim() : "";
const trimmedBody = typeof event.body === "string" ? event.body.trim() : ""; if (!trimmedTitle) return "";
if (trimmedTitle) { const prefix = event.issue_number ? `${event.is_pr ? "PR" : "Issue"} #${event.issue_number}` : "";
sections.push(`# ${trimmedTitle}`);
}
if (trimmedBody) { return prefix ? `${prefix} ("${trimmedTitle}")` : `("${trimmedTitle}")`;
sections.push(trimmedBody);
}
return sections.join("\n\n");
} }
function buildEventMetadata(event: PayloadEvent): string { function buildEventMetadata(event: PayloadEvent): string {
@@ -261,7 +252,7 @@ export interface ResolvedInstructions {
interface ContextSectionsInput { interface ContextSectionsInput {
payload: ResolvedPayload; payload: ResolvedPayload;
eventInstructions: string; eventInstructions: string;
eventTitleBody: string; eventTitle: string;
eventMetadata: string; eventMetadata: string;
userQuoted: string; userQuoted: string;
} }
@@ -276,7 +267,7 @@ function buildContextSections(ctx: ContextSectionsInput): string {
${ctx.eventInstructions}` ${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 metadataSection = ctx.eventMetadata ? `--- event context ---\n\n${ctx.eventMetadata}` : "";
const userSection = ctx.userQuoted const userSection = ctx.userQuoted
@@ -298,7 +289,7 @@ ${metadataSection}`;
// shared computation for all instruction builders // shared computation for all instruction builders
interface CommonInputs { interface CommonInputs {
eventTitleBody: string; eventTitle: string;
eventMetadata: string; eventMetadata: string;
runtime: string; runtime: string;
user: string; user: string;
@@ -308,12 +299,12 @@ interface CommonInputs {
} }
function buildCommonInputs(ctx: InstructionsContext): 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 eventMetadata = buildEventMetadata(ctx.payload.event);
const runtime = buildRuntimeContext(ctx); const runtime = buildRuntimeContext(ctx);
const user = ctx.payload.prompt; const user = ctx.payload.prompt;
const eventInstructions = ctx.payload.eventInstructions ?? ""; 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 const userQuoted = user
? user ? user
.split("\n") .split("\n")
@@ -321,7 +312,7 @@ function buildCommonInputs(ctx: InstructionsContext): CommonInputs {
.join("\n") .join("\n")
: ""; : "";
return { return {
eventTitleBody, eventTitle,
eventMetadata, eventMetadata,
runtime, runtime,
user, user,
@@ -388,7 +379,7 @@ If the task clearly requires no work, call \`${ghPullfrogMcpName}/report_progres
const contextSections = buildContextSections({ const contextSections = buildContextSections({
payload: ctx.payload, payload: ctx.payload,
eventInstructions: inputs.eventInstructions, eventInstructions: inputs.eventInstructions,
eventTitleBody: inputs.eventTitleBody, eventTitle: inputs.eventTitle,
eventMetadata: inputs.eventMetadata, eventMetadata: inputs.eventMetadata,
userQuoted: inputs.userQuoted, userQuoted: inputs.userQuoted,
}); });