Skip to content

Sanitize comment markdown on the ReScript page - #877

Merged
justin808 merged 2 commits into
masterfrom
justin808-claude/ad-hoc-sanitize-rescript-comment-markdown
Nov 2, 2026
Merged

justin808 merged 2 commits into
masterfrom
justin808-claude/ad-hoc-sanitize-rescript-comment-markdown

Conversation

@justin808

@justin808 justin808 commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

🤖 Claude Code · Anthropic · claude-opus-5-5 · medium - AI spend: $2.91

Recommendation: Merge · Code Walkthrough · Code Review · Checkpoint

Summary

Comments on the /rescript page can no longer inject scripts or event handlers, because their markdown is now sanitized before display. This matches the React and server component views, and normal markdown formatting still renders.

Intent

Why: The ReScript comment view rendered marked output directly, so any comment could run script in another visitor's browser.

Task: Claude prompt

Root cause: Comment.res passed Marked.marked output straight to dangerouslySetInnerHTML; the React and server component views sanitize first, and this one never did.

Safe because: sanitize-html is already a dependency, and only this one page's rendering changes.

Human review: 2 of 5. Skim client/app/bundles/comments/rescript/CommentList/Comment/Comment.res, client/app/bundles/comments/rescript/bindings/SanitizeHtml.res, spec/rescript/rescript_spec.rb (changes code or configuration).

Scope: Add a sanitize-html binding, use it in Comment.res with the same defaults as Comment.jsx, and add a system spec. (3 files, +19 / −1 lines)

Files: Tests 1 · Other 2

Tests:

  • spec/rescript/rescript_spec.rb

Other:

  • client/app/bundles/comments/rescript/CommentList/Comment/Comment.res
  • client/app/bundles/comments/rescript/bindings/SanitizeHtml.res

Verification

Validation passed; local review completed at 11c58d2 (evidence).

Evidence and limitations

Validation and review: validate passed (exit 0); local review by openai/codex, anthropic/claude (attestation) at 11c58d2.

  • The new spec in spec/rescript/rescript_spec.rb fails with the fix reverted and passes with it, in headless Chrome.
  • Repo validate passed: RuboCop, ESLint, 77 RSpec examples, 16 Jest tests.

Not verified: Comments arriving over Action Cable have no test; they render through the same Comment.res. The live demo was not tested before or after this fix.

Scope of the search

A search of client/ and app/ for dangerouslySetInnerHTML, marked, html_safe, and raw found no other unsanitized comment rendering. Stored comments are unchanged; they are sanitized when displayed.

Shaka metrics

Estimated token costs: $2.91 · ready in 14m

Details
Usage and cost
Report USD Codex credits Input Cached input Output Reasoning Cache writes
claude‑opus‑5‑5 implementation $2.09 — 48 7.8M 14.8K 2.6K 28.7K
claude‑opus‑5‑5 review ×2 $0.66 — 30 578K 9.3K 2.9K 44.6K
gpt‑6.1‑sol review ×2 $0.16 4.01 245K 187K 2.5K 55 0
Total $2.91 4.01+

— not reported; + some usage not reported, so the amount is a minimum.

Understand the columns and accounting rules.

How each report was measured and priced

gpt‑6.1‑sol review ×2

Rate card: installed Shaka.

Standard Codex credit and OpenAI API-equivalent rates, verified 2026-09-29.

Sources: Codex credit rates, GPT-6.1 Sol, and prompt-cache accounting.

Local adversarial reviewer usage: included below. External reviewer/tool-model usage: UNKNOWN.

Native usage is PARTIAL. Scope: all turns in selected sources.

claude‑opus‑5‑5 review ×2

Rate card: installed Shaka.

Claude CLI recorded estimated USD for an independent call; not an invoice.

Local adversarial reviewer usage: included below. External reviewer/tool-model usage: UNKNOWN.

Native usage is PARTIAL. Scope: all turns in selected sources.

claude‑opus‑5‑5 implementation

Rate card: installed Shaka.

Anthropic API list prices, verified 2026-09-23.

Sources: Anthropic pricing.

Local adversarial reviewer usage: UNKNOWN. External reviewer/tool-model usage: UNKNOWN.

Native usage is PARTIAL. Scope: responses at or after 2026-11-02T08:00:00Z (whole-second sources include the cutoff second).

Rate card: installed Shaka.

Anthropic API list prices, verified 2026-09-23.

Sources: Anthropic pricing.

Local adversarial reviewer usage: UNKNOWN. External reviewer/tool-model usage: UNKNOWN.

Native usage is PARTIAL. 18 responses. Scope: responses at or after 2026-11-02T08:00:00Z (whole-second sources include the cutoff second).

Timings · 53m between checkpoints

This head: Prompt to CI 11m · CI to ready 3m · Ready to merged pending

Totals: 53m between checkpoints

11-02 00:06 to 11-02 01:00 PST (53m)
                          00:06        00:20        00:33        00:46
PR                   53m  -----------=========================================
Validation        4m 23s     ### ###
Review anthropic   2m 3s       ##   ##
Review openai     1m 18s       ##   #
Checkpoint           44s                                                    ##

- before the PR  = PR open  . waiting on a person  # step  x failed
Stretch From To Time
Before the pull request 2026-11-02 00:06 2026-11-02 00:18 (opened) 11m
Between checkpoints 2026-11-02 00:18 2026-11-02 01:00 (this update) 41m so far

Times are America/Los_Angeles wall-clock time. A wait runs from when an attention label goes on until it comes off or the pull request merges or closes.

Before first recorded validation: 3m 7s · Task start (agent-reported): 2026-11-02 00:06 PST. Validation started: 2026-11-02 00:09 PST. This interval overlaps the stretches above.

Measured steps: Validation 4m 23s (4 runs) · Local review 3m 21s (4 runs) · Product checkpoint 44s (1 run)

Models: openai; anthropic; openai · gpt-6.1-sol · medium · Machines: m5

Each measured step is a helper run recorded on the machine that ran it. A step logged on a machine that never updated this description is missing, so the totals can be partial. A step can overlap the stretches above.

Understand the timings.

Execution details
Field Value
Task source description
Shaka skill version 66aa5d5
Recommended model / effort claude-opus-5-5 / medium
Active model / effort claude-opus-5-5 / medium
Shaka settings used

Recorded checks match this PR’s current commit. This repository’s settings do not require review.

Only the listed runs are covered. Selecting settings does not show that a check ran.

Current settings: Repository settings from commit af62fed; Shaka installation: version 1.0.0, installed revision (Shaka skill version under Execution details)

Step Settings source Run details
Validation Repository settings from commit af62fed validate: 2m 9s on m5 (Apple M5 Max, 18 cores, 128 GB RAM, 23 GB free, load 19→13)
Review Repository settings from commit af62fed openai/codex: 31s
Review Repository settings from commit af62fed anthropic/claude: 59s
Skills used

No skill use recorded; coverage is UNKNOWN.

Agent-reported use is the agent’s account. The agent selects Corroborated when a record confirms invocation or loading. Shaka does not check that record or instruction compliance. Completeness is not verified.

WIP Details
Field Value
Owner m5 · Claude Code desktop · s52u
Chat name RWRT · Fleet lane 3: React on Rails 17.2.1.rc.2 validation
Chat link claude://claude.ai/epitaxy/local_5a2845fc-b313-4a36-8a65-2b4fb5287fd9
Last observed activity 2026-11-01 22:59 HST
Revision justin808-claude/ad-hoc-sanitize-rescript-comment-markdown @ 11c58d2
Workspace scratch clone (local)
Unfinished work none
Stopped because running
Merge authority ask
State Maintainer approved merge when green; all checks passed
Next action Merge this head

Collection: alias available; host available; tag available; thread available.

PR prepared with Shaka.

Summary by CodeRabbit

  • Bug Fixes
    • Comments now display formatted text while filtering unsafe markup, including scripts and potentially harmful image behavior.

justin808 and others added 2 commits November 1, 2026 22:12
The ReScript comment view rendered marked output without sanitizing it,
unlike the React and server component views. Pass it through sanitize-html
with the same defaults the React view uses, and cover it with a system spec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Capybara skips hidden elements by default, so the script check could never
fail, and the handler check depended on timing. Check the DOM directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown

No description provided.

@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown

🚀 Quick Review App Commands

Welcome! Here are the commands you can use in this PR:
They require the repository to have cpflow review apps configured, including the CPLN_TOKEN_STAGING secret.

+review-app-deploy

Deploy your PR branch for testing.

+review-app-delete

Remove the review app when done.

+review-app-help

Show detailed instructions, environment setup, and configuration options.

Comment +review-app-help for full setup details.

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c3b102a1-dfc2-4872-9e2f-1bb91955d9e2
Commits

Reviewing files that changed from the base of the PR and between af62fed and 11c58d2.

Files selected for processing: 3
  • client/app/bundles/comments/rescript/CommentList/Comment/Comment.res
  • client/app/bundles/comments/rescript/bindings/SanitizeHtml.res
  • spec/rescript/rescript_spec.rb

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Walkthrough

Comment rendering now sanitizes generated markup before assigning it to innerHTML. A browser test checks that Markdown remains rendered and unsafe image and script elements are absent.

Changes

Comment markup rendering

Layer / File(s) Summary
Sanitize markup before rendering
client/app/bundles/comments/rescript/bindings/SanitizeHtml.res, client/app/bundles/comments/rescript/CommentList/Comment/Comment.res, spec/rescript/rescript_spec.rb
A ReScript binding exposes sanitize-html. Comment.make sanitizes generated markup before assigning it to innerHTML. The browser test checks Markdown rendering and the absence of image and script elements.

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix

Suggested reviewers: yassa-tamer

Merge Risk: Minimal · up to 11c58

No actionable merge-blocking risk was identified.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: sanitizing comment markdown on the ReScript page.
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
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.
Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 1 files. (2 skipped: 2 unsupported.)

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-02T08:19:59.719517Z 11c58d2 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@justin808

Copy link
Copy Markdown
Member Author

Local Adversarial Review

Reviewed revision: 11c58d2

Outcome: No open findings; closed and optional findings are in history.

Coverage: openai/codex: Read-only source lookup permitted; actual inspection coverage is UNKNOWN. Test execution is prohibited. · anthropic/claude: Read-only file lookup in an export of the reviewed commit; actual inspection coverage is UNKNOWN. Git history and test execution are unavailable.

Usage and attribution in the PR description.

Reviewed commits:

Commit Findings
ead1e8f openai/codex: 0 · anthropic/claude: 2 (1 fixed, 1 documented)
11c58d2 openai/codex: 0 · anthropic/claude: 0
Findings, assessments, and execution metadata

Finding details

  • F1 risk: Sanitize spec might pass with the fix reverted — fixed in 11c58d2 · Reported by: anthropic/claude · last recorded at ead1e8f

  • F2 nit: Script check ignores hidden elements — documented nit — Addressed in the same commit as F1 by counting hidden elements. · Reported by: anthropic/claude · last recorded at ead1e8f

Execution metadata

Round Commit Reviewer Findings Tokens Cost
1 ead1e8f openai/codex
model UNKNOWN
effort UNKNOWN
0 109,387 input
80,640 cached input
0 cache writes
1,322 output
36 reasoning output
$0.08 estimated
2 ead1e8f anthropic/claude
model claude-opus-5-5
effort UNKNOWN
2 (1 fixed, 1 documented) 16 input
307,025 cached input
21,350 cache writes
5,211 output
1,972 reasoning output
$0.34 estimated
3 11c58d2 openai/codex
model UNKNOWN
effort UNKNOWN
0 135,625 input
106,240 cached input
0 cache writes
1,221 output
19 reasoning output
$0.08 estimated
4 11c58d2 anthropic/claude
model claude-opus-5-5
effort UNKNOWN
0 14 input
271,288 cached input
23,202 cache writes
4,137 output
924 reasoning output
$0.32 estimated

Prompt: Shaka default · criteria af62fed

Commit ead1e8f · openai/codex, anthropic/claude · 2 findings

Commit: ead1e8f Sanitize comment markdown on the ReScript page

Review coverage:

Coverage excerpts may be incomplete or misidentified; inspect the original reports.

  • openai/codex: Read-only source lookup permitted; actual inspection coverage is UNKNOWN. Test execution is prohibited.

Unverified report excerpt · openai/codex:

Repository criteria: supplied from `af62fed35be931c86dc695afda480f7dd7c34e21:AGENTS.md`.

Inspected all three changed files, the unchanged React sanitizer usage, ReScript caller and Marked binding, compiler configuration, dependencies, route, and browser-test setup at the pinned HEAD.

Tests run: none; candidate-code execution was prohibited. Compilation and before/after browser behavior remain unverified.

The new spec should fail when sanitization is reverted because the injected image remains and can execute its handler. It covers bold formatting and this payload; unsafe URL schemes and broader markdown preservation are untested.
  • anthropic/claude: Read-only file lookup in an export of the reviewed commit; actual inspection coverage is UNKNOWN. Git history and test execution are unavailable.

Unverified report excerpt · anthropic/claude:

UNKNOWN; inspect the original report for complete coverage limits.
Round 1 · ead1e8f · openai/codex · effort UNKNOWN · 0 findings

Coverage

Repository criteria: supplied from af62fed35be931c86dc695afda480f7dd7c34e21:AGENTS.md.

Inspected all three changed files, the unchanged React sanitizer usage, ReScript caller and Marked binding, compiler configuration, dependencies, route, and browser-test setup at the pinned HEAD.

Tests run: none; candidate-code execution was prohibited. Compilation and before/after browser behavior remain unverified.

The new spec should fail when sanitization is reverted because the injected image remains and can execute its handler. It covers bold formatting and this payload; unsafe URL schemes and broader markdown preservation are untested.

Findings

no findings

The sanitizer matches the React view’s default-options contract. No gate weakening, permission widening, credentials, or unnecessary implementation complexity was identified.

REVIEWED ead1e8f BY openai/codex EFFORT UNKNOWN FINDINGS 0

Round 2 · ead1e8f · anthropic/claude · effort UNKNOWN · 2 findings

Coverage

  • Source I read (pinned commit):
    • client/app/bundles/comments/rescript/CommentList/Comment/Comment.res
    • client/app/bundles/comments/rescript/bindings/SanitizeHtml.res and Marked.res
    • bsconfig.json (module type is esmodule, so = "default" compiles to a default import)
    • client/app/bundles/comments/components/CommentBox/CommentList/Comment/Comment.jsx (the reference behaviour)
    • ReScriptShow.res (comments are fetched on the client after mount)
    • app/views/pages/rescript.html.erb (prerender: true)
    • spec/rescript/rescript_spec.rb and spec/rails_helper.rb (headless Chrome driver)
    • package.json (sanitize-html ~2.17.7 is already a dependency)
    • client/app/bundles/server-components/components/CommentsFeed.jsx
  • Tests run: none. I can't run commands here, so I haven't seen the spec pass, or fail on revert.
  • Missing context:
    • The compiled .res.js output isn't in the tree.
    • I didn't check how Chrome sizes a broken <img> with no alt.
    • I don't have the server-bundle/ExecJS config. Comment.jsx already uses sanitize-html and is prerendered, so this probably works server-side, but that's an inference.

Repository criteria: supplied (af62fed:AGENTS.md). It holds build and workflow rules only; none of them bear on this diff.

Correctness: The fix itself looks right. SanitizeHtml.sanitize is called with no options, which matches sanitizeHtml(rawMarkup) in Comment.jsx:15. Under sanitize-html's defaults, <strong> stays and <img> and <script> are removed. Comments that arrive over ActionCable render through the same Comment.res, so they are covered too.

Contract drift: none. Security: this tightens a gate and adds no credentials. Simplicity: the one-line binding is the minimum needed; there's nothing to remove.

Findings

  1. risk — spec/rescript/rescript_spec.rb:43-45: if the fix is reverted, the spec may still pass because of timing and visibility.

    • Comments load on the client after mount (ReScriptShow.res:32). have_css(... strong) waits until they render, and then evaluate_script("window.xssRan") runs straight away.
    • Without sanitizing, xssRan is only set once Chrome's request for missing.png returns a 404 and onerror fires. That happens asynchronously, so the check can run first and read nil.
    • The img check has a similar gap. have_no_css only counts visible elements. If Chrome draws the broken, alt-less image at 0×0, the check passes even though the <img> is in the DOM.
    • Neither failure-on-revert path is guaranteed. A sturdier version would count hidden elements too (visible: :all) and wait for xssRan instead of reading it once.
  2. nit — spec/rescript/rescript_spec.rb:44: the script check can never fail.

    • Capybara skips hidden elements by default, and a <script> element is never visible, so have_no_css(".js-comment-text script") passes whether or not the tag is there.
    • It can't fail when the fix is reverted. It needs visible: :all to test anything.

REVIEWED ead1e8f BY anthropic/claude EFFORT UNKNOWN FINDINGS 2

Collated as: #1 → F1, #2 → F2

Commit 11c58d2 · openai/codex, anthropic/claude · 0 findings

Commit: 11c58d2 Count hidden elements in the ReScript sanitize spec

Review coverage:

Coverage excerpts may be incomplete or misidentified; inspect the original reports.

  • openai/codex: Read-only source lookup permitted; actual inspection coverage is UNKNOWN. Test execution is prohibited.

Unverified report excerpt · openai/codex:

Repository criteria: supplied from `af62fed35be931c86dc695afda480f7dd7c34e21:AGENTS.md`.

Inspected the exact diff, pinned ReScript rendering and fetch callers, React sanitizer usage, dependency/build configuration, Rails endpoint/view, and Capybara setup.

Tests run: none; candidate-code execution was prohibited. Compilation and before/after browser behavior remain unverified.

Observation: F1 is resolved by inspection: the spec waits for rendered bold text, then checks for images and scripts including hidden elements. Reverting sanitization should leave those elements and fail the assertions. F2 is also addressed by `visible: :all`.

The regression covers initial comment loading; unsafe URL schemes, attributes on allowed tags, and live updates are not explicitly tested.
  • anthropic/claude: Read-only file lookup in an export of the reviewed commit; actual inspection coverage is UNKNOWN. Git history and test execution are unavailable.

Unverified report excerpt · anthropic/claude:

UNKNOWN; inspect the original report for complete coverage limits.
Round 3 · 11c58d2 · openai/codex · effort UNKNOWN · 0 findings

Coverage

Repository criteria: supplied from af62fed35be931c86dc695afda480f7dd7c34e21:AGENTS.md.

Inspected the exact diff, pinned ReScript rendering and fetch callers, React sanitizer usage, dependency/build configuration, Rails endpoint/view, and Capybara setup.

Tests run: none; candidate-code execution was prohibited. Compilation and before/after browser behavior remain unverified.

Observation: F1 is resolved by inspection: the spec waits for rendered bold text, then checks for images and scripts including hidden elements. Reverting sanitization should leave those elements and fail the assertions. F2 is also addressed by visible: :all.

The regression covers initial comment loading; unsafe URL schemes, attributes on allowed tags, and live updates are not explicitly tested.

Findings

no findings

REVIEWED 11c58d2 BY openai/codex EFFORT UNKNOWN FINDINGS 0

Round 4 · 11c58d2 · anthropic/claude · effort UNKNOWN · 0 findings

Coverage

  • Source inspected (pinned commit): client/app/bundles/comments/rescript/CommentList/Comment/Comment.res, rescript/bindings/SanitizeHtml.res, rescript/bindings/Marked.res, rescript/bindings/Axios.res, rescript/ReScriptShow.res, bsconfig.json, app/views/pages/rescript.html.erb, the React reference client/app/bundles/comments/components/CommentBox/CommentList/Comment/Comment.jsx, CommentsFeed.jsx (another place that uses sanitize-html), package.json/yarn.lock (sanitize-html ~2.17.7 is already a dependency), spec/rescript/rescript_spec.rb and spec/rails_helper.rb.
  • Tests run: none. This environment can't run commands, so I haven't checked the claim that the spec fails before the fix and passes after. That conclusion comes from reading the code.
  • Missing context: the compiled .res.js output isn't in the tree; I inferred it from bsconfig.json. I didn't inspect sanitize-html's source in node_modules, so I relied on its documented defaults (img not allowed by default, script dropped along with its content, strong allowed).

Repository criteria: supplied (af62fed:AGENTS.md). Nothing in those criteria applies to this change beyond the general build and test commands.

Findings

Prior rounds

  • [F1]: resolved. The spec passes only if the fix is present:
    • Comments load on the client after mount (ReScriptShow.res:66-69), and the default driver is :headless_chrome (rails_helper.rb:73).
    • The positive strong assertion at rescript_spec.rb:42 comes first. Capybara waits for it, so the comment is rendered before the negative assertions at lines 43-44 run.
    • With the fix reverted, marked passes the inline <img> and <script> through unchanged, and dangerouslySetInnerHTML puts both into the DOM. Line 43 and line 44 would then fail.
  • [F2]: resolved. visible: :all at rescript_spec.rb:43-44 now also counts the hidden <script> element.

Fresh review

  • Correctness: bsconfig.json sets "module": "esmodule", so @module("sanitize-html") ... = "default" compiles to a default import. That's the same import Comment.jsx:7 uses, so CommonJS interop is fine, and server rendering works the same way it does for the React bundle. The call sanitize(rawMarkup) takes no options, matching Comment.jsx:15 as the description asks. I found no input that bypasses the sanitizer or loses the markdown formatting.
  • Contract drift: none. CommentBox.jsx:194 says submitted Markdown is sanitized before display, and that is now also true on /rescript.
  • Security and trust: this closes a stored XSS on /rescript. No gate is weakened, no permissions are widened, and no credentials were added.
  • Tests: regression coverage is in place (see F1). Not covered: comments that arrive over ActionCable (ReScriptShow.res:46-47). They render through the same Comment.res, so they get the same protection, but no test exercises that path.
  • Simplicity: the onerror="window.xssRan = true" and window.xssRan payloads in the fixture are never asserted. Checking that the elements are absent from the DOM is a stronger test anyway, so this is only an observation.

no findings

REVIEWED 11c58d2 BY anthropic/claude EFFORT UNKNOWN FINDINGS 0

REVIEWED 11c58d2 BY anthropic/claude EFFORT UNKNOWN FINDINGS 0

@claude

claude Bot commented Nov 2, 2026

Copy link
Copy Markdown

Review: LGTM. The ReScript page now sanitizes the marked output the same way the JSX Comment.jsx does. sanitize-html is already a dependency, and the @module("sanitize-html") ... = "default" binding compiles to a default import under the esmodule config. The spec covers both the onerror attribute and the <script> tag.

Minor, non-blocking:

  • The spec only asserts that img and script are absent. It doesn't check that window.xssRan is unset, which would be a more direct check.
  • Sanitization happens on every render. Wrapping it in React.useMemo keyed on comment.text would avoid that, though it's not important at this scale.

I didn't run the specs or build the ReScript code.

@greptile-apps

greptile-apps Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge.

Summary

The ReScript comment view now sanitizes Markdown output before inserting HTML.

  • ReScript comments keep Markdown formatting without unsafe HTML.

Reviews (1) · Last reviewed commit: "Count hidden elements in the ReScript sa..." · Reviewed by Greptile

@justin808 justin808 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

🤖 Claude Code · Anthropic · claude-opus-5-5 · medium

Code Walkthrough

Comments on /rescript now show sanitized markdown, so HTML in a comment can no longer run script in a visitor's browser. The new binding exposes the sanitize-html package that was already a dependency, and the view applies it before dangerouslySetInnerHTML with the same defaults as Comment.jsx. The spec fails with the fix reverted. This head passed smoke / Demo fleet smoke, claude-review, CodeRabbit, and Greptile Review. Rollback is a revert, which restores the vulnerability.

Walkthrough for commit 11c58d2. This is a COMMENT, not an approval.

it "keeps markdown formatting and removes unsafe HTML" do
expect(page).to have_css(".js-comment-text strong", text: "bold")
expect(page).to have_no_css(".js-comment-text img", visible: :all)
expect(page).to have_no_css(".js-comment-text script", visible: :all)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

🤖 Claude Code · Anthropic · claude-opus-5-5 · medium

Capybara skips hidden elements by default, and a script element is never visible, so these checks need visible: :all to be able to fail.

@justin808

Copy link
Copy Markdown
Member Author

🤖 Codex · OpenAI · gpt-6.1-sol (configured) · medium

Checkpoint for 11c58d2

Recommendation: Merge

The fix closes the stored XSS with the existing sanitizer, the spec fails without it, and all checks passed.

Reviewer verdict: Proceed, no unresolved concerns.

Verification: Adequate, with gaps the reviewer judged acceptable.

  • Weakest claim: Protection on the live demo remains unverified. Action Cable comments share the tested renderer, making the missing delivery test acceptable.
  • Next check: After deployment, check that the live /rescript page removes the same unsafe markup and preserves bold formatting.
Supporting analysis and execution details

Reviewer summary: ReScript comments now use the existing sanitizer, protecting visitors while retaining markdown. Exploit frequency is unknown; Action Cable delivery and the live demo remain untested.

Three changed files across two commits match the planned scope. The hidden-element assertion repair addresses weak evidence, not an approach problem.

The existing sanitizer preserves markdown without adding dependencies, configuration, or shared abstractions. Matching the React view justifies removing unrestricted HTML.

The reported Chrome regression fails without the fix and passes with it. Full validation adds proportionate confidence.

Alternative considered: Plain-text rendering would remove markdown support. No evidence justifies losing that advertised capability; reuse of the existing sanitizer is already minimal.

observed model: UNKNOWN; configured model: gpt-6.1-sol · recorded effort: medium · prompt: Shaka default

Native usage (openai · gpt-6.1-sol · UNKNOWN · medium), 6 responses: 133,758 input, 102,272 cached input, 0 cache writes, 1,142 output, 48 reasoning output, 134,900 total.

Ruby checked this report's format and commit; the reviewer judged value, not correctness. Required checks, reviews, and merge authority still apply.

Squash commit message

Squash commit message for 11c58d2. Paste the title and the body into GitHub's squash merge boxes.

Sanitize comment markdown on the ReScript page (#877)
Comments on /rescript rendered marked output directly, so HTML in a
comment could run script in another visitor's browser. The React and
server component views already sanitize.

Pass the output through sanitize-html with the same defaults as
Comment.jsx, and add a system spec that fails without the fix.

Security: this closes a stored XSS on /rescript. Stored comments are
unchanged; they are sanitized when displayed.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

@justin808
justin808 merged commit 945ae9a into master Nov 2, 2026
14 checks passed
@justin808
justin808 deleted the justin808-claude/ad-hoc-sanitize-rescript-comment-markdown branch November 2, 2026 09:00
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown

No description provided.

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

✅ Review App Deleted

Review app for PR #877 is deleted

🎮 Control Plane Console
📋 View Workflow Logs

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.

1 participant