Skip to content

Restrict UI theme file loader to JSON files under the theme config dir#1391

Open
TristanInSec wants to merge 1 commit into
cdapio:developfrom
TristanInSec:restrict-ui-theme-loader
Open

Restrict UI theme file loader to JSON files under the theme config dir#1391
TristanInSec wants to merge 1 commit into
cdapio:developfrom
TristanInSec:restrict-ui-theme-loader

Conversation

@TristanInSec
Copy link
Copy Markdown

Summary

server/uiThemeWrapper.js currently loads the UI theme file by passing the caller-supplied path straight to __non_webpack_require__. The POST /updateTheme handler in server/express.js feeds this function a path taken from the request body, so the effective contract is "load any file reachable by require() and expose its contents to the UI through window.CDAP_UI_THEME".

This PR narrows that contract:

  • Allowlist the theme directory. A new resolveAllowedThemePath() helper canonicalises the supplied path (keeping the existing server/ and config/ relative-path shortcuts) and refuses to return anything that does not resolve to a location inside one of the UI theme config directories.
  • Require a .json extension. The resolver rejects paths with any other extension, so the loader can no longer be pointed at a .js module.
  • Switch from require() to fs.readFileSync + JSON.parse. Even if the allowlist check were ever weakened, the loaded file is no longer executed as a Node module.

The exported extractUITheme(cdapConfig, uiThemePath) signature and return shape are unchanged; the two existing callers (extractUIThemeWrapper and the /updateTheme express handler) continue to work with the legitimate theme paths shipped in server/config/themes/ (default.json, light.json).

Testing

I exercised the new resolver in isolation against the server's config/themes directory and confirmed:

Input Expected Actual
config/themes/light.json accept accepted
absolute path under server/config/themes/ accept accepted
../../../etc/passwd reject rejected (not .json)
/etc/cdap/conf/cdap-site.json reject rejected (outside allowed root)
config/themes/evil.js reject rejected (extension)
config/themes/../../etc/passwd.json reject rejected (outside allowed root after resolve)
'' reject rejected (missing)

default.json and light.json continue to load identically to the previous implementation.

extractUITheme accepts a path value that originates from the
POST /updateTheme HTTP handler, and currently passes it straight to
__non_webpack_require__. Because require() can load both .json and .js
modules from anywhere on the filesystem, the handler is effectively a
general-purpose file loader whose output is exposed back to the UI via
window.CDAP_UI_THEME.

Replace the require-based loader with a narrower implementation:

- resolveAllowedThemePath() canonicalises the supplied path (handling
  the existing server/ and config/ relative-path shortcuts) and rejects
  anything whose resolved location is not inside one of the UI theme
  config directories.
- The resolver enforces a .json extension, so the loader can no longer
  be pointed at a .js module.
- Loading switches from __non_webpack_require__ to
  fs.readFileSync + JSON.parse, so non-JSON contents cannot be executed
  even if the path check were ever weakened.

The public extractUITheme signature and return shape are unchanged, so
the two callers (extractUIThemeWrapper and the /updateTheme handler)
keep working with the legitimate theme paths (config/themes/light.json,
server/config/themes/default.json, etc.).
@TristanInSec
Copy link
Copy Markdown
Author

Hi @chtyim @ajainarayanan, friendly ping on this security fix and the related #1392, #1393, #1394. CLA is passing, happy to make any changes needed. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant