{"record":{"id":"0009d540dae1933e","repo":"ruvnet/ruflo","slug":"invalid-git-ref-contains-unsafe-characters","errorCode":null,"errorMessage":"Invalid git ref: contains unsafe characters","messagePattern":"Invalid git ref: contains unsafe characters","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/ruvector/diff-classifier.ts","lineNumber":373,"sourceCode":"  recommendedReviewers?: string[];\n}\n\n// ============================================================================\n// Optimized Git Diff Functions\n// ============================================================================\n\n// Cache for diff results (TTL-based)\nconst diffCache = new Map<string, { files: DiffFile[]; timestamp: number }>();\nconst CACHE_TTL_MS = 5000; // 5 seconds - short TTL since diffs change frequently\n\n/**\n * Validate git ref to prevent command injection\n * Only allows safe characters: alphanumeric, -, _, /, ., ~, ^\n */\nfunction validateGitRef(ref: string): void {\n  // Block shell metacharacters and dangerous patterns\n  if (!/^[a-zA-Z0-9_\\-./~^@]+$/.test(ref)) {\n    throw new Error(`Invalid git ref: contains unsafe characters`);\n  }\n  // Block multiple dots (path traversal)\n  if (ref.includes('..') && !ref.match(/^[a-zA-Z0-9_\\-]+\\.\\.\\.?[a-zA-Z0-9_\\-]+$/)) {\n    if (!/^\\w+\\.\\.[.\\w]+$/.test(ref)) {\n      throw new Error(`Invalid git ref: suspicious pattern`);\n    }\n  }\n  // Max length check\n  if (ref.length > 256) {\n    throw new Error(`Invalid git ref: too long`);\n  }\n}\n\n/**\n * Get git diff statistics using SINGLE combined command (optimized)\n * Replaces two separate git commands with one\n */\nexport function getGitDiffNumstat(ref: string = 'HEAD'): DiffFile[] {","sourceCodeStart":355,"sourceCodeEnd":391,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/ruvector/diff-classifier.ts#L355-L391","documentation":"validateGitRef() is the command-injection guard run before diff commands shell out to git (e.g., getGitDiffNumstat at diff-classifier.ts:389). It allows only [A-Za-z0-9_\\-./~^@]; any other character anywhere in the ref — space, colon, question mark, asterisk, bracket, quote, dollar, backtick, plus, comma, unicode — fails the regex and throws this error.","triggerScenarios":"Passing refs like 'origin/main:package.json' (path suffix), 'refs/heads/feature?' globs, user text with whitespace ('main '), shell fragments ('$(git rev-parse HEAD)'), or an empty string; any ref containing ':', '+', ' ', '*', '[', '\\'', '\"', or unicode.","commonSituations":"Feeding raw user input from a web form or CLI arg straight into diff stats; passing git URLs or refspecs where a plain ref belongs; copy-pasting refs that carry quoting or annotations; refs with a '+' (Gerrix-style) or non-ASCII branch names.","solutions":["Pass a plain branch/tag/commit ref only: HEAD, HEAD~1, main, v1.2.3, origin/feature, or a full 40-char SHA","Resolve user input to a SHA first (git rev-parse --verify) and pass the SHA","Trim whitespace and reject empty strings on your side before calling","If ranges are intended, use the narrow '..'/'...' forms the guard accepts (see the sibling 'suspicious pattern' error)"],"exampleFix":"// before\nconst files = getGitDiffNumstat(userInput); // 'origin/main:src/' → throws\n// after\nimport { execSync } from 'node:child_process';\n// resolve to a plain SHA once, then pass it\nconst sha = execSync(`git rev-parse --verify ${JSON.stringify(rawRef)}^{commit}`).toString().trim();\nconst files = getGitDiffNumstat(sha); // hex SHA always passes the charset check","handlingStrategy":"validation","validationCode":"const SAFE_REF = /^[a-zA-Z0-9_\\-./~^@]+$/;\nfunction isSafeGitRef(ref: string): boolean {\n  return typeof ref === 'string' && ref.length > 0 && ref.length <= 256 && SAFE_REF.test(ref);\n}","typeGuard":"function asSafeGitRef(ref: string): string | null {\n  return /^[a-zA-Z0-9_\\-./~^@]+$/.test(ref) ? ref : null; // null → reject input before calling","tryCatchPattern":"try {\n  files = getGitDiffNumstat(ref);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid git ref')) {\n    res.status(400).send('invalid git ref'); // user-input error, not a 500\n  } else throw e;\n}","preventionTips":["Treat this guard as your boundary check: mirror the charset regex on user input and reject early","Resolve free-form user input to a full SHA via git rev-parse before passing it in","Never accept URLs, refspecs with ':', or glob characters where a single ref is expected"],"tags":["git","security","input-validation","command-injection","diff"],"backgroundTag":"invalid-git-ref","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}