diff --git a/scripts/bedrock_classifier.ts b/scripts/bedrock_classifier.ts index 83494e911e1..4ef3f12ffe9 100644 --- a/scripts/bedrock_classifier.ts +++ b/scripts/bedrock_classifier.ts @@ -9,6 +9,7 @@ import { } from "@aws-sdk/client-bedrock-runtime"; import { ClassificationResult, LabelTaxonomy } from "./data_models.js"; import { retryWithBackoff } from "./retry_utils.js"; +import { sanitizePromptInput } from "./sanitize_utils.js"; const MODEL_ID = "us.anthropic.claude-sonnet-4-20250514-v1:0"; const MAX_TOKENS = 2048; @@ -19,49 +20,6 @@ const TOP_P = 0.9; const MAX_TITLE_LENGTH = 500; const MAX_BODY_LENGTH = 10000; -/** - * Sanitize user input to prevent prompt injection attacks - */ -function sanitizePromptInput(input: string, maxLength: number): string { - if (!input) { - return ""; - } - - // Truncate to maximum length - let sanitized = input.substring(0, maxLength); - - // Remove potential prompt injection patterns - const dangerousPatterns = [ - /ignore\s+(all\s+)?(previous|above|prior)\s+instructions?/gi, - /disregard\s+(all\s+)?(previous|above|prior)\s+instructions?/gi, - /forget\s+(all\s+)?(previous|above|prior)\s+instructions?/gi, - /new\s+instructions?:/gi, - /system\s*:/gi, - /assistant\s*:/gi, - /\[SYSTEM\]/gi, - /\[ASSISTANT\]/gi, - /\<\|im_start\|\>/gi, - /\<\|im_end\|\>/gi, - ]; - - for (const pattern of dangerousPatterns) { - sanitized = sanitized.replace(pattern, "[REDACTED]"); - } - - // Escape backticks that could break JSON formatting - sanitized = sanitized.replace(/`/g, "'"); - - // Remove excessive newlines that could break prompt structure - sanitized = sanitized.replace(/\n{4,}/g, "\n\n\n"); - - // Add truncation notice if content was cut - if (input.length > maxLength) { - sanitized += "\n\n[Content truncated for security]"; - } - - return sanitized; -} - /** * Initialize Bedrock client with AWS credentials */ diff --git a/scripts/detect_duplicates.ts b/scripts/detect_duplicates.ts index 470c27f042d..5eaf02c4745 100644 --- a/scripts/detect_duplicates.ts +++ b/scripts/detect_duplicates.ts @@ -10,6 +10,7 @@ import { } from "@aws-sdk/client-bedrock-runtime"; import { DuplicateMatch, IssueData } from "./data_models.js"; import { retryWithBackoff } from "./retry_utils.js"; +import { sanitizePromptInput } from "./sanitize_utils.js"; const MODEL_ID = "us.anthropic.claude-sonnet-4-20250514-v1:0"; const SIMILARITY_THRESHOLD = 0.8; @@ -32,50 +33,6 @@ const EXCLUDE_LABELS = new Set([ "duplicate", ]); -/** - * Sanitize user input to prevent prompt injection attacks - */ -function sanitizePromptInput(input: string, maxLength: number): string { - if (!input) { - return ""; - } - - // Truncate to maximum length - let sanitized = input.substring(0, maxLength); - - // Remove potential prompt injection patterns - // These patterns could be used to manipulate the AI's behavior - const dangerousPatterns = [ - /ignore\s+(all\s+)?(previous|above|prior)\s+instructions?/gi, - /disregard\s+(all\s+)?(previous|above|prior)\s+instructions?/gi, - /forget\s+(all\s+)?(previous|above|prior)\s+instructions?/gi, - /new\s+instructions?:/gi, - /system\s*:/gi, - /assistant\s*:/gi, - /\[SYSTEM\]/gi, - /\[ASSISTANT\]/gi, - /\<\|im_start\|\>/gi, - /\<\|im_end\|\>/gi, - ]; - - for (const pattern of dangerousPatterns) { - sanitized = sanitized.replace(pattern, "[REDACTED]"); - } - - // Escape backticks that could break JSON formatting - sanitized = sanitized.replace(/`/g, "'"); - - // Remove excessive newlines that could break prompt structure - sanitized = sanitized.replace(/\n{4,}/g, "\n\n\n"); - - // Add truncation notice if content was cut - if (input.length > maxLength) { - sanitized += "\n\n[Content truncated for security]"; - } - - return sanitized; -} - /** * Fetch existing open issues from repository as duplicate candidates. * diff --git a/scripts/sanitize_utils.ts b/scripts/sanitize_utils.ts new file mode 100644 index 00000000000..b06a3da02bc --- /dev/null +++ b/scripts/sanitize_utils.ts @@ -0,0 +1,58 @@ +/** + * Shared input-sanitization utilities. + * + * Centralizes prompt-input sanitization so every module that builds a model + * prompt relies on a single, consistent implementation instead of its own copy. + */ + +/** + * Sanitize untrusted user input before embedding it in a model prompt. + * + * Truncates the input to `maxLength`, redacts a set of known + * instruction-override patterns, replaces backticks that could break JSON + * formatting, and collapses runs of excessive newlines. A truncation notice is + * appended when the original input exceeded `maxLength`. + * + * @param input - Raw, untrusted text (for example an issue title or body). + * @param maxLength - Maximum number of characters to keep from `input`. + * @returns The sanitized string, or an empty string when `input` is falsy. + */ +export function sanitizePromptInput(input: string, maxLength: number): string { + if (!input) { + return ""; + } + + // Truncate to maximum length + let sanitized = input.substring(0, maxLength); + + // Remove potential prompt injection patterns + const dangerousPatterns = [ + /ignore\s+(all\s+)?(previous|above|prior)\s+instructions?/gi, + /disregard\s+(all\s+)?(previous|above|prior)\s+instructions?/gi, + /forget\s+(all\s+)?(previous|above|prior)\s+instructions?/gi, + /new\s+instructions?:/gi, + /system\s*:/gi, + /assistant\s*:/gi, + /\[SYSTEM\]/gi, + /\[ASSISTANT\]/gi, + /\<\|im_start\|\>/gi, + /\<\|im_end\|\>/gi, + ]; + + for (const pattern of dangerousPatterns) { + sanitized = sanitized.replace(pattern, "[REDACTED]"); + } + + // Escape backticks that could break JSON formatting + sanitized = sanitized.replace(/`/g, "'"); + + // Remove excessive newlines that could break prompt structure + sanitized = sanitized.replace(/\n{4,}/g, "\n\n\n"); + + // Add truncation notice if content was cut + if (input.length > maxLength) { + sanitized += "\n\n[Content truncated for security]"; + } + + return sanitized; +}