Fix 5 critical security vulnerabilities
1. WebSocket exec auth bypass (exec.js): Require valid JWT or API key before accepting WebSocket upgrade. Reject unauthenticated requests with 401 before the upgrade completes. 2. Shell injection in router auto-login (session-handlers.js): Validate baseUrl against safe hostname pattern before embedding in wget shell command. Reject with null session if invalid. 3. Path traversal in credentials routes (services.js): Add explicit serviceId validation (alphanumeric + dash/underscore/dot, max 100 chars) to all three credential endpoints. Removed redundant try/catch wrapper. 4. execSync injection in CA CSR generation (ca.js): Add sanitize step replacing any non-alphanumeric domain chars with underscore before interpolation into shell subj argument. Redundant with existing validation but provides defense-in-depth. 5. Auth bypass when TOTP disabled (middleware.js): Split the logic cleanly — disabled TOTP means no auth (initial setup state), enabled TOTP means all auth methods checked (session/JWT/API key). Removed the sessionDuration:never conflating shortcut.
This commit is contained in:
@@ -196,8 +196,14 @@ module.exports = function({
|
||||
// ===== SERVICE CREDENTIAL ENDPOINTS =====
|
||||
|
||||
// Store credentials for a service
|
||||
router.post('/services/:serviceId/credentials', asyncHandler(async (req, res) => {
|
||||
router.post('/:serviceId/credentials', asyncHandler(async (req, res) => {
|
||||
const { serviceId } = req.params;
|
||||
|
||||
// Validate serviceId to prevent path traversal in credential keys
|
||||
if (!serviceId || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$/.test(serviceId)) {
|
||||
return ctx.errorResponse(res, 400, 'Invalid service ID');
|
||||
}
|
||||
|
||||
const { apiKey, username, password } = req.body;
|
||||
|
||||
if (apiKey) {
|
||||
@@ -214,8 +220,14 @@ module.exports = function({
|
||||
}, 'store-service-creds'));
|
||||
|
||||
// Delete credentials for a service
|
||||
router.delete('/services/:serviceId/credentials', asyncHandler(async (req, res) => {
|
||||
router.delete('/:serviceId/credentials', asyncHandler(async (req, res) => {
|
||||
const { serviceId } = req.params;
|
||||
|
||||
// Validate serviceId to prevent path traversal in credential keys
|
||||
if (!serviceId || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$/.test(serviceId)) {
|
||||
return ctx.errorResponse(res, 400, 'Invalid service ID');
|
||||
}
|
||||
|
||||
await credentialManager.delete(`service.${serviceId}.apikey`);
|
||||
await credentialManager.delete(`service.${serviceId}.username`);
|
||||
await credentialManager.delete(`service.${serviceId}.password`);
|
||||
@@ -223,9 +235,13 @@ module.exports = function({
|
||||
}, 'delete-service-creds'));
|
||||
|
||||
// Check credential status for a service (what's stored)
|
||||
router.get('/services/:serviceId/credentials', asyncHandler(async (req, res) => {
|
||||
try {
|
||||
const { serviceId } = req.params;
|
||||
router.get('/:serviceId/credentials', asyncHandler(async (req, res) => {
|
||||
const { serviceId } = req.params;
|
||||
|
||||
// Validate serviceId to prevent path traversal in credential keys
|
||||
if (!serviceId || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$/.test(serviceId)) {
|
||||
return ctx.errorResponse(res, 400, 'Invalid service ID');
|
||||
}
|
||||
const arrKey = await credentialManager.retrieve(`arr.${serviceId}.apikey`).catch(() => null);
|
||||
const svcKey = await credentialManager.retrieve(`service.${serviceId}.apikey`).catch(() => null);
|
||||
const username = await credentialManager.retrieve(`service.${serviceId}.username`).catch(() => null);
|
||||
@@ -234,9 +250,6 @@ module.exports = function({
|
||||
hasBasicAuth: !!username,
|
||||
username: username || null
|
||||
});
|
||||
} catch (error) {
|
||||
success(res, { hasApiKey: false, hasBasicAuth: false });
|
||||
}
|
||||
}, 'service-creds'));
|
||||
|
||||
// ===== SEEDHOST CREDENTIAL ENDPOINTS =====
|
||||
|
||||
Reference in New Issue
Block a user