agent-team-composer
Version:
Transform README files into GitHub project plans with AI-powered agent teams
121 lines • 4.52 kB
JavaScript
import { z } from 'zod';
// Maximum lengths for different input types
const MAX_LENGTHS = {
title: 256,
description: 2048,
readme: 50000,
domain: 100,
role: 100,
phase: 200
};
// Schemas for validation
export const PromptInputSchema = z.object({
title: z.string().max(MAX_LENGTHS.title),
description: z.string().max(MAX_LENGTHS.description),
domain: z.string().max(MAX_LENGTHS.domain),
features: z.array(z.string().max(200)).max(50),
techStack: z.array(z.string().max(100)).max(30),
complexity: z.enum(['simple', 'moderate', 'complex'])
});
export class PromptSanitizer {
/**
* Sanitize user input for safe inclusion in prompts
* Prevents prompt injection attacks
*/
static sanitizeInput(input, maxLength = 1000) {
if (!input || typeof input !== 'string') {
return '';
}
return input
// Remove potential prompt injection patterns
.replace(/\{[^}]*\}/g, '') // Remove template syntax
.replace(/\$\{[^}]*\}/g, '') // Remove template literals
.replace(/<%[^%>]*%>/g, '') // Remove ERB-style tags
.replace(/\{\{[^}]*\}\}/g, '') // Remove mustache templates
// Remove potential command sequences
.replace(/\n\s*Human:/gi, '\n[User]:')
.replace(/\n\s*Assistant:/gi, '\n[Agent]:')
.replace(/\n\s*System:/gi, '\n[Note]:')
// Limit consecutive newlines
.replace(/\n{3,}/g, '\n\n')
// Remove control characters
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
// Normalize whitespace
.replace(/\s+/g, ' ')
.trim()
// Enforce length limit
.slice(0, maxLength);
}
/**
* Sanitize README content specifically
*/
static sanitizeReadme(readme) {
return this.sanitizeInput(readme, MAX_LENGTHS.readme);
}
/**
* Escape special characters for JSON inclusion
*/
static escapeForJson(str) {
return str
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t');
}
/**
* Build safe prompt with sanitized inputs
*/
static buildSafePrompt(template, variables) {
let prompt = template;
// Sort by key length descending to avoid partial replacements
const sortedKeys = Object.keys(variables).sort((a, b) => b.length - a.length);
for (const key of sortedKeys) {
const placeholder = `{${key}}`;
const value = variables[key];
let sanitizedValue;
if (Array.isArray(value)) {
sanitizedValue = value
.map(v => this.sanitizeInput(String(v), 200))
.join(', ');
}
else {
sanitizedValue = this.sanitizeInput(String(value), 2000);
}
// Use a function replacer to avoid issues with $ in replacement
prompt = prompt.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), () => sanitizedValue);
}
return prompt;
}
/**
* Validate and sanitize project info for LLM
*/
static sanitizeProjectInfo(projectInfo) {
// Validate with schema
const validated = PromptInputSchema.parse({
title: this.sanitizeInput(projectInfo.title, MAX_LENGTHS.title),
description: this.sanitizeInput(projectInfo.description, MAX_LENGTHS.description),
domain: this.sanitizeInput(projectInfo.domain, MAX_LENGTHS.domain),
features: projectInfo.features?.map((f) => this.sanitizeInput(f, 200)) || [],
techStack: projectInfo.techStack?.map((t) => this.sanitizeInput(t, 100)) || [],
complexity: projectInfo.complexity || 'moderate'
});
return validated;
}
/**
* Check for potentially malicious patterns
*/
static containsSuspiciousPatterns(input) {
const suspiciousPatterns = [
/ignore\s+previous\s+instructions/i,
/disregard\s+all\s+prior/i,
/new\s+instructions:/i,
/system\s+prompt:/i,
/\[INST\]/i,
/<\|im_start\|>/i,
/\n\s*###\s*Instruction/i
];
return suspiciousPatterns.some(pattern => pattern.test(input));
}
}
//# sourceMappingURL=prompt-sanitizer.js.map