tl;dr - To stop the masking you'll need to stop referencing whatever Actions Secrets are equal to the values {
and }
.
Long Version
I can reproduce the behaviour with this GitHub Actions workflow file provided you first create two Actions Secrets that are in scope for your workflow (i.e. at Repository or Organization level):
Actions Secrets
Secret Name |
Value |
LEFT_CURLY |
{ |
RIGHT_CURLY |
} |
myWorkflow.yaml
The interesting bit is the activate-masking
step...
name: myWorkflow
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- id: define-output
name: define output
shell: pwsh
run: |
$array1 = @(
[pscustomobject] @{
"variable1" = "hello1"
},
[pscustomobject] @{
"variable1" = "hello2"
}
);
write-host "::set-output name=myjson::$( $array1 | ConvertTo-JSON -Compress)"
- id: activate-masking
name: activate masking
shell: pwsh
run: |
# referencing a secret anywhere in a step activates
# log masking for its value in the *entire* workflow
write-host "left = '${{ secrets.LEFT_CURLY}}'"
write-host "right = '${{ secrets.RIGHT_CURLY }}'"
- id: consume-output
name: consume output
shell: pwsh
run: |
# decode and re-encode the data to remove "compressed"
# output format as noted by @mklement0 in comments
$json = '${{ steps.define-output.outputs.myjson }}'
$data = $json | ConvertFrom-Json
# write the json to the log output. if masking is activate
# then any secrets in the output will be masked with "***"
write-host ($data| ConvertTo-Json)
# encode the json so it bypasses masking and we can get the true
# value contained in the variable
write-host "base64 = '$([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($json)))'"
The output from the "consume output" step is as follows:
[v] consume output
1 Run write-host "left = '***'"
9 left = '***'
10 right = '***'
11 [
12 ***
13 "variable1": "hello1"
14 ***,
15 ***
16 "variable1": "hello2"
17 ***
18 ]
19 base64 = 'W3sidmFyaWFibGUxIjoiaGVsbG8xIn0seyJ2YXJpYWJsZTEiOiJoZWxsbzIifV0='
Note that the ***
masking is obfuscation added to the log file only, not the variable value itself. If you decode the base64 string you'll end up with:
[{"variable1":"hello1"},{"variable1":"hello2"}]
so you can see the value of the variable is as expected.
Note also that masking only happens if a secret is referenced in a step - for example if you remove these lines from the activate-masking
step it'll stop masking the values even though there's still Secrets defined in the workflow's scope:
# remove these lines to stop masking their values
# write-host "left = '${{ secrets.LEFT_CURLY}}'"
# write-host "right = '${{ secrets.RIGHT_CURLY }}'"
To stop the masking you'll need to stop referencing whatever Actions Secrets are equal to the values {
and }
.
Bonus Round
Note there's an injection vulnerability in my Action above for specially crafted json values due to malicious input - for example if I could somehow make the value of variable1 in your json end up being this cryptic string (e.g. by giving you it as user input):
}]';write-host aaa;$y='
then the Action above will execute this PowerShell:
$json = '[{"variable1":"}]';write-host aaa;$y='}]'
which is equivalent to
$json = '[{"variable1":"}]';
write-host aaa;
$y='}]'
and it will actually execute write-host aaa;
as a command rather than as treating it as input data.
To avoid this, you might want to consider passing your outputs as base64-encoded strings - e.g.
- id: define-output
...
$base64 = [System.Convert]::ToBase64String(
[System.Text.Encoding]::UTF8.GetBytes(
($array1 | ConvertTo-JSON -Compress)
)
)
write-host "::set-output name=myjson::$base64"
- id: consume-output
...
$base64 = '${{ steps.define-output.outputs.myjson }}'
$json = [System.Text.Encoding]::UTF8.GetString(
[System.Convert]::FromBase64String(
$base64
)
)
That way, your json data can't get inadvertently promoted to being executed as code...