Releases: gl0bal01/pai-hermes
v0.1.3 — multi-model review hardening
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.shv0.1.2 — Security review hardening
Summary
Addresses full-tree security review of 0.1.1: 2 HIGH + 5 MEDIUM + 5 LOW + 1 INFO. No CRITICAL.
All tests pass:
- bats: 18/18 (was 15, +3 regressions for M4 / M2 / M5)
- pytest: 27/27
- shellcheck: clean on
install.sh,uninstall.sh,bin/pai-accept-guard
HIGH
- M4 —
bin/pai-accept-guardheredoc command injection in arc markdown writer. Unquoted<<EOFshell-expanded${LOG}(untrusted upstream commit subjects) at write time; a malicious$(cmd)would execute. Replaced with quotedprintfper line; fence changed from triple-backtick to~~~to reduce markdown-break risk. - H1 —
install.shrollback gap.ALREADY_PRESENTbranch deleted backup before YAML validate; unexpected patcher output was non-fatal. Now: validate-then-delete in every branch; any unexpected output is a fatal rollback. - H2 —
pai-accept-guardenv-var path trust under sudo. WithEUID=0,PAI_PATHS_ENV=/etc/shadowwould overwrite. Refuses non-canonical paths when root;readlink -fsymlink-escape check.
MEDIUM
- M1 —
docs/INSTALL.mdcurl | bashreplaced with download →sha256sum -c→lessreview →bashpattern. - M2 — Lock file moved from
/tmpto$XDG_RUNTIME_DIR/pai-accept/lock(mode 700); refuses pre-existing symlink at lock path. - M3 — Trap chain replaced with
CLEANUP=()accumulator; exit code preserved. - M5 —
tools/cost_check.pysnapshot path:resolve()+~/.hermes/allowlist +O_NOFOLLOWopen.
LOW
- L1 —
install.shln -snf(atomic) replaces-L/-erace. - L2 —
uninstall.shcp --no-dereference; refuses symlink backup target. - L3 —
skills/pai-pulse/SKILL.mdadds Safety section:jq -n --argmandatory. - L4 —
docs/INSTALL.mdregex-on-YAML antipattern replaced with PyYAML. - L5 —
docs/INSTALL.mdstale cron yaml refs removed; bats count 13→15.
INFO
- I1 —
set -uo pipefail→set -euo pipefailinpai-accept-guard.
Also in this release
docs/HERMES_CONTRACT.md— operating contract for pai-hermes skills + cron, distilled from gl0bal01/contract-agentsAGENTS_CONTRACT.mdv2.0 (MIT) §1/§4/§5/§6/§7 (Scope, Evidence, Approval, Security, Verification). Adapted for the Hermes single-agent + skills + cron runtime.
Known limitations
- I3 (
SSH_CONNECTIONenv spoofable by local unprivileged user) is a design choice perCLAUDE.md— the SSH gate prevents Hermes-via-prose bypass, not local-shell attacks. - Anthropic admin-API spend fetch still deferred (carried from 0.1.1).
- Live Hermes integration runtime still unverified.
Full diff: v0.1.1...v0.1.2
v0.1.1 — review-driven fixes
Addresses external code review of 0.1.0. All 5 must-fix + 6 missing items resolved.
Fixed
- cost_check.py pct() zero-falsy bug —
0%genuine no longer falls through to utilization fallback. - install.sh / uninstall.sh — regex-on-YAML replaced with PyYAML (
safe_load/safe_dump). Pre-edit.bakbackup + post-edit parse validation + automatic rollback on failure. - Cron pivot — 3 yaml files deleted. Real Hermes cron is JSON in
~/.hermes/cron/jobs.jsonmanaged by Hermes'scronjobtool. Replaced withcron/README.mddocumenting manual prompt-based registration via Hermes. bin/pai-accept-guard(NEW) — shell-level enforcer of SSH-only constraint that was previously markdown-only. Refuses non-SSH (exit 77), validates inputs (exit 65), flock + timeout (exit 75), atomic paths.env + proposal mutation.- CLAUDE.md skill-chaining rule — clarified to permit DAG composition via prose hints. Hermes is orchestrator, skills are leaves, no cycles.
- README + docs/INSTALL.md — placeholder
<you>URLs replaced withgl0bal01.
Added
tests/test_cost_check.py(27 pytest tests) — pct edge cases, classify alert/block precedence, compose_voice silence + staleness, cache parse failures, end-to-end smoke.tools/cost_check.pystaleness check — cache mtime read; output gainscache_age_seconds+cache_stale; voice alert prepends "stale" warning when >15min old..github/workflows/test.ymlCI — shellcheck + bats + pytest + guard SSH-refusal smoke on push/PR.
Removed
api_spend_month_usdfrom output schema (was declared, never computed — honest removal until admin-key fetch lands).- 3 obsolete cron yaml files.
Test status
- 27/27 pytest ✓
- 15/15 bats ✓
- shellcheck clean ✓
pai-accept-guard fake-id(no SSH env) → exit 77 ✓
Known limitations (still in 0.1.1)
- Hermes cron job registration is manual prompt-based (see
cron/README.md). - Anthropic admin-API spend fetch deferred — removed rather than stubbed.
- Live Hermes integration runtime still unverified.
Full diff: v0.1.0...v0.1.1
v0.1.0 — Hermes Agent bridge for PAI ecosystem
First public release of pai-hermes — a bridge that makes Hermes Agent PAI-ecosystem-aware via 7 skills + 3 cron jobs.
What's in this release
7 SKILL.md routes (drop into Hermes `external_dirs`):
- `omc` — Claude Code harness routing (ralph/team/autopilot/ultrawork/ask)
- `pai-pulse` — Pulse `/notify` TTS POST wrapper (ElevenLabs server-side)
- `pai-watch` — `git fetch` × 4 sources + impact-score + JSON proposal writer
- `pai-doctor` — PAI ecosystem health probes (Pulse, Tailscale, paths, OMC CLI)
- `pai-accept` — SHA pin in paths.env + arc review markdown. SSH-only
- `pai-cost-tracker` — Claude 5h/7d usage % + voice alert at threshold (reads PAI canonical usage cache)
- `pai-statusline-banner` — daily 18:00 mobile digest
3 Hermes cron yaml (zero-AI-cost jobs):
- `pai-watch` hourly
- `pai-cost-tracker` hourly
- `pai-statusline-banner` daily 18:00
Tooling:
- Idempotent `install.sh` / `uninstall.sh` (Python-driven config patcher)
- `tools/cost_check.py` (PAI usage cache parser → voice alert text)
- `tests/skill-format.bats` (13 tests, all green)
Docs:
- `README.md`, `CLAUDE.md` (agent briefing), `docs/INSTALL.md`, `docs/ARCHITECTURE.md`
Built atop
- Hermes Agent (Nous Research, MIT)
- Personal_AI_Infrastructure v5.0.0 (Miessler, MIT)
- oh-my-claudecode v4.13.7+
- pai-anywhere v0.2.0
Install
```bash
git clone https://github.com/gl0bal01/pai-hermes ~/.hermes/pai-hermes
cd ~/.hermes/pai-hermes
./install.sh
```
See docs/INSTALL.md for full VPS deploy walkthrough.
License
MIT. AGPL boundary documented in `CLAUDE.md` for safe interaction with `pai-collab` (AGPL-3.0).
Known limitations (v0.1.0)
- Skills written, integration runtime untested on Hermes (PRs welcome to validate)
- Hermes cron yaml format guessed from `~/.hermes/cron/` examples — needs schema verification
- `tools/cost_check.py` reads PAI usage cache format — verify against your `~/.claude/PAI/MEMORY/STATE/usage-cache.json`
- No CI workflow yet