@@ -347,21 +347,27 @@ router.delete('/api/processes/:id', requireAuth, validateProcessId, async (req,
347347 ) ;
348348 return res . status ( 403 ) . json ( { error : 'CSRF token mismatch' } ) ;
349349 }
350+ // Resolve the PM2 name *before* deleting: req.params.id may be a numeric
351+ // pm_id, but the monitoring and deployment records are keyed by name. Once
352+ // the process is gone from PM2 it can no longer be resolved, so cleanup keyed
353+ // off the numeric id would silently miss (the process would reappear as an
354+ // orphan and the deploy directory would never be removed).
355+ const pm2Name = ( await resolvePm2Name ( req . params . id ) . catch ( ( ) => null ) ) ?? req . params . id ;
356+
350357 logger . info (
351358 `[ACTION] User: ${ req . session . username } is deleting process: ${ req . params . id } from identity: ${ getClientIdentity ( req ) } ` ,
352359 ) ;
353360 await pm2 . deleteProcess ( req . params . id ) ;
354361 // Remove the monitoring record if one exists so the process no longer
355362 // appears as an orphan after deletion.
356363 try {
357- removeMonitored ( req . params . id ) ;
364+ removeMonitored ( pm2Name ) ;
358365 } catch {
359366 // No monitoring record - nothing to clean up.
360367 }
361368
362369 // Optionally purge the deployment record and its directory on disk.
363370 if ( req . query . deleteDeploy === 'true' ) {
364- const pm2Name = req . params . id ;
365371 const deployment = getDeploymentByName ( pm2Name ) ;
366372 if ( deployment ) {
367373 if ( deployment . deploy_path ) {
@@ -703,6 +709,17 @@ router.post('/api/settings/general', requireAuth, (req, res) => {
703709 return res . status ( 400 ) . json ( { error : '"settings" must be a plain object.' } ) ;
704710 }
705711
712+ // Values are written verbatim as KEY=VALUE lines. A line break in a value
713+ // would inject arbitrary additional env entries (e.g. overwriting
714+ // AUTH_PASSWORD_HASH), so reject any value containing CR or LF.
715+ const lineBreakKeys = Object . entries ( settings )
716+ . filter ( ( [ key ] ) => key !== 'authPassword' )
717+ . filter ( ( [ , value ] ) => value != null && / [ \r \n ] / . test ( String ( value ) ) )
718+ . map ( ( [ key ] ) => key ) ;
719+ if ( lineBreakKeys . length > 0 ) {
720+ return res . status ( 400 ) . json ( { error : `Values must not contain line breaks: ${ lineBreakKeys . join ( ', ' ) } ` } ) ;
721+ }
722+
706723 try {
707724 const envPath = path . resolve ( __dirname , '..' , '..' , '.env' ) ;
708725
0 commit comments