Release v0.51.303 — Release JS (stage-p1a — cron toggle + config var expansion + git-discard hardening) (#3756)
Some checks failed
Release & Docker / release (push) Has been cancelled

* fix(cron): toggle run output rows instead of re-fetching when already open (#3732)

_loadRunContent() only ever expanded, so clicking an already-open cron run row
re-fetched its content pointlessly. It now toggles: an open row collapses (clears
the expansion state + resets the toggle button) and returns early, avoiding the
redundant API call.

Co-authored-by: mysoul12138 <mysoul12138@users.noreply.github.com>

* feat(config): expand ${VAR} references in config.yaml at load time (#3736)

hermes-agent already expands ${ENV_VAR} in config.yaml, but the WebUI's own
loader stored the raw dict, leaving literal ${...} strings. Recursively expand
${VAR} against os.environ on both config load paths (reload_config and
_load_yaml_config_file); unset vars are left untouched (${VAR} preserved).

Co-authored-by: Carry00 <Carry00@users.noreply.github.com>

* fix(security): anchor untracked-file deletes in git_discard (#3702)

git_discard(delete_untracked=True) used raw shutil.rmtree / Path.unlink after a
separate safe_resolve_ws validation, leaving a validation-to-use symlink-swap
window. Route untracked deletes through the anchored helpers (rmtree_anchored /
unlink_anchored) so a swapped path component is rejected at delete time; preserve
the prior missing_ok tolerance for benign concurrent-removal races. Adds
regression coverage for both the symlink-swap block and the concurrent-missing case.

Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>

* docs(changelog): stamp v0.51.303 — Release JS (stage-p1a #3732 #3736 #3702)

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: mysoul12138 <mysoul12138@users.noreply.github.com>
Co-authored-by: Carry00 <Carry00@users.noreply.github.com>
Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>
This commit is contained in:
nesquena-hermes
2026-06-06 17:24:18 -07:00
committed by GitHub
parent bf088cbbc4
commit 4580f58496
5 changed files with 117 additions and 8 deletions

View File

@@ -9,7 +9,6 @@ from __future__ import annotations
import difflib
import os
import shutil
import subprocess
import tempfile
import threading
@@ -18,7 +17,7 @@ from dataclasses import dataclass
from pathlib import Path
from typing import Iterable
from api.workspace import safe_resolve_ws
from api.workspace import rmtree_anchored, safe_resolve_ws, unlink_anchored
GIT_TIMEOUT = 5
@@ -978,9 +977,16 @@ def git_discard(workspace: str | Path, paths: Iterable[str], *, delete_untracked
raise GitWorkspaceError("Untracked files require delete_untracked=true")
target = safe_resolve_ws(ctx.workspace, workspace_rel)
if target.is_dir():
shutil.rmtree(target)
rmtree_anchored(ctx.workspace, target)
else:
target.unlink(missing_ok=True)
try:
unlink_anchored(ctx.workspace, target)
except FileNotFoundError:
# Preserve the previous Path.unlink(missing_ok=True)
# behavior for benign races where another process
# removes the untracked file after git_status() has
# reported it but before this discard reaches unlink.
pass
continue
_run_git(ctx, ["restore", "--worktree", "--", repo_rel], check=True)
return git_status(workspace)