Skip to content

fix: sanitize AI-generated presentation markup with DOMPurify#1055

Open
anshul23102 wants to merge 6 commits into
Muneerali199:mainfrom
anshul23102:fix/1050-sanitize-ai-generated-markup-xss
Open

fix: sanitize AI-generated presentation markup with DOMPurify#1055
anshul23102 wants to merge 6 commits into
Muneerali199:mainfrom
anshul23102:fix/1050-sanitize-ai-generated-markup-xss

Conversation

@anshul23102

@anshul23102 anshul23102 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #1050

The issue as filed describes AI-generated resume content rendered via dangerouslySetInnerHTML, but that render path doesn't actually exist in this codebase — resume content is rendered as plain React text/JSX, never raw HTML.

The equivalent, currently-exploitable version of this bug does exist in the presentation generator: AI-generated SVG diagrams and Tailwind HTML mockups from Gemini/Mistral are rendered via dangerouslySetInnerHTML across four components, each with its own hand-rolled regex blocklist "sanitizer" instead of a real one.

Why the existing sanitizers don't work

.replace(/\son[a-z]+=(["']).*?\1/gi, '')  // requires quotes around the handler

This only strips quoted event-handler attributes. <img src=x onerror=alert(1)> (unquoted) sails straight through. Regex can't reliably parse HTML — a real DOM parse can.

Fix

  • lib/sanitize-markup.ts (new): single shared sanitizer using DOMPurify — already listed in package.json but never actually imported anywhere. Configured with an explicit forbid-list (script, iframe, object, embed, form, base, meta, srcdoc) and the html+svg profile these visuals need.
  • Replaced the four duplicated regex sanitizers in mobile-slide-viewer.tsx, mobile-presentation-generator.tsx, presentation-preview.tsx, and real-time-generator.tsx with imports of the shared sanitizeMarkup().
  • DOMPurify requires a real DOM. All call sites are "use client" components, so sanitizeMarkup() returns "" during SSR and the sanitized markup renders after client hydration — nothing is ever server-rendered unsanitized.

Also fixed pre-existing lint warnings (no-img-element, exhaustive-deps) in the touched files, since lint-staged enforces --max-warnings=0 on any staged file — used the same eslint-disable-next-line conventions already present elsewhere in the codebase (components/optimized-image.tsx, components/showcase/showcase-card.tsx) rather than introducing new patterns.

On issue #1049

I also investigated #1049 (Stripe webhook signature verification) before starting this one. Both webhook endpoints (app/api/webhooks/stripe/route.ts, app/api/stripe/webhook/route.ts) already call stripe.webhooks.constructEvent() and reject on missing/invalid signatures, with existing test coverage in __tests__/api/stripe-webhook.test.ts. Commented on that issue with specifics rather than opening a duplicate/no-op PR.

Testing

  • npx eslint <changed files> --max-warnings=0 → 0 errors, 0 warnings
  • npm test (full suite via pre-push hook) → 410/410 passing, 30/30 suites
  • npx tsc (pre-push hook, scoped to changed files) → 0 new type errors
  • Manually verified: sanitizeMarkup('<svg onload=alert(1)>') and sanitizeMarkup('<img src=x onerror=alert(1)>') (unquoted, the exact bypass of the old regex) both strip the dangerous attribute/tag under DOMPurify

GSSoC 2026

Summary by CodeRabbit

  • New Features

    • Added richer code-driven slide visuals (Mermaid diagrams, sanitized SVG/HTML, and compact data-visualization cards with “Includes Data Visualization”).
    • Improved mobile and full preview rendering for clearer selection, outline/preview layouts, and richer preview slide cards.
    • Enhanced PDF export for slide decks.
  • Bug Fixes

    • Standardized generated slide data handling for more reliable images, charts, bullets, and content rendering.
    • Strengthened sanitization for AI-generated markup to improve safety.
    • Improved generation request/error handling and fullscreen/autoplay exit behavior.
  • Chores

    • Updated linting/build checks and project configuration; refreshed dependency versions.

@netlify

netlify Bot commented Jul 8, 2026

Copy link
Copy Markdown

👷 Deploy request for docmagic1 pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 7938fd8

@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

@anshul23102 is attempting to deploy a commit to the muneerali199's projects Team on Vercel.

A member of the Team first needs to authorize it.

@netlify

netlify Bot commented Jul 8, 2026

Copy link
Copy Markdown

👷 Deploy request for docmagic-muneer pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 7938fd8

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A shared sanitizeMarkup utility replaces local sanitization logic across presentation components. The presentation generator, slide viewer, and preview also add chart_data rendering, generation-flow updates, UI refactors, PDF export changes, and build/config updates.

Changes

AI Output Sanitization and Presentation Components

Layer / File(s) Summary
sanitizeMarkup utility with DOMPurify
lib/sanitize-markup.ts
Adds sanitizeMarkup with DOMPurify rules that forbid dangerous tags and attributes and return empty output for SSR or invalid input.
Mobile generator visual rendering
components/presentation/mobile-presentation-generator.tsx
Imports sanitizeMarkup, normalizes slide data, and extends code-visual handling for svg_code, mermaid, html_tailwind, and chart_data.
Mobile generator request flow
components/presentation/mobile-presentation-generator.tsx
Updates URL extraction, outline generation, and full presentation requests, including auth handling, step changes, and notification payloads.
Mobile generator UI steps and preview
components/presentation/mobile-presentation-generator.tsx
Refactors input, outline, theme, and preview UI, plus template cards and preview slide cards with chart-data indicators.
Mobile slide viewer visuals and PDF export
components/presentation/mobile-slide-viewer.tsx
Imports sanitizeMarkup, rewrites visual rendering and PDF export, and updates legacy image/chart/bullets markup.
Presentation preview visuals
components/presentation/presentation-preview.tsx
Imports sanitizeMarkup and refactors chart and code-visual rendering branches.
Presentation preview layouts and controls
components/presentation/presentation-preview.tsx
Refactors layouts, fullscreen/autoplay/navigation handling, removes reset UI, and updates image-editor slide content derivation.
Real-time generator sanitize and supporting updates
components/presentation/real-time-generator.tsx
Imports sanitizeMarkup, removes the local sanitizer, and adjusts effects, notifications, keyboard handling, and img lint suppressions.
Build and dependency updates
.github/workflows/dependency_check.yml, package.json, tsconfig.json
Updates linting, dependency versions, cache keys, and TypeScript module resolution settings together.

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

Sequence Diagram(s)

sequenceDiagram
  participant MobileGenerator
  participant SupabaseSession
  participant OutlineAPI
  participant PresentationFullAPI
  MobileGenerator->>OutlineAPI: POST URL or outline request
  MobileGenerator->>SupabaseSession: get session
  MobileGenerator->>OutlineAPI: POST outlines with Authorization
  OutlineAPI-->>MobileGenerator: outlines or 401/402 error
  MobileGenerator->>PresentationFullAPI: POST outlines, template, prompt, generationMode
  PresentationFullAPI-->>MobileGenerator: slides or error
Loading
sequenceDiagram
  participant PresentationComponent
  participant sanitizeMarkup
  participant DOMPurify
  participant DOM
  PresentationComponent->>sanitizeMarkup: raw AI-generated markup
  sanitizeMarkup->>DOMPurify: sanitize(markup, SANITIZE_CONFIG)
  DOMPurify-->>sanitizeMarkup: cleaned markup
  sanitizeMarkup-->>PresentationComponent: sanitized string
  PresentationComponent->>DOM: dangerouslySetInnerHTML
Loading

Suggested labels: bug, type:bug

Suggested reviewers: Muneerali199

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes unrelated dependency/version bumps and broad UI/layout refactors beyond the sanitizer fix. Split the security fix from dependency upgrades and unrelated UI refactors into separate PRs.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: sanitizing AI-generated markup with DOMPurify.
Description check ✅ Passed The description covers the security fix, issue link, testing, and rationale, though some template sections are omitted.
Linked Issues check ✅ Passed The PR implements the requested XSS mitigation by sanitizing AI-generated HTML before rendering with a shared DOMPurify helper.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
components/presentation/presentation-preview.tsx (2)

298-510: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

renderChart needs radar/funnel support
real-time-generator.tsx already allows bar|line|pie|area|radar|funnel, but this switch only renders bar|pie|line|area|scatter. radar and funnel payloads still hit default and render an empty frame. Add matching cases or a visible fallback.

🤖 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 `@components/presentation/presentation-preview.tsx` around lines 298 - 510, The
renderChart switch in presentation-preview.tsx is missing support for the radar
and funnel chart types that real-time-generator.tsx already emits, so those
payloads currently fall through to the default case. Update renderChart to
handle radar and funnel explicitly alongside the existing BarChart, PieChart,
LineChart, AreaChart, and ScatterChart branches, or add a clear visible fallback
in the default branch. Use the renderChart switch and the existing chart.type
cases as the place to wire in the new chart components.

676-745: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Hydration-safe the SVG/HTML preview branches. sanitizeMarkup returns "" on the server, so these svg_code/html_tailwind paths render the fallback HTML during SSR and swap to the real markup after hydration. Gate the markup behind a mounted check, or make the sanitizer produce the same output on both passes.

🤖 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 `@components/presentation/presentation-preview.tsx` around lines 676 - 745, The
svg_code and html_tailwind preview branches in PresentationVisualFrame are not
hydration-safe because sanitizeMarkup returns an empty string on the server,
causing the fallback UI to render during SSR and then flip to the real markup
after hydration. Update the presentation-preview.tsx logic around the visualType
checks so the markup is only rendered after mount (or make sanitizeMarkup
deterministic across server and client), keeping the fallback consistent between
passes. Focus on the svgMarkup and htmlSnippet paths and the wrapped
dangerouslySetInnerHTML rendering.
🧹 Nitpick comments (2)
lib/sanitize-markup.ts (1)

25-30: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Config is solid; consider relaxing ALLOW_DATA_ATTR for legitimate mockups.

The USE_PROFILES + FORBID_TAGS combination is a well-structured defense-in-depth approach. One consideration: ALLOW_DATA_ATTR: false strips all data-* attributes from sanitized output. AI-generated html_tailwind mockups may legitimately use data-* attributes for interactivity or styling hooks (e.g., Tailwind/Alpine.js patterns). The security benefit is marginal since DOMPurify already sanitizes style attributes and strips event handlers. If mockup functionality is a priority, consider allowing data-* attributes.

🤖 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 `@lib/sanitize-markup.ts` around lines 25 - 30, The SANITIZE_CONFIG in
sanitize-markup.ts is stripping all data-* attributes via ALLOW_DATA_ATTR:
false, which can break legitimate html_tailwind mockups. Update the DOMPurify
config to permit data attributes while keeping the existing FORBID_TAGS,
FORBID_ATTR, and USE_PROFILES protections intact, so mockup
interactivity/styling hooks continue to work without loosening the rest of the
sanitization policy.
components/presentation/mobile-presentation-generator.tsx (1)

56-76: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider consolidating the duplicated slide-normalization helpers.

normalizeVisualType, normalizeGeneratedSlides, and hasCodeVisual are byte-for-byte duplicated across mobile-slide-viewer.tsx, presentation-preview.tsx, and real-time-generator.tsx (see graph context). Since this PR already centralizes sanitizeMarkup, extracting these into the same shared module would prevent the four copies from diverging over time.

🤖 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 `@components/presentation/mobile-presentation-generator.tsx` around lines 56 -
76, The slide normalization helpers are duplicated across multiple components,
so extract the shared logic into the same central module used for
sanitizeMarkup. Move normalizeGeneratedSlides, normalizeVisualType, and
hasCodeVisual out of mobile-presentation-generator.tsx into a reusable helper,
then update mobile-slide-viewer.tsx, presentation-preview.tsx, and
real-time-generator.tsx to import and use the shared functions so the behavior
stays consistent.
🤖 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 `@lib/sanitize-markup.ts`:
- Around line 42-50: The SSR fallback in sanitizeMarkup causes hydration
mismatches because it returns an empty string during server render but real
sanitized HTML after mount in Next.js client components. Update the call sites
that use sanitizeMarkup and dangerouslySetInnerHTML to gate rendering with a
mounted state (or a small wrapper hook) so the markup is only injected after
client mount; use the sanitizeMarkup function name and the visual rendering
component(s) to locate the affected code.

---

Outside diff comments:
In `@components/presentation/presentation-preview.tsx`:
- Around line 298-510: The renderChart switch in presentation-preview.tsx is
missing support for the radar and funnel chart types that
real-time-generator.tsx already emits, so those payloads currently fall through
to the default case. Update renderChart to handle radar and funnel explicitly
alongside the existing BarChart, PieChart, LineChart, AreaChart, and
ScatterChart branches, or add a clear visible fallback in the default branch.
Use the renderChart switch and the existing chart.type cases as the place to
wire in the new chart components.
- Around line 676-745: The svg_code and html_tailwind preview branches in
PresentationVisualFrame are not hydration-safe because sanitizeMarkup returns an
empty string on the server, causing the fallback UI to render during SSR and
then flip to the real markup after hydration. Update the
presentation-preview.tsx logic around the visualType checks so the markup is
only rendered after mount (or make sanitizeMarkup deterministic across server
and client), keeping the fallback consistent between passes. Focus on the
svgMarkup and htmlSnippet paths and the wrapped dangerouslySetInnerHTML
rendering.

---

Nitpick comments:
In `@components/presentation/mobile-presentation-generator.tsx`:
- Around line 56-76: The slide normalization helpers are duplicated across
multiple components, so extract the shared logic into the same central module
used for sanitizeMarkup. Move normalizeGeneratedSlides, normalizeVisualType, and
hasCodeVisual out of mobile-presentation-generator.tsx into a reusable helper,
then update mobile-slide-viewer.tsx, presentation-preview.tsx, and
real-time-generator.tsx to import and use the shared functions so the behavior
stays consistent.

In `@lib/sanitize-markup.ts`:
- Around line 25-30: The SANITIZE_CONFIG in sanitize-markup.ts is stripping all
data-* attributes via ALLOW_DATA_ATTR: false, which can break legitimate
html_tailwind mockups. Update the DOMPurify config to permit data attributes
while keeping the existing FORBID_TAGS, FORBID_ATTR, and USE_PROFILES
protections intact, so mockup interactivity/styling hooks continue to work
without loosening the rest of the sanitization policy.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 398aa17e-e92d-4040-8778-49c1e3a5a12d

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5e22a and 3e43eaa.

📒 Files selected for processing (5)
  • components/presentation/mobile-presentation-generator.tsx
  • components/presentation/mobile-slide-viewer.tsx
  • components/presentation/presentation-preview.tsx
  • components/presentation/real-time-generator.tsx
  • lib/sanitize-markup.ts

Comment thread lib/sanitize-markup.ts
Comment on lines +42 to +50
export function sanitizeMarkup(markup: string): string {
if (typeof window === "undefined") {
return "";
}
if (typeof markup !== "string" || markup.length === 0) {
return "";
}
return DOMPurify.sanitize(markup, SANITIZE_CONFIG);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

SSR guard will cause hydration mismatches in Next.js "use client" components.

Next.js App Router still server-renders "use client" components. During SSR, sanitizeMarkup returns "", but on the client it returns the sanitized markup. React detects the dangerouslySetInnerHTML content mismatch, logs a hydration warning, and re-renders the subtree — causing a brief flash of empty content for every visual.

Consider a mounted-state pattern at the call sites (or a small wrapper hook) to defer rendering until after mount, which avoids the mismatch entirely:

const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
const html = mounted ? sanitizeMarkup(visualContent) : "";

This is a known tradeoff and the current approach is functionally safe, but the hydration warnings and visual flash may be noticeable with many slides on screen.

🤖 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 `@lib/sanitize-markup.ts` around lines 42 - 50, The SSR fallback in
sanitizeMarkup causes hydration mismatches because it returns an empty string
during server render but real sanitized HTML after mount in Next.js client
components. Update the call sites that use sanitizeMarkup and
dangerouslySetInnerHTML to gate rendering with a mounted state (or a small
wrapper hook) so the markup is only injected after client mount; use the
sanitizeMarkup function name and the visual rendering component(s) to locate the
affected code.

- Update next to 16.2.10 to fix multiple XSS and DoS vulnerabilities
- Update nodemailer to 9.0.3 to fix SMTP injection and header validation issues
- Update undici and transitive dependencies for HTTP security fixes
- Update dompurify and babel dependencies for XSS prevention
- Run npm audit fix --force and npm audit fix to resolve critical advisories
- Remove critical-level vulnerabilities from dependency audit

This resolves CI pipeline failures caused by npm audit --audit-level=critical
checks that were blocking all pull requests.
- Downgrade from Next.js 16 to 15.1.3 for better code compatibility
- Update eslint-config-next to match
- Remove --quiet flag from lint script (not supported in some versions)
- Still passes critical security audit (no critical vulnerabilities)
- Resolves TypeScript breaking changes from Next.js 16 update
- Maintains security improvements from npm audit fixes
@anshul23102
anshul23102 force-pushed the fix/1050-sanitize-ai-generated-markup-xss branch from 3e43eaa to 2c081a4 Compare July 8, 2026 05:23
- Change moduleResolution from node to bundler in tsconfig.json
  Modern packages ship conditional exports maps that classic node
  resolution cannot follow correctly, which was causing NextRequest
  and NextResponse to be seen as type-only across most API routes
  (TS2693). This was a pre-existing issue on main, unrelated to the
  dependency updates, now fixed.
- Regenerate package-lock.json from a clean install so it is back in
  sync with package.json. The previous lockfile had drifted after an
  earlier --legacy-peer-deps install, which caused npm ci to fail in
  CI with missing lockfile entries.
- All 410 tests pass and npm audit reports zero critical vulnerabilities
@anshul23102
anshul23102 force-pushed the fix/1050-sanitize-ai-generated-markup-xss branch from 2c081a4 to 5833dec Compare July 8, 2026 05:38
- Bump pinned override versions for @babel/plugin-transform-modules-systemjs,
  undici, dompurify, and file-type to their patched releases. These were
  previously pinned in the overrides block to versions that were still
  inside the vulnerable range, which was blocking the
  npm audit --audit-level=high --omit=dev check in CI
- npm audit now reports zero vulnerabilities at any severity
- Fix stale TypeScript build info cache key in CI: it only hashed
  tsconfig.build.json, not the tsconfig.json it extends, so changes to
  the base config were not invalidating the cached .tsbuildinfo and CI
  could type-check against a stale incremental cache
- Verified: 410/410 tests pass, lint passes, officeparser (which depends
  on file-type) has no direct test coverage but the version bump does not
  change its public API surface
Closes Muneerali199#1050

The issue as filed describes resume content rendered via
dangerouslySetInnerHTML, but that render path doesn't exist in this
codebase — resume content is rendered as plain React text/JSX, not raw
HTML. The equivalent, currently-exploitable version of this bug is in
the presentation generator: AI-generated SVG diagrams and Tailwind HTML
mockups from Gemini/Mistral are rendered via dangerouslySetInnerHTML
across four components, each with its own hand-rolled regex blocklist
sanitizer instead of a real sanitizer.

The regex blocklists only strip quoted event-handler attributes
(/on[a-z]+=(["']).*?\1/), so unquoted payloads like
<img src=x onerror=alert(1)> pass through untouched. They also don't
catch obfuscated/malformed markup that a real DOM parse would.

- Added lib/sanitize-markup.ts: a single shared sanitizer using
  DOMPurify (already an unused dependency in package.json) with an
  explicit forbid-list for script/iframe/object/embed/form/base/meta
  and srcdoc, matching the html+svg profile these visuals need.
- Replaced the four duplicated regex sanitizers in
  mobile-slide-viewer.tsx, mobile-presentation-generator.tsx,
  presentation-preview.tsx, and real-time-generator.tsx with imports
  of the shared sanitizeMarkup().
- DOMPurify requires a DOM; since all call sites are "use client"
  components, sanitizeMarkup() returns an empty string during SSR and
  the real sanitized markup renders after client hydration, so no
  content is ever server-rendered unsanitized.

Also fixed pre-existing lint warnings in the touched files (unrelated
<img> and exhaustive-deps warnings) since lint-staged enforces zero
warnings on any staged file.

I also checked issue Muneerali199#1049 (Stripe webhook signature verification) and
found both webhook endpoints already verify signatures correctly with
existing test coverage; commented on that issue instead of opening a
redundant PR.
@anshul23102
anshul23102 force-pushed the fix/1050-sanitize-ai-generated-markup-xss branch from 5833dec to 73af909 Compare July 8, 2026 05:55
@anshul23102

anshul23102 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@Muneerali199

Bump for review: PR ready for merge. GSSoC 2026 tracked.

Summary: Sanitizes AI-generated presentation markup with DOMPurify. Includes:

  • DOMPurify integration
  • XSS prevention
  • Markup sanitization
  • Safe HTML rendering
  • Security compliance

Please review and merge. For GSSoC 2026 points allocation, could you add the gssoc-approved label?

/review
/assign

DOMPurify.Config is not a valid type reference in dompurify 3.4.x,
Config is a plain named export, not a namespace member of the default
export. This caused two build failures:

- TS2503: Cannot find namespace 'DOMPurify'
- TS2322: Type 'TrustedHTML' is not assignable to type 'string'
  (a downstream effect of SANITIZE_CONFIG failing to type as Config,
  which caused sanitize() to resolve to the wrong overload)

Fixed by importing Config as a named type import and using it directly.
Verified locally with tsc --noEmit --project tsconfig.build.json: both
errors are gone and no new errors were introduced.

Signed-off-by: Anshul Jain <anshul23102@iiitd.ac.in>

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
components/presentation/presentation-preview.tsx (1)

675-709: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Gate these visual branches until mount
sanitizeMarkup() returns "" on the server, so svg_code and html_tailwind render the fallback markup during SSR and then switch to the sanitized visual on the client. Add a mounted guard (or make them client-only) to avoid hydration mismatch.

🤖 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 `@components/presentation/presentation-preview.tsx` around lines 675 - 709, The
svg_code visual branch in presentation-preview.tsx is rendering different markup
on the server and client because sanitizeMarkup() returns an empty string during
SSR, causing a hydration mismatch. Update the svg_code handling in
PresentationVisualFrame to be gated behind a mounted/client-only check, or
otherwise defer rendering until after mount, so the fallback “SVG visual
unavailable” state is not emitted on the server when the sanitized SVG will
appear on the client.

Source: Linters/SAST tools

🤖 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 `@components/presentation/mobile-slide-viewer.tsx`:
- Around line 147-298: The PDF export in exportToPDF is rebuilding slides from
only title/image/chart/content/bullets, so code-driven visuals are missing from
the captured output. Update the offscreen slide assembly to use the same visual
rendering path as the live view, especially renderCodeVisual for svg_code,
html_tailwind, mermaid, and chart_data, before passing slideElement to
html2canvas. Keep the fix localized to exportToPDF and reuse the existing
renderCodeVisual helper so exported PDFs match the on-screen slide.
- Around line 182-188: In mobile-slide-viewer.tsx, the image setup in the slide
rendering flow assigns img.src before img.crossOrigin, which can trigger a
non-CORS fetch and taint the canvas for cross-origin imageUrl exports. Update
the image initialization in the code that creates the img element so crossOrigin
is set before src, keeping the rest of the slide image styling and export logic
unchanged.

In `@package.json`:
- Around line 127-131: The remaining Next 15 request API usage still needs to be
migrated from synchronous to async in the code paths that call cookies() and
headers(), specifically in lib/supabase/server.ts and the Stripe webhook
handlers. Update those callers to await the Next 15 async request APIs and
adjust the surrounding logic in the relevant helper functions so the request
data is fetched asynchronously. Keep the Nodemailer 9 dependency as-is since
createTransport(), createTestAccount(), and getTestMessageUrl() are still
compatible, and focus the compatibility fix on the Next 15 API usage rather than
the nodemailer pin.

---

Outside diff comments:
In `@components/presentation/presentation-preview.tsx`:
- Around line 675-709: The svg_code visual branch in presentation-preview.tsx is
rendering different markup on the server and client because sanitizeMarkup()
returns an empty string during SSR, causing a hydration mismatch. Update the
svg_code handling in PresentationVisualFrame to be gated behind a
mounted/client-only check, or otherwise defer rendering until after mount, so
the fallback “SVG visual unavailable” state is not emitted on the server when
the sanitized SVG will appear on the client.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee4d2569-a380-4f79-aba5-63edc55b4128

📥 Commits

Reviewing files that changed from the base of the PR and between 3e43eaa and 7938fd8.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • .github/workflows/dependency_check.yml
  • components/presentation/mobile-presentation-generator.tsx
  • components/presentation/mobile-slide-viewer.tsx
  • components/presentation/presentation-preview.tsx
  • components/presentation/real-time-generator.tsx
  • lib/sanitize-markup.ts
  • package.json
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • components/presentation/real-time-generator.tsx
  • components/presentation/mobile-presentation-generator.tsx
  • lib/sanitize-markup.ts

Comment on lines 147 to 298
for (let i = 0; i < slides.length; i++) {
const slide = slides[i];

// Create a temporary slide element
const slideElement = document.createElement('div');
slideElement.style.width = '1920px';
slideElement.style.height = '1080px';
slideElement.style.position = 'absolute';
slideElement.style.left = '-9999px';
slideElement.style.background = '#ffffff';
slideElement.style.padding = '80px';
slideElement.style.boxSizing = 'border-box';
slideElement.style.display = 'flex';
slideElement.style.flexDirection = 'column';
slideElement.style.justifyContent = 'flex-start';
slideElement.style.gap = '40px';
const slideElement = document.createElement("div");
slideElement.style.width = "1920px";
slideElement.style.height = "1080px";
slideElement.style.position = "absolute";
slideElement.style.left = "-9999px";
slideElement.style.background = "#ffffff";
slideElement.style.padding = "80px";
slideElement.style.boxSizing = "border-box";
slideElement.style.display = "flex";
slideElement.style.flexDirection = "column";
slideElement.style.justifyContent = "flex-start";
slideElement.style.gap = "40px";

// Title
const title = document.createElement('h1');
const title = document.createElement("h1");
title.textContent = slide.title;
title.style.fontSize = '72px';
title.style.fontWeight = 'bold';
title.style.color = '#1a1a1a';
title.style.lineHeight = '1.2';
title.style.fontSize = "72px";
title.style.fontWeight = "bold";
title.style.color = "#1a1a1a";
title.style.lineHeight = "1.2";
slideElement.appendChild(title);

// Image - displayed as content element
if (slide.imageUrl) {
const imageContainer = document.createElement('div');
imageContainer.style.width = '100%';
imageContainer.style.maxHeight = '500px';
imageContainer.style.borderRadius = '16px';
imageContainer.style.overflow = 'hidden';
imageContainer.style.boxShadow = '0 10px 40px rgba(0,0,0,0.2)';
const img = document.createElement('img');
const imageContainer = document.createElement("div");
imageContainer.style.width = "100%";
imageContainer.style.maxHeight = "500px";
imageContainer.style.borderRadius = "16px";
imageContainer.style.overflow = "hidden";
imageContainer.style.boxShadow = "0 10px 40px rgba(0,0,0,0.2)";

const img = document.createElement("img");
img.src = slide.imageUrl;
img.style.width = '100%';
img.style.height = 'auto';
img.style.maxHeight = '500px';
img.style.objectFit = 'cover';
img.crossOrigin = 'anonymous';
img.style.width = "100%";
img.style.height = "auto";
img.style.maxHeight = "500px";
img.style.objectFit = "cover";
img.crossOrigin = "anonymous";

imageContainer.appendChild(img);
slideElement.appendChild(imageContainer);
}

// Chart - displayed as content element
if (slide.chartData && slide.chartData.data) {
const chartContainer = document.createElement('div');
chartContainer.style.padding = '30px';
chartContainer.style.background = 'linear-gradient(135deg, #eff6ff 0%, #f3e8ff 100%)';
chartContainer.style.borderRadius = '16px';
chartContainer.style.border = '2px solid #93c5fd';

const chartTitle = document.createElement('h3');
chartTitle.textContent = '📊 ' + (slide.chartData.title || 'Data Visualization');
chartTitle.style.fontSize = '36px';
chartTitle.style.fontWeight = 'bold';
chartTitle.style.color = '#1a1a1a';
chartTitle.style.marginBottom = '20px';
const chartContainer = document.createElement("div");
chartContainer.style.padding = "30px";
chartContainer.style.background =
"linear-gradient(135deg, #eff6ff 0%, #f3e8ff 100%)";
chartContainer.style.borderRadius = "16px";
chartContainer.style.border = "2px solid #93c5fd";

const chartTitle = document.createElement("h3");
chartTitle.textContent =
"📊 " + (slide.chartData.title || "Data Visualization");
chartTitle.style.fontSize = "36px";
chartTitle.style.fontWeight = "bold";
chartTitle.style.color = "#1a1a1a";
chartTitle.style.marginBottom = "20px";
chartContainer.appendChild(chartTitle);

slide.chartData.data.forEach((item: any, idx: number) => {
const dataRow = document.createElement('div');
dataRow.style.display = 'flex';
dataRow.style.alignItems = 'center';
dataRow.style.gap = '15px';
dataRow.style.marginBottom = '15px';

const dot = document.createElement('div');
dot.style.width = '16px';
dot.style.height = '16px';
dot.style.borderRadius = '50%';
dot.style.backgroundColor = slide.chartData.colors?.[idx] || '#3B82F6';

const label = document.createElement('span');
const dataRow = document.createElement("div");
dataRow.style.display = "flex";
dataRow.style.alignItems = "center";
dataRow.style.gap = "15px";
dataRow.style.marginBottom = "15px";

const dot = document.createElement("div");
dot.style.width = "16px";
dot.style.height = "16px";
dot.style.borderRadius = "50%";
dot.style.backgroundColor =
slide.chartData.colors?.[idx] || "#3B82F6";

const label = document.createElement("span");
label.textContent = item.name;
label.style.fontSize = '28px';
label.style.color = '#374151';
label.style.flex = '1';
const value = document.createElement('span');
label.style.fontSize = "28px";
label.style.color = "#374151";
label.style.flex = "1";

const value = document.createElement("span");
value.textContent = String(item.value);
value.style.fontSize = '28px';
value.style.fontWeight = 'bold';
value.style.color = '#1a1a1a';
value.style.fontSize = "28px";
value.style.fontWeight = "bold";
value.style.color = "#1a1a1a";

dataRow.appendChild(dot);
dataRow.appendChild(label);
dataRow.appendChild(value);
chartContainer.appendChild(dataRow);
});

slideElement.appendChild(chartContainer);
}

// Content text
if (slide.content) {
const contentText = document.createElement('p');
const contentText = document.createElement("p");
contentText.textContent = slide.content;
contentText.style.fontSize = '32px';
contentText.style.color = '#374151';
contentText.style.lineHeight = '1.6';
contentText.style.fontSize = "32px";
contentText.style.color = "#374151";
contentText.style.lineHeight = "1.6";
slideElement.appendChild(contentText);
}

// Bullets
if (slide.bullets && slide.bullets.length > 0) {
const bulletList = document.createElement('ul');
bulletList.style.listStyle = 'none';
bulletList.style.padding = '0';
bulletList.style.margin = '0';
const bulletList = document.createElement("ul");
bulletList.style.listStyle = "none";
bulletList.style.padding = "0";
bulletList.style.margin = "0";

slide.bullets.forEach((bullet: string, idx: number) => {
const li = document.createElement('li');
li.style.display = 'flex';
li.style.alignItems = 'start';
li.style.marginBottom = '20px';
li.style.gap = '20px';
const badge = document.createElement('span');
const li = document.createElement("li");
li.style.display = "flex";
li.style.alignItems = "start";
li.style.marginBottom = "20px";
li.style.gap = "20px";

const badge = document.createElement("span");
badge.textContent = String(idx + 1);
badge.style.width = '50px';
badge.style.height = '50px';
badge.style.borderRadius = '50%';
badge.style.background = 'linear-gradient(135deg, #3b82f6, #8b5cf6)';
badge.style.color = 'white';
badge.style.display = 'flex';
badge.style.alignItems = 'center';
badge.style.justifyContent = 'center';
badge.style.fontWeight = 'bold';
badge.style.fontSize = '24px';
badge.style.flexShrink = '0';

const text = document.createElement('span');
badge.style.width = "50px";
badge.style.height = "50px";
badge.style.borderRadius = "50%";
badge.style.background =
"linear-gradient(135deg, #3b82f6, #8b5cf6)";
badge.style.color = "white";
badge.style.display = "flex";
badge.style.alignItems = "center";
badge.style.justifyContent = "center";
badge.style.fontWeight = "bold";
badge.style.fontSize = "24px";
badge.style.flexShrink = "0";

const text = document.createElement("span");
text.textContent = bullet;
text.style.fontSize = '28px';
text.style.color = '#1f2937';
text.style.lineHeight = '1.5';
text.style.fontSize = "28px";
text.style.color = "#1f2937";
text.style.lineHeight = "1.5";

li.appendChild(badge);
li.appendChild(text);
bulletList.appendChild(li);
});

slideElement.appendChild(bulletList);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file and related symbols first.
ast-grep outline components/presentation/mobile-slide-viewer.tsx --view expanded || true

# Locate renderCodeVisual and export-related code in the file.
rg -n "renderCodeVisual|svg_code|html_tailwind|mermaid|export|offscreen|slideElement|chartData|bullets" components/presentation/mobile-slide-viewer.tsx

# Print the relevant sections with line numbers.
sed -n '1,220p' components/presentation/mobile-slide-viewer.tsx
sed -n '220,420p' components/presentation/mobile-slide-viewer.tsx

Repository: Muneerali199/Draftdeckai

Length of output: 18895


PDF export skips code-driven visuals
exportToPDF builds the offscreen slide from title/image/chart/content/bullets only, while the live view renders svg_code, html_tailwind, mermaid, and chart_data through renderCodeVisual. Slides whose main content is one of those visuals will export without it. Render the same visual into slideElement before html2canvas.

🤖 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 `@components/presentation/mobile-slide-viewer.tsx` around lines 147 - 298, The
PDF export in exportToPDF is rebuilding slides from only
title/image/chart/content/bullets, so code-driven visuals are missing from the
captured output. Update the offscreen slide assembly to use the same visual
rendering path as the live view, especially renderCodeVisual for svg_code,
html_tailwind, mermaid, and chart_data, before passing slideElement to
html2canvas. Keep the fix localized to exportToPDF and reuse the existing
renderCodeVisual helper so exported PDFs match the on-screen slide.

Comment on lines +182 to +188
const img = document.createElement("img");
img.src = slide.imageUrl;
img.style.width = '100%';
img.style.height = 'auto';
img.style.maxHeight = '500px';
img.style.objectFit = 'cover';
img.crossOrigin = 'anonymous';
img.style.width = "100%";
img.style.height = "auto";
img.style.maxHeight = "500px";
img.style.objectFit = "cover";
img.crossOrigin = "anonymous";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file first, then inspect the surrounding implementation.
ast-grep outline components/presentation/mobile-slide-viewer.tsx --view expanded || true

echo
echo "---- lines around image creation ----"
sed -n '150,230p' components/presentation/mobile-slide-viewer.tsx

echo
echo "---- lines around export/toDataURL ----"
sed -n '280,340p' components/presentation/mobile-slide-viewer.tsx

Repository: Muneerali199/Draftdeckai

Length of output: 5529


🌐 Web query:

HTMLImageElement crossOrigin must be set before src image fetch request crossOrigin after src does not restart fetch MDN

💡 Result:

Yes, it is best practice and technically necessary in many browser implementations to set the crossOrigin property on an HTMLImageElement before setting its src attribute [1][2][3]. When you set the src attribute, the browser immediately begins fetching the image resource [4][5]. If the crossOrigin attribute is not already set, the browser initiates the fetch request without CORS (Cross-Origin Resource Sharing) headers [6]. Setting the crossOrigin property after the src has already been set does not typically restart the fetch request, meaning the initial request is performed without the required CORS configuration [3]. This can cause the image to be tainted if used in a canvas, or lead to CORS errors if the server enforces specific origins [2][3]. To ensure the image is fetched with the correct CORS settings, you should configure the crossOrigin property before assigning the src [1][2][3].

Citations:


Set crossOrigin before src in components/presentation/mobile-slide-viewer.tsx:182-188. img.src starts the fetch immediately, so assigning crossOrigin afterward can leave the image load non-CORS and taint the canvas for cross-origin imageUrls, breaking canvas.toDataURL() during export. Swap the two lines.

🤖 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 `@components/presentation/mobile-slide-viewer.tsx` around lines 182 - 188, In
mobile-slide-viewer.tsx, the image setup in the slide rendering flow assigns
img.src before img.crossOrigin, which can trigger a non-CORS fetch and taint the
canvas for cross-origin imageUrl exports. Update the image initialization in the
code that creates the img element so crossOrigin is set before src, keeping the
rest of the slide image styling and export logic unchanged.

Comment thread package.json
Comment on lines +127 to +131
"next": "^15.1.3",
"next-plausible": "^4.0.0",
"next-pwa": "^5.6.0",
"next-themes": "^0.4.6",
"nodemailer": "^6.10.0",
"nodemailer": "^9.0.3",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1. Check for synchronous cookies()/headers()/params/searchParams usage (must be awaited in Next.js 15)
rg -nP '\b(cookies|headers)\s*\(\s*\)' --type ts --type tsx -g '!node_modules' -g '!.next' -g '!**/*.test.*'
rg -nP '\bparams\b|\bsearchParams\b' --type ts --type tsx -g '!node_modules' -g '!.next' -g '!**/*.test.*'

# 2. Check next-pwa usage in next.config.js
rg -n 'next-pwa' next.config.js 2>/dev/null || fd next.config -e js -e mjs -e ts --max-depth 1

# 3. Check nodemailer usage in the codebase
rg -n 'nodemailer' --type ts -g '!node_modules'

# 4. Check which packages depend on next-pwa, next-plausible, next-themes
npm ls next-pwa next-plausible next-themes 2>/dev/null || echo "Run npm ls locally to verify"

Repository: Muneerali199/Draftdeckai

Length of output: 256


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package.json =="
cat -n package.json | sed -n '1,220p'

echo "== next.config.* candidates =="
fd -H -a 'next.config.*' .

echo "== search for Next request APIs and nodemailer =="
rg -n --hidden --glob '!node_modules/**' --glob '!.next/**' --glob '!dist/**' --glob '!build/**' '\b(cookies|headers)\s*\(\s*\)|\b(searchParams|params)\b|nodemailer|next-pwa|next-plausible|next-themes' .

echo "== find email-related files =="
fd -H -a 'email' .

Repository: Muneerali199/Draftdeckai

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== next.config.js (relevant section) =="
cat -n next.config.js | sed -n '1,180p'

echo "== lib/supabase/server.ts =="
cat -n lib/supabase/server.ts | sed -n '1,220p'

echo "== app/api/stripe/webhook/route.ts =="
cat -n app/api/stripe/webhook/route.ts | sed -n '1,140p'

echo "== app/api/webhooks/stripe/route.ts =="
cat -n app/api/webhooks/stripe/route.ts | sed -n '1,140p'

echo "== lib/email.ts =="
cat -n lib/email.ts | sed -n '1,260p'

Repository: Muneerali199/Draftdeckai

Length of output: 30757


🌐 Web query:

Nodemailer 9 createTransport createTestAccount getTestMessageUrl compatibility breaking changes

💡 Result:

Nodemailer version 9.0.0 was released on June 14, 2026 [1][2]. The functions createTransport, createTestAccount, and getTestMessageUrl remain part of the core API in version 9.x and function as they did in previous versions [3][4]. The primary breaking change introduced in version 9.0.0 is related to TLS certificate validation for remote content [1][5][2]. HTTPS requests made when fetching remote resources—such as attachment href/path URLs, OAuth2 token endpoints, or HTTP/HTTPS proxy CONNECT—now validate the server's TLS certificate by default [1][6]. If your application fetches remote content from hosts with self-signed, expired, or hostname-mismatched certificates, these requests will now fail [1]. You can opt out of this strict validation on a per-request basis by setting tls.rejectUnauthorized=false in your transport options or within a per-attachment tls configuration [1][6]. There are no reported breaking changes specifically targeting createTransport, createTestAccount, or getTestMessageUrl in the transition to version 9 [1][2]. These methods continue to be standard for configuring transporters and managing Ethereal email test accounts [3][4].

Citations:


🌐 Web query:

next-pwa 5.6.0 Next.js 15 compatibility

💡 Result:

The library next-pwa (version 5.6.0) is not officially recommended for use with Next.js 15, and its compatibility is inconsistent. While some community guides and tutorials document successful implementations of next-pwa in Next.js 15 projects [1][2], the original shadowwalker/next-pwa repository has faced significant concerns regarding its maintenance status [3], and users often report compatibility issues when attempting to integrate it with newer versions of Next.js [4][5]. Key considerations include: 1. Maintenance Status: The original repository has been widely noted by the community as being infrequently maintained [3], leading many developers to seek alternatives like @ducanh2912/next-pwa or Serwist for more stable support in modern Next.js environments [3]. 2. Configuration Differences: Next.js 15 uses different architectural patterns (such as the App Router and updated webpack configurations) compared to when next-pwa 5.6.0 was originally released in 2022 [6]. Attempting to use version 5.6.0 directly often requires manual adjustments to next.config.js to avoid configuration errors [7][1]. 3. Next.js Native Support: Next.js provides built-in support for Progressive Web Apps, including guidance on manifest generation and service worker patterns [8]. Developers building PWAs with Next.js 15 are increasingly encouraged to leverage these native features or dedicated, actively maintained PWA libraries [3][8]. If you are proceeding with next-pwa, be aware that you may encounter issues with middleware, service worker generation, and App Router compatibility that may not be addressed by the original package [1][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== exact synchronous request-api call sites =="
sed -n '35,70p' lib/supabase/server.ts
echo
sed -n '35,45p' app/api/stripe/webhook/route.ts
echo
sed -n '13,18p' app/api/webhooks/stripe/route.ts

Repository: Muneerali199/Draftdeckai

Length of output: 1637


🌐 Web query:

Next.js 15 cookies headers async compatibility synchronous access runtime behavior

💡 Result:

In Next.js 15, several request-time APIs—most notably cookies() and headers()—have been transitioned from synchronous to asynchronous functions [1][2][3]. These now return a Promise that must be resolved using await before their contents can be accessed [1][4]. Recommended Usage To access cookies or headers, you must now await the function call in your Server Components, Route Handlers, or other server-side contexts [1][3]: // Recommended async usage import { cookies, headers } from 'next/headers'; export default async function Page { const cookieStore = await cookies; const token = cookieStore.get('token'); const headersList = await headers; const userAgent = headersList.get('user-agent'); //... } Runtime Behavior and Migration While the intended pattern is asynchronous, Next.js 15 includes temporary support for legacy synchronous access to facilitate migration [1][5]. This is achieved by casting the return value to specific "unsafe" types, such as UnsafeUnwrappedCookies or UnsafeUnwrappedHeaders, which allows the code to function as it did in version 14 [1]. However, there are critical considerations regarding this behavior: 1. Deprecation: Synchronous access is a compatibility bridge and is intended to be temporary; it will be deprecated in future major versions [6][7]. 2. Development Warnings: Attempting to access these APIs synchronously without the explicit "unsafe" type casting will typically trigger warnings or errors in development, alerting you to the need for migration [1][3]. 3. Future-Proofing: Relying on synchronous access is discouraged, as newer features (such as experimental cache components) may strictly disallow synchronous access to request data entirely [7]. For a smooth transition, it is highly recommended to use the official Next.js codemod (npx @next/codemod@canary upgrade latest), which is designed to automate the conversion of these synchronous calls to their correct asynchronous counterparts [1][4].

Citations:


Migrate the remaining Next 15 request APIs; Nodemailer 9 is fine. lib/supabase/server.ts and the Stripe webhook handlers still call cookies()/headers() synchronously, so move those to the async Next 15 form. nodemailer@9 still supports createTransport(), createTestAccount(), and getTestMessageUrl(), so that pin doesn’t need a compatibility fix. next-pwa@5.6.0 is the only remaining dependency here with shaky Next 15 support.

🤖 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 `@package.json` around lines 127 - 131, The remaining Next 15 request API usage
still needs to be migrated from synchronous to async in the code paths that call
cookies() and headers(), specifically in lib/supabase/server.ts and the Stripe
webhook handlers. Update those callers to await the Next 15 async request APIs
and adjust the surrounding logic in the relevant helper functions so the request
data is fetched asynchronously. Keep the Nodemailer 9 dependency as-is since
createTransport(), createTestAccount(), and getTestMessageUrl() are still
compatible, and focus the compatibility fix on the Next 15 API usage rather than
the nodemailer pin.

@Muneerali199 Muneerali199 added type:security PR type: security level:intermediate GSSoC difficulty: intermediate gssoc:approved Required GSSoC approval label mentor:muneerali199 Reviewed by mentor muneerali199 labels Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved Required GSSoC approval label level:intermediate GSSoC difficulty: intermediate mentor:muneerali199 Reviewed by mentor muneerali199 type:security PR type: security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: AI-generated resume content not sanitised before rendering, stored XSS possible

2 participants