Skip to content

Commit f31093c

Browse files
authored
fix(security): disallow marks[].data[].url and data.url in Vega when safe mode is SECURE (#1957)
1 parent a7d70da commit f31093c

2 files changed

Lines changed: 104 additions & 4 deletions

File tree

vega/src/convert.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,25 @@ async function convert (source, options) {
3636
if (specFormat === 'lite') {
3737
spec = vegaLite.compile(spec, { logger: nullLogger }).spec
3838
}
39-
if (safeMode === 'secure' && spec && spec.data && Array.isArray(spec.data)) {
40-
const dataWithUrlAttribute = spec.data.filter((item) => item.url)
41-
if (dataWithUrlAttribute && dataWithUrlAttribute.length > 0) {
39+
if (safeMode === 'secure' && spec) {
40+
if (spec.data && Array.isArray(spec.data)) {
41+
const dataWithUrlAttribute = spec.data.filter((item) => item.url)
42+
if (dataWithUrlAttribute && dataWithUrlAttribute.length > 0) {
43+
throw new UnsafeIncludeError(`Unable to load data from an URL while running in secure mode.
44+
Please include your data set as 'values' or run Kroki in unsafe mode using the KROKI_SAFE_MODE environment variable.`)
45+
}
46+
}
47+
if (spec.data && typeof spec.data === 'object' && spec.data.url) {
4248
throw new UnsafeIncludeError(`Unable to load data from an URL while running in secure mode.
4349
Please include your data set as 'values' or run Kroki in unsafe mode using the KROKI_SAFE_MODE environment variable.`)
4450
}
51+
if (spec.marks && Array.isArray(spec.marks)) {
52+
const dataWithUrlAttribute = spec.marks.flatMap(m => m.data).filter((item) => item && item.url)
53+
if (dataWithUrlAttribute && dataWithUrlAttribute.length > 0) {
54+
throw new UnsafeIncludeError(`Unable to load data from an URL while running in secure mode.
55+
Please include your data set as 'values' or run Kroki in unsafe mode using the KROKI_SAFE_MODE environment variable.`)
56+
}
57+
}
4558
}
4659
const view = new vega.View(vega.parse(spec), { renderer: 'none' }).finalize()
4760
if (format === 'svg') {

vega/tests/test.js

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const sinon = require('sinon')
99
const { convert } = require('../src/convert.js')
1010

1111
describe('#convert', function () {
12-
it('should throw UnsafeIncludeError in secure mode when the Vega-Lite specification contains data.url', async function () {
12+
it('should throw UnsafeIncludeError in secure mode when the Vega-Lite specification contains data[].url', async function () {
1313
const input = `{
1414
"data": {"url": "data/cars.json"},
1515
"mark": "point",
@@ -29,6 +29,93 @@ describe('#convert', function () {
2929
deepEqual(err.name, 'UnsafeIncludeError')
3030
}
3131
})
32+
it('should throw UnsafeIncludeError in secure mode when the Vega specification contains data.url', async () => {
33+
const input = `{
34+
"$schema": "https://vega.github.io/schema/vega/v5.json",
35+
"width": 500,
36+
"height": 200,
37+
"data": {
38+
"name": "passwd",
39+
"url": "file:///etc/passwd",
40+
"format": {
41+
"type": "dsv",
42+
"delimiter": ":",
43+
"header": [
44+
"username",
45+
"password",
46+
"uid",
47+
"gid",
48+
"comment",
49+
"home",
50+
"shell"
51+
]
52+
}
53+
},
54+
"marks": [
55+
{
56+
"type": "text",
57+
"from": {
58+
"data": "passwd"
59+
},
60+
"encode": {
61+
"enter": {
62+
"text": {
63+
"signal": "datum.username + ':' + datum.password + ':' + datum.uid + ':' + datum.gid + ':' + datum.comment + ':' + datum.home + ':' + datum.shell"
64+
}
65+
}
66+
}
67+
}
68+
],
69+
"scales": [
70+
{
71+
"name": "yscale",
72+
"type": "linear",
73+
"domain": {
74+
"data": "passwd",
75+
"field": "index"
76+
},
77+
"range": [
78+
0,
79+
1000
80+
]
81+
}
82+
]
83+
}`
84+
try {
85+
await convert(input, {
86+
specFormat: '',
87+
safeMode: 'secure',
88+
format: 'svg'
89+
})
90+
fail('', '', 'It should throw an error in secure mode when the Vega-Lite specification contains data.url')
91+
} catch (err) {
92+
deepEqual(err.name, 'UnsafeIncludeError')
93+
}
94+
})
95+
it('should throw UnsafeIncludeError in secure mode when the Vega specification contains marks[].data[].url', async function () {
96+
const input = `{
97+
"marks": [
98+
{
99+
"type": "group",
100+
"data": [
101+
{
102+
"url": "data/cars.json"
103+
}
104+
]
105+
}
106+
]
107+
}`
108+
try {
109+
await convert(input, {
110+
specFormat: '',
111+
safeMode: 'secure',
112+
format: 'svg'
113+
})
114+
fail('', '', 'It should throw an error in secure mode when the Vega-Lite specification contains data.url')
115+
} catch (err) {
116+
deepEqual(err.name, 'UnsafeIncludeError')
117+
}
118+
})
32119
it('should throw IllegalArgumentError when output format is not supported', async function () {
33120
const input = `{
34121
"data": {

0 commit comments

Comments
 (0)