Addresses a 4-reviewer (codex / deepseek / glm / kimi) audit of 0.1.2.
Headline: the SSH-only gate is now actually forge-resistant. No CRITICAL.
Fixed (HIGH)
bin/pai-accept-guardSSH gate was forgeable (G1): the gate trustedSSH_*env vars (which an LLM controls in its own child env) plus a plainPAI_LOCAL_OVERRIDE=1bypass, so a prompt-injected Hermes could defeat the documented "never callable from a remote platform" guarantee. Replaced with a process-ancestry check (ansshd/sshd-sessionparent, walked via/proc) that the agent's process tree never has. Remote use is unchanged — a Tailscale SSH session satisfies it. The env bypass is gone; the only non-SSH escape hatch is a root-owned/etc/pai/local-accept.allow(mode 0600) a non-root process can't forge. New bats regressions for env-spoof refusal; CLAUDE.md/SKILL.md realigned to the enforced model.bin/pai-accept-guardcould wipe all SHA pins (G2/G3):grep -v … || trueconflated grep's no-match (rc 1) with a read error (rc ≥ 2), so an unreadablepaths.envmid-run replaced the file with a single line, dropping every otherPAI_*_SHApin. Rewritten withawk(rc 0 on no-match); a real error now aborts. The key filter is now a fixed-string prefix (was a grep regex, so a repo name containing.could strip unrelated keys). New pin-preservation regression.
Fixed (MEDIUM)
- Config patching extracted + made safe (H9/H7/H2/H5): the duplicated inline
python3 -cheredoc is nowtools/patch_hermes_config.py. ruamel round-trip preserves user comments/anchors (PyYAMLsafe_dumpdestroyed them — H7); writes are atomic (mkstemp+fsync+os.replace, re-parsed before swap — never truncate-in-place, H2); the config path comes from argv/env as aPath, never interpolated into Python source (H5). bin/pai-pulse-sendwrapper (S7): pai-pulse's safe JSON pattern was prose only. Added an enforcing wrapper — JSON built solely viajq --arg, Pulse URL restricted to loopback http (also closes the pai-doctor SSRF), length/NUL guards. SKILL.md now mandates it.pai-watchrepo-name validation (S6):git -C <name>ran on unvalidated$PAI_WATCH_SOURCESentries. The skill now enforces^[A-Za-z0-9._-]+$, confines the resolved path under$PAI_PROJET_ROOT, and setsGIT_CONFIG_GLOBAL=/dev/null.pai-accept-guardproposals-dir symlink escape (G7): the root-onlyreadlink -fescape check now also coversPROPOSALS_DIR, not justpaths.env.install.shsafety (H3/H1): single-flightflock+ ownership guard; refuse a symlink backup path. Arc-review fence escape (G5): untrusted commit subjects are rendered as an indented code block (a~~~in a subject can no longer break out).template_varsinjection removed (H4).
Fixed (LOW)
tools/cost_check.pystring-credit crash (P1):extract_metricspassed stringused_credits/monthly_limitstraight into arithmetic →TypeError, crashing the silent cron. Now coerced withfloat()+ try/except likepct().- Stale docs (S1–S4, S8–S12): dead
cron/*.yamlreferences replaced with the jobs.json flow; cost thresholds single-sourced oncost_check.py(5h alert 80 / block 95, 7d alert 85); removedapi_spend_month_usd/ANTHROPIC_ADMIN_API_KEY; pai-doctor probes target jobs.json and dropped the retiredbin/pai doctordelegation;HOME=$HOME(was/home/pai); README status → 0.1.3. - Installer polish (H6/H8/G6): true-atomic skill symlink (
ln -s … && mv -T);mktempbackup instead of a fixed.bakclobber;install -d -m 700for the lock dir (no mkdir+chmod TOCTOU).
Tooling
- New dependency:
ruamel.yaml(round-trip YAML) —install.shauto-installs it (pip--user, PEP-668 fallback) and fails loudly if unavailable. - Tests: +28 (guard env-spoof / pin-preservation / fence; cost string inputs; pulse wrapper injection / SSRF; install append-not-replace / comment-survival / H5). bats 40, pytest 32, shellcheck clean.
Install
# review first, then run
curl -fsSLO https://raw.githubusercontent.com/gl0bal01/pai-hermes/v0.1.3/install.sh && less install.sh && bash install.sh