@@ -401,10 +401,31 @@ function nmea2000input(
401401 // PLAIN/FAST CSV on stdout, which the downstream N2kAnalyzer decodes.
402402 // Where toChildProcess is set, the same CSV format is accepted on stdin
403403 // for outbound PGNs (encoded by canboatjs in execute.ts).
404+ //
405+ // Execute runs the command through a shell, so every provider-config
406+ // value is validated before interpolation: hosts/devices/interfaces
407+ // against the character set their legitimate values need, numbers as
408+ // finite. Anything else fails the provider loudly.
409+ const safeArg = ( value : unknown , what : string ) : string => {
410+ const s = String ( value ?? '' )
411+ if ( ! / ^ [ A - Z a - z 0 - 9 _ . : / - ] + $ / . test ( s ) ) {
412+ throw new Error ( `invalid ${ what } for NMEA 2000 connection: '${ s } '` )
413+ }
414+ return s
415+ }
416+ const safeNum = ( value : unknown , what : string ) : number => {
417+ const n = Number ( value )
418+ if ( ! Number . isFinite ( n ) ) {
419+ throw new Error (
420+ `invalid ${ what } for NMEA 2000 connection: '${ String ( value ) } '`
421+ )
422+ }
423+ return n
424+ }
404425 let command : string
405426 let toChildProcess : string | undefined
406427 if ( subOptions . type === 'ngt-1' ) {
407- command = `actisense-serial -s ${ subOptions . baudrate ?? 115200 } ${ subOptions . device } `
428+ command = `actisense-serial -s ${ safeNum ( subOptions . baudrate ?? 115200 , 'baud rate' ) } ${ safeArg ( subOptions . device , 'device' ) } `
408429 toChildProcess = 'nmea2000out'
409430 } else if ( subOptions . type === 'canbus' ) {
410431 // canboat's socketcan-serial rather than candump|candump2analyzer:
@@ -415,34 +436,35 @@ function nmea2000input(
415436 command =
416437 `socketcan-serial` +
417438 ( subOptions . uniqueNumber
418- ? ` -u ${ Number ( subOptions . uniqueNumber ) } `
439+ ? ` -u ${ safeNum ( subOptions . uniqueNumber , 'unique number' ) } `
440+ : '' ) +
441+ ( subOptions . mfgCode
442+ ? ` -m ${ safeNum ( subOptions . mfgCode , 'manufacturer code' ) } `
419443 : '' ) +
420- ( subOptions . mfgCode ? ` -m ${ Number ( subOptions . mfgCode ) } ` : '' ) +
421- ` ${ subOptions . interface } `
444+ ` ${ safeArg ( subOptions . interface , 'interface' ) } `
422445 toChildProcess = 'nmea2000out'
423446 } else if ( subOptions . type === 'ikonvert' ) {
424- command = `ikonvert-serial -s ${ subOptions . baudrate ?? 230400 } ${ subOptions . device } `
447+ command = `ikonvert-serial -s ${ safeNum ( subOptions . baudrate ?? 230400 , 'baud rate' ) } ${ safeArg ( subOptions . device , 'device' ) } `
425448 toChildProcess = 'nmea2000out'
426449 } else if ( subOptions . type === 'maretron-ipg' ) {
427450 const password = ( subOptions as { password ?: string } ) . password
428451 command =
429452 `maretron-ipg` +
430453 ( password ? ` --password=${ shellescape ( password ) } ` : '' ) +
431- ` tcp://${ subOptions . host } :${ subOptions . port ?? 6543 } `
454+ ` tcp://${ safeArg ( subOptions . host , 'host' ) } :${ safeNum ( subOptions . port ?? 6543 , 'port' ) } `
432455 toChildProcess = 'nmea2000out'
433456 } else if ( subOptions . type === 'ydwg02' ) {
434- command = `canboat interface --kind ydwg tcp://${ subOptions . host } :${ subOptions . port ?? 1457 } `
457+ command = `canboat interface --kind ydwg tcp://${ safeArg ( subOptions . host , 'host' ) } :${ safeNum ( subOptions . port ?? 1457 , 'port' ) } `
435458 toChildProcess = 'nmea2000out'
436459 } else if ( subOptions . type === 'ydwg02-udp' ) {
437- // The YDWG-02's UDP mode is receive-only; the bridge drops
438- // outbound frames with a warning.
439- command = `canboat interface --kind ydwg udp://${ subOptions . port ?? 1457 } `
440- toChildProcess = 'nmea2000out'
460+ // The YDWG-02's UDP mode is receive-only — no stdin wiring, so
461+ // the admin UI never reports transmit activity for it.
462+ command = `canboat interface --kind ydwg udp://${ safeNum ( subOptions . port ?? 1457 , 'port' ) } `
441463 } else if ( subOptions . type === 'navlink2' ) {
442- command = `canboat interface --kind ikonvert tcp://${ subOptions . host } :${ subOptions . port ?? 6001 } `
464+ command = `canboat interface --kind ikonvert tcp://${ safeArg ( subOptions . host , 'host' ) } :${ safeNum ( subOptions . port ?? 6001 , 'port' ) } `
443465 toChildProcess = 'nmea2000out'
444466 } else if ( subOptions . type === 'w2k-1-ascii' ) {
445- command = `canboat interface --kind w2k-ascii tcp://${ subOptions . host } :${ subOptions . port ?? 60002 } `
467+ command = `canboat interface --kind w2k-ascii tcp://${ safeArg ( subOptions . host , 'host' ) } :${ safeNum ( subOptions . port ?? 60002 , 'port' ) } `
446468 toChildProcess = 'nmea2000out'
447469 } else {
448470 throw new Error ( `unknown NMEA2000 type ${ subOptions . type } ` )
0 commit comments