abhigyanpatwari/GitNexus · critical · ValueError
unsafe git object id: {orig_sha!r}
Error message
unsafe git object id: {orig_sha!r} What it means
Thrown by implementation_diff_digest before orig_sha is interpolated into a shell command string. Because the SHA is substituted into a `/bin/sh -c` pipeline (`git diff <sha> -- ... | sha256sum`), it must be a bare hex object id; anything else is a command-injection vector. The regex [0-9a-fA-F]{40,64} accepts both SHA-1 (40) and SHA-256 (64) git object ids and rejects everything else.
Source
Thrown at eval/workflow_bench/runner_artifacts.py:383
if not result.ok:
raise ManagedProcessError(command, result)
return result.stdout_tail
def _prepare_untracked_for_diff(sandbox: SandboxSession) -> None:
_sandbox_git(sandbox, ["add", "--intent-to-add", "-A"])
def implementation_diff_digest(
sandbox: SandboxSession,
orig_sha: str,
*,
prepare_untracked: bool = True,
) -> str:
"""Digest non-plan final work entirely inside the containment boundary."""
if not re.fullmatch(r"[0-9a-fA-F]{40,64}", orig_sha):
raise ValueError(f"unsafe git object id: {orig_sha!r}")
if prepare_untracked:
_prepare_untracked_for_diff(sandbox)
command = (
"/usr/bin/git -c core.fsmonitor=false diff --no-ext-diff --no-textconv --binary "
f"{orig_sha} -- . ':(exclude)docs/plans' ':(exclude).claude/skills' "
"| /usr/bin/sha256sum"
)
result = sandbox.run(
["/bin/sh", "-c", command],
timeout=60,
env=build_sandbox_environment(),
)
if not result.ok:
raise ManagedProcessError(command, result)
digest = result.stdout_tail.strip().split()[0] if result.stdout_tail.strip() else ""
if not re.fullmatch(r"[0-9a-f]{64}", digest):
raise RuntimeError("sandboxed git diff did not produce a SHA-256 digest")
return digestView on GitHub (pinned to d540b00184)
Solutions
- Resolve to a full object id before calling: `git rev-parse <ref>^{commit}` and pass the 40/64-char hex output.
- Never pass abbreviated SHAs, branch names, or HEAD to implementation_diff_digest.
- If you compute orig_sha from agent output, validate it with the same regex before passing it in.
- Treat any non-hex value as a programming error in the caller, not a runtime user input.
Example fix
// before — passing a short sha
implementation_diff_digest(sandbox, orig_sha='abc1234')
// after — resolve to a full object id first
full = run_checked(['git','-C',str(worktree),'rev-parse','HEAD^{commit}']).strip()
implementation_diff_digest(sandbox, orig_sha=full) Defensive patterns
Strategy: validation
Validate before calling
import re
def is_safe_object_id(sha: str) -> bool:
return isinstance(sha, str) and bool(re.fullmatch(r'[0-9a-fA-F]{40,64}', sha)) Type guard
import re
def is_full_sha(s: str) -> bool:
return isinstance(s, str) and bool(re.fullmatch(r'[0-9a-fA-F]{40,64}', s)) Try / catch
try:
implementation_diff_digest(sandbox, orig_sha)
except ValueError as e:
if 'unsafe git object id' in str(e):
# resolve the ref to a full object id and retry
raise
raise Prevention
- Always resolve refs/SHAs with `git rev-parse <ref>^{commit}` before passing to diff functions.
- Never pass abbreviated SHAs, branch names, or HEAD as orig_sha.
- Validate with is_full_sha at the boundary where untrusted input enters.
- Treat a non-hex orig_sha as a caller bug, not user input.
When it happens
Trigger: re.fullmatch(r'[0-9a-fA-F]{40,64}', orig_sha) fails — orig_sha is None, a shortened SHA, a ref name, contains uppercase G/z, or includes shell metacharacters.
Common situations: Caller passed a short SHA (git's abbreviated 7-12 chars) instead of the full id; passed a branch/tag ref; passed HEAD or a relative ref; the SHA came from an untrusted source and contains a newline/semicolon.
Related errors
- ${source}: branch name contains characters not allowed in a
- ${source}: branch name must not contain a backtick (it would
- {label} must be a real non-symlink directory: {path}
- {label} contains an unsafe path component: {relative}
- {label} must be a regular non-symlink file: {path}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/4ad4323259eeeefa.
Report an issue: GitHub.