Repository navigation
fix(frontend): use shared quote-safe escapeHtml in apikeys and users - #1214
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 40 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughConsolidates divergent ChangesHTML escaping consolidation and attribute-injection protection
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
4802828 to
862e3b2
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
862e3b2 to
53d335b
Compare
Adversarial-review passRan the playbook across the OWASP-5 escape contract, the XSS sinks the PR touches, the other frontend modules that interpolate API data into innerHTML (i.e. the regression surface the PR could have missed), and the UNSTABLE CI checks. Confirmed clean
In-scope fix pushed (
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
53d335b to
9f65d13
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
9f65d13 to
96a90ec
Compare
escapeHtml was implemented three times with diverging security semantics: the shared frontend/src/utils.ts copy escapes &, <, >, " and ', while the local copies in apikeys.ts and users/utils.ts used a DOM round-trip (div.textContent -> div.innerHTML) that leaves both quote characters unescaped. Those weak copies were interpolated inside double-quoted attributes (data-key-id in apikeys.ts, aria-label and option value across users/userList.ts, filters.ts and permissionMatrix.ts), so a value containing a double quote could break out of the attribute and inject markup. Delete both local copies: apikeys.ts now imports escapeHtml from ./utils and users/utils.ts re-exports it from ../utils (same pattern already used there for formatRelativeTime/formatDate), so all users/ modules pick up the quote-safe implementation unchanged. Update the users/utils tests that previously asserted the unescaped quote behavior, and add regression tests replicating the real scenarios: a key id containing a double quote rendered through renderApiKeysList, and a quoted value in attribute position through the users escapeHtml. Both fail against the pre-fix code. Closes #1195
96a90ec to
fbab688
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Merge-prep bot: rebased onto main (73166c2). All API fields routed through shared quote-safe escapeHtml from utils.ts (XSS rule confirmed). Gates: fe_test=0 (jest exit 0 on apikeys and users test files). |
|
Merged to main after review (rebased onto current main, CLEAN + all CI incl Frontend build green; closes #1195). Routes every API field rendered into innerHTML through the shared quote-safe escapeHtml from utils.ts, enforcing the innerHTML-XSS rule. Frontend-only. |
Problem
Closes #1195 (review finding ARCH-07, P2).
escapeHtmlwas implemented three times in the frontend with diverging security semantics:frontend/src/utils.tsescapes&,<,>,"and'(quote-safe).frontend/src/apikeys.tsandfrontend/src/users/utils.tseach re-implemented it via a DOM round-trip (div.textContent = text; return div.innerHTML;), which escapes only&,<,>and leaves both quote characters intact.The weak copies were interpolated inside double-quoted attributes:
data-key-id="${escapeHtml(key.id)}"in apikeys.ts, and aria-label / option value positions acrossusers/userList.ts,users/filters.tsandusers/permissionMatrix.ts. A value containing a double quote could break out of the attribute and inject markup (latent attribute-injection trap; exploitability today is low since the values are mostly server-generated IDs).Fix
Per the report recommendation, delete both local copies and route everything through the shared quote-safe implementation:
apikeys.ts: localescapeHtmlremoved; importsescapeHtmlfrom./utils.users/utils.ts: localescapeHtmlreplaced with a re-export from../utils(same pattern already used there forformatRelativeTime/formatDate), so allusers/modules pick up the safe implementation with no import churn.Tests that previously asserted the unescaped-quote behavior were updated to assert the quote-safe output, and the
11-M2filters test now asserts the real invariant (no injected element; option value round-trips the raw id) instead of a side effect of the old truncation.Regression tests
Replicating the real failing scenarios:
apikeys.test.ts: a key id ofkey-1" onmouseover="alert(1)rendered throughrenderApiKeysListmust not inject anonmouseoverattribute, anddata-key-idmust round-trip the raw id.users.test.ts: a quoted value interpolated in attribute position through the usersescapeHtmlcannot break out of the attribute; plus direct quote-escaping assertions.Verified fail-before / pass-after: with the source fix stashed, 7 tests fail (the new regression tests plus the updated quote assertions); with the fix applied, all pass.
Test evidence
npx tsc --noEmit: no errorsnpx jest: 2555 passed, 0 failed, 1 skippednpm run build: succeedsnpm run lintis broken onmainas well (no ESLint config file in the repo), unrelated to this changeSummary by CodeRabbit
Release Notes
Bug Fixes
Tests