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));
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
});
+11 -20
View File
@@ -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,
});