Skip to content

Add react_on_rails:install_rsc_agent_guardrails: ship RSC agent-safety skill + hook into apps#4606

Merged
justin808 merged 11 commits into
mainfrom
jg/rsc-app-safety-installer
Jul 18, 2026
Merged

Add react_on_rails:install_rsc_agent_guardrails: ship RSC agent-safety skill + hook into apps#4606
justin808 merged 11 commits into
mainfrom
jg/rsc-app-safety-installer

Conversation

@justin808

@justin808 justin808 commented Jul 12, 2026

Copy link
Copy Markdown
Member

What

Ships an installable RSC "agent safety" guardrail for apps that use React on Rails Pro React Server Components. A new rake task (and the RSC generator) drops a Claude Code skill + an advisory hook into the host app's .claude/, so AI coding agents working in that app are steered away from the RSC API footguns surfaced in the recent RSC security audit.

This is the downstream half of a two-PR effort. The framework-internals half (a repo-local rsc-guardrails skill + payload-escaping regression test) is #4599.

Why

RSC spans a trusted-server → untrusted-client boundary. The audit (issues #4595, #4596, #4597) found the framework solid but identified usage footguns an app can hit: mounting rsc_payload_route without authentication, trusting server-component props, exposing the Node renderer, or leaking secrets/PII. Those live in app code, not the gem — so the guardrail has to ship into the app.

Changes

  • react_on_rails:install_rsc_agent_guardrails rake task — idempotently installs/updates the guardrail assets into the current app's .claude/. Safe to re-run after upgrades.
  • ReactOnRails::AgentGuardrails installer — copies the skill + hook and registers the hook in .claude/settings.json via a safe JSON merge (preserves existing hooks; refuses to touch malformed JSON and tells you to add the one line manually).
  • rsc-app-safety skill template — the RSC usage footguns + a per-change checklist, installed as .claude/skills/rsc-app-safety/SKILL.md.
  • App advisory hook template — a non-blocking PostToolUse warning when config/routes.rb mounts rsc_payload_route, or an RSC controller lacks a visible before_action. Installed as .claude/hooks/rsc-app-safety-check.sh.
  • RSC generator wiringrails g react_on_rails:rsc now runs the installer, so new RSC apps get the guardrails automatically.

Verification

  • New rspec agent_guardrails_spec.rb: 4/4 pass — fresh install, idempotency, safe merge into an existing settings.json, and raise-not-clobber on malformed JSON.
  • rsc_generator_spec.rb: passes with the generator wiring.
  • Rake task exercised end-to-end into a temp app (create → idempotent re-run).
  • App hook dry-run: warns on unauthenticated rsc_payload_route / RSC controller; silent otherwise. shellcheck clean; matching anchored on the absolute path (robust to symlinked//var checkouts).
  • rubocop clean; prettier --check clean.

Deliberately deferred (fast-follow)

A dedicated docs page — the rsc-app-safety skill and the rake task desc are self-documenting for now; a docs/ page would pull in the llms-full regeneration gate. Happy to add it in a follow-up if you want it in the docs site.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added RSC agent guardrails that install a Claude Code “rsc-app-safety” skill and a safety advisory hook.
    • Added react_on_rails:install_rsc_agent_guardrails to install/update the guardrails into the app’s .claude/ directory.
    • RSC generation now automatically installs these guardrails when Pro is enabled, including payload auth guidance based on routes/controllers.
  • Bug Fixes
    • Guardrails installation is idempotent, preserves unrelated settings, supports “skip existing”, and removes any legacy hook script.
    • Install failures degrade gracefully during generation, while the rake task reports permission issues cleanly and exits with status 1.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an idempotent RSC agent-guardrails installer with a Claude skill, advisory hook, settings registration, Rake task, and RSC generator integration. Tests cover hook analysis, settings migration, reruns, error handling, and generator behavior.

Changes

RSC Agent Guardrails

Layer / File(s) Summary
Guardrail installer and hook behavior
react_on_rails/lib/react_on_rails/agent_guardrails.rb, react_on_rails/lib/react_on_rails/agent_guardrails/templates/*, react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb
Adds idempotent asset installation, settings validation and merging, legacy hook replacement, the RSC safety skill, authentication analysis, and comprehensive behavior tests.
Guardrail installation task
react_on_rails/lib/tasks/agent_guardrails.rake, CHANGELOG.md
Adds the react_on_rails:install_rsc_agent_guardrails task and documents its installation and rerun behavior.
RSC generator integration
react_on_rails/lib/generators/react_on_rails/rsc_generator.rb, react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
Runs guardrail installation after RSC dependencies, supports pretend and skip modes, continues after installation errors, and verifies generated files and settings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RSCGenerator as RSC generator
  participant Installer as AgentGuardrails::Installer
  participant App as Rails application
  participant Claude as Claude Code hook
  RSCGenerator->>Installer: install(destination_root, skip_existing)
  Installer->>App: write skill and executable hook
  Installer->>App: merge PostToolUse settings
  Claude->>App: inspect changed routes or controllers
  App-->>Claude: emit advisory warning JSON when authentication is missing
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the new RSC guardrail installer and the shipped skill/hook, matching the main change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jg/rsc-app-safety-installer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

justin808 added a commit that referenced this pull request Jul 12, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de1202cb53

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb
@greptile-apps

greptile-apps Bot commented Jul 12, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds installable RSC agent guardrails for React on Rails apps. The main changes are:

  • A new AgentGuardrails installer for .claude skill and hook files.
  • A rake task to install or update the guardrails in an app.
  • RSC generator wiring that runs the installer automatically.
  • A Claude skill and advisory shell hook for RSC safety checks.
  • Specs for install, idempotency, settings merge, and generator output.

Confidence Score: 4/5

The guardrail install path and hook checks need fixes before merging.

  • Valid but incompatible settings JSON can crash after files are copied.
  • The hook can silently do nothing when jq is missing.
  • Namespaced RSC controllers can skip the auth warning.

agent_guardrails.rb and rsc_app_safety_check.sh

Security Review

The new guardrail is security-related, and two hook paths can fail open: missing jq disables all checks, and namespaced RSC controllers are skipped by the controller glob.

Important Files Changed

Filename Overview
react_on_rails/lib/generators/react_on_rails/rsc_generator.rb Loads the new installer and runs it during RSC generation.
react_on_rails/lib/react_on_rails/agent_guardrails.rb Adds the installer and settings merge logic, but incompatible valid settings JSON can crash the install path.
react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.sh Adds the advisory hook, but it can silently miss checks when jq is unavailable or controllers are nested.
react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_skill.md Adds RSC safety guidance for app-level usage.
react_on_rails/lib/tasks/agent_guardrails.rake Adds a rake task that invokes the installer and reports actions.
react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb Adds installer coverage for fresh installs, idempotency, happy-path merges, and malformed JSON.
react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb Checks that the RSC generator installs the guardrail files and settings entry.

Reviews (1): Last reviewed commit: "Add CHANGELOG entry for RSC agent guardr..." | Re-trigger Greptile

Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb Outdated
@claude

claude Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review summary

Reviewed the diff (gh pr diff 4606). This ships a Ruby installer (ReactOnRails::AgentGuardrails), a rake task, RSC-generator wiring, and two templates (a Claude Code skill + an advisory PostToolUse shell hook) that install RSC-safety guardrails into a host app's .claude/. Overall the design is sound: the shell hook is non-blocking (always exit 0), quotes all variables (no injection risk from $FILE content), and the Ruby merge logic has solid idempotency test coverage (fresh install, re-run, merge into existing hooks, malformed-JSON guard).

Findings (posted inline)

  1. agent_guardrails.rbread_settings only rescues JSON::ParserError. If .claude/settings.json is syntactically valid JSON but not a top-level object (e.g. [], null), the subsequent settings.dig(...)/settings["hooks"] calls raise an unrescued TypeError/NoMethodError instead of the intended friendly AgentGuardrails::Error, which isn't caught by either the generator's or the rake task's rescue clause. Low-probability but produces a raw crash where the code otherwise clearly intends a graceful message.
  2. rsc_app_safety_check.sh — the hook's FILE resolution depends on jq being installed. If it isn't, the failure is swallowed (2>/dev/null || true) and the script falls back to $1 (empty, in the real stdin-piped invocation), so it silently exits 0 on every edit with no indication the guardrail never ran. For a security-advisory tool, failing open silently is worth a one-line command -v jq check that surfaces the disablement.

Other observations (not inline)

  • No automated test for the shell hook's detection logic. All the grep-pattern matching that decides whether to warn about rsc_payload_route/unauthenticated controllers (the actual security-relevant logic) is only covered by the PR description's manual dry-run, not by an automated test (e.g. bats or a simple shell test invoked from CI). The Ruby-side install/merge logic is well tested, but the detection heuristics themselves aren't.
  • No opt-out for the generator side effect. rails g react_on_rails:rsc now unconditionally writes into .claude/ and mutates .claude/settings.json, even for teams that don't use Claude Code. This generator already supports flags like --tailwind; a --skip-agent-guardrails (or similar) escape hatch would avoid surprising non-Claude-Code users with new dotfiles.
  • Missing CHANGELOG.md entry. Per this repo's changelog guidelines, user-visible features (new rake task + generator behavior change) should get an #### Added entry citing this PR. None was included.

What looks good

  • Idempotent copy/merge logic with clear "created/updated/unchanged" reporting.
  • Hook registration merges into an existing PostToolUse/Edit|Write matcher entry instead of duplicating it, and is keyed off command substring so re-running doesn't create duplicate hook entries.
  • Shell script correctly quotes "$FILE" everywhere and uses case/grep -E rather than eval, so there's no command-injection surface from the edited file's path or content.
  • Rescue path for malformed JSON explicitly avoids clobbering the user's existing (broken) settings.json and tells them the one line to add manually.

Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb
@claude

claude Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review

Reviewed the diff via gh pr diff 4606. This ships a rake task + generator hook that installs a .claude/skills/rsc-app-safety skill and a PostToolUse advisory hook into host apps, warning agents away from RSC footguns (unauthenticated payload route, trusting props, etc).

Overview

Clean, well-scoped addition: an idempotent AgentGuardrails::Installer that copies two template files and does a safe JSON merge into .claude/settings.json, wired into both a new rake task and the existing RSC generator. Good test coverage for the Ruby installer logic (fresh install, idempotency, merge-without-clobbering, malformed-JSON refusal).

Findings (posted inline)

  1. Shell case glob bug in rsc_app_safety_check.sh (lines 38-44): the patterns */config/routes.rb and */app/controllers/*.rb require a path separator immediately before the matched segment, so a bare relative path like config/routes.rb never matches. This directly breaks the file's own documented manual-testing example (... rsc-app-safety-check.sh config/routes.rb — silently produces no warning). Likely harmless in production since Claude Code probably always sends absolute paths, but there's no test exercising the hook's actual detection logic, so this class of regression won't be caught by CI.
  2. JSON schema-shape robustness gap in agent_guardrails.rb (hook_registered?/add_hook, lines 91-106): only JSON::ParserError is rescued. A syntactically valid but wrong-shaped settings.json (e.g. {"hooks": "oops"}) will raise an uncaught TypeError/NoMethodError rather than the advertised friendly AgentGuardrails::Error.

Other observations (non-blocking)

  • Re-running the installer after a template change silently overwrites any local edits a user made to the installed SKILL.md/hook script (no diff/backup/prompt). This matches the "idempotent updates" intent but is worth a one-line callout in the skill file or task desc so users know not to hand-edit the installed copy.
  • copy_file compares only file content, not permissions — if a user (or another tool) strips the executable bit from the installed .sh hook, a re-run reports "unchanged" and won't restore chmod 755.
  • Good defensive design elsewhere: the hook is non-blocking (exit 0 always), heuristic-based (advisory, not gating), and the JSON merge preserves pre-existing unrelated hooks under the same matcher — nice touch, and well tested.

Overall: solid, low-risk addition. The two inline findings are worth fixing before merge, particularly #1 since it undermines confidence that the hook actually fires as documented.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@react_on_rails/lib/generators/react_on_rails/rsc_generator.rb`:
- Around line 125-132: Update install_agent_guardrails to honor the generator’s
options before invoking ReactOnRails::AgentGuardrails.install: skip installation
when options[:skip] or options[:pretend] is set, while preserving the existing
success and error messaging for actual installations.

In `@react_on_rails/lib/react_on_rails/agent_guardrails.rb`:
- Around line 53-65: Update copy_file so executable permissions are re-applied
before returning the "unchanged" result when dest_path ends with ".sh". Ensure
existing unchanged content still reports "unchanged" while restoring mode 0755,
and preserve the current write and status behavior for changed or newly created
files.
- Around line 79-95: Update read_settings so the parsed JSON must be a Hash
before being returned; treat valid non-object values such as arrays or strings
like invalid settings by raising the same friendly Error and leaving the file
untouched. Preserve the existing JSON::ParserError message and ensure
hook_registered? and add_hook only receive validated object settings.

In
`@react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.sh`:
- Around line 38-50: Update the FILE case patterns in the routes and controller
checks to match both bare relative paths such as config/routes.rb and
app/controllers/example.rb and paths containing preceding directories, while
preserving the existing warning conditions and messages.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ec5f977b-5389-4f04-ae50-475ff466e180

📥 Commits

Reviewing files that changed from the base of the PR and between 4747499 and 0dba3f7.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • react_on_rails/lib/generators/react_on_rails/rsc_generator.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.sh
  • react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_skill.md
  • react_on_rails/lib/tasks/agent_guardrails.rake
  • react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb
  • react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb

Comment thread react_on_rails/lib/generators/react_on_rails/rsc_generator.rb
Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb
Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3288c60e31

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.sh`:
- Around line 46-47: Update the preprocessing used by the safety checks around
lines and renderer detection so Ruby line comments and =begin/=end block
comments are removed consistently before authentication and renderer evidence
checks. Ensure commented callbacks cannot suppress unsecured-renderer warnings
and commented renderer tokens cannot trigger false warnings, and add regression
coverage for both cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a30085d7-7d1e-43e5-9e43-b90f377a7b7d

📥 Commits

Reviewing files that changed from the base of the PR and between 0dba3f7 and 3288c60.

📒 Files selected for processing (7)
  • react_on_rails/lib/generators/react_on_rails/rsc_generator.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.sh
  • react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_skill.md
  • react_on_rails/lib/tasks/agent_guardrails.rake
  • react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb
  • react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
🚧 Files skipped from review as they are similar to previous changes (5)
  • react_on_rails/lib/tasks/agent_guardrails.rake
  • react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_skill.md
  • react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
  • react_on_rails/lib/generators/react_on_rails/rsc_generator.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails.rb

Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review summary

What this does: Adds rake react_on_rails:install_rsc_agent_guardrails (also wired into rails g react_on_rails:rsc), which installs a Claude Code skill (rsc-app-safety) and a non-blocking PostToolUse advisory hook into a host app's .claude/, warning when RSC endpoints look unauthenticated. Purely additive — no changes to the framework's runtime code paths.

Code quality

  • AgentGuardrails::Installer is clean and well-factored: idempotent copy-if-changed for the skill/hook files, a strict-shape validator for settings.json that refuses to touch anything it doesn't fully understand (rather than risking corruption), and a merge that folds into an existing PostToolUse/Edit|Write entry instead of duplicating matcher groups.
  • The hook is registered with "args": [] ("exec form"). I verified against the current Claude Code hooks docs that this is real and correct: exec form avoids a shell (so paths with spaces aren't word-split), and ${CLAUDE_PROJECT_DIR} placeholder substitution happens before exec regardless of form — so this isn't a hidden bug, it's a genuinely better pattern than the shell-form hooks already in this repo's own .claude/settings.json.
  • Left an inline note on agent_guardrails.rb: when register_hook raises (malformed/unsupported settings.json), the skill + hook files have already been written by that point, but both callers (rsc_generator.rb, agent_guardrails.rake) report it as fully "skipped"/failed. Worth wording those messages (or the return value) to reflect the partial success.

Security

  • The shell hook is advisory-only (set -euo pipefail, but every code path ultimately exit 0s) — it can't block or break a workflow, which is the right call for a heuristic, single-file regex/AST-lite check.
  • No shell-injection surface I could find: $FILE is consistently double-quoted everywhere it's used (case matching, grep, passed to ruby as ARGV), and the hook's JSON output is built with JSON.generate in Ruby rather than string interpolation, so crafted file paths/content can't break out of the emitted JSON.
  • The auth-detection heuristic (has_authentication_evidence) only looks at the single edited file, so it can't see before_action callbacks inherited from ApplicationController. That biases it toward false positives (nagging when auth actually exists upstream) rather than false negatives (silently missing a real gap) — an acceptable trade-off for a non-blocking nudge, but worth knowing if false-positive noise shows up in practice.
  • Left an inline nit: the controller-file check (grep -qE 'RSCPayloadRenderer|rsc_payload' "$FILE") doesn't strip comments before matching, unlike the routes.rb check right above it, so a bare comment mentioning rsc_payload can trigger a false-positive warning.
  • The settings.json merge is conservative by design (raises rather than guessing on any shape it doesn't recognize), which is the correct default for a script that mutates a user's existing config file.

Test coverage

Strong — agent_guardrails_spec.rb covers fresh install, idempotency, --skip semantics, permission restoration, malformed/unsupported settings.json (with content-preservation assertions), upgrading a legacy shell-form hook to exec form, and merging without clobbering other hooks. The auth-heuristic itself gets ~15 targeted scenarios (multiline callbacks, only/except, legacy hash-rocket syntax, skip_before_action, conditional callbacks, etc.). rsc_generator_spec.rb adds integration coverage for --pretend/--skip and the end-to-end generator run. I didn't find a test that exercises the rake task itself (agent_guardrails.rake), though it's a thin wrapper around the already-well-tested AgentGuardrails.install.

Overall this is solid, low-risk, well-tested tooling. The two inline notes are both minor (messaging accuracy and false-positive noise) rather than functional bugs.

Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb
Comment thread react_on_rails/lib/tasks/agent_guardrails.rake Outdated
Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Summary

What this does: Ships an idempotent installer (rake react_on_rails:install_rsc_agent_guardrails, also wired into the RSC generator) that drops a Claude Code skill + advisory PostToolUse hook into a host apps .claude/, steering AI coding agents away from the RSC usage footguns surfaced in the recent security audit (unauthenticated payload route, trusting props, exposed Node renderer, secret leakage).

Strengths

  • Excellent spec coverage (agent_guardrails_spec.rb): idempotency, non-clobbering merge into existing settings.json, malformed-JSON handling (raises rather than corrupting), and a long list of heuristic edge cases for the auth-detection hook (multiline callbacks, only:/except: scoping, skip_before_action, legacy hash-rocket syntax, etc.).
  • The settings.json merge is careful: it preserves unrelated hooks, dedupes its own managed hook if found in multiple places, and refuses to touch a file it cannot safely parse, telling the user to add the one line manually instead of guessing.
  • The hook is registered in exec/array args form rather than a single shell command string, avoiding word-splitting issues on paths with spaces.
  • The advisory hook is non-blocking (always exits 0) and fails safe/conservatively; when a callback scope (only:/except:) cannot be determined, it defaults toward still warning rather than silently assuming the app is protected.
  • Portable pure-Ruby hook implementation avoids a bash/jq dependency, which matters since this runs inside arbitrary host apps.

Issues raised inline

  1. agent_guardrails.rb - LEGACY_HOOK_COMMAND/remove_legacy_hook/the legacy branch of managed_hook? (plus two dedicated specs) migrate installs from an old bash hook to the new Ruby one. Per the PRs own commit history, the bash hook was added and replaced within this same still-unmerged branch ("Make RSC guardrail hook portable across platforms"), so no real app can have the .sh file installed, and this migration path is dead code from day one. Suggest removing it before merge.
  2. agent_guardrails.rake - only rescues AgentGuardrails::Error, unlike the generators equivalent call site which also rescues SystemCallError. A permission error running the rake task directly (e.g. read-only .claude/) would raise an unrescued Errno::EACCES instead of a friendly message.
  3. agent_guardrails.rb (hook_registered?/add_hook) - idempotency assumes the hooks matcher is exactly "Edit|Write". If a host app broadens that matcher (e.g. to include MultiEdit), every reinstall will create a redundant new "Edit|Write" entry instead of recognizing the hook is already registered under the wider one. Minor edge case, not a correctness issue for the default install path.

Minor / nit

  • agent_guardrails.rake:9 - ENV["DESTINATION"].to_s.empty? ? Dir.pwd : ENV.fetch("DESTINATION", nil) could be simplified (e.g. ENV["DESTINATION"].presence || Dir.pwd).
  • The auth-detection heuristic (regex-parsing before_action/skip_before_action) is inherently best-effort - e.g. it will not recognize multiple symbols in one before_action call or if:/unless: guards. That is a reasonable tradeoff for a non-blocking advisory tool, just worth noting as a documented limitation rather than a guarantee.

Security
No concerns beyond what is already well-mitigated: the hook is advisory-only, does no shell execution, and the settings merge is conservative about not corrupting existing user configuration.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
CHANGELOG.md (1)

40-47: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark this Pro-only feature with the inline Pro tag.

The installer targets React on Rails Pro RSC applications, but the changelog entry currently presents it as a general feature.

Proposed fix
-- **RSC agent guardrails installer**: New `rake react_on_rails:install_rsc_agent_guardrails` task
+- **[Pro]** **RSC agent guardrails installer**: New `rake react_on_rails:install_rsc_agent_guardrails` task

As per coding guidelines, “Pro-only changelog entries must use an inline **[Pro]** tag within standard category sections.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 40 - 47, Add the inline **[Pro]** tag to the RSC
agent guardrails installer changelog entry, keeping it within the existing
standard category section and leaving the rest of the entry unchanged.

Source: Coding guidelines

react_on_rails/lib/react_on_rails/agent_guardrails.rb (1)

132-137: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Tolerate string-based hooks to avoid aborting installation on valid settings.

Claude Code natively supports specifying hooks as basic strings (e.g., "hooks": ["npm run lint"]). Currently, valid_hook_group? strictly enforces hooks.all?(Hash). If the user's settings.json contains any string hooks, the installer treats the valid configuration as malformed, leaves the file untouched, and aborts with a manual fallback message.

Relax the validation to permit strings, and update the predicates to safely type-check the hook before accessing its keys.

🔧 Proposed fix
       def valid_hook_group?(entry)
         return false unless entry.is_a?(Hash)

         hooks = entry["hooks"]
-        hooks.nil? || (hooks.is_a?(Array) && hooks.all?(Hash))
+        hooks.nil? || hooks.is_a?(Array)
       end

And update the predicates:

       def registered_hook?(hook)
-        hook["type"] == "command" && hook["command"] == HOOK_COMMAND && hook["args"] == HOOK_ARGS
+        hook.is_a?(Hash) && hook["type"] == "command" && hook["command"] == HOOK_COMMAND && hook["args"] == HOOK_ARGS
       end

       def managed_hook?(hook)
-        registered_hook?(hook) || hook["command"] == LEGACY_HOOK_COMMAND
+        registered_hook?(hook) || (hook.is_a?(Hash) && hook["command"] == LEGACY_HOOK_COMMAND) || hook == LEGACY_HOOK_COMMAND
       end

Also applies to: 168-174

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@react_on_rails/lib/react_on_rails/agent_guardrails.rb` around lines 132 -
137, Update valid_hook_group? and the related hook predicates to accept hook
entries represented as either Hash or String, while retaining support for nil
and arrays. Ensure any hook key access is guarded by a type check so string
hooks cannot cause errors, allowing valid string-based settings to pass
validation.
🧹 Nitpick comments (1)
react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.rb (1)

55-69: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider supporting prepend_before_action and append_before_action.

The current implementation only looks for the standard before_action and skip_before_action. If an application uses prepend_before_action :authenticate_user! to guarantee ordering, this advisory hook will miss the evidence and emit a false-positive warning.

Since the parser relies on a flexible regex, you can safely expand the callback_name to include these variants.

♻️ Proposed refactor
 def authenticated_callback_names(lines)
-  logical_statements(lines, "before_action").filter_map do |statement|
-    parsed = parse_callback(statement, "before_action")
+  logical_statements(lines, "(?:prepend_|append_)?before_action").filter_map do |statement|
+    parsed = parse_callback(statement, "(?:prepend_|append_)?before_action")
     parsed&.first if parsed && applies_to_payload?(parsed.last, unknown: false)
   end
 end

 def remove_skipped_callbacks(lines, authenticated_callbacks)
-  logical_statements(lines, "skip_before_action").each do |statement|
-    parsed = parse_callback(statement, "skip_before_action")
+  logical_statements(lines, "skip_(?:prepend_|append_)?before_action").each do |statement|
+    parsed = parse_callback(statement, "skip_(?:prepend_|append_)?before_action")
     next unless parsed && authenticated_callbacks.include?(parsed.first)

     authenticated_callbacks.delete(parsed.first) if applies_to_payload?(parsed.last, unknown: true)
   end
 end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.rb`
around lines 55 - 69, Extend authenticated_callback_names and
remove_skipped_callbacks to recognize prepend_before_action and
append_before_action alongside the existing before_action and skip_before_action
callbacks. Update the callback-name matching passed to logical_statements and
parse_callback so these variants are parsed and evaluated with the same
authentication and skip semantics, preventing false-positive warnings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 140: Merge the changelog entries currently under the duplicate “Added”
heading into the existing “Added” section near the top of CHANGELOG.md, then
remove the later heading while preserving all entry content and ordering.

In `@react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb`:
- Around line 162-188: The file-access guardrail specs should use deterministic
fixtures: update the unrelated file in “does not read unrelated edited files” to
contain an RSC trigger while preserving the expectation that it is ignored, and
replace chmod-based failure setup in “remains non-blocking when a matching file
cannot be read” with an injected reader or invalid file type that reliably
causes reading to fail, including under privileged users.

---

Outside diff comments:
In `@CHANGELOG.md`:
- Around line 40-47: Add the inline **[Pro]** tag to the RSC agent guardrails
installer changelog entry, keeping it within the existing standard category
section and leaving the rest of the entry unchanged.

In `@react_on_rails/lib/react_on_rails/agent_guardrails.rb`:
- Around line 132-137: Update valid_hook_group? and the related hook predicates
to accept hook entries represented as either Hash or String, while retaining
support for nil and arrays. Ensure any hook key access is guarded by a type
check so string hooks cannot cause errors, allowing valid string-based settings
to pass validation.

---

Nitpick comments:
In
`@react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.rb`:
- Around line 55-69: Extend authenticated_callback_names and
remove_skipped_callbacks to recognize prepend_before_action and
append_before_action alongside the existing before_action and skip_before_action
callbacks. Update the callback-name matching passed to logical_statements and
parse_callback so these variants are parsed and evaluated with the same
authentication and skip semantics, preventing false-positive warnings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c86568e-da58-4e0b-9d1d-55e9b25954ce

📥 Commits

Reviewing files that changed from the base of the PR and between 3288c60 and b65ac26.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • react_on_rails/lib/generators/react_on_rails/rsc_generator.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.rb
  • react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb
  • react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
  • react_on_rails/lib/generators/react_on_rails/rsc_generator.rb

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
CHANGELOG.md (1)

40-47: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark this Pro-only feature with the inline Pro tag.

The installer targets React on Rails Pro RSC applications, but the changelog entry currently presents it as a general feature.

Proposed fix
-- **RSC agent guardrails installer**: New `rake react_on_rails:install_rsc_agent_guardrails` task
+- **[Pro]** **RSC agent guardrails installer**: New `rake react_on_rails:install_rsc_agent_guardrails` task

As per coding guidelines, “Pro-only changelog entries must use an inline **[Pro]** tag within standard category sections.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 40 - 47, Add the inline **[Pro]** tag to the RSC
agent guardrails installer changelog entry, keeping it within the existing
standard category section and leaving the rest of the entry unchanged.

Source: Coding guidelines

react_on_rails/lib/react_on_rails/agent_guardrails.rb (1)

132-137: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Tolerate string-based hooks to avoid aborting installation on valid settings.

Claude Code natively supports specifying hooks as basic strings (e.g., "hooks": ["npm run lint"]). Currently, valid_hook_group? strictly enforces hooks.all?(Hash). If the user's settings.json contains any string hooks, the installer treats the valid configuration as malformed, leaves the file untouched, and aborts with a manual fallback message.

Relax the validation to permit strings, and update the predicates to safely type-check the hook before accessing its keys.

🔧 Proposed fix
       def valid_hook_group?(entry)
         return false unless entry.is_a?(Hash)

         hooks = entry["hooks"]
-        hooks.nil? || (hooks.is_a?(Array) && hooks.all?(Hash))
+        hooks.nil? || hooks.is_a?(Array)
       end

And update the predicates:

       def registered_hook?(hook)
-        hook["type"] == "command" && hook["command"] == HOOK_COMMAND && hook["args"] == HOOK_ARGS
+        hook.is_a?(Hash) && hook["type"] == "command" && hook["command"] == HOOK_COMMAND && hook["args"] == HOOK_ARGS
       end

       def managed_hook?(hook)
-        registered_hook?(hook) || hook["command"] == LEGACY_HOOK_COMMAND
+        registered_hook?(hook) || (hook.is_a?(Hash) && hook["command"] == LEGACY_HOOK_COMMAND) || hook == LEGACY_HOOK_COMMAND
       end

Also applies to: 168-174

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@react_on_rails/lib/react_on_rails/agent_guardrails.rb` around lines 132 -
137, Update valid_hook_group? and the related hook predicates to accept hook
entries represented as either Hash or String, while retaining support for nil
and arrays. Ensure any hook key access is guarded by a type check so string
hooks cannot cause errors, allowing valid string-based settings to pass
validation.
🧹 Nitpick comments (1)
react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.rb (1)

55-69: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider supporting prepend_before_action and append_before_action.

The current implementation only looks for the standard before_action and skip_before_action. If an application uses prepend_before_action :authenticate_user! to guarantee ordering, this advisory hook will miss the evidence and emit a false-positive warning.

Since the parser relies on a flexible regex, you can safely expand the callback_name to include these variants.

♻️ Proposed refactor
 def authenticated_callback_names(lines)
-  logical_statements(lines, "before_action").filter_map do |statement|
-    parsed = parse_callback(statement, "before_action")
+  logical_statements(lines, "(?:prepend_|append_)?before_action").filter_map do |statement|
+    parsed = parse_callback(statement, "(?:prepend_|append_)?before_action")
     parsed&.first if parsed && applies_to_payload?(parsed.last, unknown: false)
   end
 end

 def remove_skipped_callbacks(lines, authenticated_callbacks)
-  logical_statements(lines, "skip_before_action").each do |statement|
-    parsed = parse_callback(statement, "skip_before_action")
+  logical_statements(lines, "skip_(?:prepend_|append_)?before_action").each do |statement|
+    parsed = parse_callback(statement, "skip_(?:prepend_|append_)?before_action")
     next unless parsed && authenticated_callbacks.include?(parsed.first)

     authenticated_callbacks.delete(parsed.first) if applies_to_payload?(parsed.last, unknown: true)
   end
 end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.rb`
around lines 55 - 69, Extend authenticated_callback_names and
remove_skipped_callbacks to recognize prepend_before_action and
append_before_action alongside the existing before_action and skip_before_action
callbacks. Update the callback-name matching passed to logical_statements and
parse_callback so these variants are parsed and evaluated with the same
authentication and skip semantics, preventing false-positive warnings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 140: Merge the changelog entries currently under the duplicate “Added”
heading into the existing “Added” section near the top of CHANGELOG.md, then
remove the later heading while preserving all entry content and ordering.

In `@react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb`:
- Around line 162-188: The file-access guardrail specs should use deterministic
fixtures: update the unrelated file in “does not read unrelated edited files” to
contain an RSC trigger while preserving the expectation that it is ignored, and
replace chmod-based failure setup in “remains non-blocking when a matching file
cannot be read” with an injected reader or invalid file type that reliably
causes reading to fail, including under privileged users.

---

Outside diff comments:
In `@CHANGELOG.md`:
- Around line 40-47: Add the inline **[Pro]** tag to the RSC agent guardrails
installer changelog entry, keeping it within the existing standard category
section and leaving the rest of the entry unchanged.

In `@react_on_rails/lib/react_on_rails/agent_guardrails.rb`:
- Around line 132-137: Update valid_hook_group? and the related hook predicates
to accept hook entries represented as either Hash or String, while retaining
support for nil and arrays. Ensure any hook key access is guarded by a type
check so string hooks cannot cause errors, allowing valid string-based settings
to pass validation.

---

Nitpick comments:
In
`@react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.rb`:
- Around line 55-69: Extend authenticated_callback_names and
remove_skipped_callbacks to recognize prepend_before_action and
append_before_action alongside the existing before_action and skip_before_action
callbacks. Update the callback-name matching passed to logical_statements and
parse_callback so these variants are parsed and evaluated with the same
authentication and skip semantics, preventing false-positive warnings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c86568e-da58-4e0b-9d1d-55e9b25954ce

📥 Commits

Reviewing files that changed from the base of the PR and between 3288c60 and b65ac26.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • react_on_rails/lib/generators/react_on_rails/rsc_generator.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.rb
  • react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb
  • react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
  • react_on_rails/lib/generators/react_on_rails/rsc_generator.rb
🛑 Comments failed to post (2)
CHANGELOG.md (1)

140-140: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Merge this duplicate Added section into the existing one.

Move Lines 142-181 beneath the Added heading at Line 38 and remove this heading; MD024 currently reports it as a duplicate.

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 140-140: Multiple headings with the same content

(MD024, no-duplicate-heading)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 140, Merge the changelog entries currently under the
duplicate “Added” heading into the existing “Added” section near the top of
CHANGELOG.md, then remove the later heading while preserving all entry content
and ordering.

Source: Linters/SAST tools

react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb (1)

162-188: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make the file-access fixtures deterministic.

The unrelated file contains nothing that would expose an accidental analysis, while chmod(0o000) is ineffective under privileged users. Write an RSC trigger into the unrelated file, and induce the matching-file failure without relying on permission bits (for example, through an injected reader or deterministic invalid file type).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb` around lines 162
- 188, The file-access guardrail specs should use deterministic fixtures: update
the unrelated file in “does not read unrelated edited files” to contain an RSC
trigger while preserving the expectation that it is ignored, and replace
chmod-based failure setup in “remains non-blocking when a matching file cannot
be read” with an injected reader or invalid file type that reliably causes
reading to fail, including under privileged users.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9b5b496d14

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review: RSC agent-safety guardrails installer

Overview

This ships an installer (rake react_on_rails:install_rsc_agent_guardrails, also wired into the RSC generator) that drops a Claude Code skill (rsc-app-safety) and an advisory PostToolUse hook into a host app's .claude/, nudging AI coding agents away from RSC footguns (unauthenticated payload route, trusting props, exposed Node renderer, secret leakage). Good motivation, and the idempotent-install / safe-JSON-merge design is solid.

Strengths

  • AgentGuardrails::Installer is careful about not clobbering user data: validates settings.json shape before writing any files (so a malformed settings file aborts the whole install rather than leaving it half-applied), preserves unrelated hooks/settings, and has a real dedup path for legacy → new hook migration.
  • The hook script (rsc_app_safety_check.rb) is genuinely careful for a regex/line-based static-analysis tool: it tokenizes with Ripper to blank out comments/embedded docs before line-matching, handles multi-line callback statements, and has thorough test coverage of skip_before_action/only:/except: edge cases.
  • Good non-blocking design throughout (exit 0 always; rescues SystemCallError/ArgumentError around file reads and encoding issues so a bad file can't break the agent's workflow).
  • Test coverage is thorough: idempotency, skip-existing, legacy migration, malformed/invalid-shape settings, Windows path separators, and manual invocation without stdin JSON.

Issues raised inline

  1. (Needs verification, high impact if true) agent_guardrails.rb:24 — the hook is registered as "command": "ruby" + a separate "args" array. Every other hook already in this repo's .claude/settings.json uses a single "command" shell string, and I couldn't find prior precedent for args anywhere in this codebase's docs/config. If the target Claude Code version doesn't support this exec-form array, the hook would register but never actually run the script — and the existing specs wouldn't catch it, since they invoke the hook file directly rather than through Claude Code's real hook dispatch. Worth an explicit end-to-end check (edit a file in a real app that has this installed, confirm the advisory context actually shows up) before merge.
  2. (Minor, informational) rsc_app_safety_check.rb:139-145 — the controller check only sees the single edited file, so a controller that inherits before_action :authenticate_user! from ApplicationController (the standard Rails/Devise convention) rather than redeclaring it locally will false-positive on every edit. Non-blocking so not a correctness bug, but worth a caveat in the warning text/skill doc to avoid alert fatigue.

Other observations (not blocking)

  • The hook fires on every Edit|Write in the app (not just RSC-related files) and spawns a fresh Ruby interpreter each time; this matches the existing bin/claude-hooks/autofix-file pattern in this repo, so it's consistent, just worth being aware of the added latency per edit.
  • The AUTH_NAME regex only recognizes authenticate*, authorize*, and require_login/user* naming conventions — apps with differently-named auth methods (e.g., a custom ensure_logged_in!) will get a false positive. Reasonable given this is explicitly a heuristic advisory tool, not a hard gate.
  • CHANGELOG entry, rake task structure (lib/tasks/*.rake auto-loaded by the engine), and ENV["DESTINATION"] handling all match existing conventions in this codebase.

Nice test discipline overall — my main ask is confirming point 1 actually fires end-to-end in a real Claude Code session before merging, since that's the entire value proposition of the PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb (1)

98-482: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared fixture-writing helpers to cut repeated boilerplate.

Nearly every example repeats FileUtils.mkdir_p(File.dirname(path)); File.write(path, content) for either config/routes.rb or a controller file. Extracting small helpers would remove this duplication across ~30 examples and make each test's intent (the file content) the only thing that stands out.

♻️ Example helper extraction
+    def write_routes(content)
+      routes_path = File.join(`@app_root`, "config/routes.rb")
+      FileUtils.mkdir_p(File.dirname(routes_path))
+      File.write(routes_path, content)
+      routes_path
+    end
+
+    def write_controller(content, name: "rsc_payload_controller.rb")
+      controller_path = File.join(`@app_root`, "app/controllers/#{name}")
+      FileUtils.mkdir_p(File.dirname(controller_path))
+      File.write(controller_path, content)
+      controller_path
+    end
+
     it "parses Claude hook input without requiring jq" do
       described_class.install(`@app_root`)
-      routes_path = File.join(`@app_root`, "config/routes.rb")
-      FileUtils.mkdir_p(File.dirname(routes_path))
-      File.write(routes_path, "rsc_payload_route\n")
+      routes_path = write_routes("rsc_payload_route\n")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb` around lines 98
- 482, Extract shared fixture-writing helpers in the spec, such as helpers for
writing routes and controller files, encapsulating directory creation plus file
writing. Replace the repeated FileUtils.mkdir_p/File.write setup across the
examples while preserving each fixture path and content; keep test behavior
unchanged and make the example bodies focus on their fixture content.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb`:
- Around line 98-482: Extract shared fixture-writing helpers in the spec, such
as helpers for writing routes and controller files, encapsulating directory
creation plus file writing. Replace the repeated FileUtils.mkdir_p/File.write
setup across the examples while preserving each fixture path and content; keep
test behavior unchanged and make the example bodies focus on their fixture
content.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 26594e4c-2ed0-40a4-8572-141d1932e86a

📥 Commits

Reviewing files that changed from the base of the PR and between b65ac26 and 9b5b496.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • react_on_rails/lib/generators/react_on_rails/rsc_generator.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_skill.md
  • react_on_rails/spec/react_on_rails/agent_guardrails_spec.rb
  • react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb
🚧 Files skipped from review as they are similar to previous changes (4)
  • react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_skill.md
  • CHANGELOG.md
  • react_on_rails/lib/react_on_rails/agent_guardrails/templates/rsc_app_safety_check.rb
  • react_on_rails/lib/react_on_rails/agent_guardrails.rb

justin808 and others added 10 commits July 17, 2026 21:19
… skill

Ship an installable RSC "agent safety" guardrail into apps that use React on Rails
Pro React Server Components, so AI coding agents avoid the RSC API footguns found in
the security audit (#4595 / #4596 / #4597).

- ReactOnRails::AgentGuardrails installer: idempotently copies an rsc-app-safety
  Claude Code skill + an advisory PostToolUse hook into the app's .claude/, and
  safe-merges the hook into .claude/settings.json (refuses to touch malformed JSON).
- rake react_on_rails:install_rsc_agent_guardrails: installs/updates the assets into
  an existing app; safe to re-run after upgrades.
- rsc-app-safety skill + app hook templates: warn about mounting rsc_payload_route
  without auth, trusting server-component props, exposing the node renderer, and
  leaking secrets/PII.
- The RSC generator now runs the installer so new RSC apps get the guardrails.

Specs: agent_guardrails_spec (install / idempotency / safe-merge / malformed-JSON)
plus an rsc_generator_spec assertion; rubocop and shellcheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Scan only executable Ruby in the RSC safety hook, write settings.json
atomically, and resolve the Rails app root for the rake task.

- The advisory hook reconstructed each line from Ripper tokens but blanked
  only comment tokens, so string and heredoc bodies (:on_tstring_content)
  were scanned as if they were code. A `skip_before_action` mentioned inside
  a heredoc cancelled a real `before_action`, producing a false "no
  authentication" warning on a properly authenticated controller. String and
  heredoc bodies are now blanked alongside comments. Blanking is
  line-start-aware so mid-line content such as `only: "rsc_payload"` still
  parses as a callback scope, and heredoc bodies are tracked explicitly
  because Ripper reports their indentation as a separate token. The routes
  check now uses the same tokenized lines instead of a raw `#` strip.

- settings.json is a user-owned file that may hold unrelated configuration,
  so it is now replaced by writing a sibling temp file and renaming over the
  target. An interrupted or failed write can no longer truncate or half-write
  it. Existing file permissions are preserved.

- The rake task resolved its destination from Dir.pwd, so invoking it from a
  subdirectory installed guardrails into the wrong place. It now prefers the
  Rails application root via AgentGuardrails.default_destination_root, still
  honouring DESTINATION and falling back to Dir.pwd outside a Rails app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@justin808
justin808 force-pushed the jg/rsc-app-safety-installer branch from 201e9ee to 6b8f044 Compare July 18, 2026 08:07
@justin808

Copy link
Copy Markdown
Member Author

+ci-force-full

Requesting the full hosted suite for head 6b8f044. This PR rebased across #4632's rsc_generator rework, and during that work real cross-file RSpec pollution was found and fixed (a stub_const("Rails", Module.new) in new specs was destroying Rails::Generators for later specs in the same process). That class of bug is invisible to per-file spec runs, so we want the full gem/generator suites to run rather than the optimized selection.

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Force-Full Hosted CI Requested

Triggered 9 workflow(s) for 6b8f044e1eda.
Skipped 0 workflow(s) with equivalent exact-head coverage.
Mode: force-full hosted CI (bypasses optimized change selection).
Added ready-for-hosted-ci and force-full-hosted-ci, so future commits will bypass optimized hosted CI selection until +ci-stop-full is used.

View progress in the Actions tab.

@github-actions github-actions Bot added force-full-hosted-ci Bypass optimized hosted CI selection and run all hosted suites ready-for-hosted-ci Run optimized hosted GitHub CI for this PR labels Jul 18, 2026
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review: RSC agent-safety guardrails installer

Overview

This PR ships rake react_on_rails:install_rsc_agent_guardrails, an idempotent installer (ReactOnRails::AgentGuardrails) that drops a Claude Code skill (rsc-app-safety) and a non-blocking PostToolUse advisory hook into a host app's .claude/, wired automatically into the RSC generator. It's the "ship guardrails into the app" half of the RSC security-audit follow-up (companion to #4599).

Code quality

  • The installer (agent_guardrails.rb) is careful and well thought out: it validates settings.json before writing anything else so a malformed settings file can't leave a partial install (validate_settings_before_copy), uses a genuine atomic write (tempfile + fsync + rename, preserving the original file's permission bits) for settings.json, and does a proper merge/dedup pass so re-installs don't duplicate hook entries or clobber unrelated hooks/settings.
  • The advisory hook (rsc_app_safety_check.rb) is a surprisingly rigorous line/token heuristic built on Ripper.lex rather than naive regex-over-raw-source — it correctly blanks comments, =begin/=end blocks, and string/heredoc bodies before scanning for before_action/skip_before_action, so callback names appearing in strings or comments don't produce false signals (and vice versa, real code isn't hidden inside a heredoc). Nice touch using exec-form hook args (command + args array) instead of a shell string, avoiding word-splitting on paths with spaces.
  • Test coverage is exceptional — 827 lines covering idempotency, permission preservation, legacy-hook migration/dedup, malformed/invalid-shape settings, partial-write failure (mocked File.rename failure leaves the original untouched, no leftover tmp files), and a long tail of controller/route heuristic edge cases (heredocs, only:/except:, skip_before_action, encoding failures, generator --pretend/--skip).
  • Design bias is consistently "warn when uncertain" (e.g. unknown: false for auth application, unknown: true for skip application) — the safe direction for a security-advisory tool, since false positives cost annoyance but false negatives could hide a real gap.

Security considerations

  • The hook is explicitly advisory/non-blocking (always exit 0), documented as a heuristic ("silence does not prove authorization"), and doesn't inspect inherited callbacks from concerns/ApplicationController — this is called out both in the skill doc and the hook's warning text, which is the right way to ship a best-effort local tool without it being mistaken for a real security control.
  • Left one inline comment on the AUTH_NAME allowlist (rsc_app_safety_check.rb:13-19): it's fairly narrow (won't match e.g. Devise scoped authenticate_admin_user! or custom names like require_authentication), which will produce false-positive warnings on already-authenticated controllers using naming conventions outside the list. Low severity since it errs toward over-warning rather than missing a real gap, but worth a mention since this ships as a fixed template for all downstream apps.
  • The skill doc's code sample (config.rsc_payload_authorizer) matches the actual Pro ReactOnRailsPro::Configuration API, so the guidance given to agents/developers is accurate.

Other notes

  • install_agent_guardrails in the generator only fires when Pro/RSC prerequisites are met, and failures are rescued to a warning rather than aborting RSC generation — good failure isolation.
  • The deferred docs page is a reasonable scope cut given the skill + rake desc are self-documenting for now.

No blocking issues found. This is a well-scoped, defensively-implemented feature with strong test coverage.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b8f044e1e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb Outdated
- The malformed-settings recovery message pointed users at
  .claude/hooks/rsc-app-safety-check.rb, but settings are validated before
  anything is copied, so that file does not exist when the error is raised.
  Following the advice registered a hook script that was never installed,
  leaving the RSC advisory guardrail silently disabled. The message now states
  that nothing was installed and directs users to fix settings.json and re-run
  the task. Kept the "write nothing on malformed settings" invariant rather
  than copying the hook before raising: it is explicitly specced, and writing
  guardrail files into an app whose settings could not be parsed is the more
  surprising behaviour.

- AUTH_NAME required an exact match against a short allowlist, so real
  authentication callbacks were missed and properly authenticated controllers
  drew a false "shows no before_action/authentication locally" warning. Devise
  generates per-scope bang callbacks (authenticate_admin_user!,
  authenticate_api_user!) and app-defined callbacks follow the same
  convention, so any authenticate*/authorize* name ending in a bang now
  counts, as do require_authentication/require_authorization and
  verify_authenticated. Names without a bang still need the narrow allowlist,
  which keeps authenticate_analytics_session and authorize_for_metrics from
  reading as authentication. Recognizing a callback only silences the
  advisory, so over-broadening would trade a false positive for a false
  negative; verify_authenticity_token (CSRF, not authentication) is specced as
  a non-match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb
Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb
Comment thread react_on_rails/lib/react_on_rails/agent_guardrails.rb
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review: RSC agent-safety guardrails installer

Overview

This ships a new react_on_rails:install_rsc_agent_guardrails rake task + ReactOnRails::AgentGuardrails installer that drops a Claude Code skill (rsc-app-safety) and an advisory PostToolUse hook into a host app's .claude/, warning agents away from RSC footguns (unauthenticated rsc_payload_route, trusting props, exposing the Node renderer). It's wired into rails g react_on_rails:rsc and comes with a large, well-thought-out spec suite (agent_guardrails_spec.rb, 885 lines) covering idempotency, safe JSON merge, malformed-settings handling, and the hook's callback-detection heuristics.

Overall this is a mature, carefully defensive piece of code — the settings.json merge logic validates before writing anything (so a malformed file is never touched), uses an atomic tempfile+rename write, preserves file permissions, and the Ruby-based heuristic hook (replacing an earlier bash version) has extensive edge-case coverage for comments/heredocs/string literals so it doesn't false-negative on real callbacks. Nice attention to the "silence must not be mistaken for safety" principle throughout (e.g. hook_registered?, the conservative AUTH_NAME regex, applies_to_payload? defaulting to "still warn" when scope is ambiguous).

Most important thing to verify

Left as an inline comment on agent_guardrails.rb:24-25: the hook is registered as "command": "ruby", "args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/rsc-app-safety-check.rb"]. Every other hook already in this repo's own .claude/settings.json uses a single "command" string with ${CLAUDE_PROJECT_DIR} embedded directly in it — none use a separate args array. If Claude Code's hook runner doesn't actually support a command+args exec form, this hook could silently never fire correctly, and nothing in the test suite would catch it (the specs only validate the JSON shape written, not that Claude Code invokes it as expected). Worth confirming against the current hooks schema before merging, since this is the entire point of the PR.

Other findings (inline)

  • copy_file writes the skill/hook templates with plain File.write, while register_hook uses the existing atomic_write tempfile+rename helper to avoid partial writes — minor inconsistency, could reuse atomic_write for the templates too.
  • add_hook strips managed hooks from every PostToolUse entry regardless of matcher when consolidating, which can leave a stray "hooks": [] entry behind under an old matcher after an upgrade — cosmetic, not functional.
  • The hook fires on every Edit/Write in the app (matcher is tool-based, not path-based), so every file edit pays a ruby process-boot cost even for files with nothing to do with RSC. Likely unavoidable given how Claude Code hook matchers work, just noting the tradeoff.

Minor / non-blocking

  • The PR description still describes a bash hook (rsc-app-safety-check.sh, shellcheck clean) and references testing that predates the switch to the current Ruby implementation with .sh.rb migration logic. Worth updating the description so reviewers aren't confused by the mismatch.

Test coverage

Excellent — idempotency, skip-existing, malformed JSON, permission preservation, legacy hook migration/dedup, and dozens of callback-heuristic edge cases (heredocs, comments, scoped only:/except:, prepend/append callbacks) are all covered. The one gap is the hook-schema assumption above, which can't be tested from within this repo since it depends on Claude Code's own hook execution semantics.

Security

The design is appropriately conservative for a security-guardrail generator: never touches a malformed settings.json, atomic writes, preserves existing hooks/permissions, and the heuristic errs toward over-warning rather than under-warning. No issues found with the RSC-safety guidance content itself (the skill doc's advice on rsc_payload_authorizer, session-based identity, and Node renderer isolation is accurate per the linked audit issues).

@justin808
justin808 added this pull request to the merge queue Jul 18, 2026
Merged via the queue into main with commit 5f6e66d Jul 18, 2026
61 checks passed
@justin808
justin808 deleted the jg/rsc-app-safety-installer branch July 18, 2026 12:34
justin808 added a commit that referenced this pull request Jul 19, 2026
…t-policy

* origin/main:
  Agents: automate public demo fleet standing health (#4751)
  Tolerate lone UTF-16 surrogates in renderer JSON (#4710) (#4745)
  Agents: add fleet validation lifecycle closeout (#4747)
  CI: split generator specs into setup-safe subshards (#4746)
  Add react_on_rails:install_rsc_agent_guardrails: ship RSC agent-safety skill + hook into apps (#4606)
  Revert "Fix SSR crash on unpaired UTF-16 surrogates from JS JSON output (#4710) (#4726)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

force-full-hosted-ci Bypass optimized hosted CI selection and run all hosted suites ready-for-hosted-ci Run optimized hosted GitHub CI for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant