@@ -2,6 +2,7 @@ const { spawnSync } = require('child_process');
2
2
const fs = require ( 'fs' ) ;
3
3
const os = require ( 'os' ) ;
4
4
const path = require ( 'path' ) ;
5
+ const { isUtf8 } = require ( "buffer" )
5
6
6
7
// Note that we are not using the `@actions/core` package as it is not available
7
8
// without either committing node_modules/ to the repository, or using something
@@ -15,6 +16,35 @@ const escapeData = (s) => {
15
16
. replace ( / \n / g, '%0A' )
16
17
}
17
18
19
+ const stringify = ( value ) => {
20
+ if ( typeof value === 'string' ) return value ;
21
+ if ( Buffer . isBuffer ( value ) && isUtf8 ( value ) ) return value . toString ( 'utf-8' ) ;
22
+ return undefined ;
23
+ }
24
+
25
+ const trimEOL = ( buf ) => {
26
+ let l = buf . length
27
+ if ( l > 0 && buf [ l - 1 ] === 0x0a ) {
28
+ l -= l > 1 && buf [ l - 2 ] === 0x0d ? 2 : 1
29
+ }
30
+ return buf . slice ( 0 , l )
31
+ }
32
+
33
+ const writeBufToFile = ( buf , file ) => {
34
+ out = fs . createWriteStream ( file )
35
+ out . write ( buf )
36
+ out . end ( )
37
+ }
38
+
39
+ const logInfo = ( message ) => {
40
+ process . stdout . write ( `${ message } ${ os . EOL } ` ) ;
41
+ }
42
+
43
+ const setFailed = ( error ) => {
44
+ process . stdout . write ( `::error::${ escapeData ( error . message ) } ${ os . EOL } ` ) ;
45
+ process . exitCode = 1 ;
46
+ }
47
+
18
48
const writeCommand = ( file , name , value ) => {
19
49
// Unique delimiter to avoid conflicts with actual values
20
50
let delim ;
@@ -29,24 +59,37 @@ const writeCommand = (file, name, value) => {
29
59
}
30
60
31
61
const setSecret = ( value ) => {
32
- process . stdout . write ( `::add-mask::${ escapeData ( value ) } ${ os . EOL } ` ) ;
62
+ value = stringify ( value ) ;
63
+
64
+ // Masking a secret that is not a valid UTF-8 string or buffer is not useful
65
+ if ( value === undefined ) return ;
66
+
67
+ process . stdout . write (
68
+ value
69
+ . split ( / \r ? \n / g)
70
+ . map (
71
+ value => `::add-mask::${ escapeData ( value ) } ${ os . EOL } `
72
+ )
73
+ . join ( '' )
74
+ ) ;
33
75
}
34
76
35
77
const setOutput = ( name , value ) => {
78
+ value = stringify ( value ) ;
79
+ if ( value === undefined ) {
80
+ throw new Error ( `Output value '${ name } ' is not a valid UTF-8 string or buffer` ) ;
81
+ }
82
+
36
83
writeCommand ( process . env . GITHUB_OUTPUT , name , value ) ;
37
84
}
38
85
39
86
const exportVariable = ( name , value ) => {
40
- writeCommand ( process . env . GITHUB_ENV , name , value ) ;
41
- }
42
-
43
- const logInfo = ( message ) => {
44
- process . stdout . write ( `${ message } ${ os . EOL } ` ) ;
45
- }
87
+ value = stringify ( value ) ;
88
+ if ( value === undefined ) {
89
+ throw new Error ( `Environment variable '${ name } ' is not a valid UTF-8 string or buffer` ) ;
90
+ }
46
91
47
- const setFailed = ( error ) => {
48
- process . stdout . write ( `::error::${ escapeData ( error . message ) } ${ os . EOL } ` ) ;
49
- process . exitCode = 1 ;
92
+ writeCommand ( process . env . GITHUB_ENV , name , value ) ;
50
93
}
51
94
52
95
( async ( ) => {
@@ -68,9 +111,7 @@ const setFailed = (error) => {
68
111
69
112
// Fetch secrets from Azure Key Vault
70
113
for ( const { input : secretName , encoding, output } of secretMappings ) {
71
- let secretValue = '' ;
72
-
73
- const az = spawnSync ( 'az' ,
114
+ let az = spawnSync ( 'az' ,
74
115
[
75
116
'keyvault' ,
76
117
'secret' ,
@@ -93,10 +134,12 @@ const setFailed = (error) => {
93
134
if ( az . error ) throw new Error ( az . error , { cause : az . error } ) ;
94
135
if ( az . status !== 0 ) throw new Error ( `az failed with status ${ az . status } ` ) ;
95
136
96
- secretValue = az . stdout . toString ( 'utf-8' ) . trim ( ) ;
137
+ // az keyvault secret show --output tsv returns a buffer with trailing \n
138
+ // (or \r\n on Windows), so we need to trim it specifically.
139
+ let secretBuf = trimEOL ( az . stdout ) ;
97
140
98
141
// Mask the raw secret value in logs
99
- setSecret ( secretValue ) ;
142
+ setSecret ( secretBuf ) ;
100
143
101
144
// Handle encoded values if specified
102
145
// Sadly we cannot use the `--encoding` parameter of the `az keyvault
@@ -106,31 +149,46 @@ const setFailed = (error) => {
106
149
if ( encoding ) {
107
150
switch ( encoding . toLowerCase ( ) ) {
108
151
case 'base64' :
109
- secretValue = Buffer . from ( secretValue , 'base64' ) . toString ( ) ;
152
+ secretBuf = Buffer . from ( secretBuf . toString ( 'utf-8' ) , 'base64' ) ;
153
+ break ;
154
+ case 'ascii' :
155
+ case 'utf8' :
156
+ case 'utf-8' :
157
+ // No need to decode the existing buffer from the az command
110
158
break ;
111
159
default :
112
- // No decoding needed
113
- }
160
+ throw new Error ( `Unsupported encoding: ${ encoding } ` ) ;
161
+ }
114
162
115
- setSecret ( secretValue ) ; // Mask the decoded value as well
163
+ // Mask the decoded value
164
+ setSecret ( secretBuf ) ;
116
165
}
117
166
118
- if ( output . startsWith ( '$env:' ) ) {
119
- // Environment variable
120
- const envVarName = output . replace ( '$env:' , '' ) . trim ( ) ;
121
- exportVariable ( envVarName , secretValue ) ;
122
- logInfo ( `Secret set as environment variable: ${ envVarName } ` ) ;
123
- } else if ( output . startsWith ( '$output:' ) ) {
124
- // GitHub Actions output variable
125
- const outputName = output . replace ( '$output:' , '' ) . trim ( ) ;
126
- setOutput ( outputName , secretValue ) ;
127
- logInfo ( `Secret set as output variable: ${ outputName } ` ) ;
128
- } else {
129
- // File output
130
- const filePath = output . trim ( ) ;
131
- fs . mkdirSync ( path . dirname ( filePath ) , { recursive : true } ) ;
132
- fs . writeFileSync ( filePath , secretValue ) ;
133
- logInfo ( `Secret written to file: ${ filePath } ` ) ;
167
+ const outputType = output . startsWith ( '$env:' )
168
+ ? 'env'
169
+ : output . startsWith ( '$output:' )
170
+ ? 'output'
171
+ : 'file' ;
172
+
173
+ switch ( outputType ) {
174
+ case 'env' :
175
+ const varName = output . replace ( '$env:' , '' ) . trim ( ) ;
176
+ exportVariable ( varName , secretBuf ) ;
177
+ logInfo ( `Secret set as environment variable: ${ varName } ` ) ;
178
+ break ;
179
+
180
+ case 'output' :
181
+ const outputName = output . replace ( '$output:' , '' ) . trim ( ) ;
182
+ setOutput ( outputName , secretBuf ) ;
183
+ logInfo ( `Secret set as output variable: ${ outputName } ` ) ;
184
+ break ;
185
+
186
+ case 'file' :
187
+ const filePath = output . trim ( ) ;
188
+ fs . mkdirSync ( path . dirname ( filePath ) , { recursive : true } ) ;
189
+ writeBufToFile ( secretBuf , filePath ) ;
190
+ logInfo ( `Secret written to file: ${ filePath } ` ) ;
191
+ break ;
134
192
}
135
193
}
136
194
} ) ( ) . catch ( setFailed ) ;
0 commit comments