diff --git a/docSite/content/zh-cn/docs/development/openapi/dataset.md b/docSite/content/zh-cn/docs/development/openapi/dataset.md
index d9d5f0d181d1..934617ebf951 100644
--- a/docSite/content/zh-cn/docs/development/openapi/dataset.md
+++ b/docSite/content/zh-cn/docs/development/openapi/dataset.md
@@ -645,7 +645,7 @@ data 为集合的 ID。
{{< /tab >}}
{{< /tabs >}}
-### 创建一个外部文件库集合(商业版)
+### 创建一个外部文件库集合(弃用)
{{< tabs tabTotal="3" >}}
{{< tab tabName="请求示例" >}}
diff --git a/projects/app/next.config.js b/projects/app/next.config.js
index a82cead0f06f..5875e1708169 100644
--- a/projects/app/next.config.js
+++ b/projects/app/next.config.js
@@ -1,6 +1,7 @@
const { i18n } = require('./next-i18next.config.js');
const path = require('path');
const fs = require('fs');
+const crypto = require('crypto');
const isDev = process.env.NODE_ENV === 'development';
@@ -14,51 +15,50 @@ const nextConfig = {
headers: async () => {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
- const csp = `'nonce-${nonce}'`;
+ const csp_nonce = `'nonce-${nonce}'`;
const scheme_source = 'data: mediastream: blob: filesystem:';
- const NECESSARY_DOMAINS = [
- '*.sentry.io',
- 'http://localhost:*',
- 'http://127.0.0.1:*',
- 'https://analytics.google.com',
- 'googletagmanager.com',
- '*.googletagmanager.com',
- 'https://www.google-analytics.com',
- 'https://api.github.com'
- ].join(' ');
+
+ const SENTRY_DOMAINS = '*.sentry.io';
+ const GOOGLE_DOMAINS = 'https://www.googletagmanager.com https://www.google-analytics.com';
+ const LOCALHOST = 'http://localhost:* http://127.0.0.1:*';
+ const OTHER_DOMAINS = 'https://api.example.com';
+
+ const csp = [
+ `default-src 'self' ${scheme_source} ${SENTRY_DOMAINS} ${GOOGLE_DOMAINS} ${OTHER_DOMAINS} ${LOCALHOST}`,
+ `script-src 'self' 'unsafe-inline' 'unsafe-eval' ${csp_nonce} ${SENTRY_DOMAINS} ${GOOGLE_DOMAINS} ${OTHER_DOMAINS} ${LOCALHOST}`,
+ `style-src 'self' 'unsafe-inline' ${SENTRY_DOMAINS} ${GOOGLE_DOMAINS} ${OTHER_DOMAINS} ${LOCALHOST}`,
+ `img-src data: blob: ${SENTRY_DOMAINS} ${GOOGLE_DOMAINS} ${OTHER_DOMAINS} ${LOCALHOST} *`,
+ `connect-src 'self' wss: https: ${SENTRY_DOMAINS} ${GOOGLE_DOMAINS} ${OTHER_DOMAINS} ${LOCALHOST}`,
+ `font-src 'self'`,
+ `media-src 'self' ${scheme_source} ${SENTRY_DOMAINS} ${GOOGLE_DOMAINS} ${OTHER_DOMAINS} ${LOCALHOST}`,
+ `worker-src 'self' ${SENTRY_DOMAINS} ${GOOGLE_DOMAINS} ${OTHER_DOMAINS} ${LOCALHOST} ${scheme_source}`,
+ `object-src 'none'`,
+ `form-action 'self'`,
+ `base-uri 'self'`,
+ `frame-src 'self' ${SENTRY_DOMAINS} ${GOOGLE_DOMAINS} ${OTHER_DOMAINS}`,
+ `sandbox allow-scripts allow-same-origin allow-popups allow-forms`,
+ `upgrade-insecure-requests`
+ ].join('; ');
return [
{
- source: '/chat/(.*)',
+ source: '/(.*)',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-XSS-Protection', value: '1; mode=block' },
{ key: 'Referrer-Policy', value: 'no-referrer' },
{
- key: 'Content-Security-Policy',
- value: [
- `default-src 'self' ${scheme_source} ${NECESSARY_DOMAINS} ${csp}`,
- `script-src 'self' 'unsafe-inline' 'unsafe-eval' ${csp} ${NECESSARY_DOMAINS}`,
- `style-src 'self' 'unsafe-inline' ${csp} ${NECESSARY_DOMAINS}`,
- `media-src 'self' http: ${scheme_source} ${NECESSARY_DOMAINS} ${csp}`,
- `worker-src 'self' ${csp} ${NECESSARY_DOMAINS} ${scheme_source}`,
- `img-src * data: blob:`,
- `font-src 'self'`,
- `connect-src 'self' wss: https: ${scheme_source} ${NECESSARY_DOMAINS} ${csp}`,
- "object-src 'none'",
- "form-action 'self'",
- "base-uri 'self'",
- "frame-src 'self' 'allow-scripts'",
- 'sandbox allow-scripts allow-same-origin allow-popups allow-forms',
- 'upgrade-insecure-requests'
- ].join('; ')
- }
+ key: 'Strict-Transport-Security',
+ value: 'max-age=63072000; includeSubDomains; preload'
+ },
+ { key: 'Content-Security-Policy', value: csp }
]
}
];
},
- webpack(config, { isServer, nextRuntime }) {
+
+ webpack: (config, { isServer, nextRuntime }) => {
Object.assign(config.resolve.alias, {
'@mongodb-js/zstd': false,
'@aws-sdk/credential-providers': false,
diff --git a/projects/app/src/components/Markdown/index.tsx b/projects/app/src/components/Markdown/index.tsx
index 418f64a0bdb2..84e1b248b848 100644
--- a/projects/app/src/components/Markdown/index.tsx
+++ b/projects/app/src/components/Markdown/index.tsx
@@ -10,7 +10,7 @@ import RehypeRaw from 'rehype-raw';
import styles from './index.module.scss';
import dynamic from 'next/dynamic';
import { Box } from '@chakra-ui/react';
-import { CodeClassNameEnum, mdTextFormat } from './utils';
+import { CodeClassNameEnum, mdTextFormat, filterSafeProps } from './utils';
import ErrorBoundary from './errorBoundry';
import SVGRenderer from './markdowSVG';
import { useCreation } from 'ahooks';
@@ -37,15 +37,7 @@ const SafeA = (props: any) => {
const href = props.href || '';
const safeHref = isSafeHref(href) ? href : '#';
- const ALLOWED_A_ATTRS = new Set([
- 'href',
- 'target',
- 'rel',
- 'className',
- 'children',
- 'style',
- 'title'
- ]);
+ const ALLOWED_A_ATTRS = new Set(['href']);
const safeProps = filterSafeProps(props, ALLOWED_A_ATTRS);
return (
@@ -130,30 +122,27 @@ const MarkdownRender = ({
node.type = 'text';
node.value = `<${node.tagName}`;
}
-
- // handle properties, filter events
+ // use filterSafeProps to filter component properties
if (node.properties) {
- Object.keys(node.properties).forEach((key) => {
- const keyLower = key.toLowerCase();
- // if event property (on开头)
- if (keyLower.startsWith('on')) {
- const value = node.properties[key];
- // if event value is not a function or contains suspicious content, delete the event
- if (
- typeof value === 'string' || // delete event handler in string format
- value === null ||
- value === undefined ||
- (typeof value === 'string' &&
- (value.includes('javascript:') ||
- value.includes('alert') ||
- value.includes('eval') ||
- value.includes('Function') ||
- /[\(\)\[\]\{\}]/.test(value))) // flag for executable code containing parentheses, etc.
- ) {
- delete node.properties[key];
- }
- }
- });
+ const ALLOWED_ATTRS = new Set([
+ 'title',
+ 'alt',
+ 'src',
+ 'href',
+ 'target',
+ 'rel',
+ 'width',
+ 'height',
+ 'align',
+ 'valign',
+ 'type',
+ 'lang',
+ 'value',
+ 'name'
+ ]);
+
+ // use filterSafeProps to filter properties
+ node.properties = filterSafeProps(node.properties, ALLOWED_ATTRS);
}
}
@@ -254,6 +243,11 @@ function sanitizeImageSrc(src?: string): string | undefined {
return undefined;
}
+function Image({ src }: { src?: string }) {
+ const safeSrc = sanitizeImageSrc(src);
+ return ;
+}
+
const ALLOWED_IMG_ATTRS = new Set([
'alt',
'width',
diff --git a/projects/app/src/components/Markdown/markdowSVG.tsx b/projects/app/src/components/Markdown/markdowSVG.tsx
index a220fe10a106..1335b7357347 100644
--- a/projects/app/src/components/Markdown/markdowSVG.tsx
+++ b/projects/app/src/components/Markdown/markdowSVG.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import ErrorBoundary from './errorBoundry';
-import { filterSafeProps } from './index';
+import { filterSafeProps } from './utils';
interface SVGProps {
children?: React.ReactNode;
@@ -26,80 +26,30 @@ const SVG_ALLOWED_ATTRS = new Set([
]);
const SVGRenderer = ({ children, className, style, ...props }: SVGProps) => {
- // filter props
- const svgProps = { ...props, className, style };
- const sanitizedProps = filterSafeProps(svgProps, SVG_ALLOWED_ATTRS, false);
+ const sanitizedProps = filterSafeProps({ ...props, className, style }, SVG_ALLOWED_ATTRS);
const sanitizeSVGContent = (content: string | React.ReactNode): string => {
- if (typeof content !== 'string') {
- return '';
- }
+ if (typeof content !== 'string') return '';
- let cleaned = content;
-
- cleaned = cleaned.replace(/