use DOMPurify instead of sanitize-html for smaller bundle (#50002)

Also sanitize HTML more strictly. Previously we allowed SVG and `data:`
URIs in some cases (for some functionality from the legacy Sourcegraph
extension API). This is no longer needed, and getting stricter in HTML
sanitization is generally good.


## Test plan

Test callers of renderMarkdown in UI.


Co-authored-by: Juliana Peña <me@julip.co>
This commit is contained in:
Quinn Slack 2023-03-28 18:30:30 -07:00 committed by GitHub
parent 691b119404
commit 972636de77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 115 additions and 286 deletions

View File

@ -169,7 +169,6 @@ ts_project(
"//:node_modules/@types/node",
"//:node_modules/@types/react",
"//:node_modules/@types/react-dom",
"//:node_modules/@types/sanitize-html",
"//:node_modules/classnames",
"//:node_modules/comlink", #keep
"//:node_modules/copy-to-clipboard",
@ -184,7 +183,6 @@ ts_project(
"//:node_modules/react-sticky-box",
"//:node_modules/react-visibility-sensor",
"//:node_modules/rxjs",
"//:node_modules/sanitize-html",
"//:node_modules/ts-key-enum",
"//:node_modules/use-resize-observer",
"//client/shared:shared_lib", #keep

View File

@ -1,10 +1,10 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import classNames from 'classnames'
import DOMPurify from 'dompurify'
import { range } from 'lodash'
import { of } from 'rxjs'
import { catchError } from 'rxjs/operators'
import sanitizeHtml from 'sanitize-html'
import { highlightNode, logger } from '@sourcegraph/common'
import { PlatformContextProps } from '@sourcegraph/shared/src/platform/context'
@ -47,7 +47,7 @@ export const CommitSearchResultMatch: React.FunctionComponent<CommitSearchResult
// Return the rendered markdown if highlighting fails.
catchError(error => {
logger.log(error)
return of('<pre>' + sanitizeHtml(item.content) + '</pre>')
return of('<pre>' + DOMPurify.sanitize(item.content) + '</pre>')
})
)
.subscribe(highlightedCommitContent => {

View File

@ -67,8 +67,6 @@ exports[`StreamingProgressSkippedPopover should render correctly 1`] = `
<p>
There was a network error retrieving search results. Check your Internet connection and try again.
</p>
</div>
</div>
<div
@ -130,8 +128,6 @@ exports[`StreamingProgressSkippedPopover should render correctly 1`] = `
<p>
Search timed out
</p>
</div>
</div>
<div
@ -193,8 +189,6 @@ exports[`StreamingProgressSkippedPopover should render correctly 1`] = `
<p>
10k forked repositories excluded
</p>
</div>
</div>
<div
@ -256,8 +250,6 @@ exports[`StreamingProgressSkippedPopover should render correctly 1`] = `
<p>
60k archived repositories excluded
</p>
</div>
</div>
<div
@ -323,8 +315,6 @@ exports[`StreamingProgressSkippedPopover should render correctly 1`] = `
</code>
in your query.
</p>
</div>
</div>
<div

View File

@ -16,5 +16,8 @@
"lint": "pnpm run lint:js",
"lint:js": "eslint --cache '**/*.[tj]s?(x)'",
"test": "jest"
},
"dependencies": {
"@sourcegraph/common": "workspace:*"
}
}

View File

@ -1,27 +1,16 @@
import { marked } from 'marked'
import sanitize from 'sanitize-html'
import { registerHighlightContributions, renderMarkdown as renderMarkdownCommon } from '@sourcegraph/common'
import { highlightCode, registerHighlightContributions } from './highlight'
const sanitizeOptions = {
...sanitize.defaults,
allowedAttributes: {
...sanitize.defaults.allowedAttributes,
a: [
...sanitize.defaults.allowedAttributes.a,
'title',
'class',
{ name: 'rel', values: ['noopener', 'noreferrer'] },
],
// Allow highlight.js styles, e.g.
// <span class="hljs-keyword">
// <code class="language-javascript">
span: ['class'],
code: ['class'],
},
}
export function renderMarkdown(text: string): string {
/**
* Render Markdown to safe HTML.
*
* NOTE: This only works when called in an environment with the DOM. In the VS Code extension, it
* only works in the webview context, not in the extension host context, because the latter lacks a
* DOM. We could use isomorphic-dompurify for that, but that adds needless complexity for now. If
* that becomes necessary, we can add that.
*/
export function renderMarkdown(markdown: string): string {
registerHighlightContributions()
return sanitize(marked.parse(text, { gfm: true, highlight: highlightCode, breaks: true }), sanitizeOptions)
// Add Cody-specific Markdown rendering if needed.
return renderMarkdownCommon(markdown)
}

View File

@ -6,7 +6,6 @@ import { MAX_CURRENT_FILE_TOKENS, MAX_HUMAN_INPUT_TOKENS } from '../../prompt/co
import { populateCodeContextTemplate } from '../../prompt/templates'
import { truncateText } from '../../prompt/truncation'
import { getShortTimestamp } from '../../timestamp'
import { renderMarkdown } from '../markdown'
import { Interaction } from '../transcript/interaction'
import { Recipe } from './recipe'
@ -24,11 +23,10 @@ export class ChatQuestion implements Recipe {
): Promise<Interaction | null> {
const timestamp = getShortTimestamp()
const truncatedText = truncateText(humanChatInput, MAX_HUMAN_INPUT_TOKENS)
const displayText = renderMarkdown(humanChatInput)
return Promise.resolve(
new Interaction(
{ speaker: 'human', text: truncatedText, displayText, timestamp },
{ speaker: 'human', text: truncatedText, displayText: humanChatInput, timestamp },
{ speaker: 'assistant', text: '', displayText: '', timestamp },
this.getContextMessages(truncatedText, editor, intentDetector, codebaseContext)
)

View File

@ -4,7 +4,6 @@ import { IntentDetector } from '../../intent-detector'
import { MAX_RECIPE_INPUT_TOKENS, MAX_RECIPE_SURROUNDING_TOKENS } from '../../prompt/constants'
import { truncateText, truncateTextStart } from '../../prompt/truncation'
import { getShortTimestamp } from '../../timestamp'
import { renderMarkdown } from '../markdown'
import { Interaction } from '../transcript/interaction'
import { getContextMessagesFromSelection, getNormalizedLanguageName, MARKDOWN_FORMAT_PROMPT } from './helpers'
@ -33,7 +32,7 @@ export class ExplainCodeDetailed implements Recipe {
const languageName = getNormalizedLanguageName(selection.fileName)
const promptMessage = `Please explain the following ${languageName} code. Be very detailed and specific, and indicate when it is not clear to you what is going on. Format your response as an ordered list.\n\`\`\`\n${truncatedSelectedText}\n\`\`\`\n${MARKDOWN_FORMAT_PROMPT}`
const displayText = renderMarkdown(`Explain the following code:\n\`\`\`\n${selection.selectedText}\n\`\`\``)
const displayText = `Explain the following code:\n\`\`\`\n${selection.selectedText}\n\`\`\``
return new Interaction(
{ speaker: 'human', text: promptMessage, displayText, timestamp },

View File

@ -4,7 +4,6 @@ import { IntentDetector } from '../../intent-detector'
import { MAX_RECIPE_INPUT_TOKENS, MAX_RECIPE_SURROUNDING_TOKENS } from '../../prompt/constants'
import { truncateText, truncateTextStart } from '../../prompt/truncation'
import { getShortTimestamp } from '../../timestamp'
import { renderMarkdown } from '../markdown'
import { Interaction } from '../transcript/interaction'
import { getContextMessagesFromSelection, getNormalizedLanguageName, MARKDOWN_FORMAT_PROMPT } from './helpers'
@ -33,9 +32,7 @@ export class ExplainCodeHighLevel implements Recipe {
const languageName = getNormalizedLanguageName(selection.fileName)
const promptMessage = `Explain the following ${languageName} code at a high level. Only include details that are essential to an overal understanding of what's happening in the code.\n\`\`\`\n${truncatedSelectedText}\n\`\`\`\n${MARKDOWN_FORMAT_PROMPT}`
const displayText = renderMarkdown(
`Explain the following code at a high level:\n\`\`\`\n${selection.selectedText}\n\`\`\``
)
const displayText = `Explain the following code at a high level:\n\`\`\`\n${selection.selectedText}\n\`\`\``
return new Interaction(
{ speaker: 'human', text: promptMessage, displayText, timestamp },

View File

@ -4,7 +4,6 @@ import { IntentDetector } from '../../intent-detector'
import { MAX_RECIPE_INPUT_TOKENS, MAX_RECIPE_SURROUNDING_TOKENS } from '../../prompt/constants'
import { truncateText, truncateTextStart } from '../../prompt/truncation'
import { getShortTimestamp } from '../../timestamp'
import { renderMarkdown } from '../markdown'
import { Interaction } from '../transcript/interaction'
import {
@ -56,9 +55,7 @@ export class GenerateDocstring implements Recipe {
docStart = '// '
}
const displayText = renderMarkdown(
`Generate documentation for the following code:\n\`\`\`\n${selection.selectedText}\n\`\`\``
)
const displayText = `Generate documentation for the following code:\n\`\`\`\n${selection.selectedText}\n\`\`\``
const assistantResponsePrefix = `Here is the generated documentation:\n\`\`\`${extension}\n${docStart}`
return new Interaction(

View File

@ -4,7 +4,6 @@ import { IntentDetector } from '../../intent-detector'
import { MAX_RECIPE_INPUT_TOKENS, MAX_RECIPE_SURROUNDING_TOKENS } from '../../prompt/constants'
import { truncateText, truncateTextStart } from '../../prompt/truncation'
import { getShortTimestamp } from '../../timestamp'
import { renderMarkdown } from '../markdown'
import { Interaction } from '../transcript/interaction'
import {
@ -41,9 +40,7 @@ export class GenerateTest implements Recipe {
const promptMessage = `Generate a unit test in ${languageName} for the following code:\n\`\`\`${extension}\n${truncatedSelectedText}\n\`\`\`\n${MARKDOWN_FORMAT_PROMPT}`
const assistantResponsePrefix = `Here is the generated unit test:\n\`\`\`${extension}\n`
const displayText = renderMarkdown(
`Generate a unit test for the following code:\n\`\`\`${extension}\n${selection.selectedText}\n\`\`\``
)
const displayText = `Generate a unit test for the following code:\n\`\`\`${extension}\n${selection.selectedText}\n\`\`\``
return new Interaction(
{ speaker: 'human', text: promptMessage, displayText, timestamp },

View File

@ -7,7 +7,6 @@ import { IntentDetector } from '../../intent-detector'
import { MAX_RECIPE_INPUT_TOKENS } from '../../prompt/constants'
import { truncateText } from '../../prompt/truncation'
import { getShortTimestamp } from '../../timestamp'
import { renderMarkdown } from '../markdown'
import { Interaction } from '../transcript/interaction'
import { Recipe } from './recipe'
@ -76,7 +75,7 @@ export class GitHistory implements Recipe {
const promptMessage = `Summarize these commits:\n${truncatedGitLogOutput}\n\nProvide your response in the form of a bulleted list. Do not mention the commit hashes.`
const assistantResponsePrefix = 'Here is a summary of recent changes:\n- '
return new Interaction(
{ speaker: 'human', text: promptMessage, displayText: renderMarkdown(rawDisplayText), timestamp },
{ speaker: 'human', text: promptMessage, displayText: rawDisplayText, timestamp },
{
speaker: 'assistant',
prefix: assistantResponsePrefix,

View File

@ -4,7 +4,6 @@ import { IntentDetector } from '../../intent-detector'
import { MAX_RECIPE_INPUT_TOKENS, MAX_RECIPE_SURROUNDING_TOKENS } from '../../prompt/constants'
import { truncateText, truncateTextStart } from '../../prompt/truncation'
import { getShortTimestamp } from '../../timestamp'
import { renderMarkdown } from '../markdown'
import { Interaction } from '../transcript/interaction'
import {
@ -37,9 +36,7 @@ export class ImproveVariableNames implements Recipe {
const truncatedFollowingText = truncateText(selection.followingText, MAX_RECIPE_SURROUNDING_TOKENS)
const extension = getFileExtension(selection.fileName)
const displayText = renderMarkdown(
`Improve the variable names in the following code:\n\`\`\`\n${selection.selectedText}\n\`\`\``
)
const displayText = `Improve the variable names in the following code:\n\`\`\`\n${selection.selectedText}\n\`\`\``
const languageName = getNormalizedLanguageName(selection.fileName)
const promptMessage = `Improve the variable names in this ${languageName} code by replacing the variable names with new identifiers which succinctly capture the purpose of the variable. We want the new code to be a drop-in replacement, so do not change names bound outside the scope of this code, like function names or members defined elsewhere. Only change the names of local variables and parameters:\n\n\`\`\`${extension}\n${truncatedSelectedText}\n\`\`\`\n${MARKDOWN_FORMAT_PROMPT}`

View File

@ -4,7 +4,6 @@ import { IntentDetector } from '../../intent-detector'
import { MAX_RECIPE_INPUT_TOKENS } from '../../prompt/constants'
import { truncateText } from '../../prompt/truncation'
import { getShortTimestamp } from '../../timestamp'
import { renderMarkdown } from '../markdown'
import { Interaction } from '../transcript/interaction'
import { languageMarkdownID, languageNames } from './langs'
@ -37,9 +36,7 @@ export class TranslateToLanguage implements Recipe {
const truncatedSelectedText = truncateText(selection.selectedText, MAX_RECIPE_INPUT_TOKENS)
const promptMessage = `Translate the following code into ${toLanguage}\n\`\`\`\n${truncatedSelectedText}\n\`\`\``
const displayText = renderMarkdown(
`Translate the following code into ${toLanguage}\n\`\`\`\n${selection.selectedText}\n\`\`\``
)
const displayText = `Translate the following code into ${toLanguage}\n\`\`\`\n${selection.selectedText}\n\`\`\``
const markdownID = languageMarkdownID[toLanguage] || ''
const assistantResponsePrefix = `Here is the code translated to ${toLanguage}:\n\`\`\`${markdownID}\n`

View File

@ -19,11 +19,11 @@ export class Transcript {
return this.interactions.length > 0 ? this.interactions[this.interactions.length - 1] : null
}
public addAssistantResponse(text: string, displayText: string): void {
public addAssistantResponse(text: string): void {
this.getLastInteraction()?.setAssistantMessage({
speaker: 'assistant',
text,
displayText,
displayText: text,
timestamp: getShortTimestamp(),
})
}

View File

@ -95,7 +95,7 @@ describe('Transcript', () => {
transcript.addInteraction(firstInteraction)
const assistantResponse = 'By setting the Authorization header.'
transcript.addAssistantResponse(assistantResponse, assistantResponse)
transcript.addAssistantResponse(assistantResponse)
const secondInteraction = await chatQuestionRecipe.getInteraction(
'how to create a batch change',
@ -135,7 +135,7 @@ describe('Transcript', () => {
transcript.addInteraction(interaction)
const assistantResponse = 'EFGH'.repeat(256) // 256 tokens
transcript.addAssistantResponse(assistantResponse, assistantResponse)
transcript.addAssistantResponse(assistantResponse)
}
const tokensPerInteraction = 512 // 256 for question + 256 for response.

View File

@ -8,4 +8,5 @@
},
"include": ["**/*", ".*"],
"exclude": ["out"],
"references": [{ "path": "../common" }],
}

View File

@ -1,7 +1,6 @@
import * as vscode from 'vscode'
import { ChatClient } from '@sourcegraph/cody-shared/src/chat/chat'
import { renderMarkdown } from '@sourcegraph/cody-shared/src/chat/markdown'
import { getRecipe } from '@sourcegraph/cody-shared/src/chat/recipes'
import { Transcript } from '@sourcegraph/cody-shared/src/chat/transcript'
import { ChatMessage } from '@sourcegraph/cody-shared/src/chat/transcript/messages'
@ -214,7 +213,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
}
private async onBotMessageChange(text: string): Promise<void> {
this.transcript.addAssistantResponse(text, renderMarkdown(text))
this.transcript.addAssistantResponse(text)
await this.sendTranscript()
}

View File

@ -79,8 +79,8 @@ suite('End-to-end', () => {
// Check the chat transcript contains markdown
const message = await getTranscript(api, 0)
assert.ok(message.displayText.startsWith('<p>Explain the following code'))
assert.ok(message.displayText.includes('<span class="hljs-keyword">public</span>'))
assert.match(message.displayText, /^Explain the following code/)
assert.match(message.displayText, /public/)
// Check the server response was handled
// "hello world" is a canned response from the server
@ -90,7 +90,7 @@ suite('End-to-end', () => {
return assistantMessage.displayText.length > 0
})
const assistantMessage = await getTranscript(api, 1)
assert.ok(assistantMessage.displayText.includes('hello, world'))
assert.match(assistantMessage.displayText, /hello, world/)
// Clean up.
await ensureExecuteCommand('cody.delete-access-token')

View File

@ -10,7 +10,7 @@ export function run(): Promise<void> {
color: true,
})
// To debug tests interactively, extend this timeout.
mocha.timeout(2000)
mocha.timeout(15000)
const testsRoot = __dirname

View File

@ -6,6 +6,8 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'
import { VSCodeButton, VSCodeTextArea } from '@vscode/webview-ui-toolkit/react'
import { renderMarkdown } from '@sourcegraph/cody-shared/src/chat/markdown'
import { Tips } from './Tips'
import { SubmitSvg } from './utils/icons'
import { ChatMessage } from './utils/types'
@ -118,7 +120,11 @@ export const Chat: React.FunctionComponent<React.PropsWithChildren<ChatboxProps>
className={`bubble-content ${bubbleClassName(message.speaker)}-bubble-content`}
>
{message.displayText && (
<p dangerouslySetInnerHTML={{ __html: message.displayText }} />
<p
dangerouslySetInnerHTML={{
__html: renderMarkdown(message.displayText),
}}
/>
)}
{message.contextFiles && message.contextFiles.length > 0 && (
<ContextFiles contextFiles={message.contextFiles} />
@ -138,7 +144,11 @@ export const Chat: React.FunctionComponent<React.PropsWithChildren<ChatboxProps>
<div className="bubble bot-bubble">
<div className="bubble-content bot-bubble-content">
{messageInProgress.displayText ? (
<p dangerouslySetInnerHTML={{ __html: messageInProgress.displayText }} />
<p
dangerouslySetInnerHTML={{
__html: renderMarkdown(messageInProgress.displayText),
}}
/>
) : (
<div className="bubble-loader">
<div className="bubble-loader-dot" />

View File

@ -69,40 +69,35 @@ describe('renderMarkdown', () => {
<td>B</td>
</tr>
</tbody></table>
<p><img src=\\"./src.jpg\\" alt=\\"image alt text\\" /></p>
"
<p><img alt=\\"image alt text\\" src=\\"./src.jpg\\"></p>"
`)
})
it('renders to plain text with plainText: true', () => {
expect(renderMarkdown('A **b**', { plainText: true })).toBe('A b\n')
expect(renderMarkdown('A **b**', { plainText: true })).toBe('A b')
})
it('sanitizes script tags', () => {
expect(renderMarkdown('<script>evil();</script>')).toBe('')
})
it('sanitizes event handlers', () => {
expect(renderMarkdown('<svg><rect onclick="evil()"></rect></svg>')).toBe('<p><svg><rect></rect></svg></p>\n')
expect(renderMarkdown('<b onclick="evil()">test</b></svg>')).toBe('<p><b>test</b></p>')
})
it('does not allow arbitrary <object> tags', () => {
expect(renderMarkdown('<object data="something"></object>')).toBe('<p></p>\n')
expect(renderMarkdown('<object data="something"></object>')).toBe('<p></p>')
})
it('drops SVG <object> tags', () => {
expect(renderMarkdown('<object data="something" type="image/svg+xml"></object>')).toBe('<p></p>\n')
expect(renderMarkdown('<object data="something" type="image/svg+xml"></object>')).toBe('<p></p>')
})
it('allows <svg> tags', () => {
it('forbids <svg> tags', () => {
const input =
'<svg viewbox="10 10 10 10" width="100"><rect x="37.5" y="7.5" width="675.0" height="16.875" fill="#e05d44" stroke="white" stroke-width="1"><title>/</title></rect></svg>'
expect(renderMarkdown(input)).toBe(`<p>${input}</p>\n`)
expect(renderMarkdown(input)).toBe('<p></p>')
})
describe('allowDataUriLinksAndDownloads option', () => {
const MARKDOWN_WITH_DOWNLOAD = '<a href="data:text/plain,foobar" download>D</a>\n[D2](data:text/plain,foobar)'
test('default disabled', () => {
expect(renderMarkdown(MARKDOWN_WITH_DOWNLOAD)).toBe('<p><a>D</a>\n<a>D2</a></p>\n')
})
test('enabled', () => {
expect(renderMarkdown(MARKDOWN_WITH_DOWNLOAD, { allowDataUriLinksAndDownloads: true })).toBe(
'<p><a href="data:text/plain,foobar" download>D</a>\n<a href="data:text/plain,foobar">D2</a></p>\n'
)
})
it('forbids rel and style attributes', () => {
const input = '<a href="/" rel="evil" style="foo:bar">Link</a><script>alert("x")</script>'
expect(renderMarkdown(input)).toBe('<p><a href="/">Link</a></p>')
})
test('forbids data URI links', () => {
const input = '<a href="data:text/plain,foobar" download>D</a>\n[D2](data:text/plain,foobar)'
expect(renderMarkdown(input)).toBe('<p><a download="">D</a>\n<a>D2</a></p>')
})
})

View File

@ -1,10 +1,8 @@
import DOMPurify, { Config as DOMPurifyConfig } from 'dompurify'
import { highlight, highlightAuto } from 'highlight.js/lib/core'
import { without } from 'lodash'
// This is the only file allowed to import this module, all other modules must use renderMarkdown() exported from here
// eslint-disable-next-line no-restricted-imports
import { marked } from 'marked'
import sanitize from 'sanitize-html'
import { Overwrite } from 'utility-types'
import { logger } from '../logger'
@ -44,15 +42,11 @@ export const highlightCodeSafe = (code: string, language?: string): string => {
}
}
const svgPresentationAttributes = ['fill', 'stroke', 'stroke-width'] as const
const ALL_VALUES_ALLOWED = [/.*/]
/**
* Renders the given markdown to HTML, highlighting code and sanitizing dangerous HTML.
* Can throw an exception on parse errors.
*
* @param markdown The markdown to render
* @param options Options object for passing additional parameters
*/
export const renderMarkdown = (
markdown: string,
@ -63,19 +57,9 @@ export const renderMarkdown = (
disableAutolinks?: boolean
renderer?: marked.Renderer
headerPrefix?: string
} & (
| {
/** Strip off any HTML and return a plain text string, useful for previews */
plainText: true
}
| {
/** Following options apply to non-plaintext output only. */
plainText?: false
/** Allow links to data: URIs and that download files. */
allowDataUriLinksAndDownloads?: boolean
}
) = {}
/** Strip off any HTML and return a plain text string, useful for previews */
plainText?: boolean
} = {}
): string => {
const tokenizer = new marked.Tokenizer()
if (options.disableAutolinks) {
@ -96,94 +80,18 @@ export const renderMarkdown = (
tokenizer,
})
let sanitizeOptions: Overwrite<sanitize.IOptions, sanitize.IDefaults>
if (options.plainText) {
sanitizeOptions = {
...sanitize.defaults,
allowedAttributes: {},
allowedSchemes: [],
allowedSchemesByTag: {},
allowedTags: [],
selfClosing: [],
}
} else {
sanitizeOptions = {
...sanitize.defaults,
// Ensure <object> must have type attribute set
exclusiveFilter: ({ tag, attribs }) => tag === 'object' && !attribs.type,
const dompurifyConfig: DOMPurifyConfig & { RETURN_DOM_FRAGMENT?: false; RETURN_DOM?: false } = options.plainText
? {
ALLOWED_TAGS: [],
ALLOWED_ATTR: [],
KEEP_CONTENT: true,
}
: {
USE_PROFILES: { html: true },
FORBID_ATTR: ['rel', 'style'],
}
allowedTags: [
...without(sanitize.defaults.allowedTags, 'iframe'),
'img',
'picture',
'source',
'svg',
'rect',
'defs',
'pattern',
'mask',
'circle',
'path',
'title',
],
allowedAttributes: {
...sanitize.defaults.allowedAttributes,
a: [
...sanitize.defaults.allowedAttributes.a,
'title',
'class',
{ name: 'rel', values: ['noopener', 'noreferrer'] },
],
img: [...sanitize.defaults.allowedAttributes.img, 'alt', 'width', 'height', 'align', 'style'],
// Support different images depending on media queries (e.g. color theme, reduced motion)
source: ['srcset', 'media'],
svg: ['width', 'height', 'viewbox', 'version', 'preserveaspectratio', 'style'],
rect: ['x', 'y', 'width', 'height', 'transform', ...svgPresentationAttributes],
path: ['d', ...svgPresentationAttributes],
circle: ['cx', 'cy', ...svgPresentationAttributes],
pattern: ['id', 'width', 'height', 'patternunits', 'patterntransform'],
mask: ['id'],
// Allow highligh.js styles, e.g.
// <span class="hljs-keyword">
// <code class="language-javascript">
span: ['class'],
code: ['class'],
// Support deep-linking
h1: ['id'],
h2: ['id'],
h3: ['id'],
h4: ['id'],
h5: ['id'],
h6: ['id'],
},
allowedStyles: {
img: {
padding: ALL_VALUES_ALLOWED,
'padding-left': ALL_VALUES_ALLOWED,
'padding-right': ALL_VALUES_ALLOWED,
'padding-top': ALL_VALUES_ALLOWED,
'padding-bottom': ALL_VALUES_ALLOWED,
},
// SVGs are usually for charts in code insights.
// Allow them to be responsive.
svg: {
flex: ALL_VALUES_ALLOWED,
'flex-grow': ALL_VALUES_ALLOWED,
'flex-shrink': ALL_VALUES_ALLOWED,
'flex-basis': ALL_VALUES_ALLOWED,
},
},
}
if (options.allowDataUriLinksAndDownloads) {
sanitizeOptions.allowedAttributes.a = [...sanitizeOptions.allowedAttributes.a, 'download']
sanitizeOptions.allowedSchemesByTag = {
...sanitizeOptions.allowedSchemesByTag,
a: [...(sanitizeOptions.allowedSchemesByTag.a || sanitizeOptions.allowedSchemes), 'data'],
}
}
}
return sanitize(rendered, sanitizeOptions)
return DOMPurify.sanitize(rendered, dompurifyConfig).trim()
}
export const markdownLexer = (markdown: string): marked.TokensList => marked.lexer(markdown)

View File

@ -98,8 +98,6 @@ exports[`HoverOverlay actions and hover present 1`] = `
<p>
v
</p>
</span>
</div>
<div
@ -164,8 +162,6 @@ exports[`HoverOverlay actions error, hover present 1`] = `
<p>
v
</p>
</span>
</div>
<div
@ -388,8 +384,6 @@ exports[`HoverOverlay hover present 1`] = `
<p>
v
</p>
</span>
</div>
<div
@ -417,8 +411,6 @@ exports[`HoverOverlay hover present, actions loading 1`] = `
<p>
v
</p>
</span>
</div>
<div
@ -446,8 +438,6 @@ exports[`HoverOverlay multiple hovers present 1`] = `
<p>
v
</p>
</span>
<hr />
<span
@ -457,8 +447,6 @@ exports[`HoverOverlay multiple hovers present 1`] = `
<p>
v2
</p>
</span>
</div>
<div

View File

@ -19,8 +19,6 @@ exports[`Notices shows notices for location 1`] = `
<p>
a
</p>
</div>
</div>
<div
@ -37,8 +35,6 @@ exports[`Notices shows notices for location 1`] = `
<p>
a
</p>
</div>
</div>
<button

View File

@ -171,6 +171,7 @@
"@types/d3-shape": "^1.3.1",
"@types/d3-time-format": "3.0.0",
"@types/d3-voronoi": "^1.1.9",
"@types/dompurify": "^3.0.0",
"@types/escape-html": "^1.0.1",
"@types/express": "4.17.11",
"@types/glob": "7.1.3",
@ -208,7 +209,6 @@
"@types/recharts": "1.8.23",
"@types/resize-observer-browser": "0.1.4",
"@types/rimraf": "^3.0.2",
"@types/sanitize-html": "2.3.0",
"@types/semver": "7.3.1",
"@types/shelljs": "0.8.8",
"@types/signale": "1.4.1",
@ -414,6 +414,7 @@
"d3-voronoi": "^1.1.2",
"date-fns": "^2.16.1",
"delay": "^4.4.1",
"dompurify": "^3.0.1",
"downshift": "^3.4.8",
"escape-html": "^1.0.3",
"eventsource": "1.1.1",
@ -470,7 +471,6 @@
"regexpp": "^3.1.0",
"resize-observer-polyfill": "^1.5.1",
"rxjs": "^6.6.3",
"sanitize-html": "^2.0.0",
"semver": "^7.3.2",
"stopword": "^2.0.7",
"stream-http": "^3.2.0",

View File

@ -131,6 +131,7 @@ importers:
'@types/d3-shape': ^1.3.1
'@types/d3-time-format': 3.0.0
'@types/d3-voronoi': ^1.1.9
'@types/dompurify': ^3.0.0
'@types/escape-html': ^1.0.1
'@types/express': 4.17.11
'@types/glob': 7.1.3
@ -168,7 +169,6 @@ importers:
'@types/recharts': 1.8.23
'@types/resize-observer-browser': 0.1.4
'@types/rimraf': ^3.0.2
'@types/sanitize-html': 2.3.0
'@types/semver': 7.3.1
'@types/shelljs': 0.8.8
'@types/signale': 1.4.1
@ -241,6 +241,7 @@ importers:
d3-voronoi: ^1.1.2
date-fns: ^2.16.1
delay: ^4.4.1
dompurify: ^3.0.1
downshift: ^3.4.8
envalid: ^7.3.1
esbuild: ^0.17.14
@ -351,7 +352,6 @@ importers:
resize-observer-polyfill: ^1.5.1
rimraf: ^3.0.2
rxjs: ^6.6.3
sanitize-html: ^2.0.0
sass: ^1.32.4
sass-loader: ^13.2.0
semver: ^7.3.2
@ -480,6 +480,7 @@ importers:
d3-voronoi: 1.1.4
date-fns: 2.29.3
delay: 4.4.1
dompurify: 3.0.1
downshift: 3.4.8_react@18.1.0
escape-html: 1.0.3
eventsource: 1.1.1
@ -536,7 +537,6 @@ importers:
regexpp: 3.2.0
resize-observer-polyfill: 1.5.1
rxjs: 6.6.7
sanitize-html: 2.3.3
semver: 7.3.8
stopword: 2.0.7
stream-http: 3.2.0
@ -636,6 +636,7 @@ importers:
'@types/d3-shape': 1.3.2
'@types/d3-time-format': 3.0.0
'@types/d3-voronoi': 1.1.9
'@types/dompurify': 3.0.0
'@types/escape-html': 1.0.1
'@types/express': 4.17.11
'@types/glob': 7.1.3
@ -673,7 +674,6 @@ importers:
'@types/recharts': 1.8.23
'@types/resize-observer-browser': 0.1.4
'@types/rimraf': 3.0.2
'@types/sanitize-html': 2.3.0
'@types/semver': 7.3.1
'@types/shelljs': 0.8.8
'@types/signale': 1.4.1
@ -924,7 +924,10 @@ importers:
openai: 3.2.1
client/cody-shared:
specifiers: {}
specifiers:
'@sourcegraph/common': workspace:*
dependencies:
'@sourcegraph/common': link:../common
client/common:
specifiers:
@ -3196,7 +3199,6 @@ packages:
cpu: [arm]
os: [android]
requiresBuild: true
dev: false
optional: true
/@esbuild/android-arm64/0.16.17:
@ -3222,7 +3224,6 @@ packages:
cpu: [arm64]
os: [android]
requiresBuild: true
dev: false
optional: true
/@esbuild/android-x64/0.16.17:
@ -3248,7 +3249,6 @@ packages:
cpu: [x64]
os: [android]
requiresBuild: true
dev: false
optional: true
/@esbuild/darwin-arm64/0.16.17:
@ -3274,7 +3274,6 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@esbuild/darwin-x64/0.16.17:
@ -3300,7 +3299,6 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: false
optional: true
/@esbuild/freebsd-arm64/0.16.17:
@ -3326,7 +3324,6 @@ packages:
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: false
optional: true
/@esbuild/freebsd-x64/0.16.17:
@ -3352,7 +3349,6 @@ packages:
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-arm/0.16.17:
@ -3378,7 +3374,6 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-arm64/0.16.17:
@ -3404,7 +3399,6 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-ia32/0.16.17:
@ -3430,7 +3424,6 @@ packages:
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-loong64/0.16.17:
@ -3456,7 +3449,6 @@ packages:
cpu: [loong64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-mips64el/0.16.17:
@ -3482,7 +3474,6 @@ packages:
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-ppc64/0.16.17:
@ -3508,7 +3499,6 @@ packages:
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-riscv64/0.16.17:
@ -3534,7 +3524,6 @@ packages:
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-s390x/0.16.17:
@ -3560,7 +3549,6 @@ packages:
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/linux-x64/0.16.17:
@ -3586,7 +3574,6 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: false
optional: true
/@esbuild/netbsd-x64/0.16.17:
@ -3612,7 +3599,6 @@ packages:
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: false
optional: true
/@esbuild/openbsd-x64/0.16.17:
@ -3638,7 +3624,6 @@ packages:
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: false
optional: true
/@esbuild/sunos-x64/0.16.17:
@ -3664,7 +3649,6 @@ packages:
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: false
optional: true
/@esbuild/win32-arm64/0.16.17:
@ -3690,7 +3674,6 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@esbuild/win32-ia32/0.16.17:
@ -3716,7 +3699,6 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@esbuild/win32-x64/0.16.17:
@ -3742,7 +3724,6 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
dev: false
optional: true
/@eslint/eslintrc/1.4.1:
@ -4835,8 +4816,8 @@ packages:
resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
dependencies:
'@types/istanbul-lib-coverage': 2.0.1
'@types/istanbul-reports': 3.0.0
'@types/istanbul-lib-coverage': 2.0.4
'@types/istanbul-reports': 3.0.1
'@types/node': 13.13.5
'@types/yargs': 16.0.4
chalk: 4.1.2
@ -8854,6 +8835,12 @@ packages:
/@types/d3-voronoi/1.1.9:
resolution: {integrity: sha512-DExNQkaHd1F3dFPvGA/Aw2NGyjMln6E9QzsiqOcBgnE+VInYnFBHBBySbZQts6z6xD+5jTfKCP7M4OqMyVjdwQ==}
/@types/dompurify/3.0.0:
resolution: {integrity: sha512-EcSqmgm/xJwH8CcJPy9AHNypp/j58CYga3nWdl93/wLxX6OH+rSD3aAj75NQazcZd1YKHJ/pjNZ9qmgVajggwQ==}
dependencies:
'@types/trusted-types': 2.0.3
dev: true
/@types/escape-html/1.0.1:
resolution: {integrity: sha512-4mI1FuUUZiuT95fSVqvZxp/ssQK9zsa86S43h9x3zPOSU9BBJ+BfDkXwuaU7BfsD+e7U0/cUUfJFk3iW2M4okA==}
dev: true
@ -8999,7 +8986,6 @@ packages:
/@types/istanbul-lib-coverage/2.0.4:
resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==}
dev: false
/@types/istanbul-lib-report/1.1.1:
resolution: {integrity: sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==}
@ -9010,7 +8996,6 @@ packages:
resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==}
dependencies:
'@types/istanbul-lib-coverage': 2.0.4
dev: false
/@types/istanbul-reports/3.0.0:
resolution: {integrity: sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==}
@ -9021,7 +9006,6 @@ packages:
resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==}
dependencies:
'@types/istanbul-lib-report': 3.0.0
dev: false
/@types/jest-image-snapshot/4.3.0:
resolution: {integrity: sha512-gb6zF1ICfvzBsQYMTq2qFhhiI46Cab/t5PtLK4Z3mpbyQoyKI2HgCFDi71iE7rjs6TrIPnsf2GXw+mnGvZSgrA==}
@ -9341,12 +9325,6 @@ packages:
'@types/node': 13.13.5
dev: true
/@types/sanitize-html/2.3.0:
resolution: {integrity: sha512-q+Xg5t8Yn0KeomXMyVMoxtKyvh2u1ywkPqFlMy/5luF8D+DN+HhFN9pesJ6BsuoLuDCukR8p922KkCZnkTHOpg==}
dependencies:
htmlparser2: 6.1.0
dev: true
/@types/sass/1.16.1:
resolution: {integrity: sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ==}
dependencies:
@ -9478,6 +9456,10 @@ packages:
/@types/tough-cookie/2.3.5:
resolution: {integrity: sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==}
/@types/trusted-types/2.0.3:
resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==}
dev: true
/@types/undertaker-registry/1.0.1:
resolution: {integrity: sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==}
dev: true
@ -9566,7 +9548,6 @@ packages:
/@types/yargs-parser/21.0.0:
resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
dev: false
/@types/yargs/15.0.3:
resolution: {integrity: sha512-XCMQRK6kfpNBixHLyHUsGmXrpEmFFxzMrcnSXFMziHd8CoNJo8l16FkHyQq4x+xbM7E2XL83/O78OD8u+iZTdQ==}
@ -9576,7 +9557,7 @@ packages:
/@types/yargs/16.0.4:
resolution: {integrity: sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==}
dependencies:
'@types/yargs-parser': 13.0.0
'@types/yargs-parser': 21.0.0
/@types/yargs/17.0.16:
resolution: {integrity: sha512-Mh3OP0oh8X7O7F9m5AplC+XHYLBWuPKNkGVD3gIZFLFebBnuFI2Nz5Sf8WLvwGxECJ8YjifQvFdh79ubODkdug==}
@ -13016,7 +12997,7 @@ packages:
postcss-modules-values: 4.0.0_postcss@8.4.21
postcss-value-parser: 4.2.0
semver: 7.3.8
webpack: 5.75.0_pdcrf7mb3dfag2zju4x4octu4a
webpack: 5.75.0_esbuild@0.17.8
/css-minimizer-webpack-plugin/4.2.2_zj7shrtzhjuywytipisjis56au:
resolution: {integrity: sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==}
@ -14255,6 +14236,7 @@ packages:
domelementtype: 2.3.0
domhandler: 4.2.0
entities: 2.1.0
dev: true
/dom-serializer/2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
@ -14273,6 +14255,7 @@ packages:
/domelementtype/2.3.0:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
dev: true
/domexception/2.0.1:
resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==}
@ -14293,6 +14276,7 @@ packages:
engines: {node: '>= 4'}
dependencies:
domelementtype: 2.3.0
dev: true
/domhandler/5.0.3:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
@ -14305,6 +14289,10 @@ packages:
resolution: {integrity: sha512-dqnqRkPMAjOZE0FogZ+ceJNM2dZ3V/yNOuFB7+39qpO93hHhfRpHw3heYQC7DPK9FqbQTfBKUJhiSfz4MvXYwg==}
dev: true
/dompurify/3.0.1:
resolution: {integrity: sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw==}
dev: false
/domutils/1.7.0:
resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==}
dependencies:
@ -14318,6 +14306,7 @@ packages:
dom-serializer: 1.3.2
domelementtype: 2.3.0
domhandler: 4.2.0
dev: true
/domutils/3.0.1:
resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==}
@ -14793,7 +14782,6 @@ packages:
'@esbuild/win32-arm64': 0.17.8
'@esbuild/win32-ia32': 0.17.8
'@esbuild/win32-x64': 0.17.8
dev: false
/escalade/3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
@ -16083,7 +16071,7 @@ packages:
/fs-extra/0.30.0:
resolution: {integrity: sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==}
dependencies:
graceful-fs: 4.2.10
graceful-fs: 4.2.11
jsonfile: 2.4.0
klaw: 1.3.1
path-is-absolute: 1.0.1
@ -17348,6 +17336,7 @@ packages:
domhandler: 4.2.0
domutils: 2.8.0
entities: 2.1.0
dev: true
/htmlparser2/8.0.1:
resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
@ -18142,6 +18131,7 @@ packages:
/is-plain-object/5.0.0:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
dev: true
/is-potential-custom-element-name/1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
@ -22266,10 +22256,6 @@ packages:
semver: 5.7.1
dev: true
/parse-srcset/1.0.2:
resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
dev: false
/parse5-htmlparser2-tree-adapter/7.0.0:
resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
dependencies:
@ -22729,7 +22715,7 @@ packages:
klona: 2.0.5
postcss: 8.4.21
semver: 7.3.8
webpack: 5.75.0_pdcrf7mb3dfag2zju4x4octu4a
webpack: 5.75.0_esbuild@0.17.8
/postcss-media-query-parser/0.2.3:
resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==}
@ -24930,18 +24916,6 @@ packages:
- supports-color
dev: true
/sanitize-html/2.3.3:
resolution: {integrity: sha512-DCFXPt7Di0c6JUnlT90eIgrjs6TsJl/8HYU3KLdmrVclFN4O0heTcVbJiMa23OKVr6aR051XYtsgd8EWwEBwUA==}
dependencies:
deepmerge: 4.2.2
escape-string-regexp: 4.0.0
htmlparser2: 6.1.0
is-plain-object: 5.0.0
klona: 2.0.5
parse-srcset: 1.0.2
postcss: 8.4.21
dev: false
/sass-loader/13.2.0_sass@1.32.4+webpack@5.75.0:
resolution: {integrity: sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==}
engines: {node: '>= 14.15.0'}
@ -24964,7 +24938,7 @@ packages:
klona: 2.0.5
neo-async: 2.6.2
sass: 1.32.4
webpack: 5.75.0_pdcrf7mb3dfag2zju4x4octu4a
webpack: 5.75.0_esbuild@0.17.8
/sass/1.32.4:
resolution: {integrity: sha512-N0BT0PI/t3+gD8jKa83zJJUb7ssfQnRRfqN+GIErokW6U4guBpfYl8qYB+OFLEho+QvnV5ZH1R9qhUC/Z2Ch9w==}
@ -26112,7 +26086,7 @@ packages:
peerDependencies:
webpack: ^5.0.0
dependencies:
webpack: 5.75.0_pdcrf7mb3dfag2zju4x4octu4a
webpack: 5.75.0_esbuild@0.17.8
/style-mod/4.0.0:
resolution: {integrity: sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==}
@ -26657,7 +26631,6 @@ packages:
serialize-javascript: 6.0.0
terser: 5.16.1
webpack: 5.75.0_esbuild@0.17.8
dev: false
/terser-webpack-plugin/5.3.6_zj7shrtzhjuywytipisjis56au:
resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==}
@ -28475,7 +28448,6 @@ packages:
- '@swc/core'
- esbuild
- uglify-js
dev: false
/webpack/5.75.0_pdcrf7mb3dfag2zju4x4octu4a:
resolution: {integrity: sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==}

View File

@ -1102,7 +1102,6 @@ Yarn,rxjs,6.6.7,Apache 2.0,https://github.com/ReactiveX/RxJS,Approved
Yarn,safe-buffer,5.1.2,MIT,https://github.com/feross/safe-buffer,Approved
Yarn,safe-buffer,5.2.1,MIT,https://github.com/feross/safe-buffer,Approved
Yarn,safer-buffer,2.1.2,MIT,https://github.com/ChALkeR,Approved
Yarn,sanitize-html,2.3.3,MIT,"",Approved
Yarn,scheduler,0.22.0,MIT,https://reactjs.org/,Approved
Yarn,schema-utils,2.7.0,MIT,https://github.com/webpack/schema-utils,Approved
Yarn,schema-utils,3.1.0,MIT,https://github.com/webpack/schema-utils,Approved

1 package_manager name version licenses homepage approved
1102 Yarn safe-buffer 5.1.2 MIT https://github.com/feross/safe-buffer Approved
1103 Yarn safe-buffer 5.2.1 MIT https://github.com/feross/safe-buffer Approved
1104 Yarn safer-buffer 2.1.2 MIT https://github.com/ChALkeR Approved
Yarn sanitize-html 2.3.3 MIT Approved
1105 Yarn scheduler 0.22.0 MIT https://reactjs.org/ Approved
1106 Yarn schema-utils 2.7.0 MIT https://github.com/webpack/schema-utils Approved
1107 Yarn schema-utils 3.1.0 MIT https://github.com/webpack/schema-utils Approved