Skip to content

release: v0.8.4 — exclude_paths fix + Hermes + policy compat#161

Merged
Raftersecurity merged 12 commits into
prodfrom
main
Jun 3, 2026
Merged

release: v0.8.4 — exclude_paths fix + Hermes + policy compat#161
Raftersecurity merged 12 commits into
prodfrom
main

Conversation

@Rome-1
Copy link
Copy Markdown
Collaborator

@Rome-1 Rome-1 commented Jun 2, 2026

Ship to prod

Promotes mainprod. The publish.yaml workflow fires on this merge and will publish v0.8.4 to npm (via OIDC Trusted Publishing), PyPI (with --skip-existing idempotency), and ClawHub, then create the v0.8.4 GitHub Release (which is what GitHub Marketplace picks up to republish the root /action.yml listing entry), then smoke-test against the live registries.

What ships in this release

Headline impact: the customer's .rafter.yml triage flow is fully working on local scans now. Backend's matching work for remote scans is still in flight.

Fixed

Changed

Added

Fixed (small follow-on after #155)

CHANGELOG note: the release chore (#155) was cut against main at the time of #154, so the file-based CHANGELOG.md doesn't yet mention #156 and #157 — they'll be backfilled into the v0.8.5 entry or amended into the v0.8.4 entry depending on what's tidier. Not blocking the release.

Pre-flight already green on main

Post-merge verification plan

Phase 1 + 2 — autonomous, ~5 min:

🤖 Generated with Claude Code

Rome-1 and others added 12 commits May 31, 2026 23:21
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>
…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 Raftersecurity merged commit 570f046 into prod Jun 3, 2026
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants