VYPR
Unrated severityNVD Advisory· Published Jun 9, 2026· Updated Jun 9, 2026

CVE-2026-34033

CVE-2026-34033

Description

Apache Answer through 2.0.0 is vulnerable to XSS, allowing authenticated users to inject HTML into notification emails.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Apache Answer through 2.0.0 is vulnerable to XSS, allowing authenticated users to inject HTML into notification emails.

Vulnerability

An Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS) vulnerability exists in Apache Answer through version 2.0.0. User-supplied content was included in notification emails without proper escaping, leading to this issue [1].

Exploitation

An attacker must be an authenticated user within Apache Answer. They can then inject arbitrary HTML into notification emails that are sent to other users [1].

Impact

Successful exploitation allows an authenticated attacker to inject arbitrary HTML into notification emails sent to other users. This could lead to cross-site scripting (XSS) attacks within the email client or other downstream processing of the email content [1].

Mitigation

Users are recommended to upgrade to Apache Answer version 2.0.1, which addresses this issue. The fixed version was released on June 9, 2026 [1].

AI Insight generated on Jun 9, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

3
d1a4092c61cc

fix(email): enhance email templates by escaping HTML characters in dynamic content

https://github.com/apache/incubator-answerLinkinStarsMay 9, 2026Fixed in 2.0.1via llm-release-walk
1 file changed · +35 4
  • internal/service/export/email_service.go+35 4 modified
    @@ -23,6 +23,7 @@ import (
     	"crypto/tls"
     	"encoding/json"
     	"fmt"
    +	"html"
     	"mime"
     	"os"
     	"strings"
    @@ -224,6 +225,10 @@ func (es *EmailService) TestTemplate(ctx context.Context) (title, body string, e
     	return title, body, nil
     }
     
    +func escapeEmailHTMLText(text string) string {
    +	return html.EscapeString(text)
    +}
    +
     // NewAnswerTemplate new answer template
     func (es *EmailService) NewAnswerTemplate(ctx context.Context, raw *schema.NewAnswerTemplateRawData) (
     	title, body string, err error) {
    @@ -246,7 +251,14 @@ func (es *EmailService) NewAnswerTemplate(ctx context.Context, raw *schema.NewAn
     
     	lang := handler.GetLangByCtx(ctx)
     	title = translator.TrWithData(lang, constant.EmailTplKeyNewAnswerTitle, templateData)
    -	body = translator.TrWithData(lang, constant.EmailTplKeyNewAnswerBody, templateData)
    +	body = translator.TrWithData(lang, constant.EmailTplKeyNewAnswerBody, &schema.NewAnswerTemplateData{
    +		SiteName:       escapeEmailHTMLText(templateData.SiteName),
    +		DisplayName:    escapeEmailHTMLText(templateData.DisplayName),
    +		QuestionTitle:  escapeEmailHTMLText(templateData.QuestionTitle),
    +		AnswerUrl:      templateData.AnswerUrl,
    +		AnswerSummary:  escapeEmailHTMLText(templateData.AnswerSummary),
    +		UnsubscribeUrl: templateData.UnsubscribeUrl,
    +	})
     	return title, body, nil
     }
     
    @@ -271,7 +283,13 @@ func (es *EmailService) NewInviteAnswerTemplate(ctx context.Context, raw *schema
     
     	lang := handler.GetLangByCtx(ctx)
     	title = translator.TrWithData(lang, constant.EmailTplKeyInvitedAnswerTitle, templateData)
    -	body = translator.TrWithData(lang, constant.EmailTplKeyInvitedAnswerBody, templateData)
    +	body = translator.TrWithData(lang, constant.EmailTplKeyInvitedAnswerBody, &schema.NewInviteAnswerTemplateData{
    +		SiteName:       escapeEmailHTMLText(templateData.SiteName),
    +		DisplayName:    escapeEmailHTMLText(templateData.DisplayName),
    +		QuestionTitle:  escapeEmailHTMLText(templateData.QuestionTitle),
    +		InviteUrl:      templateData.InviteUrl,
    +		UnsubscribeUrl: templateData.UnsubscribeUrl,
    +	})
     	return title, body, nil
     }
     
    @@ -298,7 +316,14 @@ func (es *EmailService) NewCommentTemplate(ctx context.Context, raw *schema.NewC
     
     	lang := handler.GetLangByCtx(ctx)
     	title = translator.TrWithData(lang, constant.EmailTplKeyNewCommentTitle, templateData)
    -	body = translator.TrWithData(lang, constant.EmailTplKeyNewCommentBody, templateData)
    +	body = translator.TrWithData(lang, constant.EmailTplKeyNewCommentBody, &schema.NewCommentTemplateData{
    +		SiteName:       escapeEmailHTMLText(templateData.SiteName),
    +		DisplayName:    escapeEmailHTMLText(templateData.DisplayName),
    +		QuestionTitle:  escapeEmailHTMLText(templateData.QuestionTitle),
    +		CommentUrl:     templateData.CommentUrl,
    +		CommentSummary: escapeEmailHTMLText(templateData.CommentSummary),
    +		UnsubscribeUrl: templateData.UnsubscribeUrl,
    +	})
     	return title, body, nil
     }
     
    @@ -324,7 +349,13 @@ func (es *EmailService) NewQuestionTemplate(ctx context.Context, raw *schema.New
     
     	lang := handler.GetLangByCtx(ctx)
     	title = translator.TrWithData(lang, constant.EmailTplKeyNewQuestionTitle, templateData)
    -	body = translator.TrWithData(lang, constant.EmailTplKeyNewQuestionBody, templateData)
    +	body = translator.TrWithData(lang, constant.EmailTplKeyNewQuestionBody, &schema.NewQuestionTemplateData{
    +		SiteName:       escapeEmailHTMLText(templateData.SiteName),
    +		QuestionTitle:  escapeEmailHTMLText(templateData.QuestionTitle),
    +		QuestionUrl:    templateData.QuestionUrl,
    +		Tags:           escapeEmailHTMLText(templateData.Tags),
    +		UnsubscribeUrl: templateData.UnsubscribeUrl,
    +	})
     	return title, body, nil
     }
     
    
869b040e92d6

fix(auth): enhance admin user cache management and add status checks for email verification and suspension

https://github.com/apache/incubator-answerLinkinStarsFeb 6, 2026Fixed in 2.0.1via llm-release-walk
2 files changed · +48 1
  • internal/base/middleware/auth.go+15 0 modified
    @@ -184,7 +184,22 @@ func (am *AuthUserMiddleware) AdminAuth() gin.HandlerFunc {
     			return
     		}
     		if userInfo != nil {
    +			if userInfo.EmailStatus == entity.EmailStatusToBeVerified {
    +				_ = am.authService.RemoveAdminUserCacheInfo(ctx, token)
    +				handler.HandleResponse(ctx, errors.Forbidden(reason.EmailNeedToBeVerified),
    +					&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeInactive})
    +				ctx.Abort()
    +				return
    +			}
    +			if userInfo.UserStatus == entity.UserStatusSuspended {
    +				_ = am.authService.RemoveAdminUserCacheInfo(ctx, token)
    +				handler.HandleResponse(ctx, errors.Forbidden(reason.UserSuspended),
    +					&schema.ForbiddenResp{Type: schema.ForbiddenReasonTypeUserSuspended})
    +				ctx.Abort()
    +				return
    +			}
     			if userInfo.UserStatus == entity.UserStatusDeleted {
    +				_ = am.authService.RemoveAdminUserCacheInfo(ctx, token)
     				handler.HandleResponse(ctx, errors.Unauthorized(reason.UnauthorizedError), nil)
     				ctx.Abort()
     				return
    
  • internal/service/auth/auth.go+33 1 modified
    @@ -145,7 +145,39 @@ func (as *AuthService) RemoveTokensExceptCurrentUser(ctx context.Context, userID
     // Admin
     
     func (as *AuthService) GetAdminUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) {
    -	return as.authRepo.GetAdminUserCacheInfo(ctx, accessToken)
    +	adminCacheInfo, err := as.authRepo.GetAdminUserCacheInfo(ctx, accessToken)
    +	if err != nil {
    +		return nil, err
    +	}
    +	if adminCacheInfo == nil {
    +		return nil, nil
    +	}
    +
    +	// Keep admin authorization aligned with user-token lifecycle and status refresh.
    +	refreshedUserCacheInfo, err := as.GetUserCacheInfo(ctx, accessToken)
    +	if err != nil {
    +		return nil, err
    +	}
    +	if refreshedUserCacheInfo == nil {
    +		if err = as.authRepo.RemoveAdminUserCacheInfo(ctx, accessToken); err != nil {
    +			return nil, err
    +		}
    +		return nil, nil
    +	}
    +
    +	adminCacheInfo.UserStatus = refreshedUserCacheInfo.UserStatus
    +	adminCacheInfo.EmailStatus = refreshedUserCacheInfo.EmailStatus
    +	if refreshedUserCacheInfo.RoleID > 0 {
    +		adminCacheInfo.RoleID = refreshedUserCacheInfo.RoleID
    +	}
    +	if len(refreshedUserCacheInfo.ExternalID) > 0 {
    +		adminCacheInfo.ExternalID = refreshedUserCacheInfo.ExternalID
    +	}
    +
    +	if err = as.authRepo.SetAdminUserCacheInfo(ctx, accessToken, adminCacheInfo); err != nil {
    +		return nil, err
    +	}
    +	return adminCacheInfo, nil
     }
     
     func (as *AuthService) SetAdminUserCacheInfo(ctx context.Context, accessToken string, userInfo *entity.UserCacheInfo) (err error) {
    
0db88d63e304

fix(chat): implement HTML rendering for display content

https://github.com/apache/incubator-answerLinkinStarsFeb 6, 2026Fixed in 2.0.1via llm-release-walk
3 files changed · +65 9
  • ui/src/components/BubbleAi/index.tsx+61 5 modified
    @@ -21,10 +21,9 @@ import { FC, useEffect, useState, useRef } from 'react';
     import { Button } from 'react-bootstrap';
     import { useTranslation } from 'react-i18next';
     
    -import { marked } from 'marked';
     import copy from 'copy-to-clipboard';
     
    -import { voteConversation } from '@/services';
    +import { markdownToHtml, voteConversation } from '@/services';
     import { Icon, htmlRender } from '@/components';
     
     interface IProps {
    @@ -40,6 +39,17 @@ interface IProps {
       };
     }
     
    +const escapeHtml = (text: string) =>
    +  text
    +    .replace(/&/g, '&')
    +    .replace(/</g, '&lt;')
    +    .replace(/>/g, '&gt;')
    +    .replace(/"/g, '&quot;')
    +    .replace(/'/g, '&#39;');
    +
    +const renderPlainTextAsHtml = (text: string) =>
    +  escapeHtml(text).replace(/\r?\n/g, '<br />');
    +
     const BubbleAi: FC<IProps> = ({
       canType = false,
       isLast,
    @@ -55,6 +65,7 @@ const BubbleAi: FC<IProps> = ({
       const [isHelpful, setIsHelpful] = useState(false);
       const [isUnhelpful, setIsUnhelpful] = useState(false);
       const [canShowAction, setCanShowAction] = useState(false);
    +  const [safeHtml, setSafeHtml] = useState('');
       const typewriterRef = useRef<{
         timer: NodeJS.Timeout | null;
         index: number;
    @@ -64,6 +75,8 @@ const BubbleAi: FC<IProps> = ({
         index: 0,
         isTyping: false,
       });
    +  const renderTimerRef = useRef<NodeJS.Timeout | null>(null);
    +  const renderTaskRef = useRef(0);
       const fmtContainer = useRef<HTMLDivElement>(null);
       // add ref for ScrollIntoView
       const containerRef = useRef<HTMLDivElement>(null);
    @@ -194,13 +207,56 @@ const BubbleAi: FC<IProps> = ({
         };
       }, [content, isCompleted]);
     
    +  useEffect(() => {
    +    if (renderTimerRef.current) {
    +      clearTimeout(renderTimerRef.current);
    +      renderTimerRef.current = null;
    +    }
    +    renderTaskRef.current += 1;
    +    const currentRenderTask = renderTaskRef.current;
    +
    +    if (!displayContent) {
    +      setSafeHtml('');
    +      return undefined;
    +    }
    +
    +    // During streaming, render escaped plain text to avoid executing unsanitized HTML.
    +    if (!isCompleted) {
    +      setSafeHtml(renderPlainTextAsHtml(displayContent));
    +      return undefined;
    +    }
    +
    +    renderTimerRef.current = setTimeout(() => {
    +      markdownToHtml(displayContent)
    +        .then((resp) => {
    +          if (renderTaskRef.current !== currentRenderTask) {
    +            return;
    +          }
    +          setSafeHtml(resp || renderPlainTextAsHtml(displayContent));
    +        })
    +        .catch(() => {
    +          if (renderTaskRef.current !== currentRenderTask) {
    +            return;
    +          }
    +          setSafeHtml(renderPlainTextAsHtml(displayContent));
    +        });
    +    }, 0);
    +
    +    return () => {
    +      if (renderTimerRef.current) {
    +        clearTimeout(renderTimerRef.current);
    +        renderTimerRef.current = null;
    +      }
    +    };
    +  }, [displayContent, isCompleted]);
    +
       useEffect(() => {
         setIsHelpful(actionData.helpful > 0);
         setIsUnhelpful(actionData.unhelpful > 0);
       }, [actionData]);
     
       useEffect(() => {
    -    if (fmtContainer.current && isCompleted) {
    +    if (fmtContainer.current && isCompleted && safeHtml) {
           htmlRender(fmtContainer.current, {
             copySuccessText: t('copied', { keyPrefix: 'messages' }),
             copyText: t('copy', { keyPrefix: 'messages' }),
    @@ -211,7 +267,7 @@ const BubbleAi: FC<IProps> = ({
           });
           setCanShowAction(true);
         }
    -  }, [isCompleted, fmtContainer.current]);
    +  }, [isCompleted, safeHtml, t]);
     
       return (
         <div
    @@ -223,7 +279,7 @@ const BubbleAi: FC<IProps> = ({
               className="fmt text-break text-wrap"
               ref={fmtContainer}
               style={{ transition: 'all 0.2s ease' }}
    -          dangerouslySetInnerHTML={{ __html: marked.parse(displayContent) }}
    +          dangerouslySetInnerHTML={{ __html: safeHtml }}
             />
     
             {canShowAction && (
    
  • ui/src/pages/AiAssistant/index.tsx+1 1 modified
    @@ -328,7 +328,7 @@ const Index = () => {
                             canType={isGenerate && isLastMessage}
                             chatId={item.chat_completion_id}
                             isLast={isLastMessage}
    -                        isCompleted={!isGenerate}
    +                        isCompleted={!isGenerate || !isLastMessage}
                             content={item.content}
                             actionData={{
                               helpful: item.helpful,
    
  • ui/src/pages/Search/components/AiCard/index.tsx+3 3 modified
    @@ -149,10 +149,10 @@ const Index = () => {
                     <BubbleUser content={item.content} />
                   ) : (
                     <BubbleAi
    -                  canType
    +                  canType={isGenerate && isLastMessage}
                       chatId={item.chat_completion_id}
    -                  isLast
    -                  isCompleted={!isGenerate}
    +                  isLast={isLastMessage}
    +                  isCompleted={!isGenerate || !isLastMessage}
                       content={item.content}
                       actionData={{
                         helpful: item.helpful,
    

Vulnerability mechanics

Synthesis attempt was rejected by the grounding validator. Re-run pending.

References

2

News mentions

0

No linked articles in our index yet.