diff --git a/src/runtime/nitro/plugins/02-cspSsg.ts b/src/runtime/nitro/plugins/02-cspSsg.ts index 667da99a..2fcfb6a3 100644 --- a/src/runtime/nitro/plugins/02-cspSsg.ts +++ b/src/runtime/nitro/plugins/02-cspSsg.ts @@ -23,8 +23,8 @@ export default defineNitroPlugin((nitroApp) => { return } - const scriptHashes: string[] = [] - const styleHashes: string[] = [] + const scriptHashes: Set = new Set() + const styleHashes: Set = new Set() const hashAlgorithm = 'sha256' // Scan all relevant sections of the NuxtRenderHtmlContext @@ -42,10 +42,10 @@ export default defineNitroPlugin((nitroApp) => { const integrity = scriptAttrs?.integrity if (!src && scriptText) { // Hash inline scripts with content - scriptHashes.push(generateHash(scriptText, hashAlgorithm)) + scriptHashes.add(generateHash(scriptText, hashAlgorithm)) } else if (src && integrity) { // Whitelist external scripts with integrity - scriptHashes.push(`'${integrity}'`) + scriptHashes.add(`'${integrity}'`) } }) @@ -54,7 +54,41 @@ export default defineNitroPlugin((nitroApp) => { const styleText = $(style).text() if (styleText) { // Hash inline styles with content - styleHashes.push(generateHash(styleText, hashAlgorithm)) + styleHashes.add(generateHash(styleText, hashAlgorithm)) + } + }) + + // Parse all link tags + $('link').each((i, link) => { + const linkAttrs = $(link).attr() + const integrity = linkAttrs?.integrity + // Whitelist links to external resources with integrity + if (integrity) { + const rel = linkAttrs?.rel + // HTML standard defines only 3 rel values for valid integrity attributes on links : stylesheet, preload and modulepreload + // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-integrity + if (rel === 'stylesheet') { + // style: add to style-src + styleHashes.add(`'${integrity}'`) + } else if (rel === 'preload') { + // Fetch standard defines the destination (https://fetch.spec.whatwg.org/#destination-table) + // This table is the official mapping between HTML and CSP + // We only support script-src for now, but we could populate other policies in the future + const as = linkAttrs.as + switch (as) { + case 'script': + case 'audioworklet': + case 'paintworklet': + case 'xlst': + scriptHashes.add(`'${integrity}'`) + break + default: + break + } + } else if (rel === 'modulepreload') { + // script is the default and only possible destination + scriptHashes.add(`'${integrity}'`) + } } }) } @@ -73,7 +107,7 @@ export default defineNitroPlugin((nitroApp) => { }) // Insert hashes in the CSP meta tag for both the script-src and the style-src policies - function generateCspMetaTag (policies: ContentSecurityPolicyValue, scriptHashes: string[], styleHashes: string[]) { + function generateCspMetaTag (policies: ContentSecurityPolicyValue, scriptHashes: Set, styleHashes: Set) { const unsupportedPolicies:Record = { 'frame-ancestors': true, 'report-uri': true, @@ -81,13 +115,14 @@ export default defineNitroPlugin((nitroApp) => { } const tagPolicies = defu(policies) as ContentSecurityPolicyValue - if (scriptHashes.length > 0 && moduleOptions.ssg?.hashScripts) { + if (scriptHashes.size > 0 && moduleOptions.ssg?.hashScripts) { // Remove '""' - tagPolicies['script-src'] = (tagPolicies['script-src'] ?? []).concat(scriptHashes) + tagPolicies['script-src'] = (tagPolicies['script-src'] ?? []).concat(...scriptHashes) } - if (styleHashes.length > 0 && moduleOptions.ssg?.hashStyles) { + + if (styleHashes.size > 0 && moduleOptions.ssg?.hashStyles) { // Remove '""' - tagPolicies['style-src'] = (tagPolicies['style-src'] ?? []).concat(styleHashes) + tagPolicies['style-src'] = (tagPolicies['style-src'] ?? []).concat(...styleHashes) } const contentArray: string[] = [] diff --git a/test/fixtures/ssg/.nuxtrc b/test/fixtures/ssg/.nuxtrc new file mode 100644 index 00000000..3c8c6a11 --- /dev/null +++ b/test/fixtures/ssg/.nuxtrc @@ -0,0 +1 @@ +imports.autoImport=true \ No newline at end of file diff --git a/test/fixtures/ssg/nuxt.config.ts b/test/fixtures/ssg/nuxt.config.ts new file mode 100644 index 00000000..9077d555 --- /dev/null +++ b/test/fixtures/ssg/nuxt.config.ts @@ -0,0 +1,42 @@ +import { defineNuxtConfig } from 'nuxt/config' + +export default defineNuxtConfig({ + + modules: ['../../../src/module'], + + // Per route configuration + routeRules: { + '/': { + prerender: true + }, + '/inline-script': { + prerender: true, + }, + '/inline-style': { + prerender: true + }, + '/external-script': { + prerender: true + }, + '/external-style': { + prerender: true + }, + '/external-link': { + prerender: true + }, + '/not-ssg': { + prerender: false + } + }, + + // Global configuration + security: { + rateLimiter: false, + sri: true, + ssg: { + hashScripts: true, + hashStyles: true + } + }, + +}) diff --git a/test/fixtures/ssg/package.json b/test/fixtures/ssg/package.json new file mode 100644 index 00000000..decd4334 --- /dev/null +++ b/test/fixtures/ssg/package.json @@ -0,0 +1,5 @@ +{ + "private": true, + "name": "basic", + "type": "module" +} diff --git a/test/fixtures/ssg/pages/external-link.vue b/test/fixtures/ssg/pages/external-link.vue new file mode 100644 index 00000000..f166d1a9 --- /dev/null +++ b/test/fixtures/ssg/pages/external-link.vue @@ -0,0 +1,12 @@ + + diff --git a/test/fixtures/ssg/pages/external-script.vue b/test/fixtures/ssg/pages/external-script.vue new file mode 100644 index 00000000..c2f4c53b --- /dev/null +++ b/test/fixtures/ssg/pages/external-script.vue @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/test/fixtures/ssg/pages/external-style.vue b/test/fixtures/ssg/pages/external-style.vue new file mode 100644 index 00000000..87e68fd3 --- /dev/null +++ b/test/fixtures/ssg/pages/external-style.vue @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/test/fixtures/ssg/pages/index.vue b/test/fixtures/ssg/pages/index.vue new file mode 100644 index 00000000..434562b0 --- /dev/null +++ b/test/fixtures/ssg/pages/index.vue @@ -0,0 +1,5 @@ + diff --git a/test/fixtures/ssg/pages/inline-script.vue b/test/fixtures/ssg/pages/inline-script.vue new file mode 100644 index 00000000..2b05802b --- /dev/null +++ b/test/fixtures/ssg/pages/inline-script.vue @@ -0,0 +1,12 @@ + + diff --git a/test/fixtures/ssg/pages/inline-style.vue b/test/fixtures/ssg/pages/inline-style.vue new file mode 100644 index 00000000..52e7746e --- /dev/null +++ b/test/fixtures/ssg/pages/inline-style.vue @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/test/fixtures/ssg/pages/not-ssg.vue b/test/fixtures/ssg/pages/not-ssg.vue new file mode 100644 index 00000000..bc027c81 --- /dev/null +++ b/test/fixtures/ssg/pages/not-ssg.vue @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/test/fixtures/ssg/public/external.css b/test/fixtures/ssg/public/external.css new file mode 100644 index 00000000..c4f8827f --- /dev/null +++ b/test/fixtures/ssg/public/external.css @@ -0,0 +1,3 @@ +div { + color: red; +} \ No newline at end of file diff --git a/test/fixtures/ssg/public/external.js b/test/fixtures/ssg/public/external.js new file mode 100644 index 00000000..648e422a --- /dev/null +++ b/test/fixtures/ssg/public/external.js @@ -0,0 +1 @@ +console.log('Hello World') \ No newline at end of file diff --git a/test/fixtures/ssg/public/icon.png b/test/fixtures/ssg/public/icon.png new file mode 100644 index 00000000..dbbf391c Binary files /dev/null and b/test/fixtures/ssg/public/icon.png differ diff --git a/test/ssg.test.ts b/test/ssg.test.ts new file mode 100644 index 00000000..27261894 --- /dev/null +++ b/test/ssg.test.ts @@ -0,0 +1,160 @@ +import { fileURLToPath } from 'node:url' +import { describe, it, expect } from 'vitest' +import { setup, fetch } from '@nuxt/test-utils' + +describe('[nuxt-security] SSG support of CSP', async () => { + await setup({ + rootDir: fileURLToPath(new URL('./fixtures/ssg', import.meta.url)) + }) + + const expectedIntegrityAttributes = 6 // 5 links (entry, index, error-404, vue, error-500), 1 script (entry) + const expectedInlineScriptHashes = 2 // 1 Hydration data, 1 Nuxt global + const expectedExternalScriptHashes = 2 // 1 entry (modulepreload + script), 1 index (modulepreload) + const expectedInlineStyleHashes = 0 + const expectedExternalStyleHashes = 0 + + function extractDataFromBody(body: string) { + const elementsWithIntegrity = body.match(/ integrity="sha384-/g)?.length ?? 0 + const metaTag = body.match(//) + const csp = metaTag?.[1] + const policies = csp?.split(';').map(policy => policy.trimStart()) || [] + const scriptSrcPolicy = policies.find(policy => policy.startsWith('script-src ')) + const scriptSrcValues = scriptSrcPolicy?.split(' ') || [] + const inlineScriptHashes = scriptSrcValues.filter(value => value.startsWith("'sha256-")).length + const externalScriptHashes = scriptSrcValues.filter(value => value.startsWith("'sha384-")).length + const styleSrcPolicy = policies.find(policy => policy.startsWith('style-src ')) + const styleSrcValues = styleSrcPolicy?.split(' ') || [] + const inlineStyleHashes = styleSrcValues.filter(value => value.startsWith("'sha256-")).length + const externalStyleHashes = styleSrcValues.filter(value => value.startsWith("'sha384-")).length + return { metaTag, csp, elementsWithIntegrity, inlineScriptHashes, externalScriptHashes, inlineStyleHashes, externalStyleHashes } + } + + it('sets CSP in SSG mode', async () => { + const res = await fetch('/') + + const body = await res.text() + const { metaTag, csp, elementsWithIntegrity, inlineScriptHashes, externalScriptHashes, inlineStyleHashes, externalStyleHashes } = extractDataFromBody(body) + + expect(res).toBeDefined() + expect(res).toBeTruthy() + expect(body).toBeDefined() + expect(metaTag).toBeDefined() + expect(csp).toBeDefined() + expect(elementsWithIntegrity).toBe(expectedIntegrityAttributes) + expect(inlineScriptHashes).toBe(expectedInlineScriptHashes) + expect(externalScriptHashes).toBe(expectedExternalScriptHashes) + expect(inlineStyleHashes).toBe(expectedInlineStyleHashes) + expect(externalStyleHashes).toBe(expectedExternalStyleHashes) + }) + + it('sets script-src for inline scripts', async () => { + const res = await fetch('/inline-script') + + const body = await res.text() + const { metaTag, csp, elementsWithIntegrity, inlineScriptHashes, externalScriptHashes, inlineStyleHashes, externalStyleHashes } = extractDataFromBody(body) + + expect(res).toBeDefined() + expect(res).toBeTruthy() + expect(body).toBeDefined() + expect(metaTag).toBeDefined() + expect(csp).toBeDefined() + expect(elementsWithIntegrity).toBe(expectedIntegrityAttributes) + expect(inlineScriptHashes).toBe(expectedInlineScriptHashes + 1) // Inlined script in head + expect(externalScriptHashes).toBe(expectedExternalScriptHashes + 1) // + 1 vue modulepreload + expect(inlineStyleHashes).toBe(expectedInlineStyleHashes) + expect(externalStyleHashes).toBe(expectedExternalStyleHashes) + }) + + it('sets style-src for inline styles', async () => { + const res = await fetch('/inline-style') + + const body = await res.text() + const { metaTag, csp, elementsWithIntegrity, inlineScriptHashes, externalScriptHashes, inlineStyleHashes, externalStyleHashes } = extractDataFromBody(body) + + expect(res).toBeDefined() + expect(res).toBeTruthy() + expect(body).toBeDefined() + expect(metaTag).toBeDefined() + expect(csp).toBeDefined() + expect(elementsWithIntegrity).toBe(expectedIntegrityAttributes) + expect(inlineScriptHashes).toBe(expectedInlineScriptHashes) + expect(externalScriptHashes).toBe(expectedExternalScriptHashes + 1) // + 1 vue modulepreload + expect(inlineStyleHashes).toBe(expectedInlineStyleHashes + 1) // Inlined style + expect(externalStyleHashes).toBe(expectedExternalStyleHashes) + }) + + + it('sets script-src for external scripts', async () => { + const res = await fetch('/external-script') + + const body = await res.text() + const { metaTag, csp, elementsWithIntegrity, inlineScriptHashes, externalScriptHashes, inlineStyleHashes, externalStyleHashes } = extractDataFromBody(body) + + expect(res).toBeDefined() + expect(res).toBeTruthy() + expect(body).toBeDefined() + expect(metaTag).toBeDefined() + expect(csp).toBeDefined() + expect(elementsWithIntegrity).toBe(expectedIntegrityAttributes + 1) // External script + expect(inlineScriptHashes).toBe(expectedInlineScriptHashes) + expect(externalScriptHashes).toBe(expectedExternalScriptHashes + 2) // External script + 1 vue modulepreload + expect(inlineStyleHashes).toBe(expectedInlineStyleHashes) + expect(externalStyleHashes).toBe(expectedExternalStyleHashes) + }) + + it('sets style-src for external stylesheets', async () => { + const res = await fetch('/external-style') + + const body = await res.text() + const { metaTag, csp, elementsWithIntegrity, inlineScriptHashes, externalScriptHashes, inlineStyleHashes, externalStyleHashes } = extractDataFromBody(body) + + + expect(res).toBeDefined() + expect(res).toBeTruthy() + expect(body).toBeDefined() + expect(metaTag).toBeDefined() + expect(csp).toBeDefined() + expect(elementsWithIntegrity).toBe(expectedIntegrityAttributes + 1) // External style + expect(inlineScriptHashes).toBe(expectedInlineScriptHashes) + expect(externalScriptHashes).toBe(expectedExternalScriptHashes + 1) // + 1 vue modulepreload + expect(inlineStyleHashes).toBe(expectedInlineStyleHashes) + expect(externalStyleHashes).toBe(expectedExternalStyleHashes + 1) // External style + }) + + it('does not set policy for unsupported links', async () => { + const res = await fetch('/external-link') + + const body = await res.text() + const { metaTag, csp, elementsWithIntegrity, inlineScriptHashes, externalScriptHashes, inlineStyleHashes, externalStyleHashes } = extractDataFromBody(body) + + + expect(res).toBeDefined() + expect(res).toBeTruthy() + expect(body).toBeDefined() + expect(metaTag).toBeDefined() + expect(csp).toBeDefined() + expect(elementsWithIntegrity).toBe(expectedIntegrityAttributes + 1) // External link + expect(inlineScriptHashes).toBe(expectedInlineScriptHashes) + expect(externalScriptHashes).toBe(expectedExternalScriptHashes + 1) // + 1 vue modulepreload + expect(inlineStyleHashes).toBe(expectedInlineStyleHashes) + expect(externalStyleHashes).toBe(expectedExternalStyleHashes) + }) + + it('does not CSP via meta in SSR mode', async () => { + const res = await fetch('/not-ssg') + + const body = await res.text() + const { metaTag, csp, elementsWithIntegrity, inlineScriptHashes, externalScriptHashes, inlineStyleHashes, externalStyleHashes } = extractDataFromBody(body) + + expect(res).toBeDefined() + expect(res).toBeTruthy() + expect(body).toBeDefined() + expect(metaTag).toBeNull() + expect(csp).toBeUndefined() + expect(elementsWithIntegrity).toBe(expectedIntegrityAttributes + 3) // External script + style + icon + expect(inlineScriptHashes).toBe(0) + expect(externalScriptHashes).toBe(0) + expect(inlineStyleHashes).toBe(0) + expect(externalStyleHashes).toBe(0) + }) +}) diff --git a/yarn.lock b/yarn.lock index 30c667c3..66679543 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1350,61 +1350,61 @@ "@typescript-eslint/visitor-keys" "6.7.2" debug "^4.3.4" -"@typescript-eslint/scope-manager@6.7.5": - version "6.7.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz#1cf33b991043886cd67f4f3600b8e122fc14e711" - integrity sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A== +"@typescript-eslint/scope-manager@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.2.tgz#cf59a2095d2f894770c94be489648ad1c78dc689" + integrity sha512-bgi6plgyZjEqapr7u2mhxGR6E8WCzKNUFWNh6fkpVe9+yzRZeYtDTbsIBzKbcxI+r1qVWt6VIoMSNZ4r2A+6Yw== dependencies: - "@typescript-eslint/types" "6.7.5" - "@typescript-eslint/visitor-keys" "6.7.5" + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" -"@typescript-eslint/type-utils@6.7.5": - version "6.7.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz#0a65949ec16588d8956f6d967f7d9c84ddb2d72a" - integrity sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g== +"@typescript-eslint/type-utils@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.2.tgz#ed921c9db87d72fa2939fee242d700561454f367" + integrity sha512-36F4fOYIROYRl0qj95dYKx6kybddLtsbmPIYNK0OBeXv2j9L5nZ17j9jmfy+bIDHKQgn2EZX+cofsqi8NPATBQ== dependencies: - "@typescript-eslint/typescript-estree" "6.7.5" - "@typescript-eslint/utils" "6.7.5" + "@typescript-eslint/typescript-estree" "6.7.2" + "@typescript-eslint/utils" "6.7.2" debug "^4.3.4" ts-api-utils "^1.0.1" -"@typescript-eslint/types@6.7.5": - version "6.7.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.5.tgz#4571320fb9cf669de9a95d9849f922c3af809790" - integrity sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ== +"@typescript-eslint/types@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.2.tgz#75a615a6dbeca09cafd102fe7f465da1d8a3c066" + integrity sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg== -"@typescript-eslint/typescript-estree@6.7.5": - version "6.7.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz#4578de1a26e9f24950f029a4f00d1bfe41f15a39" - integrity sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg== +"@typescript-eslint/typescript-estree@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.2.tgz#ce5883c23b581a5caf878af641e49dd0349238c7" + integrity sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ== dependencies: - "@typescript-eslint/types" "6.7.5" - "@typescript-eslint/visitor-keys" "6.7.5" + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.7.5": - version "6.7.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.5.tgz#ab847b53d6b65e029314b8247c2336843dba81ab" - integrity sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA== +"@typescript-eslint/utils@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.2.tgz#b9ef0da6f04932167a9222cb4ac59cb187165ebf" + integrity sha512-ZCcBJug/TS6fXRTsoTkgnsvyWSiXwMNiPzBUani7hDidBdj1779qwM1FIAmpH4lvlOZNF3EScsxxuGifjpLSWQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.7.5" - "@typescript-eslint/types" "6.7.5" - "@typescript-eslint/typescript-estree" "6.7.5" + "@typescript-eslint/scope-manager" "6.7.2" + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/typescript-estree" "6.7.2" semver "^7.5.4" -"@typescript-eslint/visitor-keys@6.7.5": - version "6.7.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz#84c68d6ceb5b12d5246b918b84f2b79affd6c2f1" - integrity sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg== +"@typescript-eslint/visitor-keys@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.2.tgz#4cb2bd786f1f459731b0ad1584c9f73e1c7a4d5c" + integrity sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ== dependencies: - "@typescript-eslint/types" "6.7.5" + "@typescript-eslint/types" "6.7.2" eslint-visitor-keys "^3.4.1" "@unhead/dom@1.7.4", "@unhead/dom@^1.7.4": @@ -2071,7 +2071,7 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001538: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz" integrity sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA== -caniuse-lite@^1.0.30001541: +caniuse-lite@^1.0.30001517: version "1.0.30001561" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz#752f21f56f96f1b1a52e97aae98c57c562d5d9da" integrity sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw== @@ -2111,7 +2111,7 @@ chalk@^5.3.0: resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== -check-error@^1.0.3: +check-error@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== @@ -2460,7 +2460,7 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: dependencies: ms "2.1.2" -deep-eql@^4.1.3: +deep-eql@^4.1.2: version "4.1.3" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== @@ -2631,7 +2631,7 @@ ee-first@1.1.1: resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.535: +electron-to-chromium@^1.4.477: version "1.4.578" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.578.tgz#7a3510f333bcd55e87882799ebeb7518d6ab4d95" integrity sha512-V0ZhSu1BQZKfG0yNEL6Dadzik8E1vAzfpVOapdSiT9F6yapEJ3Bk+4tZ4SMPdWiUchCgnM/ByYtBzp5ntzDMIA== @@ -3041,9 +3041,9 @@ flat@^5.0.2: resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^3.2.9: +flatted@^3.2.7, flatted@^3.2.9: version "3.2.9" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== foreground-child@^3.1.0: @@ -3141,7 +3141,7 @@ get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-func-name@^2.0.1, get-func-name@^2.0.2: +get-func-name@^2.0.0, get-func-name@^2.0.1, get-func-name@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== @@ -3892,6 +3892,13 @@ lodash@^4.17.15, lodash@^4.17.21: resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +loupe@^2.3.1: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" + loupe@^2.3.6: version "2.3.6" resolved "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz" @@ -4199,12 +4206,17 @@ mrmime@^1.0.0: resolved "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz" integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + ms@2.1.2, ms@^2.0.0: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -5866,7 +5878,7 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-detect@^4.0.0, type-detect@^4.0.8: +type-detect@^4.0.0, type-detect@^4.0.5: version "4.0.8" resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== @@ -6107,7 +6119,7 @@ untyped@^1.4.0: mri "^1.2.0" scule "^1.0.0" -update-browserslist-db@^1.0.13: +update-browserslist-db@^1.0.11: version "1.0.13" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==