Skip to content

🔒 fix: Remove Owner Email from Agent owner_contact Fallback - #14541

Merged
danny-avila merged 3 commits into
devfrom
danny-avila/agent-owner-email-leak-57ea7e
Jul 31, 2026
Merged

danny-avila merged 3 commits into
devfrom
danny-avila/agent-owner-email-leak-57ea7e

Conversation

@danny-avila

Copy link
Copy Markdown
Collaborator

Summary

Resolves a validated security finding (medium): the agent owner-contact fallback added in #13663 leaked owner account emails to VIEW-level callers.

For agents without an explicit support_contact, attachOwnerContacts resolved the first ACL owner (or agent.author), queried the User collection with a name username email projection, and served owner_contact.email in both GET /api/agents/:id and GET /api/agents responses. Any authenticated user with VIEW access to a shared or public agent could read the owner's private account email, without the owner ever opting in.

This PR makes the owner fallback display-name only. Emails are now only served when the owner explicitly configures a support_contact. Defense in depth across three layers:

  • packages/api/src/agents/contact.ts: resolveAgentOwnerContact resolves only a display name (owner name, then username, then legacy authorName) and returns undefined when none exists. email is removed from AgentOwnerContactSource.
  • api/server/services/Agents/ownerContact.js: the User query projection drops email, so the private address never enters the response-building path. All attachOwnerContacts call sites (get, list, update, duplicate, revert, actions) flow through this.
  • packages/data-provider/src/types/assistants.ts + client/src/components/Agents/AgentContact.tsx: the shared AgentOwnerContact type drops email, and the client renders the owner fallback as plain text with no mailto link, ignoring an email field even if a stale server still sends one.

owner_contact is computed at response time and never persisted, so no stored data is affected.

Change Type

  • Bug fix (non-breaking change which fixes an issue)

Testing

  • cd packages/api && npx jest src/agents/contact.spec.ts: 8 tests pass, including a new case asserting an owner email fed into the resolver never appears in the output.
  • cd api && npx jest server/controllers/agents/v1.spec.js --runInBand: all 87 tests pass; owner-contact expectations now assert name-only responses and not.toHaveProperty('email') (real in-memory MongoDB, no mocked DB).
  • cd client && npx jest AgentContact.spec AgentCard.spec AgentDetailContent.spec Landing.agent-contact: 25 tests pass; owner fallback renders a plain name without a mailto link.
  • cd client && npx tsc --noEmit: clean after the shared type change.

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

The owner-contact fallback for agents without an explicit support_contact
exposed the owner's private account email to any VIEW-level caller via
GET /agents/:id and GET /agents. The fallback now resolves a display name
only (name/username/authorName): the User query no longer projects email,
the resolver never returns one, and the shared AgentOwnerContact type drops
the field. Emails are only served when the owner opts in via support_contact.
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37fd5884b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (ownerIds.length > 0) {
try {
const users = await db.findUsers({ _id: { $in: ownerIds } }, 'name username email');
const users = await db.findUsers({ _id: { $in: ownerIds } }, 'name username');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Exclude email-valued owner names from the fallback

For OpenID users without name/username claims, openidStrategy.js:305,677-679 stores the account email in both name and username (SAML has equivalent fallbacks at samlStrategy.js:156,232-234). Keeping both fields in this projection therefore lets resolveAgentOwnerContact return that private email as owner_contact.name to VIEW/public-agent callers, so removing only the explicit email property does not close the reported disclosure. Retain enough information to reject display-name candidates equal to the account email, or otherwise exclude email-derived names/usernames.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Valid catch, thanks. Confirmed both fallbacks: openidStrategy getFullName returns userinfo.username || userinfo.email and username falls back to userinfo.email; samlStrategy has the equivalent getUserName(profile) || getEmail(profile) paths. Fixed in 1a40cbc: resolveAgentOwnerContact now rejects email-shaped display-name candidates (name, username, and legacy authorName) via a normalizeDisplayName helper, so an email can no longer surface through the name path regardless of which auth strategy stored it. This also covers stale emails left in name after an account email change, which an exact-match against the current account email would miss. Added resolver tests for the skip and all-candidates-email cases, plus an end-to-end controller test where an SSO-style owner (name = username = email) yields no owner_contact at all.

OpenID and SAML strategies fall back to the account email for the user's
name and username when no display-name claims exist, so the name-only
owner fallback could still surface the email through those fields. The
resolver now rejects email-shaped display-name candidates entirely.
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a40cbc693

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/api/src/agents/contact.ts Outdated
email?: string | null;
}

const EMAIL_LIKE = /^[^\s@]+@[^\s@]+$/;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Compare candidates against the stored account email

When an OpenID/SAML account lacks display claims and its account email does not match this narrow regex—for example, "given family"@example.com—the auth fallbacks copy that exact value into name and username (api/strategies/openidStrategy.js:305,677-693, with the SAML equivalent at api/strategies/samlStrategy.js:156,232-250). The User schema accepts this example because its validator is unanchored (packages/data-schemas/src/schema/user.ts:36-40), while EMAIL_LIKE fails to recognize values containing whitespace, causing normalizeDisplayName to return the private address to VIEW/public-agent callers. Fresh evidence in the updated code is this new shape heuristic; compare display candidates with the stored account email instead so every accepted email form is excluded.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Valid edge case, thanks; confirmed the User schema email validator is an unanchored substring match so quoted local parts with whitespace can be stored. Fixed in dd4bbd6, though with a broader guard than the suggested exact comparison: normalizeDisplayName now rejects any candidate containing '@'. Every legal email form contains '@', so this excludes all accepted email forms including quoted local parts, and it also covers cases exact-matching the stored account email would miss (a stale email left in name after the account email changes, or a different email used as username). The trade-off is suppressing rare legitimate '@'-containing display names, which fails privacy-safe for a fallback the owner never opted into. Added a resolver test for the \"given family\"@example.com form.

RFC-5321 quoted local parts may contain whitespace and the User schema
email validator is an unanchored substring match, so such addresses can
reach the name/username fields via SSO fallbacks. Rejecting on '@'
presence covers every legal email form without re-fetching the account
email.
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: dd4bbd6a33

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila
danny-avila merged commit 8e165eb into dev Jul 31, 2026
32 checks passed
@danny-avila
danny-avila deleted the danny-avila/agent-owner-email-leak-57ea7e branch July 31, 2026 03:46
fuuuzzy pushed a commit to fuuuzzy/LibreChat that referenced this pull request Aug 7, 2026
…hat-AI#14541)

* 🔒 fix: Remove Owner Email from Agent `owner_contact` Fallback

The owner-contact fallback for agents without an explicit support_contact
exposed the owner's private account email to any VIEW-level caller via
GET /agents/:id and GET /agents. The fallback now resolves a display name
only (name/username/authorName): the User query no longer projects email,
the resolver never returns one, and the shared AgentOwnerContact type drops
the field. Emails are only served when the owner opts in via support_contact.

* 🔒 fix: Reject Email-Shaped Owner Display Names in Contact Fallback

OpenID and SAML strategies fall back to the account email for the user's
name and username when no display-name claims exist, so the name-only
owner fallback could still surface the email through those fields. The
resolver now rejects email-shaped display-name candidates entirely.

* 🔒 fix: Treat Any @-Containing Display Name as Email-Derived

RFC-5321 quoted local parts may contain whitespace and the User schema
email validator is an unanchored substring match, so such addresses can
reach the name/username fields via SSO fallbacks. Rejecting on '@'
presence covers every legal email form without re-fetching the account
email.
LogicalAbsurd pushed a commit to LogicalAbsurd/LibreChat that referenced this pull request Aug 27, 2026
…hat-AI#14541)

* 🔒 fix: Remove Owner Email from Agent `owner_contact` Fallback

The owner-contact fallback for agents without an explicit support_contact
exposed the owner's private account email to any VIEW-level caller via
GET /agents/:id and GET /agents. The fallback now resolves a display name
only (name/username/authorName): the User query no longer projects email,
the resolver never returns one, and the shared AgentOwnerContact type drops
the field. Emails are only served when the owner opts in via support_contact.

* 🔒 fix: Reject Email-Shaped Owner Display Names in Contact Fallback

OpenID and SAML strategies fall back to the account email for the user's
name and username when no display-name claims exist, so the name-only
owner fallback could still surface the email through those fields. The
resolver now rejects email-shaped display-name candidates entirely.

* 🔒 fix: Treat Any @-Containing Display Name as Email-Derived

RFC-5321 quoted local parts may contain whitespace and the User schema
email validator is an unanchored substring match, so such addresses can
reach the name/username fields via SSO fallbacks. Rejecting on '@'
presence covers every legal email form without re-fetching the account
email.
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