Release v0.51.311 — Release KA (brick-wave: workspace Git RCE hardening #3769 + stale-snapshot sidebar visibility #3770) (#3776)
Some checks failed
Release & Docker / release (push) Has been cancelled
Some checks failed
Release & Docker / release (push) Has been cancelled
Brick-wave batch. #3769 (@Hinotoi-agent) hardens workspace Git config execution against repo-local RCE; #3770 (@ai-ag2026) keeps fuller pre-compression snapshots visible when _index.json is stale. Full suite 8176 passed, Codex SAFE, Opus SHIP IT. Co-authored-by: Hinotoi-agent; Co-authored-by: ai-ag2026
This commit is contained in:
@@ -33,9 +33,37 @@ _GIT_ENV_SCRUB_KEYS = (
|
||||
"GIT_CONFIG_SYSTEM",
|
||||
"GIT_CONFIG_COUNT",
|
||||
"GIT_CONFIG_PARAMETERS",
|
||||
"GIT_ASKPASS",
|
||||
"SSH_ASKPASS",
|
||||
"GIT_SSH",
|
||||
"GIT_SSH_COMMAND",
|
||||
)
|
||||
_GIT_ENV_SCRUB_PREFIXES = ("GIT_CONFIG_KEY_", "GIT_CONFIG_VALUE_")
|
||||
_HERMES_BRANCH_SWITCH_STASH_PREFIX = "hermes-webui branch switch"
|
||||
_GIT_HARDENED_CONFIG = (
|
||||
# Workspace Git operations can run against repositories provided by agents,
|
||||
# restored sessions, or mounted workspaces. Keep repo-local configuration
|
||||
# from turning read/status/fetch calls into host command execution.
|
||||
("core.fsmonitor", "false"),
|
||||
# Force the unmodified system ssh binary rather than clearing it — an empty
|
||||
# value would break legitimate ssh fetches, while "ssh" overrides any
|
||||
# repo-local core.sshCommand that points at an attacker helper.
|
||||
("core.sshCommand", "ssh"),
|
||||
("core.askPass", ""),
|
||||
("credential.helper", ""),
|
||||
("protocol.ext.allow", "never"),
|
||||
# Neutralize repo-local core.gitProxy, which specifies an external proxy
|
||||
# command reachable on `git fetch` against a git:// remote.
|
||||
("core.gitProxy", ""),
|
||||
)
|
||||
|
||||
|
||||
def _hardened_git_argv(args: list[str]) -> list[str]:
|
||||
argv = ["git"]
|
||||
for key, value in _GIT_HARDENED_CONFIG:
|
||||
argv.extend(["-c", f"{key}={value}"])
|
||||
argv.extend(args)
|
||||
return argv
|
||||
|
||||
|
||||
def workspace_git_destructive_enabled() -> bool:
|
||||
@@ -56,6 +84,7 @@ def _clean_git_env(extra: dict[str, str] | None = None) -> dict[str, str]:
|
||||
for key in list(env):
|
||||
if key.startswith(_GIT_ENV_SCRUB_PREFIXES):
|
||||
env.pop(key, None)
|
||||
env["GIT_TERMINAL_PROMPT"] = "0"
|
||||
return env
|
||||
|
||||
|
||||
@@ -140,7 +169,7 @@ def _run_git(
|
||||
run_env = _clean_git_env(env)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", *args],
|
||||
_hardened_git_argv(args),
|
||||
cwd=str(cwd),
|
||||
shell=False,
|
||||
capture_output=True,
|
||||
@@ -1047,12 +1076,12 @@ def _selected_temp_index_env(ctx: GitContext, specs: list[str]) -> tuple[dict[st
|
||||
def _selected_files(ctx: GitContext, paths: Iterable[str]) -> tuple[list[str], list[str], list[dict]]:
|
||||
requested = _clean_paths(paths)
|
||||
requested_specs = [_repo_rel(ctx, path) for path in requested]
|
||||
workspace_paths = [_workspace_rel(ctx, spec) or path for spec, path in zip(requested_specs, requested)]
|
||||
workspace_paths = [_workspace_rel(ctx, spec) or path for spec, path in zip(requested_specs, requested, strict=True)]
|
||||
status = git_status(ctx.workspace)
|
||||
by_path = {f["path"]: f for f in status.get("files", [])}
|
||||
specs: list[str] = []
|
||||
selected = []
|
||||
for path, repo_rel in zip(workspace_paths, requested_specs):
|
||||
for path, repo_rel in zip(workspace_paths, requested_specs, strict=True):
|
||||
state = by_path.get(path)
|
||||
if not state:
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user