-
-
Notifications
You must be signed in to change notification settings - Fork 36
fix: code injection security alerts #504
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
tofu apply -chdir=tests/pass_one -workspace=devDiff of 1 change.+ random_pet.name[0] will be createdApply complete! Resources: 1 added, 0 changed, 0 destroyed.
|
tofu validate -chdir=tests/fail_invalid_resource_type -workspace=devError: Invalid resource type
|
tofu validate -chdir=tests/fail_data_source_error -workspace=devSuccess! The configuration is valid.
|
tofu fmt -chdir=tests/fail_format_diff -check -diff -recursive -workspace=devView output.
|
tofu apply -chdir=tests/pass_zero -workspace=devApply complete! Resources: 0 added, 0 changed, 0 destroyed.
|
tofu apply -chdir=tests/pass_character_limit -workspace=devDiff of 10000 changes.+ random_pet.name[0] will be created
+ random_pet.name[1] will be created
+ random_pet.name[2] will be created
+ random_pet.name[3] will be created
+ random_pet.name[4] will be created
+ random_pet.name[5] will be created
+ random_pet.name[6] will be created
+ random_pet.name[7] will be created
+ random_pet.name[8] will be created
+ random_pet.name[9] will be created
+ random_pet.name[10] will be created
+ random_pet.name[11] will be created
+ random_pet.name[12] will be created
+ random_pet.name[13] will be created
+ random_pet.name[14] will be created
+ random_pet.name[15] will be created
+ random_pet.name[16] will be created
+ random_pet.name[17] will be created
+ random_pet.name[18] will be created
+ random_pet.name[19] will be created
+ random_pet.name[20] will be created
+ random_pet.name[21] will be created
+ random_pet.name[22] will be created
+ random_pet.name[23] will be created
+ random_pet.name[24] will be created
+ random_pet.name[25] will be created
+ random_pet.name[26] will be created
+ random_pet.name[27] will be created
+ random_pet.name[28] will be created
+ random_pet.name[29] will be created
+ random_pet.name[30] will be created
+ random_pet.name[31] will be created
+ random_pet.name[32] will be created
+ random_pet.name[33] will be created
+ random_pet.name[34] will be created
+ random_pet.name[35] will be created
+ random_pet.name[36] will be created
+ random_pet.name[37] will be created
+ random_pet.name[38] will be created
+ random_pet.name[39] will be created
+ random_pet.name[40] will be created
+ random_pet.name[41] will be created
+ random_pet.name[42] will be created
+ random_pet.name[43] will be created
+ random_pet.name[44] will be created
+ random_pet.name[45] will be created
+ random_pet.name[46] will be created
+ random_pet.name[47] will be created
+ random_pet.name[48] will be created
+ random_pet.name[49] will be created
+ random_pet.name[50] will be created
+ random_pet.name[51] will be created
+ random_pet.name[52] will be created
+ random_pet.name[53] will be created
+ random_pet.name[54] will be created
+ random_pet.name[55] will be created
+ random_pet.name[56] will be created
+ random_pet.name[57] will be created
+ random_pet.name[58] will be created
+ random_pet.name[59] will be created
+ random_pet.name[60] will be created
+ random_pet.name[61] will be created
+ random_pet.name[62] will be created
+ random_pet.name[63] will be created
+ random_pet.name[64] will be created
+ random_pet.name[65] will be created
+ random_pet.name[66] will be created
+ random_pet.name[67] will be created
+ random_pet.name[68] will be created
+ random_pet.name[69] will be created
+ random_pet.name[70] will be created
+ random_pet.name[71] will be created
+ random_pet.name[72] will be created
+ random_pet.name[73] will be created
+ random_pet.name[74] will be created
+ random_pet.name[75] will be created
+ random_pet.name[76] will be created
+ random_pet.name[77] will be created
+ random_pet.name[78] will be created
+ random_pet.name[79] will be created
+ random_pet.name[80] will be created
+ random_pet.name[81] will be created
+ random_pet.name[82] will be created
+ random_pet.name[83] will be created
+ random_pet.name[84] will be created
+ random_pet.name[85] will be created
+ random_pet.name[86] will be created
+ random_pet.name[87] will be created
+ random_pet.name[88] will be created
+ random_pet.name[89] will be created
+ random_pet.name[90] will be created
+ random_pet.name[91] will be created
+ random_pet.name[92] will be created
+ random_pet.name[93] will be created
+ random_pet.name[94] will be created
+ random_pet.name[95] will be created
+ random_pet.name[96] will be created
+ random_pet.name[97] will be created
+ random_pet.name[98] will be created
+ random_pet.name[99] will be created
+ random_pet.name[100] will be created
+ random_pet.name[101] will be created
+ random_pet.name[102] will be created
+ random_pet.name[103] will be created
+ random_pet.name[104] will be created
+ random_pet.name[105] will be created
+ random_pet.name[106] will be created
+ random_pet.name[107] will be created
+ random_pet.name[108] will be created
+ random_pet.name[109] will be created
+ random_pet.name[110] will be created
+ random_pet.name[111] will be created
+ random_pet.name[112] will be created
+ random_pet.name[113] will be created
+ random_pet.name[114] will be created
+ random_pet.name[115] will be created
+ random_pet.name[116] will be created
+ random_pet.name[117] will be created
+ random_pet.name[118] will be created
+ random_pet.name[119] will be created
+ random_pet.name[120] will be created
+ random_pet.name[121] will be created
+ random_pet.name[122] will be created
+ random_pet.name[123] will be created
+ random_pet.name[124] will be created
+ random_pet.name[125] will be created
+ random_pet.name[126] will be created
+ random_pet.name[127] will be created
+ random_pet.name[128] will be created
+ random_pet.name[129] will be created
+ random_pet.name[130] will be created
+ random_pet.name[131] will be created
+ random_pet.name[132] will be created
+ random_pet.name[133] will be created
+ random_pet.name[134] will be created
+ random_pet.name[135] will be created
+ random_pet.name[136] will be created
+ random_pet.name[137] will be created
+ random_pet.name[138] will be created
+ random_pet.name[139] will be created
+ random_pet.name[140] will be created
+ random_pet.name[141] will be created
+ random_pet.name[142] will be created
+ random_pet.name[143] will be created
+ random_pet.name[144] will be created
+ random_pet.name[145] will be created
+ random_pet.name[146] will be created
+ random_pet.name[147] will be created
+ random_pet.name[148] will be created
+ random_pet.name[149] will be created
+ random_pet.name[150] will be created
+ random_pet.name[151] will be created
+ random_pet.name[152] will be created
+ random_pet.name[153] will be created
+ random_pet.name[154] will be created
+ random_pet.name[155] will be created
+ random_pet.name[156] will be created
+ random_pet.name[157] will be created
+ random_pet.name[158] will be created
+ random_pet.name[159] will be created
+ random_pet.name[160] will be created
+ random_pet.name[161] will be created
+ random_pet.name[162] will be created
+ random_pet.name[163] will be created
+ random_pet.name[164] will be created
+ random_pet.name[165] will be created
+ random_pet.name[166] will be created
+ random_pet.name[167] will be created
+ random_pet.name[168] will be created
+ random_pet.name[169] will be created
+ random_pet.name[170] will be created
+ random_pet.name[171] will be created
+ random_pet.name[172] will be created
+ random_pet.name[173] will be created
+ random_pet.name[174] will be created
+ random_pet.name[175] will be created
+ random_pet.name[176] will be created
+ random_pet.name[177] will be created
+ random_pet.name[178] will be created
+ random_pet.name[179] will be created
+ random_pet.name[180] will be created
+ random_pet.name[181] will be created
+ random_pet.name[182] will be created
+ random_pet.name[183] will be created
+ random_pet.name[184] will be created
+ random_pet.name[185] will be created
+ random_pet.name[186] will be created
+ random_pet.name[187] will be created
+ random_pet.name[188] will be created
+ random_pet.name[189] will be created
+ random_pet.name[190] will be created
+ random_pet.name[191] will be created
+ random_pet.name[192] will be created
+ random_pet.name[193] will be created
+ random_pet.name[194] will be created
+ random_pet.name[195] will be created
+ random_pet.name[196] will be created
+ random_pet.name[197] will be created
+ random_pet.name[198] will be created
+ random_pet.name[199] will be created
+ random_pet.name[200] will be created
+ random_pet.name[201] will be created
+ random_pet.name[202] will be created
+ random_pet.name[203] will be created
+ random_pet.name[204] will be created
+ random_pet.name[205] will be created
+ random_pet.name[206] will be created
+ random_pet.name[207] will be created
+ random_pet.name[208] will be created
+ random_pet.name[209] will be created
+ random_pet.name[210] will be created
+ random_pet.name[211] will be created
+ random_pet.name[212] will be created
+ random_pet.name[213] will be created
+ random_pet.name[214] will be created
+ random_pet.name[215] will be created
+ random_pet.name[216] will be created
+ random_pet.name[217] will be created
+ random_pet.name[218] will be created
+ random_pet.name[219] will be created
+ random_pet.name[220] will be created
+ random_pet.name[221] will be created
+ random_pet.name[222] will be created
+ random_pet.name[223] will be created
+ random_pet.name[224] will be created
+ random_pet.name[225] will be created
+ random_pet.name[226] will be created
+ random_pet.name[227] will be created
+ random_pet.name[228] will be created
+ random_pet.name[229] will be created
+ random_pet.name[230] will be created
+ random_pet.name[231] will be created
+ random_pet.name[232] will be created
+ random_pet.name[233] will be created
+ random_pet.name[234] will be created
+ random_pet.name[235] will be created
+ random_pet.name[236] will be created
+ random_pet.name[237] will be created
+ random_pet.name[238] will be created
+ random_pet.name[239] will be created
+ random_pet.name[240] will be created
+ random_pet.name[241] will be created
+ random_pet.name[242] will be created
+ random_pet.name[243] will be created
+ random_pet.name[244] will be created
+ random_pet.name[245] will be created
+ random_pet.name[246] will be created
+ random_pet.name[247] will be created
+ random_pet.name[248] will be created
+ random_pet.name[249] will be created
+ random_pet.name[250] will be created
+ random_pet.name[251] will be created
+ random_pet.name[252] will be created
+ random_pet.name[253] will be created
+ random_pet.name[254] will be created
+ random_pet.name[255] will be created
+ random_pet.name[256] will be created
+ random_pet.name[257] will be created
+ random_pet.name[258] will be created
+ random_pet.name[259] will be created
+ random_pet.name[260] will be created
+ random_pet.name[261] will be created
+ random_pet.name[262] will be created
+ random_pet.name[263] will be created
+ random_pet.name[264] will be created
+ random_pet.name[265] will be created
+ random_pet.name[266] will be created
+ random_pet.name[267] will be created
+ random_pet.name[268] will be created
+ random_pet.name[269] will be created
+ random_pet.name[270] will be created
+ random_pet.name[271] will be created
+ random_pet.name[272] will be created
+ random_pet.name[273] will be created
+ random_pet.name[274] will be created
+ random_pet.name[275] will be created
+ random_pet.name[276] will be created
+ random_pet.name[277] will be created
+ random_pet.name[278] will be created
+ random_pet.name[279] will be created
+ random_pet.name[280] will be created
+ random_pet.name[281] will be created
+ random_pet.name[282] will be created
+ random_pet.name[283] will be created
+ random_pet.name[284] will be created
+ random_pet.name[285] will be created
+ random_pet.name[286] will be created
+ random_pet.name[287] will be created
+ random_pet.name[288] will be created
+ random_pet.name[289] will be created
+ random_pet.name[290] will be created
+ random_pet.name[291] will be created
+ random_pet.name[292] will be created
+ random_pet.name[293] will be created
+ random_pet.name[294] will be created
+ random_pet.name[295] will be created
+ random_pet.name[296] will be created
+ random_pet.name[297] will be created
+ random_pet.name[298] will be created
+ random_pet.name[299] will be created
+ random_pet.name[300] will be created
+ random_pet.name[301] will be created
+ random_pet.name[302] will be created
+ random_pet.name[303] will be created
+ random_pet.name[304] will be created
+ random_pet.name[305] will be created
+ random_pet.name[306] will be created
+ random_pet.name[307] will be created
+ random_pet.name[308] will be created
+ random_pet.name[309] will be created
+ random_pet.name[310] will be created
+ random_pet.name[311] will be created
+ random_pet.name[312] will be created
+ random_pet.name[313] will be created
+ random_pet.name[314] will be created
+ random_pet.name[315] will be created
+ random_pet.name[316] will be created
+ random_pet.name[317] will be created
+ random_pet.name[318] will be created
+ random_pet.name[319] will be created
+ random_pet.name[320] will be created
+ random_pet.name[321] will be created
+ random_pet.name[322] will be created
+ random_pet.name[323] will be created
+ random_pet.name[324] will be created
+ random_pet.name[325] will be created
+ random_pet.name[326] will be created
+ random_pet.name[327] will be created
+ random_pet.name[328] will be created
+ random_pet.name[329] will be created
+ random_pet.name[330] will be created
+ random_pet.name[331] will be created
+ random_pet.name[332] will be created
+ random_pet.name[333] will be created
+ random_pet.name[334] will be created
+ random_pet.name[335] will be created
+ random_pet.name[336] will be created
+ random_pet.name[337] will be created
+ random_pet.name[338] will be created
+ random_pet.name[339] will be created
+ random_pet.name[340] will be created
+ random_pet.name[341] will be created
+ random_pet.name[342] will be created
+ random_pet.name[343] will be created
+ random_pet.name[344] will be created
+ random_pet.name[345] will be created
+ random_pet.name[346] will be created
+ random_pet.name[347] will be created
+ random_pet.name[348] will be created
+ random_pet.name[349] will be created
+ random_pet.name[350] will be created
+ random_pet.name[351] will be created
+ random_pet.name[352] will be created
+ random_pet.name[353] will be created
+ random_pet.name[354] will be created
+ random_pet.name[355] will be created
+ random_pet.name[356] will be created
+ random_pet.name[357] will be created
+ random_pet.name[358] will be created
+ random_pet.name[359] will be created
+ random_pet.name[360] will be created
+ random_pet.name[361] will be created
+ random_pet.name[362] will be created
+ random_pet.name[363] will be created
+ random_pet.name[364] will be created
+ random_pet.name[365] will be created
+ random_pet.name[366] will be created
+ random_pet.name[367] will be created
+ random_pet.name[368] will be created
+ random_pet.name[369] will be created
+ random_pet.name[370] will be created
+ random_pet.name[371] will be created
+ random_pet.name[372] will be created
+ random_pet.name[373] will be created
+ random_pet.name[374] will be created
+ random_pet.name[375] will be created
+ random_pet.name[376] will be created
+ random_pet.name[377] will be created
+ random_pet.name[378] will be created
+ random_pet.name[379] will be created
+ random_pet.name[380] will be created
+ random_pet.name[381] will be created
+ random_pet.name[382] will be created
+ random_pet.name[383] will be created
+ random_pet.name[384] will be created
+ random_pet.name[385] will be created
+ random_pet.name[386] will be created
+ random_pet.name[387] will be created
+ random_pet.name[388] will be created
+ random_pet.name[389] will be created
+ random_pet.name[390] will be created
+ random_pet.name[391] will be created
+ random_pet.name[392] will be created
+ random_pet.name[393] will be created
+ random_pet.name[394] will be created
+ random_pet.name[395] will be created
+ random_pet.name[396] will be created
+ random_pet.name[397] will be created
+ random_pet.name[398] will be created
+ random_pet.name[399] will be created
+ random_pet.name[400] will be created
+ random_pet.name[401] will be created
+ random_pet.name[402] will be created
+ random_pet.name[403] will be created
+ random_pet.name[404] will be created
+ random_pet.name[405] will be created
+ random_pet.name[406] will be created
+ random_pet.name[407] will be created
+ random_pet.name[408] will be created
+ random_pet.name[409] will be created
+ random_pet.name[410] will be created
+ random_pet.name[411] will be created
+ random_pet.name[412] will be created
+ random_pet.name[413] will be created
+ random_pet.name[414] will be created
+ random_pet.name[415] will be created
+ random_pet.name[416] will be created
+ random_pet.name[417] will be created
+ random_pet.name[418] will be created
+ random_pet.name[419] will be created
+ random_pet.name[420] will be created
+ random_pet.name[421] will be created
+ random_pet.name[422] will be created
+ random_pet.name[423] will be created
+ random_pet.name[424] will be created
+ random_pet.name[425] will be created
+ random_pet.name[426] will be created
+ random_pet.name[427] will be created
+ random_pet.name[428] will be created
+ random_pet.name[429] will be created
+ random_pet.name[430] will be created
+ random_pet.name[431] will be created
+ random_pet.name[432] will be created
+ random_pet.name[433] will be created
+ random_pet.name[434] will be created
+ random_pet.name[435] will be created
+ random_pet.name[436] will be created
+ random_pet.name[437] will be created
+ random_pet.name[438] will be created
+ random_pet.name[439] will be created
+ random_pet.name[440] will be created
+ random_pet.name[441] will be created
+ random_pet.name[442] will be created
+ random_pet.name[443] will be created
+ random_pet.name[444] will be created
+ random_pet.name[445] will be created
+ random_pet.name[446] will be created
+ random_pet.name[447] will be created
+ random_pet.name[448] will be created
+ random_pet.name[449] will be created
+ random_pet.name[450] will be created
+ random_pet.name[451] will be created
+ random_pet.name[452] will be created
+ random_pet.name[453] will be created
+ random_pet.name[454] will be created
+ random_pet.name[455] will be created
+ random_pet.name[456] will be created
+ random_pet.name[457] will be created
+ random_pet.name[458] will be created
+ random_pet.name[459] will be created
+ random_pet.name[460] will be created
+ random_pet.name[461] will be created
+ random_pet.name[462] will be created
+ random_pet.name[463] will be created
+ random_pet.name[464] will be created
+ random_pet.name[465] will be created
+ random_pet.name[466] will be created
+ random_pet.name[467] will be created
+ random_pet.name[468] will be created
+ random_pet.name[469] will be created
+ random_pet.name[470] will be created
+ random_pet.name[471] will be created
+ random_pet.name[472] will be created
+ random_pet.name[473] will be created
+ random_pet.name[474] will be created
+ random_pet.name[475] will be created
+ random_pet.name[476] will be created
+ random_pet.name[477] will be created
+ random_pet.name[478] will be created
+ random_pet.name[479] will be created
+ random_pet.name[480] will be created
+ random_pet.name[481] will be created
+ random_pet.name[482] will be created
+ random_pet.name[483] will be created
+ random_pet.name[484] will be created
+ random_pet.name[485] will be created
+ random_pet.name[486] will be created
+ random_pet.name[487] will be created
+ random_pet.name[488] will be created
+ random_pet.name[489] will be created
+ random_pet.name[490] will be created
+ random_pet.name[491] will be created
+ random_pet.name[492] will be created
+ random_pet.name[493] will be created
+ random_pet.name[494] will be created
+ random_pet.name[495] will be created
+ random_pet.name[496] will be created
+ random_pet.name[497] will be created
+ random_pet.name[498] will be created
+ random_pet.name[499] will be created
+ random_pet.name[500] will be created
+ random_pet.name[501] will be created
+ random_pet.name[502] will be created
+ random_pet.name[503] will be created
+ random_pet.name[504] will be created
+ random_pet.name[505] will be created
+ random_pet.name[506] will be created
+ random_pet.name[507] will be created
+ random_pet.name[508] will be created
+ random_pet.name[509] will be created
+ random_pet.name[510] will be created
+ random_pet.name[511] will be created
+ random_pet.name[512] will be created
+ random_pet.name[513] will be created
+ random_pet.name[514] will be created
+ random_pet.name[515] will be created
+ random_pet.name[516] will be created
+ random_pet.name[517] will be created
+ random_pet.name[518] will be created
+ random_pet.name[519] will be created
+ random_pet.name[520] will be created
+ random_pet.name[521] will be created
+ random_pet.name[522] will be created
+ random_pet.name[523] will be created
+ random_pet.name[524] will be created
+ random_pet.name[525] will be created
+ random_pet.name[526] will be created
+ random_pet.name[527] will be created
+ random_pet.name[528] will be created
+ random_pet.name[529] will be created
+ random_pet.name[530] will be created
+ random_pet.name[531] will be created
+ random_pet.name[532] will be created
+ random_pet.name[533] will be created
+ random_pet.name[534] will be created
+ random_pet.name[535] will be created
+ random_pet.name[536] will be created
+ random_pet.name[537] will be created
+ random_pet.name[538] will be created
+ random_pet.name[539] will be created
+ random_pet.name[540] will be created
+ random_pet.name[541] will be created
+ random_pet.name[542] will be created
+ random_pet.name[543] will be created
+ random_pet.name[544] will be created
+ random_pet.name[545] will be created
+ random_pet.name[546] will be created
+ random_pet.name[547] will be created
+ random_pet.name[548] will be created
+ random_pet.name[549] will be created
+ random_pet.name[550] will be created
+ random_pet.name[551] will be created
+ random_pet.name[552] will be created
+ random_pet.name[553] will be created
+ random_pet.name[554] will be created
+ random_pet.name[555] will be created
+ random_pet.name[556] will be created
+ random_pet.name[557] will be created
+ random_pet.name[558] will be created
+ random_pet.name[559] will be created
+ random_pet.name[560] will be created
+ random_pet.name[561] will be created
+ random_pet.name[562] will be created
+ random_pet.name[563] will be created
+ random_pet.name[564] will be created
+ random_pet.name[565] will be created
+ random_pet.name[566] will be created
+ random_pet.name[567] will be created
+ random_pet.name[568] will be created
+ random_pet.name[569] will be created
+ random_pet.name[570] will be created
+ random_pet.name[571] will be created
+ random_pet.name[572] will be created
+ random_pet.name[573] will be created
+ random_pet.name[574] will be created
+ random_pet.name[575] will be created
+ random_pet.name[576] will be created
+ random_pet.name[577] will be created
+ random_pet.name[578] will be created
+ random_pet.name[579] will be created
+ random_pet.name[580] will be created
+ random_pet.name[581] will be created
+ random_pet.name[582] will be created
+ random_pet.name[583] will be created
+ random_pet.name[584] will be created
+ random_pet.name[585] will be created
+ random_pet.name[586] will be created
+ random_pet.name[587] will be created
+ random_pet.name[588] will be created
+ random_pet.name[589] will be created
+ random_pet.name[590] will be created
+ random_pet.name[591] will be created
+ random_pet.name[592] will be created
+ random_pet.name[593] will be created
+ random_pet.name[594] will be created
+ random_pet.name[595] will be created
+ random_pet.name[596] will be created
+ random_pet.name[597] will be created
+ random_pet.name[598] will be created
+ random_pet.name[599] will be created
+ random_pet.name[600] will be created
+ random_pet.name[601] will be created
+ random_pet.name[602] will be created
+ random_pet.name[603] will be created
+ random_pet.name[604] will be created
+ random_pet.name[605] will be created
+ random_pet.name[606] will be created
+ random_pet.name[607] will be created
+ random_pet.name[608] will be created
+ random_pet.name[609] will be created
+ random_pet.name[610] will be created
+ random_pet.name[611] will be created
+ random_pet.name[612] will be created
+ random_pet.name[613] will be created
+ random_pet.name[614] will be created
+ random_pet.name[615] will be created
+ random_pet.name[616] will be created
+ random_pet.name[617] will be created
+ random
…Apply complete! Resources: 10000 added, 0 changed, 0 destroyed.
|
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
| args="${{ steps.arg.outputs.arg-check }}${{ steps.arg.outputs.arg-diff }}${{ steps.arg.outputs.arg-list }}${{ steps.arg.outputs.arg-recursive }}${{ steps.arg.outputs.arg-write }}" | ||
| echo "${{ inputs.tool }} fmt${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} fmt${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL fmt${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix this issue, we must NOT directly interpolate untrusted user input into the shell command inline via ${{ ... }} expression syntax. Instead, the recommended secure practice in GitHub actions is to assign untrusted inputs to environment variables and then use these variables in the shell command using native shell syntax ("$VAR"), which does not trigger command expansion and is safe from injection.
Specifically, in action.yml, modify code on and around line 153:
- Assign the untrusted value from
steps.arg.outputs.arg-chdirto an environment variable (e.g.,ARG_CHDIR). - Replace any direct uses of
${{ steps.arg.outputs.arg-chdir }}in therun:block with references to"$ARG_CHDIR"using bash shell parameter expansion. - The definition for
ARG_CHDIRmust be set in theenv:field for that step, using${{ steps.arg.outputs.arg-chdir }}. - The same change should be applied to other steps that use the untrusted value in the shell command line.
This avoids code injection, because bash will treat the contents of "$ARG_CHDIR" as a single word and will not expand embedded shell metacharacters.
-
Copy modified line R148 -
Copy modified lines R154-R155 -
Copy modified line R161 -
Copy modified lines R167-R168
| @@ -145,25 +145,27 @@ | ||
| id: format | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF format. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ steps.arg.outputs.arg-check }}${{ steps.arg.outputs.arg-diff }}${{ steps.arg.outputs.arg-list }}${{ steps.arg.outputs.arg-recursive }}${{ steps.arg.outputs.arg-write }}" | ||
| echo "$INPUTS_TOOL fmt${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} fmt${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL fmt$ARG_CHDIR${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL"$ARG_CHDIR" fmt${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} | ||
| id: initialize | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF initialize. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL init$ARG_CHDIR${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL"$ARG_CHDIR" init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.validate == 'true' && contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} | ||
| id: validate |
| echo "${{ inputs.tool }} fmt${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} fmt${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL fmt${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} fmt${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
The best way to fix this vulnerability is to avoid direct shell interpolation of user-controlled input using ${{ ... }} syntax within the run: step. Instead, assign the user input to an environment variable, and reference it inside the shell script using Bash variable syntax (e.g., "$INPUTS_ARG_CHDIR"). This prevents malicious command injection, as the shell will treat the value as a literal string argument.
Specifically, in action.yml, line 154 currently constructs a command as:
$INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} fmt${args}
This interpolates unsafe input directly.
Instead, you should construct the command referencing the environment variable:
$INPUTS_TOOL "$INPUTS_ARG_CHDIR" fmt${args}
The environment variable INPUTS_ARG_CHDIR is already set up in the environment in step arg, so referencing it in shell-safe syntax is effective.
You may need to adapt the quoting and position so that $INPUTS_ARG_CHDIR correctly stands in for what was previously interpolated.
If ${{ steps.arg.outputs.arg-chdir }} expands to (for example) -chdir=someDir, the proper translation for safety is to append the quoted variable:
$INPUTS_TOOL fmt $INPUTS_ARG_CHDIR ${args} ...
Or, depending on argument structure,
$INPUTS_TOOL fmt ${args} $INPUTS_ARG_CHDIR ...`
The key security fix is to never interpolate ${{ ... }} directly from user input in the shell line.
Only the relevant run lines in the affected steps need editing.
-
Copy modified line R154
| @@ -151,7 +151,7 @@ | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ steps.arg.outputs.arg-check }}${{ steps.arg.outputs.arg-diff }}${{ steps.arg.outputs.arg-list }}${{ steps.arg.outputs.arg-recursive }}${{ steps.arg.outputs.arg-write }}" | ||
| echo "$INPUTS_TOOL fmt${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} fmt${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| $INPUTS_TOOL fmt "$INPUTS_ARG_CHDIR" ${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} | ||
| id: initialize |
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "${{ inputs.tool }} init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix the code injection risk, anywhere the value ${{ steps.arg.outputs.arg-chdir }} (or any other untrusted input originating from ${{ inputs.* }}) is passed into the shell, it must be set as an environment variable and referenced using native shell syntax ($VAR). In action.yml, change the steps in question so that environment variables are set for the affected values (INPUTS_ARG_CHDIR etc.), and modify the shell scripts to reference those variables directly using $INPUTS_ARG_CHDIR instead of ${{ steps.arg.outputs.arg-chdir }}.
For line 164 and 165, instead of constructing and referencing ${{ steps.arg.outputs.arg-chdir }} directly in the shell block, use $INPUTS_ARG_CHDIR (set from env) in both "args" assignment and command string construction. Apply this to all usages of dangerous interpolated input.
Specifically:
- In the step where commands are built and run with "
$INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} ...", set an environment variable for the chdir argument (e.g.,INPUTS_ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }}), then use$INPUTS_ARG_CHDIRin the script. - Remove all direct
${{ ... }}usage referencing that variable from shell code.
If other steps have similar patterns, apply the same fix, but the scope here is limited to the shown affected lines.
-
Copy modified line R160 -
Copy modified lines R165-R166
| @@ -157,13 +157,13 @@ | ||
| id: initialize | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| INPUTS_ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF initialize. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL init$INPUTS_ARG_CHDIR$args" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR init$args 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.validate == 'true' && contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} | ||
| id: validate |
| echo "${{ inputs.tool }} init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
The safe, recommended approach is:
- Do not interpolate user-controllable inputs into shell commands directly.
- Instead, assign untrusted input to an environment variable, then reference it from the shell command using standard shell parameter expansion:
"$VAR"instead of${{ ... }}. - Specifically for
${{ steps.arg.outputs.arg-chdir }}(and any other potentially-tainted-string interpolations in shell commands), assign its value to an environment variable in theenv:block of the step. Then, in the Bash command, reference it as"$INPUTS_ARG_CHDIR"(or equivalent) with proper shell quoting. - Update all relevant command invocations in the run command accordingly.
In this concrete case, add:
- An additional variable to the env map:
INPUTS_ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }}. - In the Bash script, do not interpolate
${{ steps.arg.outputs.arg-chdir }}directly, but use"$INPUTS_ARG_CHDIR"(with quotes for safety).
Files/Lines to change:
- In
action.yml, stepid: initialize, edit theenv:block (addINPUTS_ARG_CHDIR). - In
run: | ..., replace both${{ steps.arg.outputs.arg-chdir }}interpolations (lines 165, 166): instead reference"$INPUTS_ARG_CHDIR"in the Bash string.
Potential imports/definitions:
None needed beyond YAML env setup and shell variable usage.
-
Copy modified line R160 -
Copy modified lines R166-R167
| @@ -157,13 +157,14 @@ | ||
| id: initialize | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| INPUTS_ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF initialize. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL init$INPUTS_ARG_CHDIR${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| "$INPUTS_TOOL" "$INPUTS_ARG_CHDIR" init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.validate == 'true' && contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} | ||
| id: validate |
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
| args="${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| echo "${{ inputs.tool }} validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
The best way to fix this issue in GitHub Actions composite workflows is to avoid inserting untrusted input directly into the command via GitHub Actions expression interpolation (${{ ... }}) at run-time, especially inside the command line. Instead, assign the (potentially unsafe) value to an environment variable, and then reference that variable within the shell script using the shell's native $VAR syntax (not through ${{ env.VAR }} within the script). This ensures that the shell treats the variable as a single argument and does not interpret injected shell syntax, greatly reducing the risk of command injection.
How:
- Update step(s) so any use of
${{ steps.arg.outputs.arg-chdir }}in a command string is instead assigned to an environment variable. - Reference the environment variable in the shell script using
$VAR. - Specifically, in line 177, change
${{ steps.arg.outputs.arg-chdir }}to$ARG_CHDIR(where$ARG_CHDIRis set to${{ steps.arg.outputs.arg-chdir }}via theenv:block for that step).
What needs to change:
- For the
validatestep (lines 168-179):- Add an environment variable, e.g.,
ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }}. - Replace
${{ steps.arg.outputs.arg-chdir }}with$ARG_CHDIRin commands (echoand command execution).
- Add an environment variable, e.g.,
-
Copy modified line R172 -
Copy modified lines R178-R179
| @@ -169,13 +169,14 @@ | ||
| id: validate | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF validate. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL validate$ARG_CHDIR${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL$ARG_CHDIR validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.command == 'plan' }} | ||
| id: plan |
| echo "${{ inputs.tool }} validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
The best way to fix this problem is to follow GitHub's official guidance: assign any untrusted input to a shell environment variable and reference it using proper shell quoting, rather than interpolating it directly into the run: command block. Here, we need to assign the potentially tainted input (${{ steps.arg.outputs.arg-chdir }}) to an environment variable, and then use this variable within the shell script. This prevents evaluation of malicious shell content, as variable use within double quotes will prevent command injection. Specifically, in action.yml, locate the step with the direct interpolation around ${{ steps.arg.outputs.arg-chdir }} (line 178), move the interpolated value into an environment variable in the env: section of the step, and replace all usages in the shell script with a proper shell variable expression (such as "$STEP_ARG_CHDIR").
-
Copy modified line R172 -
Copy modified lines R178-R179
| @@ -169,13 +169,14 @@ | ||
| id: validate | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| STEP_ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF validate. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL validate$STEP_ARG_CHDIR${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL"$STEP_ARG_CHDIR" validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.command == 'plan' }} | ||
| id: plan |
| echo "${{ inputs.tool }} plan${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| if [[ -n "$PLAN_FILE" ]]; then mv --force --verbose "$PLAN_FILE" "$path" 2>/dev/null && exit 0; fi | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} plan${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL plan${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To prevent code injection, the recommended approach is to set the tainted input arguments as environment variables, then reference these via native shell syntax rather than interpolation. For the problematic use of ${{ steps.arg.outputs.arg-chdir }} (and similar expressions), the proper fix is:
- In the
run:blocks, replace all direct${{ ... }}interpolations that may contain untrusted data with references to environment variables (e.g.,$INPUTS_ARG_CHDIR), which are populated from the previous step's outputs. - The
env:block for each step should export required output variables (e.g., addINPUTS_ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }}). - In shell commands, use bash variable syntax (e.g.,
"$INPUTS_ARG_CHDIR") rather than interpolation (${{ ... }}).
Implement this change for all relevant lines within the step(s) using untrusted data:
- Lines 176, 177, 178, 191, and similar, changing
${{ steps.arg.outputs.arg-chdir }}to$INPUTS_ARG_CHDIR.
No new imports or method definitions are required; only environment variable passing and shell syntax changes.
-
Copy modified line R172 -
Copy modified lines R177-R178 -
Copy modified line R185 -
Copy modified line R192 -
Copy modified line R194
| @@ -169,28 +169,29 @@ | ||
| id: validate | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| INPUTS_ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF validate. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL validate$INPUTS_ARG_CHDIR${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.command == 'plan' }} | ||
| id: plan | ||
| env: | ||
| INPUTS_PLAN_FILE: ${{ inputs.plan-file }} | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| INPUTS_ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| shell: bash | ||
| run: | | ||
| # TF plan. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"; if [[ "$exit_code" == "2" ]]; then exit 0; fi' EXIT | ||
| args="${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-detailed-exitcode }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan" | ||
| echo "$INPUTS_TOOL plan${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| echo "$INPUTS_TOOL plan$INPUTS_ARG_CHDIR${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null && exit 0; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR plan${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.command == 'apply' && inputs.arg-auto-approve != 'true' && inputs.plan-file == '' }} | ||
| id: download |
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} plan${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL plan${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null && exit 0; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To prevent code injection, we must never inject untrusted input directly into a run: command using ${{ ... }} syntax. Instead, pass the input as an environment variable and reference it in the shell using native shell variable syntax (i.e., $VAR).
The fix is to:
- Add the argument in question (
steps.arg.outputs.arg-chdir) to a new environment variable, e.g.,ARG_CHDIR. - In the shell command, replace all occurrences of
${{ steps.arg.outputs.arg-chdir }}with$ARG_CHDIR.
This ensures the value is interpreted as a plain string and not allowing any executable characters or shell-breaking syntax to be interpolated.
Where to change:
- The affected run block is on line 193 in
action.yml, inside the job/step with idplan. - Add
ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }}to theenv:block for this step. - Replace every usage of
${{ steps.arg.outputs.arg-chdir }}in therun:section with$ARG_CHDIR.
No imports or function/method definitions are needed, as all changes are within the YAML action and use GitHub Actions/Unix shell features.
-
Copy modified line R185 -
Copy modified line R192 -
Copy modified line R194
| @@ -182,16 +182,16 @@ | ||
| env: | ||
| INPUTS_PLAN_FILE: ${{ inputs.plan-file }} | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| shell: bash | ||
| run: | | ||
| # TF plan. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"; if [[ "$exit_code" == "2" ]]; then exit 0; fi' EXIT | ||
| args="${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-detailed-exitcode }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan" | ||
| echo "$INPUTS_TOOL plan${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| echo "$INPUTS_TOOL plan$ARG_CHDIR${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null && exit 0; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| $INPUTS_TOOL$ARG_CHDIR plan${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| - if: ${{ inputs.command == 'apply' && inputs.arg-auto-approve != 'true' && inputs.plan-file == '' }} | ||
| id: download | ||
| env: |
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
| # Unzip the plan file to the working directory, then clean up the zip file. | ||
| unzip "${{ steps.identifier.outputs.name }}.zip" -d "${{ inputs.arg-chdir || inputs.working-directory }}" | ||
| unzip "${{ steps.identifier.outputs.name }}.zip" -d "$INPUTS_ARG_CHDIR" |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.identifier.outputs.name }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix the code injection risk, the user-controlled value ${{ steps.identifier.outputs.name }} must not be interpolated directly into the shell script using ${{ ... }} syntax inside the run: block. Instead, set this value as an environment variable in the env: section of the step and then use shell expansion ($ARTIFACT_NAME) to refer to it in the shell code. This ensures that the shell treats the value as a literal string and not as code, thus mitigating command injection problems. Concretely, you should:
- In the step at line 195–211:
- Add a new environment variable to the
env:block:ARTIFACT_NAME: ${{ steps.identifier.outputs.name }} - Replace every instance of
${{ steps.identifier.outputs.name }}in the shell script with$ARTIFACT_NAME
- Add a new environment variable to the
No new dependencies or imports are needed.
-
Copy modified line R199 -
Copy modified lines R204-R206 -
Copy modified lines R209-R210
| @@ -196,17 +196,18 @@ | ||
| id: download | ||
| env: | ||
| INPUTS_ARG_CHDIR: ${{ inputs.arg-chdir || inputs.working-directory }} | ||
| ARTIFACT_NAME: ${{ steps.identifier.outputs.name }} | ||
| shell: bash | ||
| run: | | ||
| # Download plan file. | ||
| # Get the artifact ID of the latest matching plan files for download. | ||
| artifact_id=$(gh api /repos/${{ github.repository }}/actions/artifacts --header "$GH_API" --method GET --field "name=${{ steps.identifier.outputs.name }}" --jq '.artifacts[0].id' 2>/dev/null) | ||
| if [[ -z "$artifact_id" ]]; then echo "Unable to locate plan file: ${{ steps.identifier.outputs.name }}." && exit 1; fi | ||
| gh api /repos/${{ github.repository }}/actions/artifacts/${artifact_id}/zip --header "$GH_API" --method GET > "${{ steps.identifier.outputs.name }}.zip" | ||
| artifact_id=$(gh api /repos/${{ github.repository }}/actions/artifacts --header "$GH_API" --method GET --field "name=$ARTIFACT_NAME" --jq '.artifacts[0].id' 2>/dev/null) | ||
| if [[ -z "$artifact_id" ]]; then echo "Unable to locate plan file: $ARTIFACT_NAME." && exit 1; fi | ||
| gh api /repos/${{ github.repository }}/actions/artifacts/${artifact_id}/zip --header "$GH_API" --method GET > "$ARTIFACT_NAME.zip" | ||
|
|
||
| # Unzip the plan file to the working directory, then clean up the zip file. | ||
| unzip "${{ steps.identifier.outputs.name }}.zip" -d "$INPUTS_ARG_CHDIR" | ||
| rm --force "${{ steps.identifier.outputs.name }}.zip" | ||
| unzip "$ARTIFACT_NAME.zip" -d "$INPUTS_ARG_CHDIR" | ||
| rm --force "$ARTIFACT_NAME.zip" | ||
|
|
||
| - if: ${{ inputs.plan-encrypt != '' && steps.download.outcome == 'success' }} | ||
| env: |
| run: | | ||
| # TF show. | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show tfplan > tf.console.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show tfplan > tf.console.txt |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To remediate the code injection vulnerability, the untrusted input arg-chdir (or its fallback working-directory) should not be interpolated directly into the shell command via ${{ steps.arg.outputs.arg-chdir }} within the run: block. Instead, its value should be stored beforehand in an environment variable (already done as INPUTS_ARG_CHDIR in the env: block in step with id: arg), and its value should be referenced using shell variable syntax (e.g., $INPUTS_ARG_CHDIR) within the shell command block. This ensures untrusted values cannot break out of the context, preventing code injection. Therefore, in file action.yml on line 229, replace:
$INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show tfplan > tf.console.txt
with:
$INPUTS_TOOL$INPUTS_ARG_CHDIR show tfplan > tf.console.txt
No further changes to logic or structure are required; just ensure the shell script only references the environment variable via native shell expansion, not via ${{ ... }} syntax.
-
Copy modified line R229
| @@ -226,7 +226,7 @@ | ||
| shell: bash | ||
| run: | | ||
| # TF show. | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show tfplan > tf.console.txt | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR show tfplan > tf.console.txt | ||
|
|
||
| # Diff of changes. | ||
| # Filter lines starting with " # " and save to tf.diff.txt, then prepend diff-specific symbols based on specific keywords. |
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
General approach:
Instead of directly injecting ${{ steps.arg.outputs.arg-chdir }} into the shell command, pass the value as an environment variable (ARG_CHDIR) and reference it inside the shell script using standard shell variable syntax ($ARG_CHDIR), ensuring that the shell interprets it as a simple string value. This prevents early evaluation and code injection vulnerabilities.
Specific steps:
- In the step at line 274-281 (parity block), add an environment variable for
ARG_CHDIRset to${{ steps.arg.outputs.arg-chdir }}. - In the shell command at line 287, replace
${{ steps.arg.outputs.arg-chdir }}with"$ARG_CHDIR". For concatenation, ensure that value is quoted (e.g.,"$ARG_CHDIR"). - Repeat for any other possibly tainted variable interpolations, as needed.
- Do not use
${{ ... }}or${{ env.VAR }}insiderun:blocks for user-controlled values. Always set as env variables and only reference with shell syntax.
Required:
- Update
env:section of the step to addARG_CHDIRwith value${{ steps.arg.outputs.arg-chdir }}. - Update line 287 (and possibly 288-289) replace interpolations with env variable syntax.
-
Copy modified line R279 -
Copy modified lines R288-R290
| @@ -276,6 +276,7 @@ | ||
| INPUTS_ARG_CHDIR: ${{ inputs.arg-chdir || inputs.working-directory }} | ||
| INPUTS_PLAN_FILE: ${{ inputs.plan-file }} | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| parity_path: ${{ format('{0}{1}tfplan.parity', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| shell: bash | ||
| @@ -284,9 +285,9 @@ | ||
| # Generate a new plan file, then compare it with the previous one. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and ${{ steps.arg.outputs.arg-detailed-exitcode }} to avoid false-positives. | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| $INPUTS_TOOL"$ARG_CHDIR" plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL"$ARG_CHDIR" show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL"$ARG_CHDIR" show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| # If both plan files are identical, then replace the old plan file with the new one to prevent avoidable stale apply. | ||
| diff --brief tfplan.new tfplan.old && mv --force --verbose "$parity_path" "$path" | ||
| rm --force tfplan.new tfplan.old "$parity_path" |
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-destroy }
Potential code injection in
${ steps.arg.outputs.arg-destroy }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix the code injection vulnerability, you should first assign untrusted workflow input (arg-destroy and similar arguments) to intermediate environment variables and then reference these variables in shell commands using the native shell syntax ($VAR). Avoid using workflow expression syntax to interpolate potentially tainted data directly into shell or script lines.
Specifically:
- For all uses of
${{ steps.arg.outputs.arg-destroy }}(and similar in line 287), ensure these outputs are set as environment variables. - Update the
run:command line to use the shell variable (e.g.,$INPUTS_ARG_DESTROY) in place of${{ steps.arg.outputs.arg-destroy }}. - Repeat this for all
steps.arg.outputs.arg-*values interpolated in the vulnerable command (unless you can guarantee they are not tainted). - This change is made only within
action.yml, primarily affecting lines 287 and the corresponding environment variable block above each usage.
No external dependencies are needed, but you must update both the environment variable section and the command sections.
-
Copy modified lines R279-R291 -
Copy modified lines R300-R302
| @@ -276,6 +276,19 @@ | ||
| INPUTS_ARG_CHDIR: ${{ inputs.arg-chdir || inputs.working-directory }} | ||
| INPUTS_PLAN_FILE: ${{ inputs.plan-file }} | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| INPUTS_ARG_DESTROY: ${{ steps.arg.outputs.arg-destroy }} | ||
| INPUTS_ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| INPUTS_ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| INPUTS_ARG_COMPACT_WARNINGS: ${{ steps.arg.outputs.arg-compact-warnings }} | ||
| INPUTS_ARG_CONCISE: ${{ steps.arg.outputs.arg-concise }} | ||
| INPUTS_ARG_GENERATE_CONFIG_OUT: ${{ steps.arg.outputs.arg-generate-config-out }} | ||
| INPUTS_ARG_LOCK_TIMEOUT: ${{ steps.arg.outputs.arg-lock-timeout }} | ||
| INPUTS_ARG_LOCK: ${{ steps.arg.outputs.arg-lock }} | ||
| INPUTS_ARG_PARALLELISM: ${{ steps.arg.outputs.arg-parallelism }} | ||
| INPUTS_ARG_REFRESH_ONLY: ${{ steps.arg.outputs.arg-refresh-only }} | ||
| INPUTS_ARG_REFRESH: ${{ steps.arg.outputs.arg-refresh }} | ||
| INPUTS_ARG_REPLACE: ${{ steps.arg.outputs.arg-replace }} | ||
| INPUTS_ARG_TARGET: ${{ steps.arg.outputs.arg-target }} | ||
| parity_path: ${{ format('{0}{1}tfplan.parity', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| shell: bash | ||
| @@ -284,9 +297,9 @@ | ||
| # Generate a new plan file, then compare it with the previous one. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and ${{ steps.arg.outputs.arg-detailed-exitcode }} to avoid false-positives. | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR plan$INPUTS_ARG_DESTROY$INPUTS_ARG_VAR_FILE$INPUTS_ARG_VAR$INPUTS_ARG_COMPACT_WARNINGS$INPUTS_ARG_CONCISE$INPUTS_ARG_GENERATE_CONFIG_OUT$INPUTS_ARG_LOCK_TIMEOUT$INPUTS_ARG_LOCK$INPUTS_ARG_PARALLELISM$INPUTS_ARG_REFRESH_ONLY$INPUTS_ARG_REFRESH$INPUTS_ARG_REPLACE$INPUTS_ARG_TARGET -out=tfplan.parity | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| # If both plan files are identical, then replace the old plan file with the new one to prevent avoidable stale apply. | ||
| diff --brief tfplan.new tfplan.old && mv --force --verbose "$parity_path" "$path" | ||
| rm --force tfplan.new tfplan.old "$parity_path" |
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-var-file }
Potential code injection in
${ steps.arg.outputs.arg-var-file }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix the code injection risk for ${{ steps.arg.outputs.arg-var-file }} (and any others interpolated directly with ${{ ... }} syntax into a shell command), the value should be assigned to an intermediate environment variable during the step, and then referenced using native shell syntax (e.g., $INPUTS_ARG_VAR_FILE) within the script. This ensures it is treated as data by the shell, not as executable code. The fix is applied by updating the run: command at line 287 to use $INPUTS_ARG_VAR_FILE, and making analogous replacements for all other interpolated arguments that are derived from untrusted input (all ${{ steps.arg.outputs.arg-... }} values). These changes should be limited to the script in the vulnerable step while ensuring that those values are available as environment variables as already set in step 25–61.
No additional dependencies are needed.
-
Copy modified line R287
| @@ -284,7 +284,7 @@ | ||
| # Generate a new plan file, then compare it with the previous one. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and ${{ steps.arg.outputs.arg-detailed-exitcode }} to avoid false-positives. | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR plan$INPUTS_ARG_DESTROY $INPUTS_ARG_VAR_FILE $INPUTS_ARG_VAR $INPUTS_ARG_COMPACT_WARNINGS $INPUTS_ARG_CONCISE $INPUTS_ARG_GENERATE_CONFIG_OUT $INPUTS_ARG_LOCK_TIMEOUT $INPUTS_ARG_LOCK $INPUTS_ARG_PARALLELISM $INPUTS_ARG_REFRESH_ONLY $INPUTS_ARG_REFRESH $INPUTS_ARG_REPLACE $INPUTS_ARG_TARGET -out=tfplan.parity | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| # If both plan files are identical, then replace the old plan file with the new one to prevent avoidable stale apply. |
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-var }
Potential code injection in
${ steps.arg.outputs.arg-var }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
The best way to fix this problem is to ensure that user-controlled input is not injected directly into shell commands via ${{ ... }} inline expressions, but instead is first assigned to an environment variable and then referenced within the script via shell expansion, ideally within double quotes ("$VAR") to prevent word splitting and globbing.
In this specific case, the ${{ steps.arg.outputs.arg-var }} string should be passed as an environment variable available to the step. Then, in the command, you should reference it with "$INPUTS_ARG_VAR" (mirroring the naming convention used for other environment variables). For additional safety and clarity, all other tainted inputs should also be referenced via environment variables if they aren't already.
How to fix:
- In the "plan parity" step (lines 274-292), add the necessary environment variable definitions: for
steps.arg.outputs.arg-var, add an env entry such asARG_VAR: ${{ steps.arg.outputs.arg-var }}. - In the
run:shell script (line 287), replace${{ steps.arg.outputs.arg-var }}with"$ARG_VAR". - Ensure the script is properly handling empty or unset values; this is naturally handled with double-quote shell expansion.
- Repeat this process for any other interpolated expressions from steps/inputs that may contain untrusted data and which are directly interpolated in
run:.
No package dependencies are required.
-
Copy modified lines R281-R294 -
Copy modified lines R301-R303
| @@ -278,15 +278,29 @@ | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| parity_path: ${{ format('{0}{1}tfplan.parity', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| ARG_DESTROY: ${{ steps.arg.outputs.arg-destroy }} | ||
| ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| ARG_COMPACT_WARNINGS: ${{ steps.arg.outputs.arg-compact-warnings }} | ||
| ARG_CONCISE: ${{ steps.arg.outputs.arg-concise }} | ||
| ARG_GENERATE_CONFIG_OUT: ${{ steps.arg.outputs.arg-generate-config-out }} | ||
| ARG_LOCK_TIMEOUT: ${{ steps.arg.outputs.arg-lock-timeout }} | ||
| ARG_LOCK: ${{ steps.arg.outputs.arg-lock }} | ||
| ARG_PARALLELISM: ${{ steps.arg.outputs.arg-parallelism }} | ||
| ARG_REFRESH_ONLY: ${{ steps.arg.outputs.arg-refresh-only }} | ||
| ARG_REFRESH: ${{ steps.arg.outputs.arg-refresh }} | ||
| ARG_REPLACE: ${{ steps.arg.outputs.arg-replace }} | ||
| ARG_TARGET: ${{ steps.arg.outputs.arg-target }} | ||
| shell: bash | ||
| run: | | ||
| # TF plan parity. | ||
| # Generate a new plan file, then compare it with the previous one. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and ${{ steps.arg.outputs.arg-detailed-exitcode }} to avoid false-positives. | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| $INPUTS_TOOL$ARG_CHDIR plan$ARG_DESTROY$ARG_VAR_FILE"$ARG_VAR"$ARG_COMPACT_WARNINGS$ARG_CONCISE$ARG_GENERATE_CONFIG_OUT$ARG_LOCK_TIMEOUT$ARG_LOCK$ARG_PARALLELISM$ARG_REFRESH_ONLY$ARG_REFRESH$ARG_REPLACE$ARG_TARGET -out=tfplan.parity | ||
| $INPUTS_TOOL$ARG_CHDIR show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL$ARG_CHDIR show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| # If both plan files are identical, then replace the old plan file with the new one to prevent avoidable stale apply. | ||
| diff --brief tfplan.new tfplan.old && mv --force --verbose "$parity_path" "$path" | ||
| rm --force tfplan.new tfplan.old "$parity_path" |
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-compact-warnings }
Potential code injection in
${ steps.arg.outputs.arg-compact-warnings }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
General Fix:
To prevent code injection, untrusted user input should not be interpolated directly into a shell script via ${{ ... }} in a run: block. Instead, use environment variables for all untrusted input and access them using shell-safe variable expansion (e.g., "$VAR").
Specific Fix:
- In the step at line 274-293, collect all user-controlled inputs/outputs into environment variables.
- In the
run:block, build the command line referencing only environment variables with proper quoting, e.g.,"$INPUTS_ARG_COMPACT_WARNINGS". - If complex concatenation is required, perform it inside the shell script, using quoted variables.
- Only hard-coded values or well-sanitized data may be passed via
${{ ... }}; never do${{ steps.arg.outputs.untrusted }}directly insiderun:.
Required changes:
- Extend the
env:section of the step at line 274 to include all the relevant${{ steps.arg.outputs.* }}values as separate env vars (e.g.,ARG_COMPACT_WARNINGS: ${{ steps.arg.outputs.arg-compact-warnings }}, etc.). - In the
run:script at line 287, build up the command line with double-quoted environment variables (e.g.,"$ARG_COMPACT_WARNINGS"). - Remove any direct interpolation of
${{ steps.arg.outputs.arg-compact-warnings }}(and similar) in the shell script itself.
-
Copy modified lines R281-R294 -
Copy modified line R299 -
Copy modified lines R301-R303
| @@ -278,15 +278,29 @@ | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| parity_path: ${{ format('{0}{1}tfplan.parity', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| ARG_DESTROY: ${{ steps.arg.outputs.arg-destroy }} | ||
| ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| ARG_COMPACT_WARNINGS: ${{ steps.arg.outputs.arg-compact-warnings }} | ||
| ARG_CONCISE: ${{ steps.arg.outputs.arg-concise }} | ||
| ARG_GENERATE_CONFIG_OUT: ${{ steps.arg.outputs.arg-generate-config-out }} | ||
| ARG_LOCK_TIMEOUT: ${{ steps.arg.outputs.arg-lock-timeout }} | ||
| ARG_LOCK: ${{ steps.arg.outputs.arg-lock }} | ||
| ARG_PARALLELISM: ${{ steps.arg.outputs.arg-parallelism }} | ||
| ARG_REFRESH_ONLY: ${{ steps.arg.outputs.arg-refresh-only }} | ||
| ARG_REFRESH: ${{ steps.arg.outputs.arg-refresh }} | ||
| ARG_REPLACE: ${{ steps.arg.outputs.arg-replace }} | ||
| ARG_TARGET: ${{ steps.arg.outputs.arg-target }} | ||
| shell: bash | ||
| run: | | ||
| # TF plan parity. | ||
| # Generate a new plan file, then compare it with the previous one. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and ${{ steps.arg.outputs.arg-detailed-exitcode }} to avoid false-positives. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and $ARG_DETAILED_EXITCODE to avoid false-positives. | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| $INPUTS_TOOL$ARG_CHDIR plan$ARG_DESTROY$ARG_VAR_FILE$ARG_VAR$ARG_COMPACT_WARNINGS$ARG_CONCISE$ARG_GENERATE_CONFIG_OUT$ARG_LOCK_TIMEOUT$ARG_LOCK$ARG_PARALLELISM$ARG_REFRESH_ONLY$ARG_REFRESH$ARG_REPLACE$ARG_TARGET -out=tfplan.parity | ||
| $INPUTS_TOOL$ARG_CHDIR show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL$ARG_CHDIR show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| # If both plan files are identical, then replace the old plan file with the new one to prevent avoidable stale apply. | ||
| diff --brief tfplan.new tfplan.old && mv --force --verbose "$parity_path" "$path" | ||
| rm --force tfplan.new tfplan.old "$parity_path" |
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-refresh }
Potential code injection in
${ steps.arg.outputs.arg-refresh }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To remediate the risk of code injection from untrusted workflow inputs in GitHub Actions shell commands, change the pattern to set each untrusted input value into an environment variable (using the env: block), and then reference that value within the shell script using the shell’s native syntax (e.g. $INPUTS_ARG_REFRESH, not ${{ ... }}). For each dynamic argument in line 287 (the plan command), capture all values via env, and build the command using variables, not expression interpolation.
- In file
action.yml, step on line 274+, modify the command on line 287 to reference intermediate shell variables (e.g.,$INPUTS_ARG_REFRESH) instead of direct${{ ... }}expansions. - Ensure all relevant inputs are set as environment variables in the
env:block. - Assemble the plan command in a safe way using those variables.
- Do not change the argument's semantics, simply alter how the data is passed and used to remove direct expression expansion in shell context.
-
Copy modified lines R279-R291 -
Copy modified line R298 -
Copy modified lines R300-R302
| @@ -276,17 +276,30 @@ | ||
| INPUTS_ARG_CHDIR: ${{ inputs.arg-chdir || inputs.working-directory }} | ||
| INPUTS_PLAN_FILE: ${{ inputs.plan-file }} | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| INPUTS_ARG_DESTROY: ${{ steps.arg.outputs.arg-destroy }} | ||
| INPUTS_ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| INPUTS_ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| INPUTS_ARG_COMPACT_WARNINGS: ${{ steps.arg.outputs.arg-compact-warnings }} | ||
| INPUTS_ARG_CONCISE: ${{ steps.arg.outputs.arg-concise }} | ||
| INPUTS_ARG_GENERATE_CONFIG_OUT: ${{ steps.arg.outputs.arg-generate-config-out }} | ||
| INPUTS_ARG_LOCK_TIMEOUT: ${{ steps.arg.outputs.arg-lock-timeout }} | ||
| INPUTS_ARG_LOCK: ${{ steps.arg.outputs.arg-lock }} | ||
| INPUTS_ARG_PARALLELISM: ${{ steps.arg.outputs.arg-parallelism }} | ||
| INPUTS_ARG_REFRESH_ONLY: ${{ steps.arg.outputs.arg-refresh-only }} | ||
| INPUTS_ARG_REFRESH: ${{ steps.arg.outputs.arg-refresh }} | ||
| INPUTS_ARG_REPLACE: ${{ steps.arg.outputs.arg-replace }} | ||
| INPUTS_ARG_TARGET: ${{ steps.arg.outputs.arg-target }} | ||
| parity_path: ${{ format('{0}{1}tfplan.parity', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| shell: bash | ||
| run: | | ||
| # TF plan parity. | ||
| # Generate a new plan file, then compare it with the previous one. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and ${{ steps.arg.outputs.arg-detailed-exitcode }} to avoid false-positives. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and $INPUTS_ARG_DETAILED_EXITCODE to avoid false-positives. | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR plan$INPUTS_ARG_DESTROY$INPUTS_ARG_VAR_FILE$INPUTS_ARG_VAR$INPUTS_ARG_COMPACT_WARNINGS$INPUTS_ARG_CONCISE$INPUTS_ARG_GENERATE_CONFIG_OUT$INPUTS_ARG_LOCK_TIMEOUT$INPUTS_ARG_LOCK$INPUTS_ARG_PARALLELISM$INPUTS_ARG_REFRESH_ONLY$INPUTS_ARG_REFRESH$INPUTS_ARG_REPLACE$INPUTS_ARG_TARGET -out=tfplan.parity | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| # If both plan files are identical, then replace the old plan file with the new one to prevent avoidable stale apply. | ||
| diff --brief tfplan.new tfplan.old && mv --force --verbose "$parity_path" "$path" | ||
| rm --force tfplan.new tfplan.old "$parity_path" |
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-replace }
Potential code injection in
${ steps.arg.outputs.arg-replace }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
The best way to fix the code injection problem is to ensure that user-controlled inputs are only ever referenced via environment variables in the shell, not as direct string interpolations in the command. Specifically, for all interpolated values like ${{ steps.arg.outputs.arg-replace }} that come from user input (and are already set as env vars), replace ${{ ... }} expressions with shell variable references (e.g., $INPUTS_ARG_REPLACE). This way, the shell naturally handles value quoting, and malicious values cannot escape the context.
- In
action.yml, locate the run command on line 287 (and anywhere similar pattern is used). - Replace all uses of
${{ steps.arg.outputs.arg-replace }}(and other relevant interpolations) with the corresponding shell variable, e.g.,$INPUTS_ARG_REPLACE. - Ensure the environment variable is set in the
env:block above (already present in the original code). - If necessary, update similar usages on other affected lines in the same command so that all user input is only referenced via shell variables.
- No new methods or imports are needed; just change the invocation style.
-
Copy modified line R287
| @@ -284,7 +284,7 @@ | ||
| # Generate a new plan file, then compare it with the previous one. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and ${{ steps.arg.outputs.arg-detailed-exitcode }} to avoid false-positives. | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR plan$INPUTS_ARG_DESTROY$INPUTS_ARG_VAR_FILE$INPUTS_ARG_VAR$INPUTS_ARG_COMPACT_WARNINGS$INPUTS_ARG_CONCISE$INPUTS_ARG_GENERATE_CONFIG_OUT$INPUTS_ARG_LOCK_TIMEOUT$INPUTS_ARG_LOCK$INPUTS_ARG_PARALLELISM$INPUTS_ARG_REFRESH_ONLY$INPUTS_ARG_REFRESH$INPUTS_ARG_REPLACE$INPUTS_ARG_TARGET -out=tfplan.parity | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| # If both plan files are identical, then replace the old plan file with the new one to prevent avoidable stale apply. |
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-target }
Potential code injection in
${ steps.arg.outputs.arg-target }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix the code injection vulnerability, set the untrusted input value as an intermediate environment variable, and use native Bash syntax to expand the variable in the shell command, instead of string concatenation or workflow expression interpolation. In particular, on line 287, we should not reference ${{ steps.arg.outputs.arg-target }} directly inside the command. Instead, assign the output of steps.arg.outputs.arg-target to an environment variable (e.g., ARG_TARGET), and refer to it as $ARG_TARGET inside the Bash script. The same should be done for other untrusted workflow outputs/inputs if they are interpolated directly in shell commands.
Changes to make:
- In step 274–282, move all necessary interpolations (e.g.,
steps.arg.outputs.arg-targetand other similar output variables) into environment variables. - In the Bash script (line 287), reference these via
$ARG_TARGET(and analogous variables). - Only use
$ARG_TARGET(not Bash's${{ ... }}or composite string interpolations) inside the shell script. - No need to change how the environment variables are set earlier, just ensure all workflow output/access to untrusted values in this context are mapped to shell environment variables.
Implementing the fix:
- In step 274 (
- env:), addARG_TARGET: ${{ steps.arg.outputs.arg-target }}(and similar for other variables interpolated into the command). - In step 287, change
${{ steps.arg.outputs.arg-target }}to$ARG_TARGET. - Repeat this for all other workflow outputs from
steps.arg.outputsin the command on line 287. - No new dependencies are needed; this is a YAML and shell convention change.
-
Copy modified lines R279-R292 -
Copy modified line R299 -
Copy modified lines R301-R303
| @@ -276,17 +276,31 @@ | ||
| INPUTS_ARG_CHDIR: ${{ inputs.arg-chdir || inputs.working-directory }} | ||
| INPUTS_PLAN_FILE: ${{ inputs.plan-file }} | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| ARG_DESTROY: ${{ steps.arg.outputs.arg-destroy }} | ||
| ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| ARG_COMPACT_WARNINGS: ${{ steps.arg.outputs.arg-compact-warnings }} | ||
| ARG_CONCISE: ${{ steps.arg.outputs.arg-concise }} | ||
| ARG_GENERATE_CONFIG_OUT: ${{ steps.arg.outputs.arg-generate-config-out }} | ||
| ARG_LOCK_TIMEOUT: ${{ steps.arg.outputs.arg-lock-timeout }} | ||
| ARG_LOCK: ${{ steps.arg.outputs.arg-lock }} | ||
| ARG_PARALLELISM: ${{ steps.arg.outputs.arg-parallelism }} | ||
| ARG_REFRESH_ONLY: ${{ steps.arg.outputs.arg-refresh-only }} | ||
| ARG_REFRESH: ${{ steps.arg.outputs.arg-refresh }} | ||
| ARG_REPLACE: ${{ steps.arg.outputs.arg-replace }} | ||
| ARG_TARGET: ${{ steps.arg.outputs.arg-target }} | ||
| parity_path: ${{ format('{0}{1}tfplan.parity', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| shell: bash | ||
| run: | | ||
| # TF plan parity. | ||
| # Generate a new plan file, then compare it with the previous one. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and ${{ steps.arg.outputs.arg-detailed-exitcode }} to avoid false-positives. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and $ARG_DETAILED_EXITCODE to avoid false-positives. | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| $INPUTS_TOOL$ARG_CHDIR plan$ARG_DESTROY$ARG_VAR_FILE$ARG_VAR$ARG_COMPACT_WARNINGS$ARG_CONCISE$ARG_GENERATE_CONFIG_OUT$ARG_LOCK_TIMEOUT$ARG_LOCK$ARG_PARALLELISM$ARG_REFRESH_ONLY$ARG_REFRESH$ARG_REPLACE$ARG_TARGET -out=tfplan.parity | ||
| $INPUTS_TOOL$ARG_CHDIR show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL$ARG_CHDIR show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| # If both plan files are identical, then replace the old plan file with the new one to prevent avoidable stale apply. | ||
| diff --brief tfplan.new tfplan.old && mv --force --verbose "$parity_path" "$path" | ||
| rm --force tfplan.new tfplan.old "$parity_path" |
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix the vulnerability, the action should avoid interpolating untrusted user data directly into the shell command line via ${{ ... }} in the run: step. Instead, emit all tainted user inputs such as steps.arg.outputs.arg-chdir as environment variables (as is already done elsewhere in the action), and then use the shell's native environment variable expansion (e.g., "$VAR") within the run: script shell code. For safe shell concatenation, always quote variables and avoid direct command-line expansion.
Specifically here:
- In the block beginning at line 274, move all
steps.arg.outputs.*interpolations out of the command line and into theenv:map for the step. - In the shell code (i.e., the script inside
run:), use"$ARG_CHDIR"or similar (well-quoted!) shell variable reference instead of${{ steps.arg.outputs.arg-chdir }}. - Adjust the surrounding script to concatenate or compose command arguments safely using shell variable values, not template interpolation.
You'll need to:
- Add the necessary
env:key(s) for everysteps.arg.outputs.*value used inside the run script block. - Update the shell script to use the corresponding environment variable (fully quoted for safety).
-
Copy modified lines R281-R294 -
Copy modified line R299 -
Copy modified lines R301-R303
| @@ -278,15 +278,29 @@ | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| parity_path: ${{ format('{0}{1}tfplan.parity', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| ARG_DESTROY: ${{ steps.arg.outputs.arg-destroy }} | ||
| ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| ARG_COMPACT_WARNINGS: ${{ steps.arg.outputs.arg-compact-warnings }} | ||
| ARG_CONCISE: ${{ steps.arg.outputs.arg-concise }} | ||
| ARG_GENERATE_CONFIG_OUT: ${{ steps.arg.outputs.arg-generate-config-out }} | ||
| ARG_LOCK_TIMEOUT: ${{ steps.arg.outputs.arg-lock-timeout }} | ||
| ARG_LOCK: ${{ steps.arg.outputs.arg-lock }} | ||
| ARG_PARALLELISM: ${{ steps.arg.outputs.arg-parallelism }} | ||
| ARG_REFRESH_ONLY: ${{ steps.arg.outputs.arg-refresh-only }} | ||
| ARG_REFRESH: ${{ steps.arg.outputs.arg-refresh }} | ||
| ARG_REPLACE: ${{ steps.arg.outputs.arg-replace }} | ||
| ARG_TARGET: ${{ steps.arg.outputs.arg-target }} | ||
| shell: bash | ||
| run: | | ||
| # TF plan parity. | ||
| # Generate a new plan file, then compare it with the previous one. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and ${{ steps.arg.outputs.arg-detailed-exitcode }} to avoid false-positives. | ||
| # Both plan files are normalized by sorting JSON keys, removing timestamps and $ARG_DETAILED_EXITCODE to avoid false-positives. | ||
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| "$INPUTS_TOOL""$ARG_CHDIR" plan"$ARG_DESTROY""$ARG_VAR_FILE""$ARG_VAR""$ARG_COMPACT_WARNINGS""$ARG_CONCISE""$ARG_GENERATE_CONFIG_OUT""$ARG_LOCK_TIMEOUT""$ARG_LOCK""$ARG_PARALLELISM""$ARG_REFRESH_ONLY""$ARG_REFRESH""$ARG_REPLACE""$ARG_TARGET" -out=tfplan.parity | ||
| "$INPUTS_TOOL""$ARG_CHDIR" show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| "$INPUTS_TOOL""$ARG_CHDIR" show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old | ||
| # If both plan files are identical, then replace the old plan file with the new one to prevent avoidable stale apply. | ||
| diff --brief tfplan.new tfplan.old && mv --force --verbose "$parity_path" "$path" | ||
| rm --force tfplan.new tfplan.old "$parity_path" |
| if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null; fi | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tfplan.parity | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan.parity | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.new | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} show -json tfplan | jq --sort-keys '[(.resource_changes? // [])[] | select(.change.actions != ["no-op"])]' > tfplan.old |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
| args="${{ steps.arg.outputs.arg-destroy }}${var_file}${var}${{ steps.arg.outputs.arg-backup }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-state-out }}${{ steps.arg.outputs.arg-state }}${{ steps.arg.outputs.arg-target }}${plan}" | ||
| echo "${{ inputs.tool }} apply${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} apply${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL apply${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
How to fix the problem:
To prevent code injection, user-provided input that ends up in a shell command must not be passed with inline interpolation (i.e., with ${{ ... }} inside the command string), but rather set as environment variables and referenced in the shell with proper syntax (i.e., "${VAR}"). This avoids the possibility of user input breaking shell syntax or introducing malicious commands.
Best way to fix (concrete for this case):
- On line 318 and 319, replace
${{ steps.arg.outputs.arg-chdir }}inline usage with a shell reference to an environment variable (say,$CHDIR_ARG). - In the corresponding step’s
env:block (lines 296-302), introduce a new variable, e.g.,CHDIR_ARG: ${{ steps.arg.outputs.arg-chdir }}. - In the shell script, refer to
"${CHDIR_ARG}"instead of directly interpolating from the workflow.
What is needed:
- Add
CHDIR_ARG: ${{ steps.arg.outputs.arg-chdir }}to theenvstanza in the "apply" step. - Update the
echoand command execution lines to use${CHDIR_ARG}shell-safely. - Ensure this new pattern is only applied to the relevant block.
-
Copy modified line R302 -
Copy modified lines R319-R320
| @@ -299,6 +299,7 @@ | ||
| INPUTS_PLAN_PARITY: ${{ inputs.plan-parity }} | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| CHDIR_ARG: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF apply. | ||
| @@ -315,8 +316,8 @@ | ||
| var="" | ||
| fi | ||
| args="${{ steps.arg.outputs.arg-destroy }}${var_file}${var}${{ steps.arg.outputs.arg-backup }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-state-out }}${{ steps.arg.outputs.arg-state }}${{ steps.arg.outputs.arg-target }}${plan}" | ||
| echo "$INPUTS_TOOL apply${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} apply${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL apply$CHDIR_ARG${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL$CHDIR_ARG apply${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ !cancelled() && steps.identifier.outcome == 'success' && contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} | ||
| id: post |
| echo "${{ inputs.tool }} apply${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} apply${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL apply${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} apply${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-chdir }
Potential code injection in
${ steps.arg.outputs.arg-chdir }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
General fix:
The issue is that an untrusted value is interpolated directly into a shell command. The best way to prevent code injection is to:
- Pass the untrusted input to the shell as an environment variable.
- In the shell script, access the argument using native shell variable syntax (
$VAR), rather than via workflow interpolation or expressions (i.e., no${{ ... }}insiderun:). - Additionally, ensure that when constructing the final shell command, the argument is quoted where necessary to prevent shell word splitting or injection.
Detailed fix for this action:
- Set a new environment variable, e.g.,
CHDIR_ARG, in the workflow step, assigning it the same value as is currently used for${{ steps.arg.outputs.arg-chdir }}. - In the
runshell script, refer to this value as$CHDIR_ARGwhen constructing the command string. - When combining this (possibly empty) variable with other arguments, ensure safe word splitting.
- Replace any usage of
${{ steps.arg.outputs.arg-chdir }}inside therun:block with appropriate shell variable expansion (i.e.,$CHDIR_ARG), quoting where needed.
Lines to change:
- In the step with
id: apply, addCHDIR_ARG: ${{ steps.arg.outputs.arg-chdir }}toenv. - In the shell script under
run:, replace${{ steps.arg.outputs.arg-chdir }}with$CHDIR_ARGin both places (one on theecho ... | sed ...line and one in the execution line). - Ensure that the other uses of
${{ steps.arg.outputs.arg-chdir }}elsewhere in this step are replaced as well.
What is needed:
- Add the environment variable assignment (
CHDIR_ARG) in theenv:block of theapplystep. - Edit lines 318 and 319 to use
$CHDIR_ARGrather than${{ steps.arg.outputs.arg-chdir }}.
-
Copy modified line R302 -
Copy modified lines R319-R320
| @@ -299,6 +299,7 @@ | ||
| INPUTS_PLAN_PARITY: ${{ inputs.plan-parity }} | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} | ||
| CHDIR_ARG: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF apply. | ||
| @@ -315,8 +316,8 @@ | ||
| var="" | ||
| fi | ||
| args="${{ steps.arg.outputs.arg-destroy }}${var_file}${var}${{ steps.arg.outputs.arg-backup }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-state-out }}${{ steps.arg.outputs.arg-state }}${{ steps.arg.outputs.arg-target }}${plan}" | ||
| echo "$INPUTS_TOOL apply${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} apply${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| echo "$INPUTS_TOOL apply$CHDIR_ARG${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL$CHDIR_ARG apply${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ !cancelled() && steps.identifier.outcome == 'success' && contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} | ||
| id: post |
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
| diff=" | ||
| <details${{ inputs.expand-diff == 'true' && ' open' || '' }}><summary>Diff of ${diff_count} ${diff_change}.</summary> | ||
| <details${{ env.INPUTS_EXPAND_DIFF == 'true' && ' open' || '' }}><summary>Diff of ${diff_count} ${diff_change}.</summary> |
Check warning
Code scanning / CodeQL
Code injection Medium
${ env.INPUTS_EXPAND_DIFF == 'true' && ' open' || '' }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix this issue, remove the use of ${{ ... }} for variable interpolation inside the shell script, particularly on line 423. Instead, interpolate the environment variable using shell syntax ($INPUTS_EXPAND_DIFF). This means that if you want to set the open attribute conditionally, you should perform that logic in native Bash. First, set an intermediate Bash variable (e.g., expand_diff_flag) to either " open" or "" based on the value of $INPUTS_EXPAND_DIFF. Then, interpolate that flag into the Markdown result. This should be handled just before the block where diff is set.
Specifically, in the shell script block starting from line 422, rewrite the logic as:
- Compute
expand_diff_flagin Bash, e.g.if [[ "$INPUTS_EXPAND_DIFF" == "true" ]]; then expand_diff_flag=" open"; else expand_diff_flag=""; fi - Use
expand_diff_flagin place of${{ env.INPUTS_EXPAND_DIFF == 'true' && ' open' || '' }}within the diff Markdown construction.
No new dependencies are required.
-
Copy modified line R422 -
Copy modified line R424
| @@ -419,8 +419,9 @@ | ||
| if [[ ${#diff_truncated} -eq 24000 ]]; then diff_truncated="${diff_truncated}"$'\n…'; fi | ||
| echo "diff<<EODIFFTFVIAPR"$'\n'"$diff_truncated"$'\n'EODIFFTFVIAPR >> "$GITHUB_OUTPUT" | ||
|
|
||
| if [[ "$INPUTS_EXPAND_DIFF" == "true" ]]; then expand_diff_flag=" open"; else expand_diff_flag=""; fi | ||
| diff=" | ||
| <details${{ env.INPUTS_EXPAND_DIFF == 'true' && ' open' || '' }}><summary>Diff of ${diff_count} ${diff_change}.</summary> | ||
| <details${expand_diff_flag}><summary>Diff of ${diff_count} ${diff_change}.</summary> | ||
|
|
||
| \`\`\`diff | ||
| ${diff_truncated} |
| ${diff} | ||
| <!-- placeholder-3 --> | ||
| <details${{ inputs.expand-summary == 'true' && ' open' || '' }}><summary>${summary}</br> | ||
| <details${{ env.INPUTS_EXPAND_SUMMARY == 'true' && ' open' || '' }}><summary>${summary}</br> |
Check warning
Code scanning / CodeQL
Code injection Medium
${ env.INPUTS_EXPAND_SUMMARY == 'true' && ' open' || '' }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix this problem, we must avoid the direct interpolation of untrusted user input via ${{ ... }} in places where it can affect markup or code, as in ${{ env.INPUTS_EXPAND_SUMMARY == 'true' && ' open' || '' }}. Instead, we should:
- Set the input value into an intermediate environment variable (
INPUTS_EXPAND_SUMMARY) as we are already doing. - In the shell script, evaluate the variable and assign the exact string (
openor empty) to another environment variable, such asEXPAND_FLAG. - Use native shell variable interpolation (
$EXPAND_FLAG) to insert only a controlled value. - Update line 451 (and any similar cases) to use
"$EXPAND_FLAG"via shell expansion.
Changes needed:
- In the shell script (after input variables are set), add logic to:
- Set
EXPAND_FLAG="open"ifINPUTS_EXPAND_SUMMARY == 'true', elseEXPAND_FLAG="".
- Set
- In the block that constructs the body:
- Replace
${{ env.INPUTS_EXPAND_SUMMARY == 'true' && ' open' || '' }}with$EXPAND_FLAG.
- Replace
- No extra dependencies are required.
-
Copy modified lines R24-R28 -
Copy modified line R456
| @@ -21,6 +21,11 @@ | ||
| if [[ "$INPUTS_PLAN_ENCRYPT" ]]; then which openssl > /dev/null 2>&1 || { echo "Please install openssl before running this action as it is required for plan file encryption."; exit 1; }; fi | ||
| if [[ "$INPUTS_PLAN_PARITY" ]]; then which diff > /dev/null 2>&1 || { echo "Please install diff before running this action as it is required for comparing plan file parity."; exit 1; }; fi | ||
|
|
||
| # Set expand flag for details tag to prevent code injection | ||
| EXPAND_FLAG="" | ||
| if [ "$INPUTS_EXPAND_SUMMARY" = "true" ]; then | ||
| EXPAND_FLAG=" open" | ||
| fi | ||
| - id: arg | ||
| env: | ||
| INPUTS_ARG_AUTO_APPROVE: ${{ inputs.arg-auto-approve }} | ||
| @@ -448,7 +453,7 @@ | ||
| <!-- placeholder-2 --> | ||
| ${diff} | ||
| <!-- placeholder-3 --> | ||
| <details${{ env.INPUTS_EXPAND_SUMMARY == 'true' && ' open' || '' }}><summary>${summary}</br> | ||
| <details${EXPAND_FLAG}><summary>${summary}</br> | ||
|
|
||
| <!-- placeholder-4 --> | ||
| ###### By ${tag_actor}${{ github.triggering_actor }} at ${{ github.event.pull_request.updated_at || github.event.comment.created_at || github.event.head_commit.timestamp || github.event.merge_group.head_commit.timestamp }} [(view log)](${run_url}). |
| # Determine if a PR comment should be created. | ||
| if [[ "${{ inputs.comment-pr }}" == "always" && "${{ steps.identifier.outputs.pr }}" -ne 0 ]]; then | ||
| if [[ "$INPUTS_COMMENT_PR" == "always" && "${{ steps.identifier.outputs.pr }}" -ne 0 ]]; then |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.identifier.outputs.pr }
Potential code injection in
${ steps.identifier.outputs.pr }
| if [[ "$INPUTS_COMMENT_PR" == "always" && "${{ steps.identifier.outputs.pr }}" -ne 0 ]]; then | ||
| create_comment="true" | ||
| elif [[ ("${{ inputs.comment-pr }}" == "on-diff" || "${{ inputs.comment-pr }}" == "on-change") && "$exitcode" -ne 0 && "${{ steps.identifier.outputs.pr }}" -ne 0 ]]; then | ||
| elif [[ ("$INPUTS_COMMENT_PR" == "on-diff" || "$INPUTS_COMMENT_PR" == "on-change") && "$exitcode" -ne 0 && "${{ steps.identifier.outputs.pr }}" -ne 0 ]]; then |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.identifier.outputs.pr }
Potential code injection in
${ steps.identifier.outputs.pr }
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "${{ inputs.tool }} init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-backend-config }
Potential code injection in
${ steps.arg.outputs.arg-backend-config }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
The best way to fix this problem is to avoid using GitHub Actions' string interpolation (${{ ... }}) directly within shell commands inside the run: key. Instead, all potentially untrusted inputs should be captured into environment variables using the env: block, and the shell script should reference them using native shell variable syntax, e.g. "$INPUTS_ARG_BACKEND_CONFIG".
Specifically, in this snippet:
- All occurrences of
${{ steps.arg.outputs.<something> }}within the command line inrun:(especially those influenced by user input) should be set as environment variables in theenv:block. - In the script, construct the argument string by referencing the shell variables, e.g.:
args="$INPUTS_ARG_BACKEND_CONFIG$INPUTS_ARG_BACKEND..."
You should:
- Move every
${{ steps.arg.outputs.arg-... }}referenced in the command-line in theinitializestep (lines 164, etc.) into theenv:of that step. - In the
runscript, change all${{ steps.arg.outputs.arg-... }}references to$INPUTS_ARG_...shell variables. - This preserves functionality and closes the code injection exposure.
-
Copy modified lines R160-R175 -
Copy modified lines R180-R186
| @@ -157,13 +157,33 @@ | ||
| id: initialize | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| INPUTS_ARG_BACKEND_CONFIG: ${{ steps.arg.outputs.arg-backend-config }} | ||
| INPUTS_ARG_BACKEND: ${{ steps.arg.outputs.arg-backend }} | ||
| INPUTS_ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| INPUTS_ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| INPUTS_ARG_FORCE_COPY: ${{ steps.arg.outputs.arg-force-copy }} | ||
| INPUTS_ARG_FROM_MODULE: ${{ steps.arg.outputs.arg-from-module }} | ||
| INPUTS_ARG_GET: ${{ steps.arg.outputs.arg-get }} | ||
| INPUTS_ARG_LOCK_TIMEOUT: ${{ steps.arg.outputs.arg-lock-timeout }} | ||
| INPUTS_ARG_LOCK: ${{ steps.arg.outputs.arg-lock }} | ||
| INPUTS_ARG_LOCKFILE: ${{ steps.arg.outputs.arg-lockfile }} | ||
| INPUTS_ARG_MIGRATE_STATE: ${{ steps.arg.outputs.arg-migrate-state }} | ||
| INPUTS_ARG_PLUGIN_DIR: ${{ steps.arg.outputs.arg-plugin-dir }} | ||
| INPUTS_ARG_RECONFIGURE: ${{ steps.arg.outputs.arg-reconfigure }} | ||
| INPUTS_ARG_TEST_DIRECTORY: ${{ steps.arg.outputs.arg-test-directory }} | ||
| INPUTS_ARG_UPGRADE: ${{ steps.arg.outputs.arg-upgrade }} | ||
| INPUTS_ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF initialize. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="$INPUTS_ARG_BACKEND_CONFIG$INPUTS_ARG_BACKEND" | ||
| if [ "$INPUTS_TOOL" = "tofu" ]; then | ||
| args="$args$INPUTS_ARG_VAR_FILE$INPUTS_ARG_VAR" | ||
| fi | ||
| args="$args$INPUTS_ARG_FORCE_COPY$INPUTS_ARG_FROM_MODULE$INPUTS_ARG_GET$INPUTS_ARG_LOCK_TIMEOUT$INPUTS_ARG_LOCK$INPUTS_ARG_LOCKFILE$INPUTS_ARG_MIGRATE_STATE$INPUTS_ARG_PLUGIN_DIR$INPUTS_ARG_RECONFIGURE$INPUTS_ARG_TEST_DIRECTORY$INPUTS_ARG_UPGRADE" | ||
| echo "$INPUTS_TOOL init$INPUTS_ARG_CHDIR$args" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR init$args 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.validate == 'true' && contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} | ||
| id: validate |
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "${{ inputs.tool }} init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-backend }
Potential code injection in
${ steps.arg.outputs.arg-backend }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
The best way to fix this problem is to pass all potentially untrusted inputs—such as inputs.arg-backend—through environment variables, and reference them only via shell-native syntax like $INPUTS_ARG_BACKEND (not via ${{ ... }} interpolation within the shell script). This ensures that the input will not be interpreted as additional shell commands, and any malicious content will be treated as a literal argument.
Specifically, you should:
- In the
runs.steps, for any bash shellrunsections, ensure that references tosteps.arg.outputs.arg-backendand similar expressions are not interpolated directly into the shell command. - Instead, pass these as environment variables (already done in the earlier
steps), and reference their shell values natively (i.e., use$INPUTS_ARG_BACKEND). - On line 164 and similar, update variable assignments such that the composed arguments use shell variable notation (
$VARIABLE) instead of${{ ... }}interpolation.
For this snippet, you should:
- Update the assignment to
argson line 164 in therunblock, replacing all${{ steps.arg.outputs.arg-backend }}(and similar) with shell variable references, e.g.$INPUTS_ARG_BACKEND, which are already set as environment variables in the previous step. - Ensure any other arguments flowing from user-input expressions are referenced via environment variables.
No change is required to the environment variable assignment (already safe).
-
Copy modified line R164 -
Copy modified line R176
| @@ -161,7 +161,7 @@ | ||
| run: | | ||
| # TF initialize. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| args="$INPUTS_ARG_BACKEND_CONFIG$INPUTS_ARG_BACKEND$( [ "$INPUTS_TOOL" = "tofu" ] && echo "$INPUTS_ARG_VAR_FILE" )$( [ "$INPUTS_TOOL" = "tofu" ] && echo "$INPUTS_ARG_VAR" )$INPUTS_ARG_FORCE_COPY$INPUTS_ARG_FROM_MODULE$INPUTS_ARG_GET$INPUTS_ARG_LOCK_TIMEOUT$INPUTS_ARG_LOCK$INPUTS_ARG_LOCKFILE$INPUTS_ARG_MIGRATE_STATE$INPUTS_ARG_PLUGIN_DIR$INPUTS_ARG_RECONFIGURE$INPUTS_ARG_TEST_DIRECTORY$INPUTS_ARG_UPGRADE" | ||
| echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| @@ -173,7 +173,7 @@ | ||
| run: | | ||
| # TF validate. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| args="$( [ "$INPUTS_TOOL" = "tofu" ] && echo "$INPUTS_ARG_VAR_FILE" )$( [ "$INPUTS_TOOL" = "tofu" ] && echo "$INPUTS_ARG_VAR" )$INPUTS_ARG_NO_TESTS$INPUTS_ARG_TEST_DIRECTORY" | ||
| echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "${{ inputs.tool }} init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" |
Check warning
Code scanning / CodeQL
Code injection Medium
${ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
The best way to fix this issue is to avoid constructing shell commands by directly interpolating potentially user-controlled input with ${{ ... }} inside the command string in the run: block. Instead, assign each untrusted or variable value to an environment variable at the step level, and then reference it in the script using native Bash syntax ($VAR or "${VAR}"). This prevents shell metacharacter injection, as Bash does not evaluate the contents of environment variables as additional commands, but as plain string arguments.
Specific remediation plan:
- For all command-line arguments derived from user input, set them as step environment variables in the
env:section. - In the
run:block, use native shell syntax to reference environment variables. - Replace argument construction such as
with Bash logic that safely adds the environment variables into the command arguments.
args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}..." - Ensure all potentially user-controlled values (outputs/inputs) are referenced as environment variables and used with quotes as appropriate.
- Only make edits to the YAML you have been shown, namely in lines 164 and similar argument construction patterns.
-
Copy modified lines R160-R175 -
Copy modified lines R180-R187
| @@ -157,13 +157,34 @@ | ||
| id: initialize | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| INPUTS_ARG_BACKEND_CONFIG: ${{ steps.arg.outputs.arg-backend-config }} | ||
| INPUTS_ARG_BACKEND: ${{ steps.arg.outputs.arg-backend }} | ||
| INPUTS_ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| INPUTS_ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| INPUTS_ARG_FORCE_COPY: ${{ steps.arg.outputs.arg-force-copy }} | ||
| INPUTS_ARG_FROM_MODULE: ${{ steps.arg.outputs.arg-from-module }} | ||
| INPUTS_ARG_GET: ${{ steps.arg.outputs.arg-get }} | ||
| INPUTS_ARG_LOCK_TIMEOUT: ${{ steps.arg.outputs.arg-lock-timeout }} | ||
| INPUTS_ARG_LOCK: ${{ steps.arg.outputs.arg-lock }} | ||
| INPUTS_ARG_LOCKFILE: ${{ steps.arg.outputs.arg-lockfile }} | ||
| INPUTS_ARG_MIGRATE_STATE: ${{ steps.arg.outputs.arg-migrate-state }} | ||
| INPUTS_ARG_PLUGIN_DIR: ${{ steps.arg.outputs.arg-plugin-dir }} | ||
| INPUTS_ARG_RECONFIGURE: ${{ steps.arg.outputs.arg-reconfigure }} | ||
| INPUTS_ARG_TEST_DIRECTORY: ${{ steps.arg.outputs.arg-test-directory }} | ||
| INPUTS_ARG_UPGRADE: ${{ steps.arg.outputs.arg-upgrade }} | ||
| INPUTS_ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF initialize. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| # Pass potentially unsafe inputs as environment variables and reference via shell expansion. | ||
| args="${INPUTS_ARG_BACKEND_CONFIG}${INPUTS_ARG_BACKEND}" | ||
| if [ "${INPUTS_TOOL}" = "tofu" ]; then | ||
| args="${args}${INPUTS_ARG_VAR_FILE}${INPUTS_ARG_VAR}" | ||
| fi | ||
| args="${args}${INPUTS_ARG_FORCE_COPY}${INPUTS_ARG_FROM_MODULE}${INPUTS_ARG_GET}${INPUTS_ARG_LOCK_TIMEOUT}${INPUTS_ARG_LOCK}${INPUTS_ARG_LOCKFILE}${INPUTS_ARG_MIGRATE_STATE}${INPUTS_ARG_PLUGIN_DIR}${INPUTS_ARG_RECONFIGURE}${INPUTS_ARG_TEST_DIRECTORY}${INPUTS_ARG_UPGRADE}" | ||
| echo "${INPUTS_TOOL} init${INPUTS_ARG_CHDIR}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| "${INPUTS_TOOL}"${INPUTS_ARG_CHDIR} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.validate == 'true' && contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} | ||
| id: validate |
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "${{ inputs.tool }} init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" |
Check warning
Code scanning / CodeQL
Code injection Medium
${ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
General approach:
Replace any usage of untrusted or user-influenced values constructed with ${{ ... }} within a shell command (in run:) with a pattern that sets the value to an environment variable, and then uses standard shell variable expansion (e.g., $VAR) within the shell. This approach ensures that the shell interprets the value as a single argument, preventing command injection.
Detailed fix for this case:
For the block starting with args=... on line 164, move the potentially unsafe interpolations of steps.arg.outputs.arg-var-file and steps.arg.outputs.arg-var out to environment variables with unique names, e.g., ARG_VAR_FILE, ARG_VAR. Reference these in the args definition using $ARG_VAR_FILE and $ARG_VAR. You should also repeat this safer pattern for any other untrusted step/output variables interpolated in shell commands throughout the workflow.
Changes needed:
- In each relevant step, update the
env:section to export the outputs referenced in the command as individual environment variables (e.g., forarg-var, addARG_VAR: ${{ steps.arg.outputs.arg-var }}). - In the
run:commands, replace direct GitHub expression interpolations (e.g.,${{ steps.arg.outputs.arg-var }}) with corresponding$ARG_VARshell variables. - This should be done for each usage of such potentially unsafe interpolations, focusing at minimum on the block highlighted by CodeQL.
-
Copy modified lines R160-R161 -
Copy modified lines R166-R172 -
Copy modified lines R176-R177 -
Copy modified lines R182-R186
| @@ -157,11 +157,19 @@ | ||
| id: initialize | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| shell: bash | ||
| run: | | ||
| # TF initialize. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && env.ARG_VAR_FILE || '' }${{ env.INPUTS_TOOL == 'tofu' && env.ARG_VAR || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| # Use shell variable references for untrusted values. | ||
| tofu_args="" | ||
| if [ "$INPUTS_TOOL" = "tofu" ]; then | ||
| tofu_args="$ARG_VAR_FILE$ARG_VAR" | ||
| fi | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${tofu_args}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| @@ -169,11 +173,17 @@ | ||
| id: validate | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| shell: bash | ||
| run: | | ||
| # TF validate. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| tofu_args="" | ||
| if [ "$INPUTS_TOOL" = "tofu" ]; then | ||
| tofu_args="$ARG_VAR_FILE$ARG_VAR" | ||
| fi | ||
| args="${tofu_args}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "${{ inputs.tool }} init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-force-copy }
Potential code injection in
${ steps.arg.outputs.arg-force-copy }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix the issue, we should avoid directly interpolating ${{ steps.arg.outputs.arg-force-copy }} (and other tainted outputs) into the shell command line, and instead assign it to an environment variable. Then, the shell script should use native shell variable syntax (e.g., $INPUTS_ARG_FORCE_COPY) when constructing or executing commands. This prevents untrusted inputs from being evaluated as part of the shell script or command.
Specifically, in the step beginning at line 157 (id: initialize), we should add a new environment variable mapping for the force-copy argument:
env:
INPUTS_TOOL: ${{ inputs.tool }}
INPUTS_ARG_FORCE_COPY: ${{ steps.arg.outputs.arg-force-copy }}Then, replace any usage of ${{ steps.arg.outputs.arg-force-copy }} (inside the shell script) with $INPUTS_ARG_FORCE_COPY. We should also update the construction of the args variable and related command execution lines to use the shell variable rather than direct expression interpolation.
Repeat this process for all other possible outputs from steps.arg.outputs.* that are derived from untrusted user input and interpolated in the shell, to fully mitigate code injection risks for those paths.
-
Copy modified line R160 -
Copy modified line R164
| @@ -157,11 +157,11 @@ | ||
| id: initialize | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| INPUTS_ARG_FORCE_COPY: ${{ steps.arg.outputs.arg-force-copy }} | ||
| shell: bash | ||
| run: | | ||
| # TF initialize. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}$INPUTS_ARG_FORCE_COPY${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "${{ inputs.tool }} init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-upgrade }
Potential code injection in
${ steps.arg.outputs.arg-upgrade }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix the problem, the vulnerable argument (${{ steps.arg.outputs.arg-upgrade }}) should not be interpolated directly into the shell command as part of the command string. Instead, the untrusted input value should be passed into an environment variable during the step definition. In the shell command, when constructing arguments, you should use native shell variable expansion (e.g., $INPUTS_ARG_UPGRADE) so the shell will treat it as a single argument and not parse it as code. This ensures that even if the input contains malicious shell characters, they'll be handled safely as argument values, not as part of the shell command.
Specifically:
- In the
- id: argstep (lines 25-63), the environment variables are already correctly assigned from inputs. - In the affected command (line 164), replace
${{ steps.arg.outputs.arg-upgrade }}with$INPUTS_ARG_UPGRADE. - For completeness and to defend against similar risks, all other command argument expansions (
${{ steps.arg.outputs.arg-* }}) should likewise be converted to their corresponding environment variable references ($INPUTS_ARG_*). - If any arguments might be empty, use bash logic to avoid adding extraneous spaces (e.g.,
${VAR:+$VAR}).
Only the snippet shown for the initialize step (lines 164, 165, 166) is required for this fix, but other steps with similar usage should be considered for a full security review.
-
Copy modified lines R164-R166
| @@ -161,9 +161,9 @@ | ||
| run: | | ||
| # TF initialize. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}" | ||
| echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="$INPUTS_ARG_BACKEND_CONFIG$INPUTS_ARG_BACKEND$( [ "$INPUTS_TOOL" = "tofu" ] && echo "$INPUTS_ARG_VAR_FILE" )$( [ "$INPUTS_TOOL" = "tofu" ] && echo "$INPUTS_ARG_VAR" )$INPUTS_ARG_FORCE_COPY$INPUTS_ARG_FROM_MODULE$INPUTS_ARG_GET$INPUTS_ARG_LOCK_TIMEOUT$INPUTS_ARG_LOCK$INPUTS_ARG_LOCKFILE$INPUTS_ARG_MIGRATE_STATE$INPUTS_ARG_PLUGIN_DIR$INPUTS_ARG_RECONFIGURE$INPUTS_ARG_TEST_DIRECTORY$INPUTS_ARG_UPGRADE" | ||
| echo "$INPUTS_TOOL init$INPUTS_ARG_CHDIR$args" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL$INPUTS_ARG_CHDIR init$args 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.validate == 'true' && contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} | ||
| id: validate |
| args="${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| echo "${{ inputs.tool }} validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" |
Check warning
Code scanning / CodeQL
Code injection Medium
${ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To mitigate this code injection risk, we should avoid using ${{ ... }} expressions to interpolate potentially untrusted or user-controlled input directly into run: scripts. Instead, we should assign each potentially untrusted/interpolated value to an environment variable in the env: block, and then reference those variables inside the shell using ${VAR} (native shell syntax). This prevents the shell from interpreting malicious characters injected via the values.
In particular, lines constructing args=... in the run: sections (e.g., line 176 of action.yml) should not use ${{ ... }} for anything that comes from inputs or interpolated outputs, but instead reference $VAR environment variables, where the value originates from the env: mapping.
Therefore, for every potentially dangerous interpolation like ${{ steps.arg.outputs.arg-var-file }} and similar, we need to:
- Assign it to an environment variable, e.g.,
ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }}under theenv:subsection of that step. - Replace all occurrences in
args=assignments from${{ steps.arg.outputs.arg-var-file }}to"$ARG_VAR_FILE", and so on for other variables. - The logic checking for
env.INPUTS_TOOL == 'tofu'inside${{ ... }}should also be handled in shell (e.g., using a shell conditional when composingargs).
These changes should be made on every step where arguments from inputs/outputs are interpolated into command lines, with special attention to steps using run: and where shell expansion/injection is possible.
-
Copy modified lines R172-R176 -
Copy modified lines R181-R187
| @@ -169,13 +169,22 @@ | ||
| id: validate | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| ARG_NO_TESTS: ${{ steps.arg.outputs.arg-no-tests }} | ||
| ARG_TEST_DIRECTORY: ${{ steps.arg.outputs.arg-test-directory }} | ||
| ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF validate. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="" | ||
| if [ "$INPUTS_TOOL" = "tofu" ]; then | ||
| args="$args$ARG_VAR_FILE$ARG_VAR" | ||
| fi | ||
| args="$args$ARG_NO_TESTS$ARG_TEST_DIRECTORY" | ||
| echo "$INPUTS_TOOL validate$ARG_CHDIR$args" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL$ARG_CHDIR validate$args 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.command == 'plan' }} | ||
| id: plan |
| args="${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| echo "${{ inputs.tool }} validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" |
Check warning
Code scanning / CodeQL
Code injection Medium
${ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix the potential code injection in the workflow, avoid passing untrusted inputs (in this case, interpolated outputs from previous steps that may be influenced by users) directly into a command line via ${{ ... }} inside run:. Instead, pass those values as environment variables, and reference them within the command using native shell variable syntax (e.g. $ARG_VAR, $ARG_VAR_FILE, etc). Specifically, for the affected step (id: validate, lines 169-178), set each of the relevant argument outputs as step environment variables (e.g., ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} etc.), and then construct the args variable using only shell variable references (e.g., args="$ARG_VAR_FILE$ARG_VAR$ARG_NO_TESTS$ARG_TEST_DIRECTORY"). Then, use it natively in the shell command.
You must only modify the shown code in action.yml and only the failing step (id: validate, lines 169-178).
-
Copy modified lines R172-R176 -
Copy modified lines R181-R188
| @@ -169,13 +169,23 @@ | ||
| id: validate | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| ARG_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| ARG_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| ARG_NO_TESTS: ${{ steps.arg.outputs.arg-no-tests }} | ||
| ARG_TEST_DIRECTORY: ${{ steps.arg.outputs.arg-test-directory }} | ||
| ARG_CHDIR: ${{ steps.arg.outputs.arg-chdir }} | ||
| shell: bash | ||
| run: | | ||
| # TF validate. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| # Only use var-file/var if INPUTS_TOOL is tofu. | ||
| args="" | ||
| if [ "$INPUTS_TOOL" = "tofu" ]; then | ||
| args="$ARG_VAR_FILE$ARG_VAR" | ||
| fi | ||
| args="$args$ARG_NO_TESTS$ARG_TEST_DIRECTORY" | ||
| echo "$INPUTS_TOOL validate$ARG_CHDIR$args" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL$ARG_CHDIR validate$args 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
||
| - if: ${{ inputs.command == 'plan' }} | ||
| id: plan |
| args="${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| echo "${{ inputs.tool }} validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-no-tests }
Potential code injection in
${ steps.arg.outputs.arg-no-tests }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix the code injection vulnerability, we should avoid inlining untrusted expressions directly into the shell script. Instead, we should set potentially unsafe values as environment variables and reference them with standard shell variable syntax (e.g., $VAR) in the run script. Specifically, instead of inlining ${{ steps.arg.outputs.arg-no-tests }} into the shell command string, set its value as a dedicated environment variable (e.g., ARGS_NO_TESTS: ${{ steps.arg.outputs.arg-no-tests }}) and use it in the shell by referencing $ARGS_NO_TESTS. Similar handling should be applied to any other interpolated values from untrusted sources. Only trusted, non-user-controlled values ("internal" constants or hardcoded values) should be interpolated with ${{ ... }} in the workflow.
In action.yml, we need to:
- Add a new environment variable in the
env:block for the shell step, mapping the output to an environment variable (e.g.,ARGS_NO_TESTS). - In the shell script, change the expansion logic to incorporate
$ARGS_NO_TESTSrather than the direct expression. - Repeat for any similar user-controlled fields where necessary.
-
Copy modified lines R172-R175 -
Copy modified lines R180-R184
| @@ -169,11 +169,19 @@ | ||
| id: validate | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| ARGS_VAR_FILE: ${{ steps.arg.outputs.arg-var-file }} | ||
| ARGS_VAR: ${{ steps.arg.outputs.arg-var }} | ||
| ARGS_NO_TESTS: ${{ steps.arg.outputs.arg-no-tests }} | ||
| ARGS_TEST_DIRECTORY: ${{ steps.arg.outputs.arg-test-directory }} | ||
| shell: bash | ||
| run: | | ||
| # TF validate. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| args="" | ||
| if [ "$INPUTS_TOOL" = "tofu" ]; then | ||
| args="$args$ARGS_VAR_FILE$args$ARGS_VAR" | ||
| fi | ||
| args="$args$ARGS_NO_TESTS$ARGS_TEST_DIRECTORY" | ||
| echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
| args="${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ inputs.tool == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| echo "${{ inputs.tool }} validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" |
Check warning
Code scanning / CodeQL
Code injection Medium
${ steps.arg.outputs.arg-test-directory }
Potential code injection in
${ steps.arg.outputs.arg-test-directory }
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 months ago
To fix this code injection vulnerability, the value of steps.arg.outputs.arg-test-directory must be made available to the shell script only via environment variables, and must not be injected via ${{ }} expression interpolation directly in scripts. Instead, the variable should be set within the env: block and referenced in the script using shell-native syntax, such as $INPUTS_ARG_TEST_DIRECTORY. Specifically, line 176 should be changed so that instead of ${{ steps.arg.outputs.arg-test-directory }} (and any other directly-injected step outputs/inputs), the corresponding environment variable is referenced using shell-native syntax. This ensures that the input is not interpreted as shell code and does not allow for code injection.
For full correctness and consistency, all instances of ${{ steps.arg.outputs.arg-test-directory }} within shell scripts should be replaced with $INPUTS_ARG_TEST_DIRECTORY, and an env: assignment should ensure it is set to steps.arg.outputs.arg-test-directory before the shell runs. In this case, the relevant environment variable is already defined on lines 25-63 for the arg step (step id: arg), and must also be defined for the validate step (id: validate, lines 170-173).
-
Copy modified line R172 -
Copy modified line R176
| @@ -169,11 +169,11 @@ | ||
| id: validate | ||
| env: | ||
| INPUTS_TOOL: ${{ inputs.tool }} | ||
| INPUTS_ARG_TEST_DIRECTORY: ${{ steps.arg.outputs.arg-test-directory }} | ||
| shell: bash | ||
| run: | | ||
| # TF validate. | ||
| trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" | ||
| args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}$INPUTS_ARG_TEST_DIRECTORY" | ||
| echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt | ||
| $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) | ||
|
|
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
Signed-off-by: Rishav Dhar <19497993+rdhar@users.noreply.github.com>
Secured