@@ -84,11 +84,16 @@ actor GatewayEndpointStore {
8484 env: [ String : String ] ,
8585 launchdSnapshot: LaunchAgentPlistSnapshot ? ) -> String ?
8686 {
87+ let serviceEnv = launchdSnapshot? . environment ?? [ : ]
8788 let raw = env [ " OPENCLAW_GATEWAY_PASSWORD " ] ?? " "
8889 let trimmed = raw. trimmingCharacters ( in: . whitespacesAndNewlines)
8990 if !trimmed. isEmpty {
90- if let configPassword = self . resolveConfigPassword ( isRemote: isRemote, root: root) ,
91- !configPassword. isEmpty
91+ if let configPassword = self . resolveConfigPassword (
92+ isRemote: isRemote,
93+ root: root,
94+ env: env,
95+ serviceEnv: serviceEnv) ,
96+ !configPassword. isEmpty
9297 {
9398 self . warnEnvOverrideOnce (
9499 kind: . password,
@@ -113,8 +118,11 @@ actor GatewayEndpointStore {
113118 let auth = gateway [ " auth " ] as? [ String : Any ] ,
114119 let password = auth [ " password " ] as? String
115120 {
116- let pw = password. trimmingCharacters ( in: . whitespacesAndNewlines)
117- if !pw. isEmpty {
121+ if let pw = self . resolveLocalConfigAuthString (
122+ password,
123+ env: env,
124+ serviceEnv: serviceEnv)
125+ {
118126 return pw
119127 }
120128 }
@@ -126,7 +134,12 @@ actor GatewayEndpointStore {
126134 return nil
127135 }
128136
129- private static func resolveConfigPassword( isRemote: Bool , root: [ String : Any ] ) -> String ? {
137+ private static func resolveConfigPassword(
138+ isRemote: Bool ,
139+ root: [ String : Any ] ,
140+ env: [ String : String ] = [ : ] ,
141+ serviceEnv: [ String : String ] = [ : ] ) -> String ?
142+ {
130143 if isRemote {
131144 if let gateway = root [ " gateway " ] as? [ String : Any ] ,
132145 let remote = gateway [ " remote " ] as? [ String : Any ] ,
@@ -141,7 +154,7 @@ actor GatewayEndpointStore {
141154 let auth = gateway [ " auth " ] as? [ String : Any ] ,
142155 let password = auth [ " password " ] as? String
143156 {
144- return password . trimmingCharacters ( in : . whitespacesAndNewlines )
157+ return self . resolveLocalConfigAuthString ( password , env : env , serviceEnv : serviceEnv )
145158 }
146159 return nil
147160 }
@@ -152,12 +165,17 @@ actor GatewayEndpointStore {
152165 env: [ String : String ] ,
153166 launchdSnapshot: LaunchAgentPlistSnapshot ? ) -> String ?
154167 {
168+ let serviceEnv = launchdSnapshot? . environment ?? [ : ]
155169 let raw = env [ " OPENCLAW_GATEWAY_TOKEN " ] ?? " "
156170 let trimmed = raw. trimmingCharacters ( in: . whitespacesAndNewlines)
157171 if !trimmed. isEmpty {
158- if let configToken = self . resolveConfigToken ( isRemote: isRemote, root: root) ,
159- !configToken. isEmpty,
160- configToken != trimmed
172+ if let configToken = self . resolveConfigToken (
173+ isRemote: isRemote,
174+ root: root,
175+ env: env,
176+ serviceEnv: serviceEnv) ,
177+ !configToken. isEmpty,
178+ configToken != trimmed
161179 {
162180 self . warnEnvOverrideOnce (
163181 kind: . token,
@@ -167,8 +185,12 @@ actor GatewayEndpointStore {
167185 return trimmed
168186 }
169187
170- if let configToken = self . resolveConfigToken ( isRemote: isRemote, root: root) ,
171- !configToken. isEmpty
188+ if let configToken = self . resolveConfigToken (
189+ isRemote: isRemote,
190+ root: root,
191+ env: env,
192+ serviceEnv: serviceEnv) ,
193+ !configToken. isEmpty
172194 {
173195 return configToken
174196 }
@@ -186,7 +208,12 @@ actor GatewayEndpointStore {
186208 return nil
187209 }
188210
189- private static func resolveConfigToken( isRemote: Bool , root: [ String : Any ] ) -> String ? {
211+ private static func resolveConfigToken(
212+ isRemote: Bool ,
213+ root: [ String : Any ] ,
214+ env: [ String : String ] = [ : ] ,
215+ serviceEnv: [ String : String ] = [ : ] ) -> String ?
216+ {
190217 if isRemote {
191218 return GatewayRemoteConfig . resolveTokenString ( root: root)
192219 }
@@ -195,11 +222,52 @@ actor GatewayEndpointStore {
195222 let auth = gateway [ " auth " ] as? [ String : Any ] ,
196223 let token = auth [ " token " ] as? String
197224 {
198- return token. trimmingCharacters ( in: . whitespacesAndNewlines)
225+ return self . resolveLocalConfigAuthString ( token, env: env, serviceEnv: serviceEnv)
226+ }
227+ return nil
228+ }
229+
230+ private static func resolveLocalConfigAuthString(
231+ _ raw: String ,
232+ env: [ String : String ] ,
233+ serviceEnv: [ String : String ] ) -> String ?
234+ {
235+ let trimmed = raw. trimmingCharacters ( in: . whitespacesAndNewlines)
236+ guard !trimmed. isEmpty else { return nil }
237+ guard let envName = self . envSecretRefName ( trimmed) else {
238+ return trimmed
239+ }
240+ // Finder-launched apps cannot see gateway-service-only env values. Resolve
241+ // local refs from app env first, then the gateway LaunchAgent snapshot.
242+ for source in [ env, serviceEnv] {
243+ let value = source [ envName] ? . trimmingCharacters ( in: . whitespacesAndNewlines)
244+ if let value, !value. isEmpty {
245+ return value
246+ }
199247 }
200248 return nil
201249 }
202250
251+ private static func envSecretRefName( _ value: String ) -> String ? {
252+ let name : Substring
253+ if value. hasPrefix ( " ${ " ) , value. hasSuffix ( " } " ) {
254+ let nameStart = value. index ( value. startIndex, offsetBy: 2 )
255+ let nameEnd = value. index ( before: value. endIndex)
256+ name = value [ nameStart..< nameEnd]
257+ } else if value. hasPrefix ( " $ " ) {
258+ let nameStart = value. index ( after: value. startIndex)
259+ name = value [ nameStart..< value. endIndex]
260+ } else {
261+ return nil
262+ }
263+ let candidate = String ( name)
264+ return self . isValidEnvSecretRefID ( candidate) ? candidate : nil
265+ }
266+
267+ private static func isValidEnvSecretRefID( _ value: String ) -> Bool {
268+ value. range ( of: #"^[A-Z][A-Z0-9_]{0,127}$"# , options: . regularExpression) != nil
269+ }
270+
203271 private static func warnEnvOverrideOnce(
204272 kind: EnvOverrideWarningKind ,
205273 envVar: String ,
0 commit comments