From c8693b81c162ce89db2df4164afe1d2ee8d866cc Mon Sep 17 00:00:00 2001 From: LuisMIguelFurlanettoSousa Date: Wed, 25 Mar 2026 21:29:59 -0300 Subject: [PATCH 1/8] fix(security): add SSRF validation to map download URLs from manifest --- admin/app/services/map_service.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/admin/app/services/map_service.ts b/admin/app/services/map_service.ts index beb74b2..0cbef4d 100644 --- a/admin/app/services/map_service.ts +++ b/admin/app/services/map_service.ts @@ -17,6 +17,7 @@ import { join, resolve, sep } from 'path' import urlJoin from 'url-join' import { RunDownloadJob } from '#jobs/run_download_job' import logger from '@adonisjs/core/services/logger' +import { assertNotPrivateUrl } from '#validators/common' import InstalledResource from '#models/installed_resource' import { CollectionManifestService } from './collection_manifest_service.js' import type { CollectionWithStatus, MapsSpec } from '../../types/collections.js' @@ -109,6 +110,13 @@ export class MapService implements IMapService { const downloadFilenames: string[] = [] for (const resource of toDownload) { + try { + assertNotPrivateUrl(resource.url) + } catch { + logger.warn(`[MapService] Blocked download from private/loopback URL: ${resource.url}`) + continue + } + const existing = await RunDownloadJob.getByUrl(resource.url) if (existing) { logger.warn(`[MapService] Download already in progress for URL ${resource.url}, skipping.`) @@ -233,6 +241,7 @@ export class MapService implements IMapService { url: string ): Promise<{ filename: string; size: number } | { message: string }> { try { + assertNotPrivateUrl(url) const parsed = new URL(url) if (!parsed.pathname.endsWith('.pmtiles')) { throw new Error(`Invalid PMTiles file URL: ${url}. URL must end with .pmtiles`) @@ -256,7 +265,8 @@ export class MapService implements IMapService { return { filename, size } } catch (error: any) { - return { message: `Preflight check failed: ${error.message}` } + logger.error({ err: error }, '[MapService] Preflight check failed for URL') + return { message: 'Preflight check failed. Please verify the URL is valid and accessible.' } } } From fa1036bd31c8d197de424831d13a6e7bd322bb35 Mon Sep 17 00:00:00 2001 From: LuisMIguelFurlanettoSousa Date: Wed, 25 Mar 2026 21:30:17 -0300 Subject: [PATCH 2/8] fix(security): sanitize verbose error in rag controller scan endpoint --- admin/app/controllers/rag_controller.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/admin/app/controllers/rag_controller.ts b/admin/app/controllers/rag_controller.ts index ce94876..88d33c7 100644 --- a/admin/app/controllers/rag_controller.ts +++ b/admin/app/controllers/rag_controller.ts @@ -6,6 +6,7 @@ import app from '@adonisjs/core/services/app' import { randomBytes } from 'node:crypto' import { sanitizeFilename } from '../utils/fs.js' import { deleteFileSchema, getJobStatusSchema } from '#validators/rag' +import logger from '@adonisjs/core/services/logger' @inject() export default class RagController { @@ -79,7 +80,8 @@ export default class RagController { const syncResult = await this.ragService.scanAndSyncStorage() return response.status(200).json(syncResult) } catch (error) { - return response.status(500).json({ error: 'Error scanning and syncing storage', details: error.message }) + logger.error({ err: error }, '[RagController] Error scanning and syncing storage') + return response.status(500).json({ error: 'Error scanning and syncing storage' }) } } } From a85a38e92297e25891c709c1beeb12fb6dfc324d Mon Sep 17 00:00:00 2001 From: LuisMIguelFurlanettoSousa Date: Wed, 25 Mar 2026 21:30:35 -0300 Subject: [PATCH 3/8] fix(security): sanitize verbose errors in benchmark controller --- admin/app/controllers/benchmark_controller.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/admin/app/controllers/benchmark_controller.ts b/admin/app/controllers/benchmark_controller.ts index b3e5343..da483c0 100644 --- a/admin/app/controllers/benchmark_controller.ts +++ b/admin/app/controllers/benchmark_controller.ts @@ -5,6 +5,7 @@ import { runBenchmarkValidator, submitBenchmarkValidator } from '#validators/ben import { RunBenchmarkJob } from '#jobs/run_benchmark_job' import type { BenchmarkType } from '../../types/benchmark.js' import { randomUUID } from 'node:crypto' +import logger from '@adonisjs/core/services/logger' @inject() export default class BenchmarkController { @@ -52,9 +53,10 @@ export default class BenchmarkController { result, }) } catch (error) { + logger.error({ err: error }, '[BenchmarkController] Benchmark run failed') return response.status(500).send({ success: false, - error: error.message, + error: 'An internal error occurred while running the benchmark.', }) } } @@ -181,9 +183,10 @@ export default class BenchmarkController { } catch (error) { // Pass through the status code from the service if available, otherwise default to 400 const statusCode = (error as any).statusCode || 400 + logger.error({ err: error }, '[BenchmarkController] Benchmark submit failed') return response.status(statusCode).send({ success: false, - error: error.message, + error: 'Failed to submit benchmark results.', }) } } From 8ca8c6c6b88e23d97589300fc7191d1435568b16 Mon Sep 17 00:00:00 2001 From: LuisMIguelFurlanettoSousa Date: Wed, 25 Mar 2026 21:30:49 -0300 Subject: [PATCH 4/8] fix(security): sanitize verbose error in system controller version fetch --- admin/app/controllers/system_controller.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/admin/app/controllers/system_controller.ts b/admin/app/controllers/system_controller.ts index fbc872a..8967e6d 100644 --- a/admin/app/controllers/system_controller.ts +++ b/admin/app/controllers/system_controller.ts @@ -6,6 +6,7 @@ import { CheckServiceUpdatesJob } from '#jobs/check_service_updates_job' import { affectServiceValidator, checkLatestVersionValidator, installServiceValidator, subscribeToReleaseNotesValidator, updateServiceValidator } from '#validators/system'; import { inject } from '@adonisjs/core' import type { HttpContext } from '@adonisjs/core/http' +import logger from '@adonisjs/core/services/logger' @inject() export default class SystemController { @@ -144,7 +145,8 @@ export default class SystemController { ) response.send({ versions: updates }) } catch (error) { - response.status(500).send({ error: `Failed to fetch versions: ${error.message}` }) + logger.error({ err: error }, `[SystemController] Failed to fetch versions for ${serviceName}`) + response.status(500).send({ error: 'Failed to fetch available versions for this service.' }) } } From a107dbe4296e0fdd7313284fb1142d39a0f11f93 Mon Sep 17 00:00:00 2001 From: LuisMIguelFurlanettoSousa Date: Wed, 25 Mar 2026 21:31:30 -0300 Subject: [PATCH 5/8] fix(security): sanitize verbose errors in chats controller (6 instances) --- admin/app/controllers/chats_controller.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/admin/app/controllers/chats_controller.ts b/admin/app/controllers/chats_controller.ts index 005e60d..ff25f8b 100644 --- a/admin/app/controllers/chats_controller.ts +++ b/admin/app/controllers/chats_controller.ts @@ -5,6 +5,7 @@ import { createSessionSchema, updateSessionSchema, addMessageSchema } from '#val import KVStore from '#models/kv_store' import { SystemService } from '#services/system_service' import { SERVICE_NAMES } from '../../constants/service_names.js' +import logger from '@adonisjs/core/services/logger' @inject() export default class ChatsController { @@ -45,8 +46,9 @@ export default class ChatsController { const session = await this.chatService.createSession(data.title, data.model) return response.status(201).json(session) } catch (error) { + logger.error({ err: error }, '[ChatsController] Failed to create session') return response.status(500).json({ - error: error instanceof Error ? error.message : 'Failed to create session', + error: 'Failed to create session', }) } } @@ -56,8 +58,9 @@ export default class ChatsController { const suggestions = await this.chatService.getChatSuggestions() return response.status(200).json({ suggestions }) } catch (error) { + logger.error({ err: error }, '[ChatsController] Failed to get suggestions') return response.status(500).json({ - error: error instanceof Error ? error.message : 'Failed to get suggestions', + error: 'Failed to get suggestions', }) } } @@ -69,8 +72,9 @@ export default class ChatsController { const session = await this.chatService.updateSession(sessionId, data) return session } catch (error) { + logger.error({ err: error }, '[ChatsController] Failed to update session') return response.status(500).json({ - error: error instanceof Error ? error.message : 'Failed to update session', + error: 'Failed to update session', }) } } @@ -81,8 +85,9 @@ export default class ChatsController { await this.chatService.deleteSession(sessionId) return response.status(204) } catch (error) { + logger.error({ err: error }, '[ChatsController] Failed to delete session') return response.status(500).json({ - error: error instanceof Error ? error.message : 'Failed to delete session', + error: 'Failed to delete session', }) } } @@ -94,8 +99,9 @@ export default class ChatsController { const message = await this.chatService.addMessage(sessionId, data.role, data.content) return response.status(201).json(message) } catch (error) { + logger.error({ err: error }, '[ChatsController] Failed to add message') return response.status(500).json({ - error: error instanceof Error ? error.message : 'Failed to add message', + error: 'Failed to add message', }) } } @@ -105,8 +111,9 @@ export default class ChatsController { const result = await this.chatService.deleteAllSessions() return response.status(200).json(result) } catch (error) { + logger.error({ err: error }, '[ChatsController] Failed to delete all sessions') return response.status(500).json({ - error: error instanceof Error ? error.message : 'Failed to delete all sessions', + error: 'Failed to delete all sessions', }) } } From 07348c293e0428f5df6694d4e59d313c52e01378 Mon Sep 17 00:00:00 2001 From: LuisMIguelFurlanettoSousa Date: Wed, 25 Mar 2026 21:32:15 -0300 Subject: [PATCH 6/8] fix(security): sanitize verbose errors in docker service (6 instances) --- admin/app/services/docker_service.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/admin/app/services/docker_service.ts b/admin/app/services/docker_service.ts index 72ff4ea..40287f6 100644 --- a/admin/app/services/docker_service.ts +++ b/admin/app/services/docker_service.ts @@ -92,10 +92,10 @@ export class DockerService { message: `Invalid action: ${action}. Use 'start', 'stop', or 'restart'.`, } } catch (error) { - logger.error(`Error starting service ${serviceName}: ${error.message}`) + logger.error({ err: error }, `[DockerService] Error controlling service ${serviceName}`) return { success: false, - message: `Failed to start service ${serviceName}: ${error.message}`, + message: `Failed to ${action} service ${serviceName}. Check server logs for details.`, } } } @@ -308,8 +308,8 @@ export class DockerService { ) } } catch (error) { - logger.warn(`Error during container cleanup: ${error.message}`) - this._broadcast(serviceName, 'cleanup-warning', `Warning during cleanup: ${error.message}`) + logger.warn({ err: error }, `[DockerService] Error during container cleanup for ${serviceName}`) + this._broadcast(serviceName, 'cleanup-warning', 'Warning during container cleanup. Check server logs for details.') } // Step 3: Clear volumes/data if needed @@ -335,11 +335,11 @@ export class DockerService { this._broadcast(serviceName, 'no-volumes', `No volumes found to clear`) } } catch (error) { - logger.warn(`Error during volume cleanup: ${error.message}`) + logger.warn({ err: error }, `[DockerService] Error during volume cleanup for ${serviceName}`) this._broadcast( serviceName, 'volume-cleanup-warning', - `Warning during volume cleanup: ${error.message}` + 'Warning during volume cleanup. Check server logs for details.' ) } @@ -363,11 +363,11 @@ export class DockerService { message: `Service ${serviceName} force reinstall initiated successfully. You can receive updates via server-sent events.`, } } catch (error) { - logger.error(`Force reinstall failed for ${serviceName}: ${error.message}`) + logger.error({ err: error }, `[DockerService] Force reinstall failed for ${serviceName}`) await this._cleanupFailedInstallation(serviceName) return { success: false, - message: `Failed to force reinstall service ${serviceName}: ${error.message}`, + message: `Failed to force reinstall service ${serviceName}. Check server logs for details.`, } } } @@ -601,10 +601,10 @@ export class DockerService { return { success: true, message: `Service ${serviceName} container removed successfully` } } catch (error) { - logger.error(`Error removing service container: ${error.message}`) + logger.error({ err: error }, `[DockerService] Error removing service container ${serviceName}`) return { success: false, - message: `Failed to remove service ${serviceName} container: ${error.message}`, + message: `Failed to remove service ${serviceName} container. Check server logs for details.`, } } } @@ -1028,10 +1028,10 @@ export class DockerService { this._broadcast( serviceName, 'update-rollback', - `Update failed: ${error.message}` + 'Update failed. Check server logs for details.' ) - logger.error(`[DockerService] Update failed for ${serviceName}: ${error.message}`) - return { success: false, message: `Update failed: ${error.message}` } + logger.error({ err: error }, `[DockerService] Update failed for ${serviceName}`) + return { success: false, message: 'Update failed. Check server logs for details.' } } } From 9d7e7cbf4cb81626c8d69675b3c05dc778847e5e Mon Sep 17 00:00:00 2001 From: LuisMIguelFurlanettoSousa Date: Wed, 25 Mar 2026 21:32:26 -0300 Subject: [PATCH 7/8] fix(security): sanitize verbose error in system update service --- admin/app/services/system_update_service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/app/services/system_update_service.ts b/admin/app/services/system_update_service.ts index 20031eb..73f0791 100644 --- a/admin/app/services/system_update_service.ts +++ b/admin/app/services/system_update_service.ts @@ -47,10 +47,10 @@ export class SystemUpdateService { message: 'System update initiated. The admin container will restart during the process.', } } catch (error) { - logger.error('[SystemUpdateService]: Failed to request system update:', error) + logger.error({ err: error }, '[SystemUpdateService] Failed to request system update') return { success: false, - message: `Failed to request update: ${error.message}`, + message: 'Failed to request system update. Check server logs for details.', } } } From d0ef6c46d33453b47f35939cc06feeb141239f45 Mon Sep 17 00:00:00 2001 From: LuisMIguelFurlanettoSousa Date: Wed, 25 Mar 2026 21:32:48 -0300 Subject: [PATCH 8/8] fix(security): sanitize verbose errors in collection update service --- admin/app/services/collection_update_service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/app/services/collection_update_service.ts b/admin/app/services/collection_update_service.ts index b1e06d1..fee6c14 100644 --- a/admin/app/services/collection_update_service.ts +++ b/admin/app/services/collection_update_service.ts @@ -65,7 +65,7 @@ export class CollectionUpdateService { return { updates: [], checked_at: new Date().toISOString(), - error: `Nomad API returned status ${error.response.status}`, + error: 'Failed to check for content updates. The update service may be temporarily unavailable.', } } const message = @@ -74,7 +74,7 @@ export class CollectionUpdateService { return { updates: [], checked_at: new Date().toISOString(), - error: `Failed to contact Nomad API: ${message}`, + error: 'Failed to contact the update service. Please try again later.', } } }