Repository navigation
Escape git-installed plugin metadata in the Plugin Manager installed list - #31
Merged
Merged
Conversation
The Plugin Manager's installed list built each row with innerHTML and interpolated the cloned repository's plugin.json name, description and version into it, put plugin.name into a data-name attribute, and interpolated the listInstalled error string the same way. Both renderer windows run with webSecurity:false, so there is no CSP to make a stray < inert: a repository whose plugin.json carries markup in its description executed in the app as soon as the list was drawn. The rows are now built from nodes — metadata via textContent, the button target via dataset — so there is nothing left to escape. The listInstalled failure is rendered as text for the same reason, and refreshCatalog's catch picks up the same e?.message || e idiom the rest of the file already uses. The same pattern turned up in the audio screen's two list rows, which read names off disk (a VST bundle is a directory name): the signal chain row's processor name and the VST browser row's name, manufacturer and format now go through escHtml, the helper the rest of that screen uses. tests/renderer-html-escaping.test.js runs the plugin manager screen against a stubbed document with a hostile plugin.json and asserts the metadata survives as text, that no part of the row is built from markup, and that a quote in the plugin name still reaches the bridge intact; it lifts refreshChain and renderVSTList for the audio screen. Each guard was verified to fail when its regression is re-injected.
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Security | 2 critical 1 high |
🟢 Metrics 0 complexity · 0 duplication
Metric Results Complexity 0 Duplication 0
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (3 files)
Reviewed by free · Input: 216.5K · Output: 3.7K · Cached: 302.4K |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #29.
The problem
refreshList()insrc/renderer/plugin-manager/screen.jsbuilt its rows withinnerHTMLand interpolated git-installed plugin metadata straight into the template:manifest.name,manifest.description,plugin.versionplugin.nameinto adata-name="${…}"attribute, where a"would truncate itlistInstallederror string, unescapedThose values come from the
plugin.jsonof whatever repository the user cloned, and the renderer windows run withwebSecurity: false(src/main/main.ts:413) — no CSP, no same-origin fallback. A repository shipping"description": "<img src=x onerror=…>"executed in the app the moment the list was drawn.The fix
The rows are built from nodes instead of a template: metadata goes in through
textContentand the button target throughdataset, so there is nothing to escape and no way to get the escaping wrong.installedRow()/installedAction()hold the shape the old template produced (same classes, samepm-update/pm-removehooks, so the existing bindings still match). ThelistInstalledfailure is rendered as text for the same reason, andrefreshCatalog's catch picked up thee?.message || eidiom the rest of the file already uses at two other call sites.Following the issue's suggestion to sweep the other renderers while the helper was fresh: the audio screen's two list rows read names off disk — a VST bundle is a directory name — so
refreshChain's processor name andrenderVSTList's name, manufacturer and format now go throughescHtml, the helper the rest of that screen uses. I swept every remaining${…}insrc/renderer/screen.jsandsrc/main/wizard.js; the unescaped remainder is fixed-map lookups (typeNames[slot.type]) and numeric counts, andwizard.jsis alreadytextContent-only.slot.idis left interpolated bare into the chain row's inlineonclickhandlers. It comes from the native addon's slot counter rather than off disk, so it isn't in the attacker-controlled set the sweep targets; the trust boundary is our own compiled.node. Worth a separate look if you disagree.Tests
New
tests/renderer-html-escaping.test.js(7 tests, no dependencies beyond builtins). It runs the plugin manager screen whole against a stubbed document with a hostileplugin.json, and asserts:"in it — and that name reaches the bridge intact when clickedlistInstalledfailure renders as text, thrown as anErroror as a bare stringFor the audio screen it lifts
refreshChainandrenderVSTListout of the source and asserts the hostile string is present escaped, not dropped, so the assertions can't pass vacuously. Each guard was verified to fail when its regression is re-injected, including a targeted check that the "no markup" assertion bites on its own.Verification
npm run typecheck— clean.npm test— 244 tests, 237 pass, 6 skipped, 1 fail. The one failure is pre-existing and unrelated:tests/juce-exclusive-type-name.test.jsreadsJUCE/modules/juce_audio_devices/native/juce_WASAPI_windows.cpp, and the five git submodules are not populated in a clean checkout, so it fails withENOENT.node --checkon both renderer screens.Full
npm run build:ts/npm run build:audiowas not run — the latter needs the submodules and a native toolchain this environment doesn't have.🤖 Generated with Pullfrog
big-pickle(free) | 𝕏