UNPKG

claude-git-hooks

Version:

Git hooks with Claude CLI for code analysis and automatic commit messages

181 lines (156 loc) 5.07 kB
/** * File: sanitize.js * Purpose: Input sanitization utilities for security * * Why: Prevent prompt injection and ensure valid API input * Used by: github-client.js, and any module handling external input */ import logger from './logger.js'; /** * Sanitize string input for use in prompts and API calls * Why: Prevent prompt injection and ensure valid GitHub API input * * @param {string} input - Raw input string * @param {Object} options - Sanitization options * @param {number} options.maxLength - Maximum allowed length (default: 65536) * @param {boolean} options.allowNewlines - Allow newline characters (default: true) * @param {boolean} options.stripControlChars - Remove control characters (default: true) * @returns {string} - Sanitized string */ export const sanitizeInput = (input, { maxLength = 65536, allowNewlines = true, stripControlChars = true } = {}) => { if (typeof input !== 'string') { return ''; } let sanitized = input; // Strip control characters (except newlines/tabs if allowed) if (stripControlChars) { if (allowNewlines) { // Keep \n, \r, \t but remove other control chars sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ''); } else { // Remove all control characters including newlines sanitized = sanitized.replace(/[\x00-\x1F\x7F]/g, ' '); } } // Truncate to max length if (sanitized.length > maxLength) { sanitized = sanitized.substring(0, maxLength); logger.debug('sanitize - sanitizeInput', 'Input truncated', { originalLength: input.length, maxLength }); } return sanitized.trim(); }; /** * Sanitize PR title for GitHub API * Why: Titles have specific requirements (single line, reasonable length) * * @param {string} title - Raw title * @returns {string} - Sanitized title */ export const sanitizePRTitle = (title) => { return sanitizeInput(title, { maxLength: 256, allowNewlines: false, stripControlChars: true }); }; /** * Sanitize PR body/description for GitHub API * Why: Body can be longer but still needs control char removal * * @param {string} body - Raw body text * @returns {string} - Sanitized body */ export const sanitizePRBody = (body) => { return sanitizeInput(body, { maxLength: 65536, // GitHub's limit is 65536 chars allowNewlines: true, stripControlChars: true }); }; /** * Sanitize array of strings (labels, reviewers) * Why: Ensure valid GitHub usernames/label names * * @param {Array<string>} items - Array of strings to sanitize * @param {RegExp} validPattern - Pattern for valid items (default: alphanumeric + hyphen + dot) * @returns {Array<string>} - Sanitized array */ export const sanitizeStringArray = (items, validPattern = /^[\w.-]+$/) => { if (!Array.isArray(items)) { return []; } return items .filter(item => typeof item === 'string') .map(item => item.trim()) .filter(item => item.length > 0 && item.length <= 100) .filter(item => validPattern.test(item)); }; /** * Sanitize GitHub username * Why: Usernames have specific format requirements * * @param {string} username - Raw username * @returns {string|null} - Sanitized username or null if invalid */ export const sanitizeUsername = (username) => { if (typeof username !== 'string') { return null; } const trimmed = username.trim().replace(/^@/, ''); // Remove leading @ // GitHub username rules: alphanumeric + hyphen, 1-39 chars, no consecutive hyphens if (!/^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(trimmed)) { return null; } if (trimmed.length > 39 || trimmed.includes('--')) { return null; } return trimmed; }; /** * Sanitize GitHub label name * Why: Labels have format requirements * * @param {string} label - Raw label name * @returns {string|null} - Sanitized label or null if invalid */ export const sanitizeLabel = (label) => { if (typeof label !== 'string') { return null; } const trimmed = label.trim(); // Labels can contain most chars except commas, max 50 chars if (trimmed.length === 0 || trimmed.length > 50) { return null; } // Remove problematic characters return trimmed.replace(/[,\x00-\x1F\x7F]/g, ''); }; /** * Sanitize branch name * Why: Branch names have git format requirements * * @param {string} branch - Raw branch name * @returns {string|null} - Sanitized branch or null if invalid */ export const sanitizeBranchName = (branch) => { if (typeof branch !== 'string') { return null; } const trimmed = branch.trim(); // Git branch name rules (simplified) if (!/^[\w./-]+$/.test(trimmed)) { return null; } // No consecutive dots, no ending with .lock if (trimmed.includes('..') || trimmed.endsWith('.lock')) { return null; } return trimmed; };