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

  1. 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
  2. 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
  3. 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
  4. 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

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


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/6f438ae7a7e260e4. Report an issue: GitHub.