{"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":"Error","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/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/ruvector/diff-classifier.ts#L355-L391","documentation":"Thrown by validateGitRef(ref) when ref contains any character outside the safe set [a-zA-Z0-9_\\-./~^@]. This is the first of three guards in getGitDiffNumstat's input validation, designed to block shell metacharacters before ref is passed to git via execFileSync. Even though execFileSync uses an args array (no shell), the denylist is defense-in-depth against argument-injection vectors. Refuses hard.","triggerScenarios":"Ref contains spaces, semicolons, pipes, backticks, $, (), {}, [], angle brackets, quotes, backslashes, commas, colons, or any other punctuation; a ref like 'origin/main;rm -rf /' or 'HEAD & cmd'; a ref built by string concatenation that picked up a stray space or newline.","commonSituations":"User-supplied ref from an HTTP query param or webhook payload passed unfiltered; ref read from a config file with trailing whitespace/newline; branch name containing a slash-separated team prefix that happens to include a blocked char; Windows ref string with a backslash path separator.","solutions":["Strip/trim the ref before validation and reject anything containing whitespace.","If you allow user input, validate against an explicit allowlist of known refs (git branch --list output) rather than passing raw.","Normalize backslashes to forward slashes on Windows BEFORE validation.","Never build the ref by concatenating untrusted segments — use a fixed schema."],"exampleFix":"// before\nconst files = getGitDiffNumstat(req.query.ref);\n\n// after — sanitize then validate\nconst ref = String(req.query.ref ?? 'HEAD').trim().replace(/\\\\/g, '/');\nif (!/^[a-zA-Z0-9_\\-./~^@]+$/.test(ref)) {\n  throw new Error('invalid git ref');\n}\nconst files = getGitDiffNumstat(ref);","handlingStrategy":"validation","validationCode":"function sanitizeGitRef(ref: unknown): string {\n  const s = String(ref ?? 'HEAD').trim().replace(/\\\\/g, '/');\n  if (!/^[a-zA-Z0-9_\\-./~^@]+$/.test(s)) {\n    throw new Error(`ref contains unsafe characters: ${JSON.stringify(s)}`);\n  }\n  return s;\n}\n\nconst safe = sanitizeGitRef(req.query.ref);\nconst files = getGitDiffNumstat(safe);","typeGuard":"function isSafeGitRef(ref: string): boolean {\n  return typeof ref === 'string' && /^[a-zA-Z0-9_\\-./~^@]+$/.test(ref) && ref.length <= 256;\n}","tryCatchPattern":"try {\n  return getGitDiffNumstat(ref);\n} catch (e) {\n  if (/Invalid git ref/.test(String(e))) {\n    // Refused on purpose — do NOT mutate and retry. Surface to caller.\n    throw new Error(`rejected ref ${JSON.stringify(ref)}`);\n  }\n  throw e;\n}","preventionTips":["Treat any user-supplied ref as untrusted — validate against the safe-character regex yourself.","Prefer an allowlist of known refs (output of git branch --list) for user-facing inputs.","Strip whitespace and normalize backslashes BEFORE validation.","Never concatenate untrusted segments into a ref string."],"tags":["security","git","input-validation","command-injection","defense-in-depth"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}