ruvnet/ruflo · error

Invalid git ref: contains unsafe characters

Error message

Invalid git ref: contains unsafe characters

What it means

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.

Solutions

  1. Pass a plain branch/tag/commit ref only: HEAD, HEAD~1, main, v1.2.3, origin/feature, or a full 40-char SHA
  2. Resolve user input to a SHA first (git rev-parse --verify) and pass the SHA
  3. Trim whitespace and reject empty strings on your side before calling
  4. If ranges are intended, use the narrow '..'/'...' forms the guard accepts (see the sibling 'suspicious pattern' error)

Example fix

// before
const files = getGitDiffNumstat(userInput); // 'origin/main:src/' → throws
// after
import { execSync } from 'node:child_process';
// resolve to a plain SHA once, then pass it
const sha = execSync(`git rev-parse --verify ${JSON.stringify(rawRef)}^{commit}`).toString().trim();
const files = getGitDiffNumstat(sha); // hex SHA always passes the charset check
Defensive patterns

Strategy: validation

Validate before calling

const SAFE_REF = /^[a-zA-Z0-9_\-./~^@]+$/;
function isSafeGitRef(ref: string): boolean {
  return typeof ref === 'string' && ref.length > 0 && ref.length <= 256 && SAFE_REF.test(ref);
}

Type guard

function asSafeGitRef(ref: string): string | null {
  return /^[a-zA-Z0-9_\-./~^@]+$/.test(ref) ? ref : null; // null → reject input before calling

Try / catch

try {
  files = getGitDiffNumstat(ref);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Invalid git ref')) {
    res.status(400).send('invalid git ref'); // user-input error, not a 500
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/0009d540dae1933e. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/ruvector/diff-classifier.ts:373

  recommendedReviewers?: string[];
}

// ============================================================================
// Optimized Git Diff Functions
// ============================================================================

// Cache for diff results (TTL-based)
const diffCache = new Map<string, { files: DiffFile[]; timestamp: number }>();
const CACHE_TTL_MS = 5000; // 5 seconds - short TTL since diffs change frequently

/**
 * Validate git ref to prevent command injection
 * Only allows safe characters: alphanumeric, -, _, /, ., ~, ^
 */
function validateGitRef(ref: string): void {
  // Block shell metacharacters and dangerous patterns
  if (!/^[a-zA-Z0-9_\-./~^@]+$/.test(ref)) {
    throw new Error(`Invalid git ref: contains unsafe characters`);
  }
  // Block multiple dots (path traversal)
  if (ref.includes('..') && !ref.match(/^[a-zA-Z0-9_\-]+\.\.\.?[a-zA-Z0-9_\-]+$/)) {
    if (!/^\w+\.\.[.\w]+$/.test(ref)) {
      throw new Error(`Invalid git ref: suspicious pattern`);
    }
  }
  // Max length check
  if (ref.length > 256) {
    throw new Error(`Invalid git ref: too long`);
  }
}

/**
 * Get git diff statistics using SINGLE combined command (optimized)
 * Replaces two separate git commands with one
 */
export function getGitDiffNumstat(ref: string = 'HEAD'): DiffFile[] {

View on GitHub (pinned to fa13ee4ad6)