Skip to content

fix: TypeScript strict mode, security hardening, and bug fixes#545

Merged
berry-13 merged 3 commits into
mainfrom
fix/deep-audit-cleanup
Mar 29, 2026
Merged

fix: TypeScript strict mode, security hardening, and bug fixes#545
berry-13 merged 3 commits into
mainfrom
fix/deep-audit-cleanup

Conversation

@berry-13
Copy link
Copy Markdown
Member

Summary

TypeScript Safety

  • Turned on strict: true and ignoreBuildErrors: false. We were silently shipping type errors to prod
  • Fixed all the implicit-any fallout across 6 components
  • Added missing type declarations (@glidejs/glide, @types/validator, @types/js-yaml)
  • Cleaned up stale tsconfig.json includes pointing at deleted files

Security Hardening

  • Feedback action: now validates inputs (opinion enum, relative-path URL, 2000-char cap) and rate limits (3/URL/60s)
  • Scarf pixel: sanitize the env var before dangerouslySetInnerHTML so we're not just trusting it blindly
  • API routes: added rate limiting (5 req/IP/60s) to subscribe/unsubscribe
  • Email validation: ripped out the janky regex, using validator for RFC 5322 compliance
  • Supabase URL: was hardcoded in generate_embeddings.yml, moved to ${{ secrets.SUPABASE_URL }}
  • Dropped the X-Powered-By header (poweredByHeader: false)

Bug Fixes

  • "Open in GitHub": was linking to the wrong repo entirely. Now points to the right repo + source MDX file
  • proxy.ts: switched to export default for Next.js 16 compat
  • lint-staged: removed ESLINT_USE_FLAT_CONFIG=false which was blowing up pre-commit
  • Playwright: fixed deprecated Locator.tagName() call in e2e tests

Code Quality

  • Merged duplicate style.css into app/global.css and deleted the extra copy
  • Pulled shared Supabase client + email validation into lib/supabase.ts
  • Standardized cn imports to @/lib/utils (unblocks tailwind-merge)
  • Fixed all 31 ESLint errors in e2e tests; added ESLint config for e2e/
  • Renamed scripts/clean-cache.ts.cjs since it's actually CommonJS
  • Killed dead imports

CI & Cleanup

  • Bumped generate_embeddings.yml to actions/checkout@v4, Node 20, pnpm/action-setup@v4
  • Deleted bun.lockb (490 KB) and 8 orphaned PNGs (~1.2 MB)
  • Tidied up .gitignore

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
librechat-ai Ready Ready Preview, Comment Mar 29, 2026 1:30am

Request Review

@berry-13 berry-13 changed the title fix: project health cleanup; TypeScript strict mode, security hardening, and bug fixes fix: TypeScript strict mode, security hardening, and bug fixes Mar 29, 2026
@github-actions
Copy link
Copy Markdown

📦 Next.js Bundle Analysis for librechat.ai

This analysis was generated by the Next.js Bundle Analysis action. 🤖

This PR introduced no changes to the JavaScript bundle! 🙌

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR tightens correctness and security across the Next.js docs site by enabling TypeScript strictness, hardening server-side inputs/rate limits, and cleaning up build/lint/test tooling and assets.

Changes:

  • Enable TypeScript strict mode and disallow shipping type errors; add missing type packages/declarations.
  • Add security hardening: payload validation + rate limiting for feedback and newsletter subscribe/unsubscribe; sanitize Scarf pixel env usage; disable X-Powered-By.
  • Tooling/cleanup: merge global styles, fix repo links, update CI workflow, add Playwright e2e coverage and eslint config updates.

Reviewed changes

Copilot reviewed 37 out of 44 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
tsconfig.json Enables strict type-checking and removes stale includes.
next.config.mjs Disables X-Powered-By and enforces TS build correctness (ignoreBuildErrors: false).
package.json Updates postbuild script extension, adds type deps, fixes lint-staged invocation.
pnpm-lock.yaml Locks newly added @types/* packages.
global.d.ts Adds Window typing tightening and local module types for @glidejs/glide.
lib/supabase.ts Centralizes Supabase client creation + email helpers.
app/api/subscribe/route.ts Adds validation + rate limiting and uses shared Supabase/email helpers.
app/api/unsubscribe/route.ts Adds validation + rate limiting and uses shared Supabase/email helpers.
app/actions/feedback.ts Adds input validation + rate limiting before posting to GitHub/Discord.
app/layout.tsx Sanitizes Scarf pixel ID before injecting script.
app/global.css Consolidates styles previously duplicated in style.css.
style.css Removes duplicate Tailwind/style definitions (merged into app/global.css).
tailwind.config.js Updates comment to reflect global CSS source.
components.json Points shadcn/tailwind CSS reference to app/global.css.
components/page-actions.tsx Fixes “Open in GitHub” targeting and supports per-page GitHub URLs.
app/docs/[[...slug]]/page.tsx Passes correct GitHub file URL for the current MDX page.
components/screenshot.tsx Standardizes cn import to @/lib/utils.
components/features/index.tsx Standardizes cn import to @/lib/utils.
components/features/themes-animation.tsx Adds proper SVG props typing under strict TS.
components/feature.tsx Adds prop typing for image source and children.
components/cards.tsx Adds prop typing and standardizes cn import.
components/carousel/Carousel.tsx Adds component prop typing and ref typing for strict TS.
components/home/index.tsx Marks unused component as deprecated and removes unused content.
components/Newsletter/SubscribeForm.tsx Types form submit event and uses validator.isEmail.
components/Newsletter/UnsubscribeForm.tsx Types form submit event and uses validator.isEmail.
eslint.config.mjs Adds e2e/test-file ESLint override.
playwright.config.ts Adds Playwright config targeting ./e2e with local dev webServer.
e2e/yaml-cards-check.spec.ts Adds UI verification for YAML docs cards section.
e2e/user-guides-verify.spec.ts Adds user guides page audit screenshots/logging.
e2e/toolkit-fullpage.spec.ts Adds toolkit page screenshot + basic assertions.
e2e/toolkit-debug.spec.ts Adds debug-oriented toolkit DOM logging.
e2e/toolkit-debug2.spec.ts Adds additional debug-oriented toolkit logging.
e2e/stats-section-check.spec.ts Adds layout/value checks for homepage stats section.
e2e/mobile-features-check.spec.ts Adds mobile overflow/layout checks and screenshots.
e2e/google-search-verify.spec.ts Adds content/screenshot audit for Google Search docs page.
e2e/docker-page-verify.spec.ts Adds screenshot/content checks for Docker docs page.
e2e/custom-endpoints-verify.spec.ts Adds callout/tabs/cards checks for custom endpoints docs page.
e2e/config-overview-verify.spec.ts Adds FileTree/callout/cards verification for configuration overview.
e2e/check-redirect.js Adds standalone redirect-check script (non-test).
scripts/clean-cache.cjs Replaces TS script with CJS for postbuild cache cleanup.
.github/workflows/generate_embeddings.yml Updates actions versions/node/pnpm setup and moves Supabase URL to secrets.
.gitignore Tracks Playwright config, ignores bun lockfile, and tidies tooling artifact ignores.
proxy.ts Switches to default export and adds explicit NextResponse return type.
bun.lockb Removed from repo (and now ignored).
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

components/page-actions.tsx:110

  • fullMarkdownUrl is set to the literal string 'loading' during server render (client components are still SSR’d), which means the generated q (and the external link hrefs) will differ between SSR and hydration. Prefer building q without reading window in render (e.g., use markdownUrl directly, a known base URL, or compute the absolute URL in an effect and store it in state) to avoid hydration inconsistencies.
  const items = useMemo(() => {
    const fullMarkdownUrl =
      typeof window === 'undefined' ? 'loading' : new URL(markdownUrl, window.location.origin)
    const q = `Read ${fullMarkdownUrl}, I want to ask questions about it.`


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread e2e/yaml-cards-check.spec.ts Outdated
Comment on lines +12 to +18
// Screenshot the Reference/Cards area
const box = await refHeading.boundingBox()
await page.screenshot({
path: '/home/berry13/librechat.ai/e2e/screenshots/yaml-light-reference-cards.png',
clip: {
x: box!.x - 20,
y: box!.y - 10,
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

boundingBox() can return null if the element isn’t rendered/visible yet, and the current code non-null asserts box! before the visibility expectation runs (so the test can crash instead of failing with a clear assertion). Move await expect(refHeading).toBeVisible() before calling boundingBox(), and handle the null case explicitly.

Copilot uses AI. Check for mistakes.
Comment thread e2e/check-redirect.js Outdated
Comment on lines +34 to +36
const screenshotPath = path.resolve('/home/berry13/librechat.ai/e2e/redirect-check.png')
await page.screenshot({ path: screenshotPath, fullPage: false })
console.log('Screenshot saved to:', screenshotPath)
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

This script writes its screenshot to an absolute, developer-specific path (/home/berry13/...), which will fail on other machines. Prefer writing under test-results/ (or using a relative path resolved from process.cwd()), and consider keeping this kind of ad-hoc script out of the Playwright test directory to avoid confusion.

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +57
// Full-page screenshot for overview
await page.evaluate(() => window.scrollTo({ top: 0, behavior: 'instant' }))
await page.waitForTimeout(300)
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

Same issue here: behavior: 'instant' isn’t a valid ScrollBehavior type and will fail TypeScript checking under strict: true. Use 'auto' or omit the behavior option.

Copilot uses AI. Check for mistakes.
Comment thread app/actions/feedback.ts
Comment on lines +136 to +145
export async function submitFeedback(raw: FeedbackPayload): Promise<{ success: boolean }> {
try {
// Rate limit by URL to prevent spamming the same page
if (isRateLimited(raw.url ?? 'unknown')) {
return { success: false }
}

const payload = validatePayload(raw)
if (!payload) {
return { success: false }
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

This rate limiter runs before payload validation and uses raw.url directly as the key. A malformed request with many unique/large url values can grow rateLimitMap without bound even though the payload is rejected later. Validate/sanitize first (and cap key length) before using it for rate limiting, and add eviction/cleanup to prevent unbounded memory growth.

Copilot uses AI. Check for mistakes.
Comment thread app/api/subscribe/route.ts Outdated
Comment on lines +24 to +27
const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? 'unknown'
if (isRateLimited(ip)) {
return NextResponse.json({ message: 'Too many requests' }, { status: 429 })
}
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

ip falls back to 'unknown' when x-forwarded-for is missing, which will cause unrelated clients to share the same bucket and get rate-limited together in environments that don’t set that header. Consider checking other common headers (e.g., x-real-ip, cf-connecting-ip) and/or skipping rate limiting when the client IP can’t be determined reliably.

Copilot uses AI. Check for mistakes.
Comment thread e2e/user-guides-verify.spec.ts Outdated
Comment on lines +3 to +15
const SHOTS = '/home/berry13/librechat.ai/test-results'

test.describe('user_guides page verification', () => {
test('light mode - verify sections and cards', async ({ page }) => {
await page.emulateMedia({ colorScheme: 'light' })
await page.goto('/docs/user_guides', { waitUntil: 'networkidle' })

await page.waitForSelector('main', { timeout: 15000 })

await page.screenshot({
path: `${SHOTS}/user-guides-light.png`,
fullPage: true,
})
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

This test writes screenshots to an absolute, developer-specific directory (/home/berry13/...), which will fail on CI/other machines. Use testInfo.outputPath(...) / testInfo.outputDir or a repo-relative test-results/ path instead.

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +6
const URL = 'http://localhost:3333/docs/configuration/tools/google_search'
const SCREENSHOT_DIR = '/home/berry13/librechat.ai/test-results/google-search-verify'

Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

SCREENSHOT_DIR is set to an absolute, developer-specific path (/home/berry13/...), which will break on CI/other machines. Use Playwright’s output directory (testInfo.outputPath) or a repo-relative test-results/ path instead.

Copilot uses AI. Check for mistakes.
Comment thread e2e/toolkit-fullpage.spec.ts Outdated
Comment on lines +4 to +5
const SCREENSHOT_DIR = '/tmp/toolkit-final'

Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

These screenshots are written to a fixed /tmp/... location. This is OS/environment-specific and can also cause collisions when tests run in parallel. Prefer using Playwright’s per-test output path (testInfo.outputPath(...)) so artifacts are isolated and portable.

Copilot uses AI. Check for mistakes.
Comment thread app/api/unsubscribe/route.ts Outdated
Comment on lines +4 to +7
/** Simple in-memory rate limiter: max 5 requests per IP per 60 seconds. */
const rateLimitMap = new Map<string, { count: number; resetAt: number }>()
const RATE_LIMIT_WINDOW_MS = 60_000
const RATE_LIMIT_MAX = 5
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

This in-memory rateLimitMap has no eviction and will grow unbounded with unique IPs, and it won’t enforce limits across multiple server instances/serverless invocations. Consider using a shared store (Redis/KV) or middleware-based rate limiting, and add cleanup/bounds if keeping an in-memory fallback.

Copilot uses AI. Check for mistakes.
Comment thread e2e/yaml-cards-check.spec.ts Outdated
Comment on lines +14 to +16
await page.screenshot({
path: '/home/berry13/librechat.ai/e2e/screenshots/yaml-light-reference-cards.png',
clip: {
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

This test writes screenshots to an absolute, developer-specific path (/home/berry13/...), which will fail on CI and other machines. Use Playwright’s testInfo.outputPath(...) / testInfo.outputDir (or a repo-relative test-results/ path) so artifacts are portable.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown

📦 Next.js Bundle Analysis for librechat.ai

This analysis was generated by the Next.js Bundle Analysis action. 🤖

This PR introduced no changes to the JavaScript bundle! 🙌

@berry-13 berry-13 force-pushed the fix/deep-audit-cleanup branch from 0700d6f to 9453115 Compare March 29, 2026 00:59
@github-actions
Copy link
Copy Markdown

📦 Next.js Bundle Analysis for librechat.ai

This analysis was generated by the Next.js Bundle Analysis action. 🤖

This PR introduced no changes to the JavaScript bundle! 🙌

1 similar comment
@github-actions
Copy link
Copy Markdown

📦 Next.js Bundle Analysis for librechat.ai

This analysis was generated by the Next.js Bundle Analysis action. 🤖

This PR introduced no changes to the JavaScript bundle! 🙌

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 39 out of 48 changed files in this pull request and generated 15 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread e2e/toolkit-debug.spec.ts Outdated
Comment thread e2e/toolkit-debug.spec.ts Outdated
Comment thread e2e/toolkit-debug2.spec.ts Outdated
Comment thread e2e/custom-endpoints-verify.spec.ts
Comment thread app/api/subscribe/route.ts
Comment thread lib/rate-limit.ts Outdated
Comment thread e2e/yaml-cards-check.spec.ts
Comment thread e2e/toolkit-debug2.spec.ts Outdated
Comment thread e2e/docker-page-verify.spec.ts
Comment thread e2e/google-search-verify.spec.ts
@github-actions
Copy link
Copy Markdown

📦 Next.js Bundle Analysis for librechat.ai

This analysis was generated by the Next.js Bundle Analysis action. 🤖

This PR introduced no changes to the JavaScript bundle! 🙌

… code removal

- Enable strict: true in tsconfig.json and ignoreBuildErrors: false in next.config.mjs
- Fix all implicit-any errors; install @types/validator and @types/js-yaml
- Add type declarations for @glidejs/glide; fix nullable searchParams in CategoryFilter
- Remove stale tsconfig includes (pages/, utils/)
- Merge duplicate style.css into app/global.css; remove dead font-mono on h1
- Add rate limiting and strict email validation to subscribe/unsubscribe routes
- Extract shared Supabase client (cached singleton) to lib/supabase.ts
- Extract shared rate limiter with bounded eviction to lib/rate-limit.ts
- getClientIp: multi-header detection with 'unknown' fallback (never skips limiting)
- Replace import cn from 'clsx' with import { cn } from '@/lib/utils' in 3 components
- Fix all ESLint errors in e2e tests; add ESLint config section for e2e/ files
- Fix hardcoded paths in e2e tests to relative paths; fix /tmp/ → test-results/
- Fix behavior: 'instant' to 'auto' in scroll calls (invalid ScrollBehavior type)
- Fix boundingBox null checks and clamp negative clip coordinates
- Mark debug-only e2e tests as test.skip; add assertions to dark mode tests
- Move ad-hoc check-redirect.js to scripts/check-redirect.cjs
- Fix lint-staged: remove broken ESLINT_USE_FLAT_CONFIG=false
- Rename scripts/clean-cache.ts to .cjs (uses require(), not ESM)
- Remove dead Companies import from components/home/index.tsx
- Update generate_embeddings workflow to actions v4, Node 20, pnpm/action-setup@v4
- Move hardcoded Supabase URL to ${{ secrets.SUPABASE_URL }}
- Delete bun.lockb, 8 orphaned root PNGs; clean up .gitignore
- Disable X-Powered-By header (poweredByHeader: false)
The dropdown 'Open in GitHub' button was hardcoded to danny-avila/LibreChat
(the app repo, repo root). Now points to LibreChat-AI/librechat.ai and links
directly to the source MDX file for the current page.
- Feedback action: validate opinion ('good'|'bad' only), enforce relative URL
  (no open redirects), cap message at 2000 chars
- Use shared createRateLimiter from lib/rate-limit.ts (3/URL/60s)
- Scarf pixel: sanitize NEXT_PUBLIC_SCARF_PIXEL_ID with /^[\w-]+$/ before
  interpolating into dangerouslySetInnerHTML to prevent XSS
@berry-13 berry-13 force-pushed the fix/deep-audit-cleanup branch from d81dbde to 176a6b6 Compare March 29, 2026 01:27
@github-actions
Copy link
Copy Markdown

📦 Next.js Bundle Analysis for librechat.ai

This analysis was generated by the Next.js Bundle Analysis action. 🤖

This PR introduced no changes to the JavaScript bundle! 🙌

@berry-13 berry-13 merged commit 34f39bc into main Mar 29, 2026
3 checks passed
@berry-13 berry-13 deleted the fix/deep-audit-cleanup branch March 29, 2026 01:40
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.

2 participants