feat(web): add ALLOW_INLINE_STYLES env var to opt-in inline CSS in Markdown rendering (#34719)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
s-kawamura-upgrade 2026-04-08 17:38:24 +09:00 committed by GitHub
parent 546062d2cd
commit 0e0bb3582f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 25 additions and 1 deletions

View File

@ -1176,6 +1176,11 @@ TEXT_GENERATION_TIMEOUT_MS=60000
# Enable the experimental vinext runtime shipped in the image.
EXPERIMENTAL_ENABLE_VINEXT=false
# Allow inline style attributes in Markdown rendering.
# Enable this if your workflows use Jinja2 templates with styled HTML.
# Only recommended for self-hosted deployments with trusted content.
ALLOW_INLINE_STYLES=false
# Allow rendering unsafe URLs which have "data:" scheme.
ALLOW_UNSAFE_DATA_SCHEME=false

View File

@ -165,6 +165,7 @@ services:
TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000}
CSP_WHITELIST: ${CSP_WHITELIST:-}
ALLOW_EMBED: ${ALLOW_EMBED:-false}
ALLOW_INLINE_STYLES: ${ALLOW_INLINE_STYLES:-false}
ALLOW_UNSAFE_DATA_SCHEME: ${ALLOW_UNSAFE_DATA_SCHEME:-false}
MARKETPLACE_API_URL: ${MARKETPLACE_API_URL:-https://marketplace.dify.ai}
MARKETPLACE_URL: ${MARKETPLACE_URL:-https://marketplace.dify.ai}

View File

@ -510,6 +510,7 @@ x-shared-env: &shared-api-worker-env
MAX_ITERATIONS_NUM: ${MAX_ITERATIONS_NUM:-99}
TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000}
EXPERIMENTAL_ENABLE_VINEXT: ${EXPERIMENTAL_ENABLE_VINEXT:-false}
ALLOW_INLINE_STYLES: ${ALLOW_INLINE_STYLES:-false}
ALLOW_UNSAFE_DATA_SCHEME: ${ALLOW_UNSAFE_DATA_SCHEME:-false}
MAX_TREE_DEPTH: ${MAX_TREE_DEPTH:-50}
PGDATA: ${PGDATA:-/var/lib/postgresql/data/pgdata}
@ -875,6 +876,7 @@ services:
TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000}
CSP_WHITELIST: ${CSP_WHITELIST:-}
ALLOW_EMBED: ${ALLOW_EMBED:-false}
ALLOW_INLINE_STYLES: ${ALLOW_INLINE_STYLES:-false}
ALLOW_UNSAFE_DATA_SCHEME: ${ALLOW_UNSAFE_DATA_SCHEME:-false}
MARKETPLACE_API_URL: ${MARKETPLACE_API_URL:-https://marketplace.dify.ai}
MARKETPLACE_URL: ${MARKETPLACE_URL:-https://marketplace.dify.ai}

View File

@ -48,6 +48,9 @@ NEXT_PUBLIC_CSP_WHITELIST=
# Default is not allow to embed into iframe to prevent Clickjacking: https://owasp.org/www-community/attacks/Clickjacking
NEXT_PUBLIC_ALLOW_EMBED=
# Allow inline style attributes in Markdown rendering (self-hosted opt-in).
NEXT_PUBLIC_ALLOW_INLINE_STYLES=false
# Allow rendering unsafe URLs which have "data:" scheme.
NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME=false

View File

@ -16,7 +16,7 @@ import {
ThinkBlock,
VideoBlock,
} from '@/app/components/base/markdown-blocks'
import { ENABLE_SINGLE_DOLLAR_LATEX } from '@/config'
import { ALLOW_INLINE_STYLES, ENABLE_SINGLE_DOLLAR_LATEX } from '@/config'
import dynamic from '@/next/dynamic'
import { customUrlTransform } from './markdown-utils'
import 'katex/dist/katex.min.css'
@ -118,6 +118,11 @@ function buildRehypePlugins(extraPlugins?: PluggableList): PluggableList {
// component validates names with `isSafeName()`, so remove it.
const clobber = (defaultSanitizeSchema.clobber ?? []).filter(k => k !== 'name')
if (ALLOW_INLINE_STYLES) {
const globalAttrs = mergedAttributes['*'] ?? []
mergedAttributes['*'] = [...globalAttrs, 'style']
}
const customSchema: SanitizeSchema = {
...defaultSanitizeSchema,
tagNames: [...tagNamesSet],

View File

@ -304,6 +304,7 @@ export const LOOP_NODE_MAX_COUNT = env.NEXT_PUBLIC_LOOP_NODE_MAX_COUNT
export const MAX_ITERATIONS_NUM = env.NEXT_PUBLIC_MAX_ITERATIONS_NUM
export const MAX_TREE_DEPTH = env.NEXT_PUBLIC_MAX_TREE_DEPTH
export const ALLOW_INLINE_STYLES = env.NEXT_PUBLIC_ALLOW_INLINE_STYLES
export const ALLOW_UNSAFE_DATA_SCHEME = env.NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME
export const ENABLE_WEBSITE_JINAREADER = env.NEXT_PUBLIC_ENABLE_WEBSITE_JINAREADER
export const ENABLE_WEBSITE_FIRECRAWL = env.NEXT_PUBLIC_ENABLE_WEBSITE_FIRECRAWL

View File

@ -30,6 +30,7 @@ export NEXT_PUBLIC_AMPLITUDE_API_KEY=${AMPLITUDE_API_KEY}
export NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS=${TEXT_GENERATION_TIMEOUT_MS}
export NEXT_PUBLIC_CSP_WHITELIST=${CSP_WHITELIST}
export NEXT_PUBLIC_ALLOW_EMBED=${ALLOW_EMBED}
export NEXT_PUBLIC_ALLOW_INLINE_STYLES=${ALLOW_INLINE_STYLES:-false}
export NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME=${ALLOW_UNSAFE_DATA_SCHEME:-false}
export NEXT_PUBLIC_TOP_K_MAX_VALUE=${TOP_K_MAX_VALUE}
export NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH}

View File

@ -19,6 +19,11 @@ const clientSchema = {
* Default is not allow to embed into iframe to prevent Clickjacking: https://owasp.org/www-community/attacks/Clickjacking
*/
NEXT_PUBLIC_ALLOW_EMBED: coercedBoolean.default(false),
/**
* Allow inline style attributes in Markdown rendering.
* Self-hosted opt-in for workflows using styled Jinja2 templates.
*/
NEXT_PUBLIC_ALLOW_INLINE_STYLES: coercedBoolean.default(false),
/**
* Allow rendering unsafe URLs which have "data:" scheme.
*/
@ -153,6 +158,7 @@ export const env = createEnv({
client: clientSchema,
experimental__runtimeEnv: {
NEXT_PUBLIC_ALLOW_EMBED: isServer ? process.env.NEXT_PUBLIC_ALLOW_EMBED : getRuntimeEnvFromBody('allowEmbed'),
NEXT_PUBLIC_ALLOW_INLINE_STYLES: isServer ? process.env.NEXT_PUBLIC_ALLOW_INLINE_STYLES : getRuntimeEnvFromBody('allowInlineStyles'),
NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME: isServer ? process.env.NEXT_PUBLIC_ALLOW_UNSAFE_DATA_SCHEME : getRuntimeEnvFromBody('allowUnsafeDataScheme'),
NEXT_PUBLIC_AMPLITUDE_API_KEY: isServer ? process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY : getRuntimeEnvFromBody('amplitudeApiKey'),
NEXT_PUBLIC_API_PREFIX: isServer ? process.env.NEXT_PUBLIC_API_PREFIX : getRuntimeEnvFromBody('apiPrefix'),