Skip to content

Conversation

@vmaerten
Copy link
Member

@vmaerten vmaerten commented Nov 16, 2025

🔐 Add secret variable masking support

Problem

Sensitive values (API keys, passwords, tokens) were exposed in Task command logs, creating security risks when logs are shared or stored.
I’ve done a few talks on Task, and people always ask about secret variables. This fixes #2066

Solution

This PR adds a secret: true flag for variables that masks their values as ***** in command logs while keeping the actual execution unchanged.

Key features:

  • 🎭 Template-based masking prevents false positives (e.g., SECRET="echo" won't mask the echo command)
  • 🔄 Works with all variable types: value, sh, ref, and task-level vars
  • ⏱ Supports both regular and deferred commands
  • 🚀 Zero performance impact on fingerprinting/caching

Example

  Taskfile.yml:
  vars:
    API_KEY:
      value: "secret-key-123"
      secret: true
    APP_NAME: "myapp"

  tasks:
    deploy:
      cmds:
        - echo "Deploying {{.APP_NAME}} with key {{.API_KEY}}"

Before:
task: [deploy] echo "Deploying myapp with key secret-key-123"

After:
task: [deploy] echo "Deploying myapp with key *****"

Changes

  • Added Secret field to ast.Var struct with YAML parsing support
  • Created templater.MaskSecrets() using Go template engine for robust masking
  • Added CmdTemplate field to preserve original templates for masking
  • Comprehensive test coverage including deferred commands

Documentation

  • Updated user guide with examples and security warnings
  • Updated schema reference

@vmaerten vmaerten marked this pull request as ready for review November 16, 2025 09:40
@trulede
Copy link
Contributor

trulede commented Nov 16, 2025

Would it be potentially interesting to represent secrets as their own objects within the schema? Similar to GitHub (and others). Potentially interfacing with secret stores (via sh commands, or code integrations).

version: '3'

secrets:
  USER: '{{ .FOO }}'

tasks:
  user:
    env:
      USER: {{ secrets.USER }}
    cmds:
       - echo User is: {{ secrets.USER }}
       - echo User really is: $USER
task user -- FOO=bar
User is: ******
User really is: bar       <--- this might also be hidden, with an output package supporting "scrubbing", and 
                                          configured with the secrets used.

@vmaerten
Copy link
Member Author

Thanks for the proposal! I like the idea of aligning with patterns from GitHub Actions and other CI/CD tools. I actually considered this approach myself when designing the feature.

The philosophy I adopted treats a secret as simply a variable with special handling (masking), which is why I chose secret: true rather than a separate section.
This approach works well for several reasons.

First, it already integrates seamlessly with secret managers via the sh: syntax:

vars:
  DB_PASSWORD:
    sh: "vault kv get -field=password secret/db"
    secret: true
  AWS_KEY:
    sh: "aws secretsmanager get-secret-value --secret-id prod/api --query SecretString -o text"
    secret: true

Second, the architecture stays simple, no native provider integrations or SDK dependencies are needed, since any secret manager with a CLI works out of the box.

Last, it maintains consistency: secrets inherit all variable features (refs, task-level vars, etc.) without duplication.

A separate secrets: section would primarily offer organizational and semantic benefits. While clearer separation has merit, this approach introduces architectural complexity, particularly around duplication and how features like requires, ref, or future interactive variables would be handled.


User really is: bar <--- this might also be hidden, with an output package supporting "scrubbing", and
configured with the secrets used.

I intentionally chose not to hide this output for two reasons:

  • I believe it's the user's responsibility to control what gets echoed in their commands. Additionally, leaving it visible allows for debugging secrets when needed.
  • The current version of mvan/cc doesn't support output scrubbing anyway.

That said, this could potentially be hidden using an output package that supports "scrubbing" and is configured with the relevant secrets.

I think it's a first good step anyway.

Any thought on this @andreynering / @pd93 ?

@trulede
Copy link
Contributor

trulede commented Nov 16, 2025

Internally, a secret might be the same base type as a vars & envar (both of which are the same type internally) . So the implementation would also be clean, and none of the capability's you mention are lost (they are essential). But then you do have a bit more capability programmatically, in dealing with those objects (reflection etc). I think you are correct in those observations.

Details aside, the GitHub way is more familiar, from a user perspective.

@andreynering
Copy link
Member

The idea of having a separate secrets attribute is interesting, but I think keeping the existing structure may also be the right decision. I'm open to more opinions on this.

Some thoughts:

  • Perhaps we should allow env: and dotenv: to declare secret: true as well? Values in .env files are often secrets.
  • If someone runs echo $SECRET_API_KEY, we probably won't be able to sanitize. That may be fine, as the user likely won't echo these files intentionally.

@vmaerten
Copy link
Member Author

Perhaps we should allow env: and dotenv: to declare secret: true as well? Values in .env files are often secrets.

Env are not shown in our logger, I've added an example with a Golden.

If someone runs echo $SECRET_API_KEY, we probably won't be able to sanitize. That may be fine, as the user likely won't echo these files intentionally.

I totally agree with this!

@vmaerten vmaerten requested review from andreynering and pd93 and removed request for andreynering November 23, 2025 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Never print contents of secret variables

4 participants