Skip to content

Commit 7258c24

Browse files
committed
Updates
1 parent 3eba84d commit 7258c24

5 files changed

Lines changed: 66 additions & 14 deletions

File tree

src/JBrowse/Browser.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2179,7 +2179,13 @@ define([
21792179
if (accounts && !lang.isArray(userAccounts)) {
21802180
userAccounts = userAccounts.replace(/^\s*|\s*$/, '').split(/\s*,\s*/)
21812181
}
2182-
accounts.push.apply(accounts, userAccounts)
2182+
// validate GA account IDs to prevent script injection (UA-XXXXX-X or G-XXXXXXXXXX format)
2183+
var gaPattern = /^(UA-\d+-\d+|G-[A-Z0-9]+)$/i
2184+
for (var i = 0; i < userAccounts.length; i++) {
2185+
if (gaPattern.test(userAccounts[i])) {
2186+
accounts.push(userAccounts[i])
2187+
}
2188+
}
21832189
}
21842190

21852191
var analyticsScript =

src/JBrowse/ConfigAdaptor/AdaptorUtil.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ define([
2727
return arguments[0]
2828
}
2929
try {
30-
eval(`arguments[0]=${arguments[0]};`)
30+
// use Function constructor instead of eval for slightly better isolation
31+
// note: this still executes untrusted code, but doesn't have access to local scope
32+
var fn = new Function(`return (${arguments[0]});`)
33+
return fn()
3134
} catch (e) {
3235
console.error(`${e} parsing config callback '${arguments[0]}'`)
3336
}

src/JBrowse/Util.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,43 @@ define([
3838
return dompurify.sanitize(str)
3939
},
4040

41+
isSafeUrl: function (url) {
42+
if (!url || typeof url !== 'string') {
43+
return false
44+
}
45+
var trimmed = url.trim().toLowerCase()
46+
// block dangerous URL schemes
47+
if (
48+
trimmed.startsWith('javascript:') ||
49+
trimmed.startsWith('data:') ||
50+
trimmed.startsWith('vbscript:')
51+
) {
52+
return false
53+
}
54+
return true
55+
},
56+
57+
sanitizeCss: function (css) {
58+
if (!css || typeof css !== 'string') {
59+
return ''
60+
}
61+
// remove dangerous CSS patterns: url(), expression(), @import, behavior, -moz-binding
62+
return css
63+
.replace(/url\s*\([^)]*\)/gi, '')
64+
.replace(/expression\s*\([^)]*\)/gi, '')
65+
.replace(/@import[^;]*/gi, '')
66+
.replace(/behavior\s*:[^;]*/gi, '')
67+
.replace(/-moz-binding\s*:[^;]*/gi, '')
68+
},
69+
70+
sanitizeClassName: function (className) {
71+
if (!className || typeof className !== 'string') {
72+
return ''
73+
}
74+
// only allow alphanumeric, hyphens, underscores, and spaces (for multiple classes)
75+
return className.replace(/[^a-zA-Z0-9_\- ]/g, '')
76+
},
77+
4178
unescapeHTML: function (str) {
4279
return str
4380
.toString()

src/JBrowse/View/Track/BlockBased.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,9 +1015,13 @@ define([
10151015
}[`${spec.action}`.toLowerCase()]
10161016

10171017
if (spec.action == 'newWindow') {
1018-
window.open(url, '_blank')
1018+
if (Util.isSafeUrl(url)) {
1019+
window.open(url, '_blank')
1020+
}
10191021
} else if (spec.action == 'navigateTo') {
1020-
window.location = url
1022+
if (Util.isSafeUrl(url)) {
1023+
window.location = url
1024+
}
10211025
} else if (
10221026
spec.action in
10231027
{
@@ -1321,11 +1325,11 @@ define([
13211325
width: iframeDims.w,
13221326
height: iframeDims.h,
13231327
style: { border: 'none' },
1324-
src: spec.url,
1328+
src: Util.isSafeUrl(spec.url) ? spec.url : 'about:blank',
13251329
})
13261330

13271331
dialog.set('content', iframe)
1328-
if (!spec.hideIframeDialogUrl) {
1332+
if (!spec.hideIframeDialogUrl && Util.isSafeUrl(spec.url)) {
13291333
dojo.create(
13301334
'a',
13311335
{

src/JBrowse/View/Track/HTMLFeatures.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,11 +1117,10 @@ define([
11171117
var glyphBox
11181118
heightTest = document.createElement('div')
11191119
//cover all the bases: stranded or not, phase or not
1120-
heightTest.className = `feature ${this.config.style.className} plus-${
1121-
this.config.style.className
1122-
} plus-${this.config.style.className}1`
1120+
var safeClassName = Util.sanitizeClassName(this.config.style.className)
1121+
heightTest.className = `feature ${safeClassName} plus-${safeClassName} plus-${safeClassName}1`
11231122
if (this.config.style.featureCss) {
1124-
heightTest.style.cssText = this.config.style.featureCss
1123+
heightTest.style.cssText = Util.sanitizeCss(this.config.style.featureCss)
11251124
}
11261125
heightTest.style.visibility = 'hidden'
11271126
if (Util.is_ie6) {
@@ -1338,7 +1337,9 @@ define([
13381337
dojo.addClass(featDiv, 'feature')
13391338
var className = this.config.style.className
13401339
if (className == '{type}') {
1341-
className = feature.get('type')
1340+
className = Util.sanitizeClassName(feature.get('type'))
1341+
} else {
1342+
className = Util.sanitizeClassName(className)
13421343
}
13431344
var strand = feature.get('strand')
13441345
switch (strand) {
@@ -1386,7 +1387,7 @@ define([
13861387
`left:${(100 * (displayStart - block.startBase)) / blockWidth}%;` +
13871388
`top:${top}px;` +
13881389
` width:${featwidth}%;${
1389-
this.config.style.featureCss ? this.config.style.featureCss : ''
1390+
this.config.style.featureCss ? Util.sanitizeCss(this.config.style.featureCss) : ''
13901391
}`
13911392

13921393
// Store the containerStart/End so we can resolve the truncation
@@ -1398,16 +1399,17 @@ define([
13981399
var ah = document.createElement('div')
13991400
var featwidth_px = (featwidth / 100) * blockWidth * scale
14001401

1402+
var safeArrowClass = Util.sanitizeClassName(this.config.style.arrowheadClass)
14011403
switch (strand) {
14021404
case 1:
14031405
case '+':
1404-
ah.className = `plus-${this.config.style.arrowheadClass}`
1406+
ah.className = `plus-${safeArrowClass}`
14051407
ah.style.cssText = `right: ${-this.plusArrowWidth}px`
14061408
featDiv.appendChild(ah)
14071409
break
14081410
case -1:
14091411
case '-':
1410-
ah.className = `minus-${this.config.style.arrowheadClass}`
1412+
ah.className = `minus-${safeArrowClass}`
14111413
ah.style.cssText = `left: ${-this.minusArrowWidth}px`
14121414
featDiv.appendChild(ah)
14131415
break

0 commit comments

Comments
 (0)