@@ -101,6 +101,11 @@ function shellEscape (value: string): string {
101101 return "'" + value . replace ( / ' / g, "'\\''" ) + "'"
102102}
103103
104+ function psEncodedCommand ( expression : string ) : string {
105+ const utf16le = Buffer . from ( expression , 'utf16le' )
106+ return `powershell -NoProfile -NonInteractive -EncodedCommand ${ utf16le . toString ( 'base64' ) } `
107+ }
108+
104109const MAX_FILE_SIZE = 64 * 1024 // 64 KB
105110
106111function fileResolver ( params : string ) : string {
@@ -137,42 +142,136 @@ function cmdResolver (params: string): string {
137142 }
138143}
139144
145+ const PRINTABLE_ASCII_RE = / ^ [ \x20 - \x7e ] + $ /
146+
147+ function parseServiceAccount ( params : string , resolverName : string ) : { service : string ; account : string } {
148+ if ( ! PRINTABLE_ASCII_RE . test ( params ) ) {
149+ throw new Error (
150+ `Invalid ${ resolverName } parameter "${ params } ": contains non-printable characters`
151+ )
152+ }
153+
154+ const slashIndex = params . indexOf ( '/' )
155+ if ( slashIndex === - 1 || slashIndex === 0 || slashIndex === params . length - 1 ) {
156+ throw new Error (
157+ `Invalid ${ resolverName } parameter "${ params } ": expected format "service/account" ` +
158+ `(e.g., "elastic-cli/api-key")`
159+ )
160+ }
161+
162+ return { service : params . slice ( 0 , slashIndex ) , account : params . slice ( slashIndex + 1 ) }
163+ }
164+
140165function keychainResolver ( params : string ) : string {
141166 if ( _platform !== 'darwin' ) {
142167 throw new Error (
143168 `The keychain resolver is only supported on macOS (current platform: ${ _platform } )`
144169 )
145170 }
146171
147- if ( ! / ^ [ \x20 - \x7e ] + $ / . test ( params ) ) {
172+ const { service, account } = parseServiceAccount ( params , 'keychain' )
173+
174+ try {
175+ return _execSync (
176+ `security find-generic-password -s ${ shellEscape ( service ) } -a ${ shellEscape ( account ) } -w` ,
177+ execOpts ( 5_000 )
178+ ) . trimEnd ( )
179+ } catch ( err ) {
180+ const message = err instanceof Error ? err . message : String ( err )
148181 throw new Error (
149- `Invalid keychain parameter "${ params } ": contains non-printable characters`
182+ `Keychain lookup failed for service="${ service } ", account="${ account } ": ${ message } ` ,
183+ { cause : err }
150184 )
151185 }
186+ }
152187
153- const slashIndex = params . indexOf ( '/' )
154- if ( slashIndex === - 1 || slashIndex === 0 || slashIndex === params . length - 1 ) {
188+ function secretServiceResolver ( params : string ) : string {
189+ if ( _platform !== 'linux' ) {
155190 throw new Error (
156- `Invalid keychain parameter "${ params } ": expected format "service/account" ` +
157- `(e.g., "elastic-cli/api-key")`
191+ `The secret_service resolver is only supported on Linux (current platform: ${ _platform } )`
158192 )
159193 }
160194
161- const service = params . slice ( 0 , slashIndex )
162- const account = params . slice ( slashIndex + 1 )
195+ const { service, account } = parseServiceAccount ( params , 'secret_service' )
163196
164197 try {
165198 return _execSync (
166- `security find-generic-password -s ${ shellEscape ( service ) } -a ${ shellEscape ( account ) } -w ` ,
199+ `secret-tool lookup service ${ shellEscape ( service ) } account ${ shellEscape ( account ) } ` ,
167200 execOpts ( 5_000 )
168201 ) . trimEnd ( )
169202 } catch ( err ) {
170203 const message = err instanceof Error ? err . message : String ( err )
171204 throw new Error (
172- `Keychain lookup failed for service="${ service } ", account="${ account } ": ${ message } ` ,
205+ `Secret Service lookup failed for service="${ service } ", account="${ account } ": ${ message } ` ,
206+ { cause : err }
207+ )
208+ }
209+ }
210+
211+ function passResolver ( params : string ) : string {
212+ const path = params . trim ( )
213+ if ( path === '' ) {
214+ throw new Error ( 'Invalid pass parameter: path must not be empty' )
215+ }
216+ if ( ! PRINTABLE_ASCII_RE . test ( path ) ) {
217+ throw new Error (
218+ `Invalid pass parameter "${ path } ": contains non-printable characters`
219+ )
220+ }
221+
222+ let output : string
223+ try {
224+ output = _execSync (
225+ `pass show ${ shellEscape ( path ) } ` ,
226+ execOpts ( 5_000 )
227+ ) . trimEnd ( )
228+ } catch ( err ) {
229+ const message = err instanceof Error ? err . message : String ( err )
230+ throw new Error (
231+ `pass lookup failed for "${ path } ": ${ message } ` ,
173232 { cause : err }
174233 )
175234 }
235+
236+ const nl = output . indexOf ( '\n' )
237+ const firstLine = nl === - 1 ? output : output . slice ( 0 , nl )
238+ if ( firstLine === '' ) {
239+ throw new Error ( `pass returned empty output for "${ path } "` )
240+ }
241+ return firstLine
242+ }
243+
244+ function credentialManagerResolver ( params : string ) : string {
245+ if ( _platform !== 'win32' ) {
246+ throw new Error (
247+ `The credential_manager resolver is only supported on Windows (current platform: ${ _platform } )`
248+ )
249+ }
250+
251+ const { service, account } = parseServiceAccount ( params , 'credential_manager' )
252+ const target = `${ service } /${ account } `
253+ const expression = `(Get-StoredCredential -Target '${ target . replace ( / ' / g, "''" ) } ').GetNetworkCredential().Password`
254+
255+ let result : string
256+ try {
257+ result = _execSync (
258+ psEncodedCommand ( expression ) ,
259+ execOpts ( 10_000 )
260+ ) . trimEnd ( )
261+ } catch ( err ) {
262+ const message = err instanceof Error ? err . message : String ( err )
263+ throw new Error (
264+ `Credential Manager lookup failed for service="${ service } ", account="${ account } ": ${ message } ` ,
265+ { cause : err }
266+ )
267+ }
268+
269+ if ( result === '' ) {
270+ throw new Error (
271+ `Credential Manager returned empty password for service="${ service } ", account="${ account } "`
272+ )
273+ }
274+ return result
176275}
177276
178277// ---------------------------------------------------------------------------
@@ -184,6 +283,9 @@ function registerBuiltins (): void {
184283 registerResolver ( 'env' , envResolver )
185284 registerResolver ( 'cmd' , cmdResolver )
186285 registerResolver ( 'keychain' , keychainResolver )
286+ registerResolver ( 'secret_service' , secretServiceResolver )
287+ registerResolver ( 'pass' , passResolver )
288+ registerResolver ( 'credential_manager' , credentialManagerResolver )
187289}
188290
189291registerBuiltins ( )
0 commit comments