@@ -3,12 +3,6 @@ import Foundation
33enum CommandResolver {
44 private static let projectRootDefaultsKey = " openclaw.gatewayProjectRootPath "
55 private static let helperName = " openclaw "
6- static let strictHostKeyCheckingSSHOptions = [
7- " -o " , " StrictHostKeyChecking=yes " ,
8- ]
9- static let updateHostKeysSSHOptions = [
10- " -o " , " UpdateHostKeys=yes " ,
11- ]
126
137 static func gatewayEntrypoint( in root: URL ) -> String ? {
148 let distEntry = root. appendingPathComponent ( " dist/index.js " ) . path
@@ -401,9 +395,10 @@ enum CommandResolver {
401395 echo " openclaw CLI missing on remote host " ; exit 127;
402396 fi
403397 """
398+ // Remote credentials require strict host verification unless config explicitly opts into OpenSSH policy.
404399 let options : [ String ] = [
405400 " -o " , " BatchMode=yes " ,
406- ] + self . strictHostKeyCheckingSSHOptions + self . updateHostKeysSSHOptions
401+ ] + settings . sshHostKeyPolicy . commandOptions
407402 let args = self . sshArguments (
408403 target: parsed,
409404 identity: settings. identity,
@@ -412,12 +407,39 @@ enum CommandResolver {
412407 return [ " /usr/bin/ssh " ] + args
413408 }
414409
410+ enum SSHHostKeyPolicy : String {
411+ case strict
412+ case openssh
413+
414+ var hostKeyOptions : [ String ] {
415+ switch self {
416+ case . strict:
417+ [
418+ " -o " , " StrictHostKeyChecking=yes " ,
419+ " -o " , " UpdateHostKeys=yes " ,
420+ ]
421+ case . openssh:
422+ [ ]
423+ }
424+ }
425+
426+ var commandOptions : [ String ] {
427+ [
428+ " -o " , " ControlMaster=no " ,
429+ " -o " , " ControlPath=none " ,
430+ " -o " , " ControlPersist=no " ,
431+ " -o " , " ForkAfterAuthentication=no " ,
432+ ] + self . hostKeyOptions
433+ }
434+ }
435+
415436 struct RemoteSettings {
416437 let mode : AppState . ConnectionMode
417438 let target : String
418439 let identity : String
419440 let projectRoot : String
420441 let cliPath : String
442+ let sshHostKeyPolicy : SSHHostKeyPolicy
421443 }
422444
423445 static func connectionSettings(
@@ -427,20 +449,31 @@ enum CommandResolver {
427449 let root = configRoot ?? OpenClawConfigFile . loadDict ( )
428450 let mode = ConnectionModeResolver . resolve ( root: root, defaults: defaults) . mode
429451 let remote = ( root [ " gateway " ] as? [ String : Any ] ) ? [ " remote " ] as? [ String : Any ]
430- let target = defaults . string ( forKey : remoteTargetKey ) ? . nonEmpty
431- ?? remote ? [ " sshTarget " ] as? String
432- ?? " "
452+ let configuredTarget = self . sanitizedTarget ( remote ? [ " sshTarget " ] as? String ?? " " )
453+ let target = self . sanitizedTarget (
454+ defaults . string ( forKey : remoteTargetKey ) ? . nonEmpty ?? configuredTarget )
433455 let identity = defaults. string ( forKey: remoteIdentityKey) ? . nonEmpty
434456 ?? remote ? [ " sshIdentity " ] as? String
435457 ?? " "
436458 let projectRoot = defaults. string ( forKey: remoteProjectRootKey) ? . nonEmpty ?? " "
437459 let cliPath = defaults. string ( forKey: remoteCliPathKey) ? . nonEmpty ?? " "
460+ let rawHostKeyPolicy = remote ? [ " sshHostKeyPolicy " ] as? String
461+ let configuredHostKeyPolicy = rawHostKeyPolicy. flatMap ( SSHHostKeyPolicy . init ( rawValue: ) ) ?? . strict
462+ let sshHostKeyPolicy : SSHHostKeyPolicy = if configuredHostKeyPolicy == . openssh,
463+ !target. isEmpty,
464+ target == configuredTarget
465+ {
466+ . openssh
467+ } else {
468+ . strict
469+ }
438470 return RemoteSettings (
439471 mode: mode,
440- target: self . sanitizedTarget ( target) ,
472+ target: target,
441473 identity: identity,
442474 projectRoot: projectRoot,
443- cliPath: cliPath)
475+ cliPath: cliPath,
476+ sshHostKeyPolicy: sshHostKeyPolicy)
444477 }
445478
446479 static func connectionModeIsRemote( defaults: UserDefaults = . standard) -> Bool {
0 commit comments