fix(plugins): harden input validation and scan reliability

- Validate plugin names against [a-zA-Z0-9_-] allowlist in
  manifest and asset routes to prevent path traversal via URL
- Strip embedded credentials (user:pass@) from git remote URLs
  before exposing them to the client
- Skip .tmp-* directories during scan to avoid partial installs
  from in-progress updates appearing as broken plugins
- Deduplicate plugins sharing the same manifest name to prevent
  ambiguous state
- Guard RPC proxy error handler against writing to an already-sent
  response, preventing uncaught exceptions on aborted requests
This commit is contained in:
simosmik
2026-03-09 07:59:46 +00:00
parent ca247cddae
commit ca16342a20
4 changed files with 51 additions and 6 deletions

View File

@@ -39,6 +39,9 @@ router.get('/', (req, res) => {
// GET /:name/manifest — Get single plugin manifest
router.get('/:name/manifest', (req, res) => {
try {
if (!/^[a-zA-Z0-9_-]+$/.test(req.params.name)) {
return res.status(400).json({ error: 'Invalid plugin name' });
}
const plugins = scanPlugins();
const plugin = plugins.find(p => p.name === req.params.name);
if (!plugin) {
@@ -53,6 +56,9 @@ router.get('/:name/manifest', (req, res) => {
// GET /:name/assets/* — Serve plugin static files
router.get('/:name/assets/*', (req, res) => {
const pluginName = req.params.name;
if (!/^[a-zA-Z0-9_-]+$/.test(pluginName)) {
return res.status(400).json({ error: 'Invalid plugin name' });
}
const assetPath = req.params[0];
if (!assetPath) {
@@ -252,7 +258,11 @@ router.all('/:name/rpc/*', async (req, res) => {
});
proxyReq.on('error', (err) => {
res.status(502).json({ error: 'Plugin server error', details: err.message });
if (!res.headersSent) {
res.status(502).json({ error: 'Plugin server error', details: err.message });
} else {
res.end();
}
});
// Forward body (already parsed by express JSON middleware, so re-stringify).