(yaml *strings.Builder, yamlContent string, data *WorkflowData)
| 142 | } |
| 143 | |
| 144 | func (c *Compiler) generateSecretRedactionStep(yaml *strings.Builder, yamlContent string, data *WorkflowData) { |
| 145 | // Extract secret references from the generated YAML |
| 146 | secretReferences := CollectSecretReferences(yamlContent) |
| 147 | |
| 148 | // Always record that we're adding a secret redaction step, even if no secrets found |
| 149 | // This is important for validation to ensure the step ordering is correct |
| 150 | c.stepOrderTracker.RecordSecretRedaction("Redact secrets in logs") |
| 151 | |
| 152 | // If no secrets found, we still generate the step but it will be a no-op at runtime |
| 153 | // This ensures consistent step ordering and validation |
| 154 | if len(secretReferences) == 0 { |
| 155 | secretMaskingLog.Print("No secrets found, generating no-op redaction step") |
| 156 | // Generate a minimal no-op redaction step for validation purposes |
| 157 | yaml.WriteString(" - name: Redact secrets in logs\n") |
| 158 | yaml.WriteString(" if: always()\n") |
| 159 | yaml.WriteString(" run: echo 'No secrets to redact'\n") |
| 160 | } else { |
| 161 | secretMaskingLog.Printf("Generating redaction step for %d secret(s)", len(secretReferences)) |
| 162 | yaml.WriteString(" - name: Redact secrets in logs\n") |
| 163 | yaml.WriteString(" if: always()\n") |
| 164 | fmt.Fprintf(yaml, " uses: %s\n", getCachedActionPin("actions/github-script", data)) |
| 165 | yaml.WriteString(" with:\n") |
| 166 | yaml.WriteString(" script: |\n") |
| 167 | |
| 168 | // Load redact_secrets script from external file |
| 169 | // Use setupGlobals helper to attach GitHub Actions builtin objects to global scope |
| 170 | yaml.WriteString(" const { setupGlobals } = require('" + SetupActionDestination + "/setup_globals.cjs');\n") |
| 171 | yaml.WriteString(" setupGlobals(core, github, context, exec, io, getOctokit);\n") |
| 172 | yaml.WriteString(" const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');\n") |
| 173 | yaml.WriteString(" await main();\n") |
| 174 | |
| 175 | // Add environment variables |
| 176 | yaml.WriteString(" env:\n") |
| 177 | |
| 178 | // Pass the list of secret names as a comma-separated string |
| 179 | // Escape each secret reference to prevent injection when embedding in YAML |
| 180 | escapedRefs := make([]string, len(secretReferences)) |
| 181 | for i, ref := range secretReferences { |
| 182 | escapedRefs[i] = escapeSingleQuoteBackslash(ref) |
| 183 | } |
| 184 | fmt.Fprintf(yaml, " GH_AW_SECRET_NAMES: '%s'\n", strings.Join(escapedRefs, ",")) |
| 185 | |
| 186 | // Pass the actual secret values as environment variables so they can be redacted |
| 187 | // Each secret will be available as an environment variable |
| 188 | for _, secretName := range secretReferences { |
| 189 | // Escape secret name to prevent injection in YAML |
| 190 | escapedSecretName := escapeSingleQuoteBackslash(secretName) |
| 191 | // Use original secretName in GitHub Actions expression since it's already validated |
| 192 | // to only contain safe characters (uppercase letters, numbers, underscores) |
| 193 | fmt.Fprintf(yaml, " SECRET_%s: ${{ secrets.%s }}\n", escapedSecretName, secretName) |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | // Inject custom secret masking steps if configured |
| 198 | if data.SecretMasking != nil && len(data.SecretMasking.Steps) > 0 { |
| 199 | secretMaskingLog.Printf("Injecting %d custom secret masking steps", len(data.SecretMasking.Steps)) |
| 200 | for _, step := range data.SecretMasking.Steps { |
| 201 | c.generateCustomSecretMaskingStep(yaml, step, data) |
no test coverage detected