Skip to content

Release 0.8.9 → prod - #176

Merged
Raftersecurity merged 11 commits into
prodfrom
main
Jun 20, 2026
Merged

Release 0.8.9 → prod#176
Raftersecurity merged 11 commits into
prodfrom
main

Conversation

@Rome-1

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

Copy link
Copy Markdown
Collaborator

Promotes mainprod (0.8.7 → 0.8.9). 9 commits; node + python versions matched at 0.8.9.

What ships

Notes

  • All five feature/fix PRs already merged + reviewed on main; this is the promotion.
  • rafter run (remote SAST) on this branch if the prod pipeline has RAFTER_API_KEY.

🤖 Generated with Claude Code

atoniolo76 and others added 9 commits June 17, 2026 14:11
…hook

`rafter hook pretool/posttool` read stdin with a 5s timeout, but on Node the
timeout only bounded OUTPUT latency, not process exit: a piped stdin with no
EOF stays in flowing mode and keeps the event loop alive, so after emitting its
decision at 5s the process hung indefinitely. Reproduced via `sleep 30 | rafter
hook pretool` (outer guard had to SIGKILL it). The fix pauses stdin and removes
listeners on the timeout/end/error paths so the loop drains and the process
exits (~5s, fail-open). Python already exited (daemon reader thread) — unchanged
in behavior.

Adds RAFTER_HOOK_STDIN_TIMEOUT_MS (ms, default 5000) as an operator safety
valve and to keep the regression tests fast. Both implementations parse it
identically: finite and > 0, else fall back to the default — so Infinity / nan
/ 0 / negative / garbage can never re-create the unbounded wait (Python guarded
with math.isfinite to match Node's Number.isFinite, preventing join(timeout=inf)).

Regression tests spawn the real hook with a never-closing stdin and assert
bounded exit + fail-open decision (Node), and assert the bound + env parsing
including non-finite rejection (Python). CLI_SPEC.md documents the bounded read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(hook): bound stdin read so a never-closing stdin can't wedge the hook
… of #171)

Review fixes for the added/modified-line secret scanner:

1. Parity blocker — Python parsed the diff with str.splitlines(), which also
   breaks on bare CR, form-feed, NEL and U+2028/2029. An added line whose
   content contained any of those was split onto a token that no longer starts
   with '+', so the secret tail was silently dropped on Python while Node (which
   splits on /\r?\n/) caught it. Switch Python to re.split(r"\r?\n", patch).

2. Added-line false-negative (both impls) — a line whose CONTENT starts with
   '++' serializes as '+++...' and was dropped by the `!startsWith("+++")` guard;
   a line whose content starts with '++ ' serializes as '+++ ...' and was
   misread as a file header, corrupting the current file + line counter. Headers
   only appear before the first @@ (newLine<=0), so guard '+++ '/'--- ' header
   detection on newLine<=0 and treat any '+' line inside a hunk as content.

Adds regression tests in both suites: '++'/'++ ' content captured, CRLF endings,
form-feed not split (parity), and rename-with-no-content ignored.

Co-Authored-By: Alessio Toniolo <atoniolo76@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update scan.ts to run secret dection on added/modified lines and remove old path for checking modified files.
…+Python parity (sable-7g7) (#170)

* feat(audit-skill): opt-in --deep engine via skill-scanner (PoC, sable-7g7)

Rebased onto current main; fix CliRunner stderr access in missing-tool test.

* feat(audit-skill): complete --deep engine — Node parity, installer, dir support (sable-7g7)

Brings the opt-in skill-scanner deep engine to merge-ready per Rome's three
design decisions:

1. Node↔Python parity — node/src/scanners/skill-scanner.ts mirrors the Python
   wrapper (same offline-safe argv + FORBIDDEN_FLAGS invariant, severity map,
   exit floor, and the cross-runtime deepScan JSON shape). --deep/--engine wired
   into the Node audit-skill command.
2. Directory targets — audit-skill now accepts a skill file OR directory; the
   deep engine scans the whole directory (its most thorough mode), quick scan
   reads SKILL.md. Both runtimes.
3. Managed installer — `agent update-skill-scanner` + `agent init
   --with-skill-scanner` (both runtimes). Isolated, version-pinned
   `uv tool install cisco-ai-skill-scanner==2.0.11` (pip --user fallback),
   list-form subprocess, no shell, no elevation. Opt-in only (not in --all).

The zero-dependency quick scan stays the default — this couples, not swaps.
Offline guarantee (no --use-llm/--use-virustotal/--use-aidefense/--use-behavioral)
is test-enforced in both suites.

Docs: deepScan schema + --deep/--engine + offline guarantee + update-skill-scanner
in CLI_SPEC.md; README deep-engine note; INTEGRATION_NOTES status updated.
Version 0.8.7 -> 0.8.8 (node + python matched).

Tests: node tests/skill-scanner.test.ts (14 + 3 binary-gated); python
tests/test_agent_audit_skill_deep.py (13 + 4 binary-gated) green. Also fixed a
pre-existing CliRunner stderr-access bug in the missing-tool test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(skill review): bring --deep to the current command + easier onboarding (sable-7g7)

Moves the deep engine onto `rafter skill review` (the non-deprecated command),
up to date with current architecture, and makes it easier to use.

- `rafter skill review <path|dir|github:/gitlab:/npm:|--installed> --deep`
  (alias --engine skill-scanner), both runtimes. Attaches the same `deepScan`
  block; actionable (medium+) deep findings escalate severity/worst and the
  exit code. Works across single-skill, multi-skill, and --installed modes
  (per-skill deepScan), and over remote-fetched temp dirs.
- Easier onboarding: when --deep is requested and the engine isn't installed,
  rafter now OFFERS TO INSTALL IT interactively (TTY) via the managed installer;
  non-interactive / --json falls back to the install hint + exit 2.
- `audit-skill --deep` stays as a deprecated back-compat alias.

Shared helpers ensureSkillScanner / ensure_skill_scanner + deepSeverityTier in
the scanner modules. Offline-flag invariant unchanged and still test-enforced.

Docs: CLI_SPEC `skill review` gains --deep/--engine + deepScan schema; README
points --deep at `skill review`. Tests: node tests/skill-review-deep.test.ts +
python tests/test_skill_review_deep.py (engine selection always; real deep scans
binary-gated). Fixed skill-review-remote.test.ts for the now-async runSkillReview.

Validated end-to-end with skill-scanner 2.0.11 installed: a planted malicious
skill flags prompt_injection + data_exfiltration (critical, exit 1) identically
on both runtimes; benign findings (FILE_MAGIC_MISMATCH etc.) map consistently.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(skill-scanner): add `rafter agent remove-skill-scanner` (easy uninstall)

The inverse of update-skill-scanner. Cleanly removes the optional deep engine.

- SkillScannerInstaller.uninstall() (both runtimes): tries `uv tool uninstall`
  first (how install prefers to set it up), then `pip uninstall -y` fallback —
  we don't durably record which path installed it. Idempotent: a success no-op
  when it isn't installed. List-form subprocess, fixed package name (no
  injection), 120s timeouts.
- `rafter agent remove-skill-scanner` command (Node + Python). Exit 0 removed /
  already absent; exit 1 if still on PATH afterward (installed by another tool).
- Skills + Rafter's own deps untouched.

Tests: build_uninstall_argv (uv + pip forms) and idempotent-when-absent, both
runtimes. Docs: CLI_SPEC + README. Dogfooded end-to-end (uninstall → reinstall).
update/install success message now points at `rafter skill review --deep`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…wire backend.apiKey (sable-q9to) (#173)

* fix(config): harden RAFTER_API_KEY handling — 0600, redaction, wire backend.apiKey (sable-q9to)

Three credential-handling gaps (both Node + Python; intended env/flag path was
already fine):

1. Plaintext, world-readable. ConfigManager.save() wrote ~/.rafter/config.json
   with the default umask (typically 0644). Now writes 0600 (dir 0700) and
   chmods an existing looser file on the next write — it can hold a backend key.

2. Unredacted echo. `config show`/`get`, the `config set` confirmation, and the
   MCP get_config tool + rafter://config / rafter://policy resources serialized
   the full config verbatim, handing any stored key to the terminal / MCP client.
   Added redactConfigSecrets / maskSecretValue (mask values under keys matching
   api_?key|token|secret|password|credential → "abcd****"); applied at every
   render path. Pure — the stored config is never mutated.

3. Dead credential path. backend.apiKey existed in the schema but resolveKey
   never read it. Wired it as the LOWEST-precedence source: --api-key flag >
   RAFTER_API_KEY env > global config backend.apiKey. Read only from the global
   ~/.rafter/config.json (load(), never loadWithPolicy/.rafter.yml — verified it
   does not merge backend.*), so a hostile repo cannot inject a key that
   redirects scans to another account.

Secure-design pass recorded on the bead: it's a bearer token, so 0600 + display
redaction is the right boundary (encrypting a local config just moves a KEK next
to the ciphertext); precedence mirrors gh/aws/npm.

Tests (both suites): redaction helpers, 0600 on fresh + tightened-on-rewrite,
and resolveKey precedence flag>env>config. Node 67 / Python 19 config tests
pass; broader config+mcp+api suites green. CLI_SPEC.md documents the resolution
order, redaction, perms, and the global-only trust boundary.

Note: Node key is backend.apiKey, Python is backend.api_key (dataclass
snake_case) — same value, per-language path; redaction regex covers both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(mcp): fix stale config-manager mock after redaction exports (sable-q9to)

The redaction work added redactConfigSecrets/isSecretConfigKey/maskSecretValue
to config-manager, which server.ts now imports — but the vi.mock in
mcp-server-integration.test.ts only stubbed ConfigManager, so those imports
resolved to undefined and 4 get_config/resource tests threw. Spread the real
module (keeping the pure redaction helpers real) and override only ConfigManager.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Patch bump for the opt-in --deep skill-review engine (sable-7g7) and the
RAFTER_API_KEY hardening (sable-q9to). node + python versions matched 0.8.8 -> 0.8.9;
CHANGELOG [0.8.9] section added.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The rafter-security-skill.md frontmatter version (Node + Python copies) was
left at 0.8.7 through the 0.8.8 and 0.8.9 package bumps, failing the
validate-release 'ClawHub skill version matches package version' gate on the
main→prod PR (#176). More importantly, publish-clawhub uses this frontmatter
version as the ClawHub release version — so deploying as-is would ship the skill
at a stale 0.8.7 while npm/PyPI publish 0.8.9 (cross-registry skew). Bump both to
match the package version.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n-0.8.9

fix(release): bump ClawHub skill resource versions to 0.8.9
@Raftersecurity
Raftersecurity merged commit 992fa49 into prod Jun 20, 2026
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