Skip to content

feat(web): add optional LLM user email header#1455

Open
brendan-kellam wants to merge 7 commits into
mainfrom
brendan/llm-user-email-header-SOU-1515
Open

feat(web): add optional LLM user email header#1455
brendan-kellam wants to merge 7 commits into
mainfrom
brendan/llm-user-email-header-SOU-1515

Conversation

@brendan-kellam

@brendan-kellam brendan-kellam commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Fixes SOU-1515

Summary

  • add an opt-in environment variable for forwarding authenticated user emails to LLM providers
  • send lower-cased emails through the X-Sourcebot-User-Email header across user-initiated LLM requests
  • document the setting and cover header merging and environment validation with tests

Validation

  • yarn workspace @sourcebot/web vitest run src/features/chat/languageModelHeaders.server.test.ts
  • yarn workspace @sourcebot/shared vitest run src/env.server.test.ts
  • yarn workspace @sourcebot/shared build
  • yarn workspace @sourcebot/web lint

Note

Medium Risk
When enabled, user email (PII) is sent to external LLM APIs on every authenticated call path that uses shared model resolution; behavior is off by default and failures omit the header.

Overview
Adds an opt-in SOURCEBOT_LLM_USER_EMAIL_HEADER_ENABLED setting (default false) so deployments can forward the signed-in user’s email to language model providers on outbound LLM requests.

LLM provider setup in llm.server.ts now resolves headers through resolveLanguageModelHeaders, which still merges configured/token-backed headers but can add X-Sourcebot-User-Email (lower-cased) when the flag is on. The header is skipped for anonymous users and when auth context resolution fails; any configured variant of that header name is replaced by the authenticated email. Changelog, env docs, and unit tests cover the new behavior.

Reviewed by Cursor Bugbot for commit 60220c0. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features
    • Added an opt-in environment setting to forward the authenticated user’s lowercased email to language model providers via the X-Sourcebot-User-Email header.
    • Anonymous/automated requests, and requests where authentication fails with a service error, omit the header.
    • Other configured provider headers are preserved, and any existing email header (case-insensitive) is overridden.
  • Documentation
    • Documented the new environment variable behavior in the configuration guide and changelog.
  • Tests
    • Added tests covering enabled/disabled, anonymous/error, and configured-header scenarios.

@github-actions

This comment has been minimized.

@mintlify

mintlify Bot commented Jul 16, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
sourcebot 🟢 Ready View Preview Jul 16, 2026, 12:14 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds an opt-in environment variable that forwards authenticated users’ lower-cased email addresses to language-model providers through a request header, with centralized resolution, provider integration, tests, and documentation.

Changes

User email header propagation

Layer / File(s) Summary
Configuration and documentation
packages/shared/src/env.server.ts, docs/docs/configuration/environment-variables.mdx, CHANGELOG.md
Adds the disabled-by-default environment variable and documents its authenticated, anonymous, and automated request behavior.
Header resolution and validation
packages/web/src/features/chat/llm.server.ts, packages/web/src/features/chat/llm.server.test.ts
Resolves string and token-backed headers, removes case-insensitive configured email headers, conditionally injects the authenticated lower-cased email, and tests these cases.
Provider header integration
packages/web/src/features/chat/llm.server.ts
Resolves headers once and reuses them across language-model provider constructors while retaining provider-specific query parameter handling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChatRequest
  participant getAISDKLanguageModelAndOptions
  participant resolveLanguageModelHeaders
  participant LanguageModelProvider
  ChatRequest->>getAISDKLanguageModelAndOptions: model configuration
  getAISDKLanguageModelAndOptions->>resolveLanguageModelHeaders: configured headers
  resolveLanguageModelHeaders-->>getAISDKLanguageModelAndOptions: resolved headers
  getAISDKLanguageModelAndOptions->>LanguageModelProvider: construct provider with headers
  LanguageModelProvider-->>ChatRequest: language model instance
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: an optional LLM user email header in the web app.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/llm-user-email-header-SOU-1515

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.

Comment thread packages/web/src/features/chat/languageModelHeaders.server.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/web/src/features/chat/languageModelHeaders.server.ts (1)

12-18: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider resolving token-backed headers in parallel.

Since each token resolution is independent, you can use Promise.all alongside Object.fromEntries to resolve them concurrently rather than sequentially in a for...of loop. This is a functional approach that provides a minor performance optimization, especially if multiple token-backed headers are configured.

♻️ Proposed refactor
-    const headers: Record<string, string> = {};
-
-    for (const [key, value] of Object.entries(configuredHeaders ?? {})) {
-        headers[key] = typeof value === 'string'
-            ? value
-            : await getTokenFromConfig(value);
-    }
+    const headers: Record<string, string> = Object.fromEntries(
+        await Promise.all(
+            Object.entries(configuredHeaders ?? {}).map(async ([key, value]) => [
+                key,
+                typeof value === 'string' ? value : await getTokenFromConfig(value),
+            ])
+        )
+    );
🤖 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 `@packages/web/src/features/chat/languageModelHeaders.server.ts` around lines
12 - 18, Update the header resolution logic to resolve token-backed values
concurrently using Promise.all and construct the result with Object.fromEntries,
while preserving string values and the existing configuredHeaders fallback
behavior. Replace the sequential loop that builds headers.
🤖 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 `@packages/web/src/features/chat/languageModelHeaders.server.ts`:
- Around line 12-18: Update the header resolution logic to resolve token-backed
values concurrently using Promise.all and construct the result with
Object.fromEntries, while preserving string values and the existing
configuredHeaders fallback behavior. Replace the sequential loop that builds
headers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: de4c3c29-df7e-49be-a505-4e1797e74c15

📥 Commits

Reviewing files that changed from the base of the PR and between af6f1e6 and 6346cfd.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • docs/docs/configuration/environment-variables.mdx
  • packages/shared/src/env.server.test.ts
  • packages/shared/src/env.server.ts
  • packages/web/src/app/api/(server)/ee/chat/route.ts
  • packages/web/src/ee/features/chat/actions.ts
  • packages/web/src/ee/features/chat/llm.server.ts
  • packages/web/src/ee/features/mcp/askCodebase.ts
  • packages/web/src/features/chat/languageModelHeaders.server.test.ts
  • packages/web/src/features/chat/languageModelHeaders.server.ts
  • packages/web/src/features/chat/llm.server.ts
  • packages/web/src/features/searchAssist/actions.ts

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b872f26. Configure here.

}

headers[SOURCEBOT_USER_EMAIL_HEADER] = userEmail.toLowerCase();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Placeholder emails sent to providers

Medium Severity

When SOURCEBOT_LLM_USER_EMAIL_HEADER_ENABLED is active, resolveLanguageModelHeaders includes synthetic placeholder-*@no-email.invalid emails in the X-Sourcebot-User-Email header. This is contrary to the intent to omit these placeholder accounts, potentially exposing non-user-specific data to LLM providers.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b872f26. Configure here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/web/src/features/chat/llm.server.ts (1)

351-351: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Consider passing the user email directly to avoid duplicate database queries.

getAuthContext() executes database queries (e.g., org.findUnique, userToOrg.findUnique) to resolve the authenticated user. If resolveLanguageModelHeaders is invoked within an API Route or Server Action that is already protected by the withAuth middleware upstream, fetching the context again will result in redundant database roundtrips unless getAuthContext is memoized via React.cache().

Consider refactoring getAISDKLanguageModelAndOptions and resolveLanguageModelHeaders to accept the user's email as an argument. This allows the caller to pass down the already authenticated context and avoid duplicate database lookups.

🤖 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 `@packages/web/src/features/chat/llm.server.ts` at line 351, Refactor
getAISDKLanguageModelAndOptions and resolveLanguageModelHeaders to accept the
authenticated user email as an argument, and pass it through from protected
callers instead of invoking getAuthContext again. Remove the redundant
getAuthContext lookup while preserving the existing model and header resolution
behavior.
packages/web/src/features/chat/llm.server.test.ts (1)

67-71: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a test for stripping configured email headers on anonymous requests.

If the implementation in llm.server.ts is updated to unconditionally scrub statically configured variants of the email header, please add a corresponding test case to verify this behavior for anonymous requests.

🧪 Proposed test case
    test('strips statically configured email header for anonymous requests', async () => {
        mocks.env.SOURCEBOT_LLM_USER_EMAIL_HEADER_ENABLED = 'true';

        await expect(resolveLanguageModelHeaders({
            'x-sourcebot-user-email': 'configured@example.com',
            'X-Custom-Header': 'custom-value',
        })).resolves.toEqual({
            'X-Custom-Header': 'custom-value',
        });
    });
🤖 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 `@packages/web/src/features/chat/llm.server.test.ts` around lines 67 - 71, Add
a test alongside “omits the user email header for anonymous requests” that
enables SOURCEBOT_LLM_USER_EMAIL_HEADER_ENABLED and passes configured headers
containing x-sourcebot-user-email plus an unrelated custom header to
resolveLanguageModelHeaders. Assert the email header is removed while the custom
header remains unchanged.
🤖 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 `@packages/web/src/features/chat/llm.server.ts`:
- Around line 354-364: Move the case-insensitive header-removal loop in the
request header construction flow outside the if (userEmail) block so configured
SOURCEBOT_USER_EMAIL_HEADER variants are removed for every request. Keep
assigning the lowercased userEmail only when userEmail is present, ensuring
anonymous requests forward no user-email header.

---

Nitpick comments:
In `@packages/web/src/features/chat/llm.server.test.ts`:
- Around line 67-71: Add a test alongside “omits the user email header for
anonymous requests” that enables SOURCEBOT_LLM_USER_EMAIL_HEADER_ENABLED and
passes configured headers containing x-sourcebot-user-email plus an unrelated
custom header to resolveLanguageModelHeaders. Assert the email header is removed
while the custom header remains unchanged.

In `@packages/web/src/features/chat/llm.server.ts`:
- Line 351: Refactor getAISDKLanguageModelAndOptions and
resolveLanguageModelHeaders to accept the authenticated user email as an
argument, and pass it through from protected callers instead of invoking
getAuthContext again. Remove the redundant getAuthContext lookup while
preserving the existing model and header resolution behavior.
🪄 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: 7ff43ac5-b5ce-4ce7-9a62-dfcd338b5402

📥 Commits

Reviewing files that changed from the base of the PR and between a20f849 and b872f26.

📒 Files selected for processing (2)
  • packages/web/src/features/chat/llm.server.test.ts
  • packages/web/src/features/chat/llm.server.ts

Comment on lines +354 to +364
if (userEmail) {
// Header names are case-insensitive. Remove any configured variant so
// the authenticated user's email is always the authoritative value.
for (const key of Object.keys(headers)) {
if (key.toLowerCase() === SOURCEBOT_USER_EMAIL_HEADER.toLowerCase()) {
delete headers[key];
}
}

headers[SOURCEBOT_USER_EMAIL_HEADER] = userEmail.toLowerCase();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Unconditionally scrub statically configured email headers for anonymous requests.

Currently, the case-insensitive removal of any configured X-Sourcebot-User-Email variant only executes if userEmail is truthy. For anonymous requests, a statically configured fallback header will be forwarded to the provider, violating the PR objective to omit this header for unauthenticated users and potentially allowing header spoofing via static config.

Move the scrubbing loop outside of the if (userEmail) block to ensure the header is strictly removed for all requests.

🔒️ Proposed fix to scrub the header unconditionally
-    if (userEmail) {
-        // Header names are case-insensitive. Remove any configured variant so
-        // the authenticated user's email is always the authoritative value.
-        for (const key of Object.keys(headers)) {
-            if (key.toLowerCase() === SOURCEBOT_USER_EMAIL_HEADER.toLowerCase()) {
-                delete headers[key];
-            }
-        }
-
-        headers[SOURCEBOT_USER_EMAIL_HEADER] = userEmail.toLowerCase();
-    }
+    // Header names are case-insensitive. Remove any configured variant so
+    // the authenticated user's email is always the authoritative value,
+    // and to ensure it is strictly omitted for anonymous requests.
+    for (const key of Object.keys(headers)) {
+        if (key.toLowerCase() === SOURCEBOT_USER_EMAIL_HEADER.toLowerCase()) {
+            delete headers[key];
+        }
+    }
+
+    if (userEmail) {
+        headers[SOURCEBOT_USER_EMAIL_HEADER] = userEmail.toLowerCase();
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (userEmail) {
// Header names are case-insensitive. Remove any configured variant so
// the authenticated user's email is always the authoritative value.
for (const key of Object.keys(headers)) {
if (key.toLowerCase() === SOURCEBOT_USER_EMAIL_HEADER.toLowerCase()) {
delete headers[key];
}
}
headers[SOURCEBOT_USER_EMAIL_HEADER] = userEmail.toLowerCase();
}
// Header names are case-insensitive. Remove any configured variant so
// the authenticated user's email is always the authoritative value,
// and to ensure it is strictly omitted for anonymous requests.
for (const key of Object.keys(headers)) {
if (key.toLowerCase() === SOURCEBOT_USER_EMAIL_HEADER.toLowerCase()) {
delete headers[key];
}
}
if (userEmail) {
headers[SOURCEBOT_USER_EMAIL_HEADER] = userEmail.toLowerCase();
}
🤖 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 `@packages/web/src/features/chat/llm.server.ts` around lines 354 - 364, Move
the case-insensitive header-removal loop in the request header construction flow
outside the if (userEmail) block so configured SOURCEBOT_USER_EMAIL_HEADER
variants are removed for every request. Keep assigning the lowercased userEmail
only when userEmail is present, ensuring anonymous requests forward no
user-email header.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant