mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
The in-process atomic_write created its temp 0o666 (umask-filtered, 0644 under 022), wrote the new contents, and only then chmod-ed it to the target's mode, so a rewritten 0600 file's new contents sat world-readable in the target's directory until the chmod. The shell path it replaces used mktemp's 0600 temp, which never exposed them. An existing target's temp is now created 0o600 and fchmod-ed to the target's mode before the write (best effort, as before: a failed fchmod leaves it 0600, never wider). A new target keeps the umask default (O_CREAT 0o666), the same mode the shell path's chmod "=rw" gave it. Probe (mode of the temp at the first write, umask 022, existing 0600 target): 0o644 on f980eca8e4, 0o600 now; final modes unchanged (0600 existing, 0644 new).
84 lines
3.6 KiB
Python
84 lines
3.6 KiB
Python
"""Tests for write_file post-write content verification (verified flag)."""
|
|
|
|
import json
|
|
from unittest.mock import patch as mock_patch
|
|
|
|
import pytest
|
|
|
|
from tools.file_tools import write_file_tool
|
|
|
|
@pytest.fixture
|
|
def workdir(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
|
|
return tmp_path
|
|
|
|
class TestWriteVerification:
|
|
def test_successful_write_reports_verified(self, workdir):
|
|
f = workdir / "out.txt"
|
|
r = json.loads(write_file_tool(str(f), "hello verified world\n", task_id="t-wv"))
|
|
assert r.get("bytes_written") == len("hello verified world\n")
|
|
assert r.get("verified") is True
|
|
|
|
def test_unicode_content_verified(self, workdir):
|
|
f = workdir / "uni.txt"
|
|
content = "línea → uno · ✓\n"
|
|
r = json.loads(write_file_tool(str(f), content, task_id="t-wv"))
|
|
assert r.get("verified") is True
|
|
|
|
def test_crlf_preservation_still_verifies(self, workdir):
|
|
# Existing CRLF file: write_file converts LF content to CRLF before
|
|
# writing; verification hashes the shim-adjusted content, so it must
|
|
# still report verified.
|
|
f = workdir / "win.txt"
|
|
f.write_bytes(b"old line\r\n")
|
|
# Establish a full-read baseline: write_file refuses to overwrite
|
|
# existing files the task has never read (stale-write guard).
|
|
from tools.file_tools import read_file_tool
|
|
json.loads(read_file_tool(str(f), task_id="t-wv"))
|
|
r = json.loads(write_file_tool(str(f), "new line\nsecond\n", task_id="t-wv"))
|
|
assert "error" not in r
|
|
assert r.get("verified") is True
|
|
assert b"\r\n" in f.read_bytes()
|
|
|
|
def test_hash_mismatch_is_hard_error(self, workdir, monkeypatch):
|
|
monkeypatch.setenv("HERMES_NATIVE_FILE_READ", "0") # the shell sha256sum transport
|
|
f = workdir / "bad.txt"
|
|
import tools.file_operations as fo
|
|
real_sha = fo.hashlib.sha256
|
|
|
|
class _WrongHash:
|
|
def __init__(self, *a, **k):
|
|
self._h = real_sha(b"different content entirely")
|
|
def hexdigest(self):
|
|
return self._h.hexdigest()
|
|
|
|
with mock_patch.object(fo.hashlib, "sha256", _WrongHash):
|
|
r = json.loads(write_file_tool(str(f), "actual content\n", task_id="t-wv"))
|
|
assert "error" in r
|
|
|
|
@pytest.mark.platforms("posix") # the native write path is POSIX-only (_native_read_enabled)
|
|
def test_native_hash_mismatch_is_hard_error(self, workdir):
|
|
f = workdir / "bad_native.txt"
|
|
with mock_patch("tools.file_operations_native.sha256_of", return_value="0" * 64):
|
|
r = json.loads(write_file_tool(str(f), "actual content\n", task_id="t-wv3"))
|
|
assert "error" in r and "verification failed" in r["error"]
|
|
|
|
def test_verification_failure_never_breaks_write(self, workdir, monkeypatch):
|
|
# sha256sum unavailable/failing -> verified omitted, write still ok.
|
|
monkeypatch.setenv("HERMES_NATIVE_FILE_READ", "0") # the shell sha256sum transport
|
|
f = workdir / "ok.txt"
|
|
import tools.file_operations as fo
|
|
|
|
real_exec = fo.ShellFileOperations._exec
|
|
|
|
def flaky_exec(self, cmd, **kw):
|
|
if "sha256sum" in cmd:
|
|
raise RuntimeError("no hash binary")
|
|
return real_exec(self, cmd, **kw)
|
|
|
|
with mock_patch.object(fo.ShellFileOperations, "_exec", flaky_exec):
|
|
r = json.loads(write_file_tool(str(f), "content lands anyway\n", task_id="t-wv2"))
|
|
assert "error" not in r
|
|
assert f.read_text() == "content lands anyway\n"
|
|
assert "verified" not in r or r.get("verified") is None
|