Allow BYOM links and disallow HTTP links (#28560)

This commit is contained in:
Brian R. Bondy
2025-04-09 14:01:47 -04:00
committed by GitHub
parent 9b320b5e5b
commit 30b5fbcd8e
4 changed files with 42 additions and 6 deletions
@@ -52,9 +52,10 @@ function AssistantEvent(props: {
event: Mojom.ConversationEntryEvent,
hasCompletionStarted: boolean,
isEntryInProgress: boolean,
allowedLinks: string[]
allowedLinks: string[],
isLeoModel: boolean
}) {
const { allowedLinks, event, isEntryInProgress } = props;
const { allowedLinks, event, isEntryInProgress, isLeoModel } = props;
if (event.completionEvent) {
const numberedLinks =
@@ -75,6 +76,7 @@ function AssistantEvent(props: {
shouldShowTextCursor={isEntryInProgress}
text={fullText}
allowedLinks={allowedLinks}
disableLinkRestrictions={!isLeoModel}
/>
)
}
@@ -103,7 +105,8 @@ function AssistantEvent(props: {
export default function AssistantResponse(props: {
entry: Mojom.ConversationTurn,
isEntryInProgress: boolean,
allowedLinks: string[]
allowedLinks: string[],
isLeoModel: boolean
}) {
// Extract certain events which need to render at specific locations (e.g. end of the events)
const searchQueriesEvent = props.entry.events?.find(event => event.searchQueriesEvent)?.searchQueriesEvent
@@ -121,6 +124,7 @@ export default function AssistantResponse(props: {
hasCompletionStarted={hasCompletionStarted}
isEntryInProgress={props.isEntryInProgress}
allowedLinks={props.allowedLinks}
isLeoModel={props.isLeoModel}
/>
)
}
@@ -148,6 +148,7 @@ function ConversationEntries() {
entry={latestTurn}
isEntryInProgress={isEntryInProgress}
allowedLinks={allAllowedLinks}
isLeoModel={conversationContext.isLeoModel}
/>
)}
{isHuman && !turn.selectedText && !showEditInput && (
@@ -85,18 +85,20 @@ function CursorDecorator(props: CursorDecoratorProps) {
interface RenderLinkProps {
a: React.ComponentProps<'a'>
allowedLinks?: string[]
disableLinkRestrictions?: boolean
}
export function RenderLink(props: RenderLinkProps) {
const { a, allowedLinks } = props
const { a, allowedLinks, disableLinkRestrictions} = props
const { href, children } = a
// Context
const context = useUntrustedConversationContext()
// Computed
const isLinkAllowed =
allowedLinks?.some((link) => href?.startsWith(link)) ?? false
const isHttps = href?.toLowerCase().startsWith('https://')
const isLinkAllowed = isHttps && (disableLinkRestrictions ||
(allowedLinks?.some((link) => href?.startsWith(link)) ?? false))
const handleLinkClicked = React.useCallback(() => {
if (href && isLinkAllowed) {
@@ -132,6 +134,7 @@ interface MarkdownRendererProps {
text: string
shouldShowTextCursor: boolean
allowedLinks?: string[]
disableLinkRestrictions?: boolean
}
export default function MarkdownRenderer(mainProps: MarkdownRendererProps) {
@@ -203,6 +206,7 @@ export default function MarkdownRenderer(mainProps: MarkdownRendererProps) {
<RenderLink
a={props}
allowedLinks={mainProps.allowedLinks}
disableLinkRestrictions={mainProps.disableLinkRestrictions}
/>
)
}}
@@ -43,3 +43,30 @@ test('Test RenderLink component with citations.', async () => {
expect(screen.getByText('1').tagName).toBe('A')
expect(screen.getByText('1').className).toBe('conversationLink citation')
})
test('Test RenderLink component with disableLinkRestrictions.', async () => {
render(
<RenderLink
a={{ href: 'https://example.com', children: 'Test Link' }}
allowedLinks={[]}
disableLinkRestrictions={true}
/>
)
expect(screen.getByText('Test Link')).toBeInTheDocument()
expect(screen.getByText('Test Link').tagName).toBe('A')
expect(screen.getByText('Test Link').className).toBe('conversationLink')
})
// HTTP links should never be allowed
test('Test RenderLink component with http links.', async () => {
render(
<RenderLink
a={{ href: 'http://example.com', children: 'Test Link' }}
allowedLinks={['http://example.com']}
disableLinkRestrictions={true}
/>
)
expect(screen.getByText('Test Link')).toBeInTheDocument()
expect(screen.getByText('Test Link').tagName).toBe('SPAN')
expect(screen.getByText('Test Link').className).toBe('')
})