fix(security): escape user-controlled output in plugin action UI (#323)

* style: trim trailing whitespace and fix EOF in plugins_manager.js

Autofix from pre-commit hooks (trailing-whitespace, end-of-file-fixer).
No code logic changes — purely whitespace.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(security): escape user-controlled output in plugin action UI

The plugin action result handlers in executePluginAction() injected
data.message, data.output, and data.auth_url directly into innerHTML
template literals without escaping. A plugin's action handler returning
malicious content (e.g., from a third-party plugin or compromised
upstream) could execute arbitrary JavaScript in the web UI context.

Wrap user-controlled strings in escapeHtml() at all four sites:
- Step-2 (continuation) error path (message + output)
- OAuth flow auth_url (link href + display text, with http:// guard)
- Step-1 simple-success output
- Step-1 failure path (message + output)

The escapeHtml() helper is already defined in this file and used
elsewhere (validation errors, plugin store cards).

Co-Authored-By: 5ymb01 <5ymb01@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: 5ymb01 <5ymb01@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
5ymb01
2026-05-06 10:12:31 -04:00
committed by GitHub
parent ceb4c4105f
commit b361866679

View File

@@ -4851,9 +4851,9 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
showNotification(data.message || 'Action completed successfully!', 'success');
}
} else {
statusDiv.innerHTML = `<div class="text-red-600"><i class="fas fa-exclamation-circle mr-2"></i>${data.message}</div>`;
statusDiv.innerHTML = `<div class="text-red-600"><i class="fas fa-exclamation-circle mr-2"></i>${escapeHtml(data.message || 'Error')}</div>`;
if (data.output) {
statusDiv.innerHTML += `<pre class="mt-2 text-xs bg-red-50 p-2 rounded overflow-auto max-h-32">${data.output}</pre>`;
statusDiv.innerHTML += `<pre class="mt-2 text-xs bg-red-50 p-2 rounded overflow-auto max-h-32">${escapeHtml(data.output)}</pre>`;
}
btn.innerHTML = originalText;
btn.disabled = false;
@@ -4897,8 +4897,8 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
</div>
<div class="mb-3">
<p class="text-sm text-blue-700 mb-2">1. Click the link below to authorize:</p>
<a href="${data.auth_url}" target="_blank" class="text-blue-600 hover:text-blue-800 underline break-all">
${data.auth_url}
<a href="${data.auth_url && data.auth_url.startsWith('http') ? escapeHtml(data.auth_url) : '#'}" target="_blank" class="text-blue-600 hover:text-blue-800 underline break-all">
${escapeHtml(data.auth_url || '')}
</a>
</div>
<div class="mb-2">
@@ -4920,7 +4920,7 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
<div class="text-green-900 font-medium mb-2">
<i class="fas fa-check-circle mr-2"></i>${data.message || 'Action completed successfully'}
</div>
${data.output ? `<pre class="mt-2 text-xs bg-green-50 p-2 rounded overflow-auto max-h-32">${data.output}</pre>` : ''}
${data.output ? `<pre class="mt-2 text-xs bg-green-50 p-2 rounded overflow-auto max-h-32">${escapeHtml(data.output)}</pre>` : ''}
</div>
`;
btn.innerHTML = originalText;
@@ -4933,9 +4933,9 @@ window.executePluginAction = function(actionId, actionIndex, pluginIdParam = nul
statusDiv.innerHTML = `
<div class="bg-red-50 border border-red-200 rounded p-3">
<div class="text-red-900 font-medium mb-2">
<i class="fas fa-exclamation-circle mr-2"></i>${data.message || 'Action failed'}
<i class="fas fa-exclamation-circle mr-2"></i>${escapeHtml(data.message || 'Action failed')}
</div>
${data.output ? `<pre class="mt-2 text-xs bg-red-50 p-2 rounded overflow-auto max-h-32">${data.output}</pre>` : ''}
${data.output ? `<pre class="mt-2 text-xs bg-red-50 p-2 rounded overflow-auto max-h-32">${escapeHtml(data.output)}</pre>` : ''}
</div>
`;
btn.innerHTML = originalText;
@@ -8065,4 +8065,3 @@ setTimeout(function() {
}
});
})();