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
- 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)
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
- 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
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
- Invalid git ref: suspicious pattern
- Invalid git ref: too long
- Invalid argument: contains shell metacharacters
- Invalid command: contains shell metacharacters
- Cannot select from empty array
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)