{"record":{"id":"86893b0a56bbb3c5","repo":"ruvnet/ruflo","slug":"invalid-git-ref-suspicious-pattern","errorCode":null,"errorMessage":"Invalid git ref: suspicious pattern","messagePattern":"Invalid git ref: suspicious pattern","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/ruvector/diff-classifier.ts","lineNumber":378,"sourceCode":"// ============================================================================\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[] {\n  // SECURITY: Validate git ref to prevent command injection\n  validateGitRef(ref);\n\n  // Check cache first\n  const cacheKey = `numstat:${ref}`;","sourceCodeStart":360,"sourceCodeEnd":396,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/ruvector/diff-classifier.ts#L360-L396","documentation":"Thrown by validateGitRef when the ref contains '..' (path-traversal indicator) but does NOT match the allowed range/range-abbrev patterns like 'a..b' or 'a..b' shorthand. Git ranges legitimately use '..' and '...' (e.g. 'main..feature', 'main...upstream'), so the validator permits those specific shapes and refuses anything else with double-dot — defending against '../' path escapes and '..@{-1}' style tricks.","triggerScenarios":"Ref like '../secret' or '../../etc/passwd'; ref like 'foo..bar/baz/../x' mixing a range with traversal; ref like 'a..b..c' (more than one double-dot); a branch name someone literally created with '..' that isn't a clean range.","commonSituations":"Path-style ref ('../feature/foo') mistaken for a git range; ref constructed by joining path segments that included parent-dir traversal; branch name with '..' submitted via an API without normalization.","solutions":["If you intend a git range, use the canonical 'a..b' or 'a...b' form with simple branch names on each side.","If the ref is meant as a path, reject '..' at the application layer and never let it reach git.","Pre-validate with git rev-parse --verify <ref> in a sandbox to confirm the ref resolves before handing it to the diff tool."],"exampleFix":"// before\nconst files = getGitDiffNumstat('../main');\n\n// after — use canonical range syntax\nconst files = getGitDiffNumstat('main..feature');","handlingStrategy":"validation","validationCode":"function asGitRange(a: string, b: string): string {\n  const clean = (s: string) => s.replace(/[^a-zA-Z0-9_\\-.]/g, '');\n  return `${clean(a)}..${clean(b)}`; // canonical range, no traversal chars possible\n}\n\nconst ref = asGitRange(baseBranch, featureBranch);\n// passes validateGitRef because both sides match the range pattern\nconst files = getGitDiffNumstat(ref);","typeGuard":"function isSafeGitRange(ref: string): boolean {\n  return /^[a-zA-Z0-9_\\-]+\\.\\.\\.?[a-zA-Z0-9_\\-]+$/.test(ref);\n}","tryCatchPattern":"try {\n  return getGitDiffNumstat(ref);\n} catch (e) {\n  if (/suspicious pattern/.test(String(e))) {\n    // The ref contains '..' but isn't a clean range. Refuse rather than rewrite —\n    // path traversal attempts must not be 'fixed' by normalization.\n    throw new Error(`refused suspicious ref: ${JSON.stringify(ref)}`);\n  }\n  throw e;\n}","preventionTips":["Construct git ranges from two clean branch names with '..' or '...' — never join path-like segments.","Reject any ref containing '../' at the application boundary; do not attempt to normalize it.","For user input, resolve via git rev-parse --verify in a sandbox before passing to the diff tool."],"tags":["security","git","path-traversal","input-validation"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}