mirror of
https://gitea.com/gitea/runner.git
synced 2026-08-26 13:57:46 +00:00
fix: mask secrets on every path they leave a job (#1188)
A secret in a matrix value reached the log in the clear:
```yaml
strategy:
matrix:
include: "${{ github.token }}"
```
Chasing that one route is pointless, so this masks every sink a secret leaves a job by: the uploaded log rows and the on-disk `job.log`, both through one choke point in `appendLogRow`; the runner's own log, which is where planning errors like that one land with no job logger in reach; the job logger's stdout under debug logging; job summaries; job outputs; and the job name that becomes a container name.
Values the runner knows but the job never declared, the proxy password and the task token, are hidden the same way. Masks apply longest first, since `strings.Replacer` matches in argument order and one secret prefixing another would otherwise mask the prefix and print the rest.
### What changes for users
An output whose value carries a secret is skipped with a warning instead of sent, matching GitHub. Output that showed a secret now shows `***`. `ACTIONS_STEP_DEBUG` and `ACTIONS_RUNNER_DEBUG` are never masked, also matching GitHub, so an output of `true` still reaches the jobs that need it.
Each fix has a test that fails without it.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1188
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -36,6 +36,7 @@ type Config struct {
|
||||
JSONLogger bool // use json or text logger
|
||||
Env map[string]string // env for containers
|
||||
Secrets map[string]string // list of secrets
|
||||
ExtraMasks []string // values to hide that are not in Secrets, such as the proxy password
|
||||
Vars map[string]string // list of vars
|
||||
Token string // GitHub token
|
||||
InsecureSecrets bool // switch hiding output when printing to terminal
|
||||
@@ -238,7 +239,7 @@ func (runner *runnerImpl) NewPlanExecutor(plan *model.Plan) common.Executor {
|
||||
maxJobNameLen = len(rc.String())
|
||||
}
|
||||
if rc.caller != nil { // For Gitea
|
||||
rc.caller.setReusedWorkflowJobResult(rc.JobName, "pending")
|
||||
rc.caller.setReusedWorkflowJobResult(rc.Run.JobID, "pending")
|
||||
}
|
||||
stageExecutor = append(stageExecutor, func(ctx context.Context) error {
|
||||
jobName := fmt.Sprintf("%-*s", maxJobNameLen, rc.String())
|
||||
@@ -324,7 +325,7 @@ func (runner *runnerImpl) newRunContext(ctx context.Context, run *model.Run, mat
|
||||
caller: runner.caller,
|
||||
}
|
||||
rc.ExprEval = rc.NewExpressionEvaluator(ctx)
|
||||
rc.Name = rc.ExprEval.Interpolate(ctx, run.String())
|
||||
rc.Name = rc.maskSecrets(rc.ExprEval.Interpolate(ctx, run.String()))
|
||||
// Snapshot the job's pristine output expressions now, before any matrix combo runs and
|
||||
// rewrites the shared Job.Outputs (see interpolateOutputs).
|
||||
if job := run.Job(); job != nil {
|
||||
@@ -335,8 +336,9 @@ func (runner *runnerImpl) newRunContext(ctx context.Context, run *model.Run, mat
|
||||
}
|
||||
|
||||
// For Gitea
|
||||
func (c *caller) setReusedWorkflowJobResult(jobName, result string) {
|
||||
// Keyed by job id, not name: only the values are read, and masking can collapse two names into one.
|
||||
func (c *caller) setReusedWorkflowJobResult(jobID, result string) {
|
||||
c.updateResultLock.Lock()
|
||||
defer c.updateResultLock.Unlock()
|
||||
c.reusedWorkflowJobResults[jobName] = result
|
||||
c.reusedWorkflowJobResults[jobID] = result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user