From 31f297bd8c03e8d625b93a7b9daab3f50cd9e87b Mon Sep 17 00:00:00 2001 From: Valentin Maerten Date: Sun, 1 Feb 2026 16:32:07 +0100 Subject: [PATCH] refactor: compute masked command at compile time Move secret masking from runtime (task.go) to compile time (variables.go). This avoids recalculating variables on each log. - Add MaskSecretsWithExtra for loop vars and deferred commands - Rename CmdTemplate to LogCmd (clearer intent) - Simplify logging in runCommand --- internal/templater/secrets.go | 33 +++++++++++++++++++++++++++++++++ task.go | 14 +++----------- taskfile/ast/cmd.go | 4 ++-- variables.go | 5 ++++- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/internal/templater/secrets.go b/internal/templater/secrets.go index 8420c583..59e37fff 100644 --- a/internal/templater/secrets.go +++ b/internal/templater/secrets.go @@ -35,3 +35,36 @@ func MaskSecrets(cmdTemplate string, vars *ast.Vars) string { return result } + +// MaskSecretsWithExtra is like MaskSecrets but also resolves extra variables (e.g., loop vars). +func MaskSecretsWithExtra(cmdTemplate string, vars *ast.Vars, extra map[string]any) string { + if vars == nil || vars.Len() == 0 { + // Still need to resolve extra vars even if no vars + cache := &Cache{Vars: ast.NewVars()} + result := ReplaceWithExtra(cmdTemplate, cache, extra) + if cache.Err() != nil { + return cmdTemplate + } + return result + } + + // Create a cache map with secrets masked + maskedVars := vars.DeepCopy() + for name, v := range maskedVars.All() { + if v.Secret { + maskedVars.Set(name, ast.Var{ + Value: "*****", + Secret: true, + }) + } + } + + cache := &Cache{Vars: maskedVars} + result := ReplaceWithExtra(cmdTemplate, cache, extra) + + if cache.Err() != nil { + return cmdTemplate + } + + return result +} diff --git a/task.go b/task.go index a7eadff5..47d3245b 100644 --- a/task.go +++ b/task.go @@ -349,8 +349,8 @@ func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, vars *ast.Vars, d extra["EXIT_CODE"] = fmt.Sprintf("%d", *deferredExitCode) } - // Save template before resolving for secret masking in logs - cmd.CmdTemplate = cmd.Cmd + // Resolve template with secrets masked for logging + cmd.LogCmd = templater.MaskSecretsWithExtra(cmd.Cmd, vars, extra) cmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra) cmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra) cmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra) @@ -395,15 +395,7 @@ func (e *Executor) runCommand(ctx context.Context, t *ast.Task, call *Call, i in } if e.Verbose || (!call.Silent && !cmd.Silent && !t.IsSilent() && !e.Taskfile.Silent && !e.Silent) { - // Get runtime vars for masking - varsForMasking, err := e.Compiler.FastGetVariables(t, call) - if err != nil { - return fmt.Errorf("task: failed to get variables: %w", err) - } - - // Mask secret variables in the command template before logging - cmdToLog := templater.MaskSecrets(cmd.CmdTemplate, varsForMasking) - e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmdToLog) + e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmd.LogCmd) } if e.Dry { diff --git a/taskfile/ast/cmd.go b/taskfile/ast/cmd.go index 91147ff9..961a1998 100644 --- a/taskfile/ast/cmd.go +++ b/taskfile/ast/cmd.go @@ -10,7 +10,7 @@ import ( // Cmd is a task command type Cmd struct { Cmd string // Resolved command (used for execution and fingerprinting) - CmdTemplate string // Original template before variable resolution (used for secret masking) + LogCmd string // Command with secrets masked (used for logging) Task string For *For If string @@ -29,7 +29,7 @@ func (c *Cmd) DeepCopy() *Cmd { } return &Cmd{ Cmd: c.Cmd, - CmdTemplate: c.CmdTemplate, + LogCmd: c.LogCmd, Task: c.Task, For: c.For.DeepCopy(), If: c.If, diff --git a/variables.go b/variables.go index 1cc0a282..38a85b89 100644 --- a/variables.go +++ b/variables.go @@ -228,6 +228,8 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err extra["KEY"] = keys[i] } newCmd := cmd.DeepCopy() + // Resolve template with secrets masked + loop vars for logging + newCmd.LogCmd = templater.MaskSecretsWithExtra(cmd.Cmd, cache.Vars, extra) newCmd.Cmd = templater.ReplaceWithExtra(cmd.Cmd, cache, extra) newCmd.Task = templater.ReplaceWithExtra(cmd.Task, cache, extra) newCmd.If = templater.ReplaceWithExtra(cmd.If, cache, extra) @@ -243,7 +245,8 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err continue } newCmd := cmd.DeepCopy() - newCmd.CmdTemplate = cmd.Cmd + // Resolve template with secrets masked for logging + newCmd.LogCmd = templater.MaskSecrets(cmd.Cmd, cache.Vars) newCmd.Cmd = templater.Replace(cmd.Cmd, cache) newCmd.Task = templater.Replace(cmd.Task, cache) newCmd.If = templater.Replace(cmd.If, cache)