[chore]: security dependency update and maintenance#351
Conversation
…ics in ListingsHeader
…te UI configuration
…ser for stricter input sanitization
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughPins and upgrades dependencies, centralizes CORS origin parsing and allowlist checks, unifies markdown sanitization (DOMPurify for browser+Node) with expanded tests, gates notifications by Clerk auth with typed category parsing and logging, updates UI spinners and Swagger embedding, and bumps Docker production base image. ChangesCORS Security & Dependency Updates
Markdown Sanitization & Security
UI/UX & Authentication Updates
Sequence Diagram(s)sequenceDiagram
participant User
participant NotificationsPage
participant Clerk
participant QueryHooks
User->>NotificationsPage: Open notifications page
NotificationsPage->>Clerk: call useUser()
Clerk-->>NotificationsPage: isLoaded, isSignedIn
alt Clerk not loaded
NotificationsPage->>User: show NotificationsLoading
else Not signed in
NotificationsPage->>User: show SignInRequired (SignInButton)
else Signed in
NotificationsPage->>QueryHooks: enable queries (selected category)
QueryHooks-->>NotificationsPage: notifications & stats
NotificationsPage->>User: render notifications list
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
src/app/notifications/NotificationsPage.tsx (1)
40-46: 💤 Low valueSimplify
parseSelectedCategoryby checking enum membership directly.The four-way if-chain duplicates every enum member by name and will silently rot if a new
NotificationCategoryvariant is added — the new value would be coerced to'all'instead of itself. A single membership check against the enum's values keeps it data-driven:♻️ Proposed simplification
-function parseSelectedCategory(value: string): NotificationCategory | 'all' { - if (value === NotificationCategory.ENGAGEMENT) return NotificationCategory.ENGAGEMENT - if (value === NotificationCategory.CONTENT) return NotificationCategory.CONTENT - if (value === NotificationCategory.SYSTEM) return NotificationCategory.SYSTEM - if (value === NotificationCategory.MODERATION) return NotificationCategory.MODERATION - return 'all' -} +const CATEGORY_VALUES = Object.values(NotificationCategory) as NotificationCategory[] + +function parseSelectedCategory(value: string): NotificationCategory | 'all' { + return (CATEGORY_VALUES as string[]).includes(value) ? (value as NotificationCategory) : 'all' +}🤖 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 `@src/app/notifications/NotificationsPage.tsx` around lines 40 - 46, The function parseSelectedCategory repeats each NotificationCategory member and will miss future enum values; change it to perform a membership check against the enum values (e.g. use Object.values(NotificationCategory).includes(value)) and, if present, return the value cast to NotificationCategory, otherwise return 'all'—update parseSelectedCategory to rely on enum membership rather than hard-coded branches.src/app/docs/api/swagger/SwaggerPage.tsx (1)
8-8: 💤 Low valueBundle the 563 KB OpenAPI spec only when needed, not in the initial page chunk.
Two concerns with this static import:
Non-idiomatic path traversal: Importing from
public/via../../../../../is brittle (any file move breaks it) and violates the intended use ofpublic/for static asset serving, not module resolution.Deferred loading lost: Switching from
urltoconfiguration.content = openApiDocumentbundles the ~563 KB spec into the page's client JavaScript chunk, downloaded by every visitor. The component itself is lazy-loaded (line 13), but the spec payload is not, negating the performance benefit of deferred loading.Suggested fix: Move
mobile-openapi.jsontosrc/with a path alias (e.g.,@/server/api), or restoreurl-based loading so Scalar fetches the spec on demand—especially given the file size.🤖 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 `@src/app/docs/api/swagger/SwaggerPage.tsx` at line 8, The static import of mobile-openapi.json in SwaggerPage.tsx (openApiDocument) pulls a 563KB spec into the initial client bundle and uses a brittle relative path into public/; instead, stop the direct import and either (A) move the JSON into src/ and import it via your project path alias (e.g., from `@/server/api`) so it becomes a proper module you can lazy-load, or (B) restore URL-based loading so the component sets configuration.content by fetching the JSON at runtime (keep the lazy-loaded component intact) and assign configuration.content only after the fetch resolves; also remove the ../../../../../public import and update any references to openApiDocument and configuration.content accordingly.src/utils/markdown.ts (1)
8-10: ⚖️ Poor tradeoffThe workaround is partially redundant; jsdom is already marked as server-external in next.config.js.
serverExternalPackages: ['jsdom']is already configured, which mitigates the bundling risk. However, the fragile'js' + 'dom'concatenation and__non_webpack_require__workaround remain unnecessary. Simplify to a plainimport { JSDOM } from 'jsdom'at the top of the file, since jsdom is already guaranteed to be server-external.🤖 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 `@src/utils/markdown.ts` around lines 8 - 10, Remove the fragile runtime workaround using __non_webpack_require__ and the JSDOM_PACKAGE_NAME concatenation and replace it with a direct top-level import; specifically delete the declare __non_webpack_require__ and const JSDOM_PACKAGE_NAME = 'js' + 'dom' lines and add a plain import { JSDOM } from 'jsdom' at the top so the file uses the JSDOM symbol directly (serverExternalPackages already prevents bundling).src/utils/markdown.test.ts (2)
153-158: 💤 Low valueConsider tightening the
data:assertion to avoid false negatives.
expect(result).not.toContain('data:')will pass if the sanitizer simply HTML-encodes the colon (e.g.,data:) or any other encoding form, even though the underlying URI would still resolve in some contexts. Since the input also contains a base64-encoded<script>payload (PHNjcmlwdD4=), it might be worth additionally assertingnot.toContain('PHNjcmlwdD4=')(or the decoded<script>) to ensure the full payload is dropped rather than just the scheme literal.♻️ Suggested additional assertion
it('should strip unsafe data URI markdown links', () => { const result = parseMarkdown('[bad](data:text/html;base64,PHNjcmlwdD4=)') expect(result).not.toContain('data:') expect(result).not.toContain('href=') + expect(result).not.toContain('PHNjcmlwdD4=') })🤖 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 `@src/utils/markdown.test.ts` around lines 153 - 158, The test "should strip unsafe data URI markdown links" currently only asserts absence of the literal "data:" which can be evaded by encoding; update the test around parseMarkdown('[bad](data:text/html;base64,PHNjcmlwdD4=)') to also assert that the base64 payload and decoded tag are removed — e.g., add negative assertions for the base64 chunk "PHNjcmlwdD4=" and for the decoded "<script>" token — so parseMarkdown is verified to drop the actual payload, not just the scheme literal.
169-178: ⚡ Quick winWall-clock ReDoS assertion is likely to flake on shared CI runners.
expect(duration).toBeLessThan(1000)is sensitive to runner load, GC pauses, and noisy neighbors. A 30k-char linkify input that normally completes in tens of milliseconds locally can occasionally cross 1s on a contended CI VM, leading to false positives that look like security regressions. A few options to make this more robust:
- Raise the budget to something clearly catastrophic (e.g., 3–5s) so only true ReDoS regressions trip it, while still failing fast on quadratic/exponential blowups.
- Combine the time bound with a process-level timeout (Vitest
{ timeout }) and preferperformance.now()overDate.now()for sub-ms precision.- Optionally gate strict timing in CI via an env flag, or mark the test as
it.concurrentonly when timing is loose.♻️ Suggested adjustment
- it('should handle markdown-it linkify ReDoS input quickly', () => { - const redosInput = `https://example.com/${'*'.repeat(30000)}!` - const start = Date.now() - - const result = parseMarkdown(redosInput) - const duration = Date.now() - start - - expect(typeof result).toBe('string') - expect(duration).toBeLessThan(1000) - }) + it( + 'should handle markdown-it linkify ReDoS input quickly', + () => { + const redosInput = `https://example.com/${'*'.repeat(30000)}!` + const start = performance.now() + + const result = parseMarkdown(redosInput) + const duration = performance.now() - start + + expect(typeof result).toBe('string') + // Generous bound: catches catastrophic backtracking without + // flaking on contended CI runners. + expect(duration).toBeLessThan(3000) + }, + 5000, + )🤖 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 `@src/utils/markdown.test.ts` around lines 169 - 178, The wall-clock assertion in the test for parseMarkdown is flaky; switch timing to use performance.now() instead of Date.now(), raise the allowed duration to a safer threshold (e.g., 3000 ms) to avoid CI noise, and add a Vitest per-test timeout (e.g., set the it(...) timeout to 5000 ms) so the test fails by the test runner on catastrophic hangs rather than flaky assertions; update the test case surrounding parseMarkdown(redosInput) to compute duration via performance.now(), assert duration < 3000, and pass a timeout option to the it call.
🤖 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 `@src/lib/cors.ts`:
- Around line 80-91: The isAllowedRequestOrigin function signature includes
requestOrigin but never uses it, so either implement same-origin logic or remove
the dead parameter: Option A (honor same-origin) — inside isAllowedRequestOrigin
(and using getOriginFromUrl) add an early check that if params.requestOrigin
equals the parsed sourceOrigin (or if requestOrigin is present and is included
in allowedOrigins) return true, then fall back to existing source parsing and
allowedOrigins checks; update tests in src/lib/cors.test.ts to assert
same-origin behavior. Option B (remove param) — remove requestOrigin from the
params shape of isAllowedRequestOrigin, drop passing req.nextUrl.origin from the
middleware call sites in middleware.ts, and update the tests to stop relying on
a requestOrigin argument. Choose one approach and make corresponding changes to
isAllowedRequestOrigin, its call sites, and tests (referenced symbols:
isAllowedRequestOrigin, getOriginFromUrl, and the middleware call that passes
req.nextUrl.origin).
In `@src/utils/markdown.ts`:
- Around line 108-123: The code calls process.getBuiltinModule directly in
getServerRequire (used by loadJSDOM), which doesn't exist on Node 20; change
getServerRequire to feature-detect process.getBuiltinModule (typeof
process.getBuiltinModule === 'function') and only call it when available,
otherwise fall back to requiring the built-in Module via plain require('module')
and use its createRequire; keep the existing error path if neither approach
yields a Module and preserve how loadJSDOM still prefers __non_webpack_require__
and then getServerRequire(JSDOM_PACKAGE_NAME).JSDOM so behavior and identifiers
(loadJSDOM, getServerRequire, JSDOM_PACKAGE_NAME, __non_webpack_require__)
remain the same.
- Around line 75-88: The pre-check using DANGEROUS_PATTERNS /
DANGEROUS_TEXT_REPLACEMENTS and the short‑circuit via containsDangerousInput
that forces parseMarkdown and stripMarkdown to call sanitizePlainText should be
removed: stop gating parseMarkdown and stripMarkdown on containsDangerousInput
and let DOMPurify’s ALLOWED_ATTR whitelist handle sanitization; remove or stop
using the DANGEROUS_* arrays and the branch that collapses to sanitizePlainText
(but if you need a fast deny-path, move any heuristic checks to the rendered
HTML sanitization step, e.g., inspect the produced DOM/html for <script> or
suspicious attribute values instead of the raw markdown).
---
Nitpick comments:
In `@src/app/docs/api/swagger/SwaggerPage.tsx`:
- Line 8: The static import of mobile-openapi.json in SwaggerPage.tsx
(openApiDocument) pulls a 563KB spec into the initial client bundle and uses a
brittle relative path into public/; instead, stop the direct import and either
(A) move the JSON into src/ and import it via your project path alias (e.g.,
from `@/server/api`) so it becomes a proper module you can lazy-load, or (B)
restore URL-based loading so the component sets configuration.content by
fetching the JSON at runtime (keep the lazy-loaded component intact) and assign
configuration.content only after the fetch resolves; also remove the
../../../../../public import and update any references to openApiDocument and
configuration.content accordingly.
In `@src/app/notifications/NotificationsPage.tsx`:
- Around line 40-46: The function parseSelectedCategory repeats each
NotificationCategory member and will miss future enum values; change it to
perform a membership check against the enum values (e.g. use
Object.values(NotificationCategory).includes(value)) and, if present, return the
value cast to NotificationCategory, otherwise return 'all'—update
parseSelectedCategory to rely on enum membership rather than hard-coded
branches.
In `@src/utils/markdown.test.ts`:
- Around line 153-158: The test "should strip unsafe data URI markdown links"
currently only asserts absence of the literal "data:" which can be evaded by
encoding; update the test around
parseMarkdown('[bad](data:text/html;base64,PHNjcmlwdD4=)') to also assert that
the base64 payload and decoded tag are removed — e.g., add negative assertions
for the base64 chunk "PHNjcmlwdD4=" and for the decoded "<script>" token — so
parseMarkdown is verified to drop the actual payload, not just the scheme
literal.
- Around line 169-178: The wall-clock assertion in the test for parseMarkdown is
flaky; switch timing to use performance.now() instead of Date.now(), raise the
allowed duration to a safer threshold (e.g., 3000 ms) to avoid CI noise, and add
a Vitest per-test timeout (e.g., set the it(...) timeout to 5000 ms) so the test
fails by the test runner on catastrophic hangs rather than flaky assertions;
update the test case surrounding parseMarkdown(redosInput) to compute duration
via performance.now(), assert duration < 3000, and pass a timeout option to the
it call.
In `@src/utils/markdown.ts`:
- Around line 8-10: Remove the fragile runtime workaround using
__non_webpack_require__ and the JSDOM_PACKAGE_NAME concatenation and replace it
with a direct top-level import; specifically delete the declare
__non_webpack_require__ and const JSDOM_PACKAGE_NAME = 'js' + 'dom' lines and
add a plain import { JSDOM } from 'jsdom' at the top so the file uses the JSDOM
symbol directly (serverExternalPackages already prevents bundling).
🪄 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: 81d84f57-cd91-4f8b-95c0-0906bd428990
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (10)
package.jsonsrc/app/docs/api/swagger/SwaggerPage.tsxsrc/app/notifications/NotificationsPage.tsxsrc/app/v2/listings/components/ListingsHeader.tsxsrc/lib/cors.test.tssrc/lib/cors.tssrc/middleware.tssrc/utils/markdown.node.test.tssrc/utils/markdown.test.tssrc/utils/markdown.ts
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/utils/markdown.ts (1)
178-209:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReplace the raw-input dangerous-pattern gate in
validateMarkdownand fix the user-facing error.This block reintroduces the same false-positive problem that was just removed from
parseMarkdown/stripMarkdown, only here it surfaces aserrors/isValid=false:
- False positives on benign prose. Patterns like
/javascript:/i,/vbscript:/i,/<script/i,/onload=/i,/onerror=/i,/data:.*base64/imatch any markdown that describes these schemes (e.g., a doc note about "Never usejavascript:URLs", a code block showing<script>, or text containingonload=true). These will be reported as invalid even thoughparseMarkdown(line 194) already routes through DOMPurify with the allowlistedPURIFY_CONFIG, which is the actual security boundary.- Unprofessional/joke error string.
'Masterhacker detected.'(line 189) is user-visible via the returnederrorsarray and is neither actionable nor appropriate for production UI/logs.- Duplicated error pushes. The
forEachloop pushes the same string once per matching pattern, so a single input that hits both<scriptandonload=yieldserrors.length === 2with identical entries.DOMPurify’s whitelist already strips event handlers, dangerous URI schemes, and
<script>/<object>/<embed>etc., so the regex layer adds no security but breaks legitimate inputs and validation UX. If a fast deny-path is genuinely required, run the heuristic against the rendered HTML (or compare pre/post-sanitization output) rather than the raw markdown source, and use a single, generic, deduplicated message.♻️ Suggested simplification
export function validateMarkdown(markdownText: string): { isValid: boolean cleanText: string errors: string[] } { const errors: string[] = [] if (!markdownText) { return { isValid: true, cleanText: '', errors: [] } } - const dangerousPatterns = [ - /javascript:/i, - /data:.*base64/i, - /vbscript:/i, - /<script/i, - /onload=/i, - /onerror=/i, - ] - - dangerousPatterns.forEach((pattern) => { - if (pattern.test(markdownText)) { - errors.push('Masterhacker detected.') - } - }) - try { const cleanText = parseMarkdown(markdownText) return { isValid: errors.length === 0, cleanText, errors, } } catch (error) { console.warn('Markdown validation failed:', error) errors.push('Invalid markdown syntax') return { isValid: false, cleanText: markdownText, errors, } } }If a "potentially unsafe content was removed" signal is still required, derive it by comparing
md.render(markdownText)to the DOMPurify output and emitting a single generic message when they differ.🤖 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 `@src/utils/markdown.ts` around lines 178 - 209, The validateMarkdown function currently scans raw markdownText with dangerousPatterns and pushes the jokey 'Masterhacker detected.' message repeatedly; remove that raw-input regex gate and instead detect removed/modified dangerous content by comparing the rendered HTML from md.render(markdownText) (or parseMarkdown output) with the DOMPurify-sanitized cleanText returned by parseMarkdown/stripMarkdown, and if they differ push a single, professional, generic error into errors (e.g., "Potentially unsafe content was removed") and ensure only one entry is added (dedupe), leaving parseMarkdown/stripMarkdown/DOMPurify as the primary security boundary.
🤖 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.
Outside diff comments:
In `@src/utils/markdown.ts`:
- Around line 178-209: The validateMarkdown function currently scans raw
markdownText with dangerousPatterns and pushes the jokey 'Masterhacker
detected.' message repeatedly; remove that raw-input regex gate and instead
detect removed/modified dangerous content by comparing the rendered HTML from
md.render(markdownText) (or parseMarkdown output) with the DOMPurify-sanitized
cleanText returned by parseMarkdown/stripMarkdown, and if they differ push a
single, professional, generic error into errors (e.g., "Potentially unsafe
content was removed") and ensure only one entry is added (dedupe), leaving
parseMarkdown/stripMarkdown/DOMPurify as the primary security boundary.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e5852d7b-0790-4085-ba3f-5199c36e6022
📒 Files selected for processing (7)
Dockerfilesrc/lib/cors.test.tssrc/lib/cors.tssrc/middleware.tssrc/utils/markdown.node.test.tssrc/utils/markdown.test.tssrc/utils/markdown.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- src/utils/markdown.node.test.ts
- src/lib/cors.test.ts
- src/lib/cors.ts
- src/middleware.ts
- src/utils/markdown.test.ts
There was a problem hiding this comment.
🧹 Nitpick comments (3)
src/utils/markdown.ts (3)
10-10: 💤 Low valueAdd a brief comment explaining the
'js' + 'dom'concatenation.The string concatenation is a deliberate trick to prevent webpack from statically analyzing and bundling
jsdominto client output, but this intent is non-obvious. A short comment will save future maintainers from "simplifying" it back to a literal.📝 Proposed comment
-const JSDOM_PACKAGE_NAME = 'js' + 'dom' +// Concatenated to hide the literal from webpack's static analyzer so `jsdom` +// is never bundled into client output; it must only be resolved at runtime on the server. +const JSDOM_PACKAGE_NAME = 'js' + 'dom'🤖 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 `@src/utils/markdown.ts` at line 10, Add a brief inline comment above the JSDOM_PACKAGE_NAME constant explaining that the 'js' + 'dom' concatenation is intentional to avoid static bundlers (e.g., webpack) from detecting and bundling the "jsdom" package into client builds; reference the constant name JSDOM_PACKAGE_NAME and keep the comment short and explicit about the bundling avoidance intent.
101-112: 💤 Low valueRedundant guard, but functionally correct.
The
typeof process.getBuiltinModule !== 'function'check at line 102 and theif (!moduleBuiltin)check at line 107 throw the same error. In practice, whengetBuiltinModuleexists, calling it with'module'will always return the builtin (or throwERR_UNKNOWN_BUILTIN_MODULE), so the second guard is unreachable defensive coding. Not worth changing on its own, but consider consolidating if you touch this again.🤖 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 `@src/utils/markdown.ts` around lines 101 - 112, The getServerRequire function contains a redundant second guard that checks moduleBuiltin after verifying process.getBuiltinModule exists; consolidate the checks by either using a single try/catch around calling process.getBuiltinModule('module') or keep the initial typeof check and remove the unreachable if (!moduleBuiltin) branch. Update getServerRequire to call process.getBuiltinModule('module') inside a try block (or after the typeof check) and throw the same Error('Node module loader is unavailable for server-side markdown sanitization') if obtaining the builtin fails, then return moduleBuiltin.createRequire(`${process.cwd()}/package.json`) as before.
122-165: 💤 Low valueFallback in
catchreuses the same DOMPurify instance that may have caused the failure.If
getPurify()ever throws during initialization (e.g., jsdom resolution fails at runtime on the server path),sanitizeHtml(html)throws on line 128, then the catch invokessanitizePlainText(markdownText)which also callsgetPurify()— re-throwing the exact same error and propagating out ofparseMarkdown. Same applies tostripMarkdownat line 163.This is a narrow edge case (only matters when DOMPurify init itself fails, not when markdown-it fails), so it's a defense-in-depth concern rather than a blocker. If you want a truly safe terminal fallback, escape with a tiny inline helper instead of routing back through DOMPurify:
♻️ Optional safer fallback
+function escapeHtml(text: string): string { + return text + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} + export function parseMarkdown(markdownText: string): string { if (!markdownText) return '' try { const html = md.render(markdownText) return sanitizeHtml(html) } catch (error) { console.warn('Markdown parsing failed:', error) - return sanitizePlainText(markdownText) + return escapeHtml(markdownText) } }🤖 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 `@src/utils/markdown.ts` around lines 122 - 165, parseMarkdown and stripMarkdown currently call sanitizePlainText in their catch blocks which in turn invokes getPurify(), so if DOMPurify/init fails the fallback will re-throw the same error; replace those fallbacks with a safe, pure-JS escape that does not call getPurify() (e.g., a small inline escapeHtml helper that replaces &, <, >, ", ' and collapses whitespace/newlines to single spaces) and return that escaped string from the catch in both parseMarkdown and stripMarkdown; keep references to md.render usage intact but ensure the catch never calls sanitizePlainText or any function that calls getPurify().
🤖 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 `@src/utils/markdown.ts`:
- Line 10: Add a brief inline comment above the JSDOM_PACKAGE_NAME constant
explaining that the 'js' + 'dom' concatenation is intentional to avoid static
bundlers (e.g., webpack) from detecting and bundling the "jsdom" package into
client builds; reference the constant name JSDOM_PACKAGE_NAME and keep the
comment short and explicit about the bundling avoidance intent.
- Around line 101-112: The getServerRequire function contains a redundant second
guard that checks moduleBuiltin after verifying process.getBuiltinModule exists;
consolidate the checks by either using a single try/catch around calling
process.getBuiltinModule('module') or keep the initial typeof check and remove
the unreachable if (!moduleBuiltin) branch. Update getServerRequire to call
process.getBuiltinModule('module') inside a try block (or after the typeof
check) and throw the same Error('Node module loader is unavailable for
server-side markdown sanitization') if obtaining the builtin fails, then return
moduleBuiltin.createRequire(`${process.cwd()}/package.json`) as before.
- Around line 122-165: parseMarkdown and stripMarkdown currently call
sanitizePlainText in their catch blocks which in turn invokes getPurify(), so if
DOMPurify/init fails the fallback will re-throw the same error; replace those
fallbacks with a safe, pure-JS escape that does not call getPurify() (e.g., a
small inline escapeHtml helper that replaces &, <, >, ", ' and collapses
whitespace/newlines to single spaces) and return that escaped string from the
catch in both parseMarkdown and stripMarkdown; keep references to md.render
usage intact but ensure the catch never calls sanitizePlainText or any function
that calls getPurify().
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7401a8e4-ca5d-4cb0-8159-76aeb4fa848c
📒 Files selected for processing (4)
.vercelignoresrc/middleware.tssrc/utils/markdown.test.tssrc/utils/markdown.ts
✅ Files skipped from review due to trivial changes (1)
- .vercelignore
🚧 Files skipped from review as they are similar to previous changes (2)
- src/middleware.ts
- src/utils/markdown.test.ts
Description
Security-floor dependency update/maintenance
Type of change
How Has This Been Tested?
Checklist
Notes for reviewers
This PR intentionally stays on the security floor. Major upgrades to
next@16,@clerk/nextjs@7, and other latest-major work should be handled in a separate PR.Summary by CodeRabbit
New Features
Bug Fixes
Style
Tests
Chores