Skip to content
Open
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
44 changes: 1 addition & 43 deletions scripts/bedrock_classifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
*/
Expand Down
45 changes: 1 addition & 44 deletions scripts/detect_duplicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,50 +33,6 @@ const EXCLUDE_LABELS = new Set<string>([
"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.
*
Expand Down
58 changes: 58 additions & 0 deletions scripts/sanitize_utils.ts
Original file line number Diff line number Diff line change
@@ -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;
}