release: v0.8.4 — exclude_paths fix + Hermes + policy compat#161
Merged
Conversation
Adds Hermes to the supported-platforms list (now: Claude Code, Codex CLI, OpenClaw, Gemini CLI, Cursor, Windsurf, Continue.dev, Aider, Hermes). MCP-only v0 mirroring how Gemini and Continue.dev were initially shipped. Hooks (preToolUse/postToolUse equivalents) deferred to a follow-on bead pending confirmation Hermes exposes a hook surface. Both runtimes: - `installHermesMcp()` / `_install_hermes_mcp()` — merges Rafter into `~/.hermes/config.yaml` under `mcp_servers.rafter` (snake_case, unlike Cursor/Windsurf which use mcpServers camelCase). Existing servers and other top-level YAML keys preserved. Non-dict mcp_servers is coerced to a dict rather than crashing. Unparseable YAML triggers a warning + fresh-config behavior matching the Aider installer (consistent UX). - `--with-hermes` flag wired end-to-end: option declaration → detection (~/.hermes) → want resolution (excluded from --all in --local since Hermes has no project-local config story) → warning for "requested but not detected" → detected[] entry → install call site → agent.environments.hermes.enabled config flag → dry-run plan entry → final-message platform hint. Recipe at recipes/hermes.md covers automatic + manual setup, documents the four MCP tools Hermes gets (scan_secrets, evaluate_command, read_audit_log, get_config), and warns about YAML round-trip comment loss for hand-curated configs. Tests: 7 Node assertions (subprocess-driven against built dist) and 5 Python assertions (direct installer call) covering: fresh-config creation, existing-server preservation, idempotency, malformed-YAML recovery, array-shape mcp_servers coercion, undetected-warning, and dry-run plan rendering. Deferred (tracked in follow-on beads): - `agent verify` / `agent list` / `agent status` Hermes detection. - Hook support (no documented Hermes hook surface yet). - `--local` scope (Hermes lacks a project-local config story). Replaces the placeholder rf-01b reference in shared-docs/PLATFORM_PARITY_AUDIT.md:248 — that bead never existed; sable-gyw is the real tracking bead. Rafter agent review: GO, no findings. YAML safety verified (js-yaml v4+ DEFAULT_SCHEMA, Python safe_load). No new shell, no subprocesses, no network calls. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(agent-init): add Hermes platform support (sable-gyw)
* feat: add JSON output for agent status * refactor: rename gitleaks_available JSON field to betterleaks_available The field is assigned from the existing isBetterleaksAvailable()/ _betterleaks_available() helpers — the name simply lagged the gitleaks->betterleaks migration. Rename before it locks into the public agent-status JSON schema. No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: cettyTheDev <286532771+cettyTheDev@users.noreply.github.com> Co-authored-by: wren <romethorstenson@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…sable-c1c) (#154) Backend triage (hq-4tcey, closed) confirmed rafter-backend reads policy at .rafter/config.yml (subdir + config.yml) with exclude_paths / custom_patterns flat at the top level, while the CLI canonical is .rafter.yml with scan.* nested. Customers writing either shape get honored by only one tool — the customer's .rafter.yml worked locally (after sable-yz0) and silently no-op'd on the remote cloud scan. Bilateral alignment plan: - Backend adds .rafter.yml fallback with scan.* schema compat (their preferred direction). - CLI (this PR) reads .rafter/config.yml indefinitely and accepts the backend flat shape alongside the canonical nested form. No deprecation — both shapes supported permanently. Two changes in policy-loader.ts / policy_loader.py: 1. findPolicyFile / find_policy_file walks all four candidates in precedence order: .rafter.yml > .rafter.yaml > .rafter/config.yml > .rafter/config.yaml. The canonical dotfile wins if both shapes exist (matching prior CLI behavior for users who already wrote .rafter.yml). 2. mapPolicy / _map_policy accepts top-level `exclude_paths` and `custom_patterns` (backend flat shape) and folds them into policy.scan.* — but only if the nested form wasn't already set. Nested form takes precedence on collision. Top-level compat keys are added to VALID_TOP_LEVEL_KEYS so they don't trigger "Unknown policy key" warnings on validate. Tests: 8 Node + 7 Python new assertions covering subdir-only discovery, extension variants, dotfile-wins precedence, top-level schema accepted on both file paths, nested wins on collision, no warnings for compat keys. Broader regression sweeps (25 Python + 16 Node) still pass. Paired with sable-yz0 (PR #152), this unblocks customers writing either CLI-shape or backend-shape policy. Once rafter-backend adds the .rafter.yml fallback (their side of the bilateral fix), both tools honor either file in either shape. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…e-yz0) (#152) Customer report: planted fake Stripe keys in three scan.exclude_paths entries AND a non-excluded path; all three got flagged — exclude_paths silently ignored. Two root causes: 1. BetterleaksScanner.scanDirectory() never received excludePaths. The patterns engine got it, the betterleaks happy-path didn't — and `auto` (the default when the binary is on disk) picks betterleaks. Customer was running through the betterleaks path, which is why nothing was excluded. 2. The patterns engine's walker only matched excludePaths entries against single directory NAMES (`entry.name`). Customers writing `components/common/Mermaid.tsx` (file path) or `supabase/migrations/foo.sql` (multi-segment path) got no filtering at all. Fix: post-filter chokepoint after both engines (and after staged / diff aggregation), using path-aware semantics. Both runtimes: - Exact match: rel_path == pattern - Directory prefix: rel_path starts with pattern + "/". Trailing "/" on the pattern is normalized away (`scripts/` == `scripts`). - Dir-name anywhere: any segment of rel_path equals pattern. Preserves the historical RegexScanner walker behavior so existing `node_modules` policies still work for nested copies. - Glob: patterns containing `* ? [` run through minimatch (Node) / fnmatch (Python), with auto-anchor for relative globs. Customer's exact repro now passes on both engines: only safe/leaky.ts fires, all three excluded paths filtered, exit 1. Rafter agent review: PASS for merge, no critical/high. Two low-severity ReDoS observations (minimatch / fnmatch pattern-length cap as defense-in-depth) tracked in follow-up bead. Tests: 5 Node + 12 Python assertions, all green. Broader regression suites pass: Python test_agent_scan_history + test_agent_baseline (23 cases), Node baseline.test.ts (12 cases). Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Bundles the four PRs landed since v0.8.3: #151 (sable-gyw) — Hermes platform support #152 (sable-yz0) — scan.exclude_paths honored on both engines #153 — rafter agent status --json #154 (sable-c1c) — .rafter/config.yml + flat-shape schema compat Headline impact: the customer's .rafter.yml triage flow is now fully working on local scans — #152 makes exclude_paths enforcement actually fire on the default code path, and #154 lets either file (CLI dotfile or backend subdir) work with either schema. Bilateral remote-scan alignment still depends on rafter-backend's matching work (in flight). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Hermes install (--with-hermes), recipe, README, and dry-run plan already shipped MCP-only v0. This completes item (6) of sable-gyw: detection of an existing Hermes install across the three inspection commands, in both runtimes. Node: - verify.ts: checkHermes() — reads ~/.hermes/config.yaml (js-yaml safe load), asserts mcp_servers.rafter; registered in the verify results. - status.ts: Hermes added to the mcpAgents table + detectAgents() (JSON agents_detected). - components.ts: hermes.mcp ComponentSpec (list/enable/disable) mirroring the continueMcp install/uninstall shape but on the snake_case mcp_servers YAML. Python parity (agent.py + agent_components.py): _check_hermes, mcp_agents/_detect_agent_platforms entries, _hermes_mcp ComponentSpec. Tests: 7 Node detection tests (verify/status/list, --json shapes) and 6 Python tests (check + detect + component install/uninstall round-trip). Deserialization uses yaml.safe_load (Python) / js-yaml 4.1.1 load (safe schema); paths are fixed home()/.hermes literals; the written MCP entry is static. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chore(release): bump to v0.8.4
…le-1vq) rf-zgwj moved the OpenClaw skill to ~/.openclaw/workspace/skills/rafter-security/ SKILL.md and strips the legacy ~/.openclaw/skills/rafter-security.md flat file. `agent verify` and the installer were updated; `agent status` and the Python SkillManager were not. Effect: after a correct `agent init --with-openclaw`, `agent status` reported "detected but skill missing" (false negative) while `agent verify` reported it installed, and `audit-skill` saw openClawAvailable=false. Fixes: - Node status.ts: detect via SkillManager (canonical path) with a legacy-file migration hint; matches verify. - Node init.ts: success message now prints the real destination (result.destPath) instead of the hardcoded legacy path. - Python SkillManager: ported to the canonical ClawHub paths in parity with node/src/utils/skill-manager.ts (get_openclaw_root, workspace skills dir, get_legacy_rafter_skill_path, has_legacy_rafter_skill). This also corrects audit-skill's openClawAvailable / rafterSkillInstalled fields. - Python status: same canonical detection + migration hint. Tests: 5 Node (status installed/missing/legacy/not-detected + status↔verify agree + init message path) and 8 Python (status reporting + SkillManager canonical paths and installer round-trip). 139 agent-suite Python tests green. Detection-only change: paths are fixed home()-relative literals (no user input, no traversal); no new deserialization, shell, or exec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ical-path-sable-1vq fix(agent): report OpenClaw via canonical ClawHub path in status (sable-1vq)
…le-gyw feat(agent): surface Hermes in verify/status/list detection (sable-gyw)
Co-authored-by: cettyTheDev <286532771+cettyTheDev@users.noreply.github.com>
Raftersecurity
approved these changes
Jun 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ship to prod
Promotes
main→prod. Thepublish.yamlworkflow fires on this merge and will publish v0.8.4 to npm (via OIDC Trusted Publishing), PyPI (with--skip-existingidempotency), and ClawHub, then create thev0.8.4GitHub Release (which is what GitHub Marketplace picks up to republish the root/action.ymllisting entry), then smoke-test against the live registries.What ships in this release
Headline impact: the customer's
.rafter.ymltriage flow is fully working on local scans now. Backend's matching work for remote scans is still in flight.Fixed
rafter secretshonors.rafter.yml scan.exclude_pathson both engines. Customer-reported P0. Betterleaks engine no longer silently dropsexclude_paths; pattern semantics now cover file paths, dir prefixes, dir-name-anywhere, and globs.Changed
.rafter/config.ymlindefinitely + accepts backend's flat-shape schema. Both file paths and both schemas accepted, with nestedscan.*winning on collision.Added
rafter agent init --with-hermeswrites~/.hermes/config.yaml(feat(agent-init): add Hermes platform support (sable-gyw) #151); detection now surfaces inrafter agent verify/status/list(feat(agent): surface Hermes in verify/status/list detection (sable-gyw) #156, the follow-on for the surface I'd explicitly deferred).rafter agent status --jsonwith a documented schema inshared-docs/CLI_SPEC.md.Fixed (small follow-on after #155)
rafter agent statusreports OpenClaw via the canonical ClawHub path. Small consistency fix; not user-blocking, but cleans up status output.Pre-flight already green on main
validate-release.yml(push-to-main trigger) — verified all four version sites at0.8.4:node/package.json,python/pyproject.toml, bothrafter-security-skill.mdfrontmatter copies.Post-merge verification plan
Phase 1 + 2 — autonomous, ~5 min:
curl https://registry.npmjs.org/@rafter-security%2Fcli/0.8.4→ 200curl https://pypi.org/pypi/rafter-cli/0.8.4/json→ 200gh release view v0.8.4npm i -g @rafter-security/cli@0.8.4 && rafter --version→0.8.4pip install rafter-cli==0.8.4 && rafter --version→0.8.4scripts/dev.ts+safe/leaky.tswith.rafter.yml scan.exclude_paths: [scripts/]→ expect exit 1, onlysafe/leaky.tsreported (proves fix(scan): honor .rafter.yml scan.exclude_paths on both engines (sable-yz0) #152). Then move the same policy to.rafter/config.ymlwith top-levelexclude_paths: [scripts/]→ same result (proves feat(policy): read backend's .rafter/config.yml + flat-shape compat (sable-c1c) #154).🤖 Generated with Claude Code