@budibase/bbui 3.46.0 → 3.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/bbui",
3
3
  "description": "A UI solution used in the different Budibase projects.",
4
- "version": "v3.46.0",
4
+ "version": "v3.47.0",
5
5
  "license": "MPL-2.0",
6
6
  "module": "dist/bbui.mjs",
7
7
  "exports": {
@@ -108,5 +108,5 @@
108
108
  }
109
109
  }
110
110
  },
111
- "gitHead": "1444a702ce29cf158b10c31d3f205fc84c463169"
111
+ "gitHead": "adfff86585b1d393941175f58cbe74f2005616b4"
112
112
  }
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from "vitest"
2
+
3
+ import { renderMarkdown } from "./renderMarkdown"
4
+
5
+ describe("renderMarkdown", () => {
6
+ it("preserves safe HTML used for email layouts", () => {
7
+ const html = renderMarkdown(
8
+ '<div style="color:red;padding:4px"><span>Alert</span><table width="600" cellpadding="4"><tr><td colspan="2">Body</td></tr></table></div>'
9
+ )
10
+
11
+ expect(html).toBe(
12
+ '<div style="color:red;padding:4px"><span>Alert</span><table width="600" cellpadding="4"><tr><td colspan="2">Body</td></tr></table></div>'
13
+ )
14
+ })
15
+
16
+ it("blocks the stored XSS advisory payload", () => {
17
+ const html = renderMarkdown(
18
+ '<iframe srcdoc="<script src=https://example.com/payload.mjs></script>"></iframe>'
19
+ )
20
+
21
+ expect(html).toBe("")
22
+ })
23
+
24
+ it("removes executable attributes, URLs, and styles", () => {
25
+ const html = renderMarkdown(
26
+ '<div class="spectrum-Modal" onclick="alert(1)" style="color:red;position:fixed;background-image:url(https://example.com/pixel)"><img src="https://example.com/image.png" onerror="alert(1)"><a href="javascript:alert(1)">Link</a></div>'
27
+ )
28
+
29
+ expect(html).toBe(
30
+ '<div style="color:red"><img src="https://example.com/image.png" /><a>Link</a></div>'
31
+ )
32
+ })
33
+
34
+ it("removes unsafe values from allowed style properties", () => {
35
+ const html = renderMarkdown(
36
+ '<div style="color:url(https://example.com/x);width:expression(alert(1));padding:4px">Direct</div><div style="color:u\\72l(https://example.com/x);width:e\\78pression(alert(1));font-family:u/**/rl(https://example.com/x);padding:4px">Obfuscated</div>'
37
+ )
38
+
39
+ expect(html).toBe(
40
+ '<div style="padding:4px">Direct</div><div style="padding:4px">Obfuscated</div>'
41
+ )
42
+ })
43
+ })
@@ -28,8 +28,81 @@ const MARKDOWN_TAGS = [
28
28
  "thead",
29
29
  "tr",
30
30
  "ul",
31
+ "div",
32
+ "span",
31
33
  ]
32
34
 
35
+ const MARKDOWN_ATTRIBUTES = {
36
+ "*": [
37
+ "align",
38
+ "bgcolor",
39
+ "dir",
40
+ "height",
41
+ "lang",
42
+ "role",
43
+ "style",
44
+ "valign",
45
+ "width",
46
+ ],
47
+ a: ["href", "title"],
48
+ img: ["alt", "src", "title"],
49
+ li: ["value"],
50
+ ol: ["reversed", "start", "type"],
51
+ table: ["border", "cellpadding", "cellspacing"],
52
+ td: ["colspan", "headers", "nowrap", "rowspan"],
53
+ th: ["colspan", "headers", "nowrap", "rowspan", "scope"],
54
+ }
55
+
56
+ const SAFE_STYLE_VALUE =
57
+ /^(?![\s\S]*(?:\\|\/\*|(?:expression|url)\s*\())[\s\S]*$/i
58
+ const MARKDOWN_STYLE_PROPERTIES = [
59
+ "background-color",
60
+ "border",
61
+ "border-bottom",
62
+ "border-collapse",
63
+ "border-color",
64
+ "border-left",
65
+ "border-right",
66
+ "border-spacing",
67
+ "border-style",
68
+ "border-top",
69
+ "border-width",
70
+ "color",
71
+ "display",
72
+ "font",
73
+ "font-family",
74
+ "font-size",
75
+ "font-style",
76
+ "font-weight",
77
+ "height",
78
+ "letter-spacing",
79
+ "line-height",
80
+ "margin",
81
+ "margin-bottom",
82
+ "margin-left",
83
+ "margin-right",
84
+ "margin-top",
85
+ "max-height",
86
+ "max-width",
87
+ "min-height",
88
+ "min-width",
89
+ "padding",
90
+ "padding-bottom",
91
+ "padding-left",
92
+ "padding-right",
93
+ "padding-top",
94
+ "text-align",
95
+ "text-decoration",
96
+ "vertical-align",
97
+ "width",
98
+ ]
99
+
100
+ const MARKDOWN_STYLES = {
101
+ "*": Object.fromEntries(
102
+ MARKDOWN_STYLE_PROPERTIES.map(property => [property, [SAFE_STYLE_VALUE]])
103
+ ),
104
+ }
105
+
33
106
  export const renderMarkdown = (markdown: string | undefined) => {
34
107
  if (!markdown) {
35
108
  return ""
@@ -39,10 +112,8 @@ export const renderMarkdown = (markdown: string | undefined) => {
39
112
 
40
113
  return sanitizeHtml(html, {
41
114
  allowedTags: MARKDOWN_TAGS,
42
- allowedAttributes: {
43
- a: ["href", "title"],
44
- img: ["alt", "src", "title"],
45
- },
115
+ allowedAttributes: MARKDOWN_ATTRIBUTES,
116
+ allowedStyles: MARKDOWN_STYLES,
46
117
  allowedSchemes: ["http", "https", "mailto", "tel"],
47
118
  allowedSchemesByTag: {
48
119
  img: ["http", "https"],