mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-04 07:31:58 +00:00
fix(update): reject symlink members in update ZIP
_update_via_zip downloads a source ZIP from GitHub and calls zipfile.ZipFile.extractall. The existing zip-slip path guard validates each member's path stays under tmp_dir, but does not check member type — so a ZIP containing a symlink member would still be materialized by extractall, and a symlink target could point outside the extracted tree (or to a sensitive system path). This isn't a high-likelihood threat for hermes-agent's actual GitHub source ZIPs (we don't ship symlinks), but the extractall path runs as the user's account and a compromised mirror could plant arbitrary files via the symlink → target → write chain. Reject any member whose Unix mode bits (upper 16 bits of external_attr) are S_IFLNK before extractall. Hermes source ZIPs contain only regular files and directories; a symlink member is unambiguously suspicious. Regression tests cover: symlink member rejection (raises ValueError, caught by the outer try/except as a clean SystemExit, no extraction), and the happy-path verification that a normal ZIP doesn't trigger the symlink reject message. Salvaged from PR #15881 by @codeblackhole1024. The remaining pieces of that PR were already on main or contradicted explicit design decisions: - config.yaml write-deny: already in agent/file_safety.py's control_file_names denylist (the modern guard); the proposed addition to build_write_denied_paths was the legacy path. - Quick commands danger detection: contradicts the explicit cli.py:8491-8492 comment 'shell=True is intentional: quick_commands are user-defined shell snippets from config.yaml — not agent/LLM controlled.' - Memory plugin shlex.split for dep checks: already on main (hermes_cli/memory_setup.py:133). Co-authored-by: teknium1 <127238744+teknium1@users.noreply.github.com>
This commit is contained in:
parent
5f20322d23
commit
bd2756dd22
2 changed files with 129 additions and 1 deletions
|
|
@ -7000,8 +7000,13 @@ def _update_via_zip(args):
|
|||
urlretrieve(zip_url, zip_path)
|
||||
|
||||
print("→ Extracting...")
|
||||
import stat as _stat
|
||||
with zipfile.ZipFile(zip_path, "r") as zf:
|
||||
# Validate paths to prevent zip-slip (path traversal)
|
||||
# Validate paths to prevent zip-slip (path traversal) AND reject
|
||||
# symlink members. A GitHub source ZIP for hermes-agent itself
|
||||
# should never contain symlinks — they'd point outside the
|
||||
# extracted tree and let an attacker who can compromise the
|
||||
# update mirror plant arbitrary files via the update path.
|
||||
tmp_dir_real = os.path.realpath(tmp_dir)
|
||||
for member in zf.infolist():
|
||||
member_path = os.path.realpath(os.path.join(tmp_dir, member.filename))
|
||||
|
|
@ -7012,6 +7017,13 @@ def _update_via_zip(args):
|
|||
raise ValueError(
|
||||
f"Zip-slip detected: {member.filename} escapes extraction directory"
|
||||
)
|
||||
# Unix mode lives in the upper 16 bits of external_attr;
|
||||
# mask to the file-type bits.
|
||||
mode = (member.external_attr >> 16) & 0o170000
|
||||
if _stat.S_ISLNK(mode):
|
||||
raise ValueError(
|
||||
f"ZIP contains unsupported symlink member: {member.filename}"
|
||||
)
|
||||
zf.extractall(tmp_dir)
|
||||
|
||||
# GitHub ZIPs extract to hermes-agent-<branch>/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue