xai-org/grok-build · error
refusing unsafe fetch refspec
Error message
refusing unsafe fetch refspec
What it means
fetch_refspec_from_origin runs `git fetch origin <spec>` and first validates the spec with is_safe_fetch_refspec. A spec is safe only if it is a full 40/64-hex object id, or a git ref free of option-injection and refspec metacharacters ('-', ':', '*', '?', '[', backslash, whitespace, NUL, '..', '@{') and not a plausible abbreviated SHA. This error is a deliberate guard: it refuses to hand untrusted or malformed specs to git, preventing argument injection (specs starting with '-') and meaningless fetches of short SHAs, which are not remote refspecs.
Source
Thrown at crates/codegen/xai-grok-workspace/src/restore_fetch.rs:326
repo: &Path,
oid: &str,
is_shallow: bool,
) -> std::process::Command {
let mut cmd = git_command_locking();
cmd.current_dir(repo)
.args(targeted_fetch_args(oid, is_shallow))
.stdout(Stdio::null())
.stderr(Stdio::piped());
cmd
}
fn fetch_oid_from_origin(repo: &Path, oid: &str, timeout: Duration) -> Result<()> {
fetch_refspec_from_origin(repo, oid, timeout)
}
fn fetch_refspec_from_origin(repo: &Path, spec: &str, timeout: Duration) -> Result<()> {
if !is_safe_fetch_refspec(spec) {
bail!("refusing unsafe fetch refspec");
}
let is_shallow = is_shallow_repository(repo);
tracing::info!(
spec = %spec,
is_shallow,
timeout_secs = timeout.as_secs(),
"restore_fetch: targeted fetch"
);
let mut child = FetchChild::spawn(targeted_fetch_command(repo, spec, is_shallow))?;
let result = child.wait_success(timeout, spec);
if result.is_err() {
warn_leftover_git_locks(repo);
}
result
}
struct FetchChild {View on GitHub (pinned to bc7f02eddd)
Solutions
- Validate/normalize the spec before calling: use only full lowercase hex oids or plain ref names, and run is_safe_fetch_refspec on it first
- Replace abbreviated SHAs with full 40/64-char object ids (git rev-parse --verify <abbrev>^{commit}) or fetch the branch/tag that contains the commit
- Strip illegal characters/whitespace from the target at its source (config, manifest, or user input) and reject ranges/wildcards — they are not fetchable single refs
- If this fires on data you did not construct, treat it as suspicious input: log the raw target and refuse restore rather than loosening the safety check
Example fix
// before: abbreviated hex is not a valid origin refspec fetch_oid_from_origin(repo, "deadbee", timeout)?; // after: expand to the full oid or validate first let full_oid = resolve_full_oid(repo, "deadbee")?; // 40/64 hex chars assert!(is_safe_fetch_refspec(&full_oid)); fetch_oid_from_origin(repo, &full_oid, timeout)?;
Defensive patterns
Strategy: validation
Validate before calling
use crate::restore_fetch::is_safe_fetch_refspec;
fn ensure_safe_spec(spec: &str) -> Result<(), String> {
if is_safe_fetch_refspec(spec) { Ok(()) }
else { Err(format!("unsafe fetch refspec: {spec:?} (use a full oid or plain ref)")) }
} Type guard
fn is_fetch_safe(value: &str) -> bool {
let hex_ok = value.len() == 40 || value.len() == 64;
let is_full_oid = hex_ok && value.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b));
let plausible_short_sha = (4..40).contains(&value.len()) && value.bytes().all(|b| b.is_ascii_hexdigit());
is_full_oid || (!value.is_empty()
&& !value.starts_with('-')
&& !plausible_short_sha
&& !value.contains([':', '*', '?', '[', '\\', ' ', '\t', '\n', '\r'])
&& !value.contains("..")
&& !value.contains("{"))
} Try / catch
match fetch_oid_from_origin(repo, spec, timeout) {
Err(e) if e.to_string() == "refusing unsafe fetch refspec" => {
// never loosen the check; normalize the input instead
let normalized = normalize_to_full_oid_or_ref(repo, spec)?;
fetch_oid_from_origin(repo, &normalized, timeout)
}
other => other,
} Prevention
- Expand abbreviated SHAs to full oids (git rev-parse --verify <abbrev>^{commit}) before any fetch
- Sanitize user/config-supplied refs: strip whitespace, reject '-', ':', '*', '?', '[', backslash, '..', '@{', and empty strings
- Treat snapshot/manifest targets as untrusted input and validate with is_safe_fetch_refspec before fetch calls
- Never construct fetch specs by concatenating user input with options; pass plain ref names or full oids only
When it happens
Trigger: fetch_refspec_from_origin invoked (via fetch_oid_from_origin or fetch_checkout_target_if_missing) with a spec that fails is_safe_fetch_refspec: a dash-prefixed string, a refspec containing ':' or glob characters, a 4–39 or 41–63 character hex string (abbreviated SHA), an empty spec, or a ref containing spaces/'..'/'@{'.
Common situations: Upstream code or config passing abbreviated SHAs to fetch_oid_from_origin; restoring from untrusted snapshot metadata containing crafted targets (option-injection attempt); targets derived from user input like 'HEAD^..HEAD' or 'refs/heads/*'; whitespace-contaminated ref names from copied log output.
Related errors
- unsupported checkout target (need a full commit oid or a sim
- {what} must not be empty
- {what} '{value}' must not start with '-'
- invalid worktree id {:?}
- not a git repository: {}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/6f438ae7a7e260e4.
Report an issue: GitHub.