Skip to content

Commit 7811ba9

Browse files
authored
Address potentially security flaws in panel.js (#8501)
1 parent ecb87f7 commit 7811ba9

2 files changed

Lines changed: 36 additions & 9 deletions

File tree

panel/models/plotly.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,29 @@ import {convertUndefined, deepCopy, get, reshape, throttle} from "./util"
1515

1616
import plotly_css from "styles/models/plotly.css"
1717

18+
const FORBIDDEN_KEYS = new Set(["__proto__", "prototype", "constructor"])
19+
20+
function isRecord(value: unknown): value is Record<string, unknown> {
21+
return typeof value === "object" && value !== null
22+
}
23+
24+
function isSafePath(path: string[]): boolean {
25+
return path.every((part) => part.length > 0 && !FORBIDDEN_KEYS.has(part))
26+
}
27+
28+
function getSafeParent(obj: unknown, path: string[]): Record<string, unknown> | null {
29+
let current: unknown = obj
30+
31+
for (const key of path) {
32+
if (!isRecord(current) || FORBIDDEN_KEYS.has(key) || !Object.hasOwn(current, key)) {
33+
return null
34+
}
35+
current = current[key]
36+
}
37+
38+
return isRecord(current) ? current : null
39+
}
40+
1841
export class PlotlyEvent extends ModelEvent {
1942
constructor(readonly data: any) {
2043
super()
@@ -422,17 +445,21 @@ export class PlotlyPlotView extends HTMLBoxView {
422445
if (array.shape != null && array.shape.length > 1) {
423446
array = reshape(array, array.shape)
424447
}
425-
const prop_path = column.split(".")
426-
const prop = prop_path[prop_path.length - 1]
427-
let prop_parent = trace
428-
for (const k of prop_path.slice(0, -1)) {
429-
prop_parent = (prop_parent[k])
448+
449+
// Column name used for resolving object data needs to be validated
450+
// to avoid prototype pollution
451+
const propPath = column.split(".")
452+
const prop = propPath[propPath.length - 1]
453+
const propParent = getSafeParent(trace, propPath.slice(0, -1))
454+
if (!propParent || !prop || !isSafePath(propPath) || FORBIDDEN_KEYS.has(prop)) {
455+
console.warn("Attempted prototype pollution detected via Plotly column resolution.")
456+
continue
430457
}
431458

432-
if (update && prop_path.length == 1) {
433-
prop_parent[prop] = [array]
459+
if (update && propPath.length == 1) {
460+
propParent[prop] = [array]
434461
} else {
435-
prop_parent[prop] = array
462+
propParent[prop] = array
436463
}
437464
}
438465
return trace

panel/models/util.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export function formatError(error: SyntaxError, code: string): string {
143143
const col = parseInt(match[2])
144144
const start = Math.max(0, line_num-5)
145145
const col_index = line_num-start
146-
const lines = code.replace(">", "&lt;").replace("<", "&gt;").split(/\r?\n/).slice(start, line_num+5)
146+
const lines = code.replace(/>/g, "&lt;").replace(/</g, "&gt;").split(/\r?\n/).slice(start, line_num+5)
147147
msg += "<br><br>"
148148
for (let i = 0; i < col_index; i++) {
149149
const cls = (i == (col_index-1)) ? " class=\"highlight\"" : ""

0 commit comments

Comments
 (0)