Restrict /updateTheme to the configured theme file and harden market host verification#1405
Restrict /updateTheme to the configured theme file and harden market host verification#1405adilburaksen wants to merge 2 commits into
Conversation
…host verification
There was a problem hiding this comment.
Code Review
This pull request addresses security vulnerabilities by validating the theme file path against the configured theme file to prevent arbitrary file loading, and by hardening market host verification using proper URL parsing to prevent SSRF bypasses. A review comment suggests optimizing the URL verification logic by caching parsed base URLs in a WeakMap to avoid repeated parsing overhead.
| return !!getMarketUrls(cdapConfig).find((element) => { | ||
| let base; | ||
| try { | ||
| base = new URL(element); | ||
| } catch (e) { | ||
| return false; | ||
| } | ||
| if (requested.origin !== base.origin) { | ||
| return false; | ||
| } | ||
| const basePath = base.pathname.endsWith('/') ? base.pathname : `${base.pathname}/`; | ||
| return requested.pathname === base.pathname || requested.pathname.startsWith(basePath); | ||
| }); |
There was a problem hiding this comment.
Parsing the configured market URLs using 'new URL()' on every single request validation is inefficient, especially since 'getMarketUrls(cdapConfig)' is already memoized and the configuration rarely changes. We can optimize this by caching the parsed 'URL' objects using a 'WeakMap' keyed by the 'cdapConfig' object. This avoids repeated parsing overhead while preventing memory leaks and key collisions.
if (cdapConfig && typeof cdapConfig === 'object') {
if (!isVerifiedMarketHost.cache) {
isVerifiedMarketHost.cache = new WeakMap();
}
let parsedBases = isVerifiedMarketHost.cache.get(cdapConfig);
if (!parsedBases) {
parsedBases = getMarketUrls(cdapConfig)
.map((element) => {
try {
return new URL(element);
} catch (e) {
return null;
}
})
.filter(Boolean);
isVerifiedMarketHost.cache.set(cdapConfig, parsedBases);
}
return !!parsedBases.find((base) => {
if (requested.origin !== base.origin) {
return false;
}
const basePath = base.pathname.endsWith('/') ? base.pathname : base.pathname + '/';
return requested.pathname === base.pathname || requested.pathname.startsWith(basePath);
});
}
return false;
1.
/updateTheme: arbitrary file load viauiThemePathThe
/updateThemehandler readsreq.body.uiThemePathand passes it touiThemeWrapper.extractUITheme(), which for an absolute path calls__non_webpack_require__(uiThemePath)— loading any absolute path on the server as a Node module. The endpoint's only legitimate use is reloading the operator-configured theme.Fix: reject any
uiThemePaththat does not exactly match the configuredui.theme.file. This preserves the reload-theme behavior while removing the arbitrary-requireprimitive.2.
isVerifiedMarketHost: allowlist bypass viastartsWithisVerifiedMarketHostvalidates a market URL withurl.startsWith(configuredMarketUrl). A prefix check is bypassable: with a host-only configured market URL,https://market.example.com.evil.com/...andhttps://market.example.com@evil.com/...both pass, letting the market proxy (/market,/forwardMarketToCdap) fetch an attacker-controlled host (server-side request forgery).Fix: parse both the requested and configured URLs with
URL, require an exactoriginmatch, and require the requested path to be contained within the configured base path.