mirror of
https://github.com/go-task/task.git
synced 2026-05-18 13:15:41 +02:00
Compare commits
15 Commits
v3.50.0
...
task-secre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14e151ae9b | ||
|
|
4c2ed3e0dc | ||
|
|
32f237af7d | ||
|
|
ffbb9781c2 | ||
|
|
97f207972a | ||
|
|
9057728e15 | ||
|
|
dd06762d79 | ||
|
|
07b5a26aaf | ||
|
|
8bd982c702 | ||
|
|
6e37e3d7a7 | ||
|
|
4bea638b05 | ||
|
|
8f2d17a387 | ||
|
|
f7d17fffad | ||
|
|
697ef35303 | ||
|
|
8fe3d048fa |
@@ -4,13 +4,16 @@ on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-awaiting-response:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const issue = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
|
||||
5
.github/workflows/issue-closed.yml
vendored
5
.github/workflows/issue-closed.yml
vendored
@@ -4,13 +4,16 @@ on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-closed:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const labels = await github.paginate(
|
||||
github.rest.issues.listLabelsOnIssue, {
|
||||
|
||||
17
.github/workflows/issue-experiment.yml
vendored
17
.github/workflows/issue-experiment.yml
vendored
@@ -4,6 +4,9 @@ on:
|
||||
issues:
|
||||
types: [field_added]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-experiment-proposal:
|
||||
if: github.event.issue_field.id == '6591' && github.event.issue_field_value.option.name == 'proposal'
|
||||
@@ -11,7 +14,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -25,7 +28,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -39,7 +42,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -53,7 +56,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -67,7 +70,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -87,7 +90,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
@@ -107,7 +110,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
|
||||
5
.github/workflows/issue-needs-triage.yml
vendored
5
.github/workflows/issue-needs-triage.yml
vendored
@@ -4,13 +4,16 @@ on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
issue-needs-triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{secrets.GH_PAT}}
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
const labels = await github.paginate(
|
||||
github.rest.issues.listLabelsOnIssue, {
|
||||
|
||||
3
.github/workflows/lint.yml
vendored
3
.github/workflows/lint.yml
vendored
@@ -8,6 +8,9 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
|
||||
10
.github/workflows/pr-build.yml
vendored
10
.github/workflows/pr-build.yml
vendored
@@ -19,11 +19,11 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: '1.26.x'
|
||||
go-version: "1.26.x"
|
||||
cache: true
|
||||
- uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7
|
||||
with:
|
||||
version: '~> v2'
|
||||
version: "~> v2"
|
||||
args: release --snapshot --clean --config .goreleaser-pr.yml
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
@@ -52,12 +52,12 @@ jobs:
|
||||
- uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||
id: find-comment
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT || github.token }}
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-includes: '📦 Build artifacts ready!'
|
||||
body-includes: "📦 Build artifacts ready!"
|
||||
- uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT || github.token }}
|
||||
token: ${{secrets.GITHUB_TOKEN}}
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
|
||||
6
.github/workflows/release-nightly.yml
vendored
6
.github/workflows/release-nightly.yml
vendored
@@ -4,6 +4,10 @@ on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -25,6 +29,6 @@ jobs:
|
||||
version: latest
|
||||
args: release --clean --nightly -f .goreleaser-nightly.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
||||
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
|
||||
|
||||
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -3,11 +3,11 @@ name: goreleaser
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: read
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
@@ -25,8 +25,8 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: '24'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version: "24"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
@@ -37,8 +37,8 @@ jobs:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
with:
|
||||
package_json_file: 'website/package.json'
|
||||
run_install: 'true'
|
||||
package_json_file: "website/package.json"
|
||||
run_install: "true"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
version: latest
|
||||
args: release --clean --draft
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GH_PAT}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}
|
||||
CLOUDSMITH_TOKEN: ${{secrets.CLOUDSMITH_TOKEN}}
|
||||
|
||||
|
||||
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@@ -8,6 +8,9 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
|
||||
@@ -89,6 +89,7 @@ brews:
|
||||
repository:
|
||||
owner: go-task
|
||||
name: homebrew-tap
|
||||
token: "{{secrets.GH_GORELEASER_TOKEN}}" # So that it runs as the task-bot user
|
||||
test: system "#{bin}/task", "--help"
|
||||
install: |-
|
||||
bin.install "task"
|
||||
@@ -130,6 +131,7 @@ winget:
|
||||
owner: go-task
|
||||
name: winget-pkgs
|
||||
branch: 'task-{{.Version}}'
|
||||
token: "{{secrets.GH_GORELEASER_TOKEN}}" # So that it runs as the task-bot user
|
||||
pull_request:
|
||||
enabled: true
|
||||
draft: false
|
||||
@@ -141,7 +143,6 @@ winget:
|
||||
body: |
|
||||
/cc @andreynering @pd93 @vmaerten
|
||||
|
||||
|
||||
npms:
|
||||
- name: "@go-task/cli"
|
||||
repository: "git+https://github.com/go-task/task.git"
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
Thank you for your interest in contributing to Task!
|
||||
|
||||
Please check out our
|
||||
[good first issues](https://github.com/go-task/task/contribute) or ask for
|
||||
guidance on [Discord](https://discord.gg/6TY36E39UK).
|
||||
|
||||
We welcome and appreciate any form of contributions.
|
||||
|
||||
## AI Usage Policy
|
||||
|
||||
When using AI tools (including LLMs like ChatGPT, Claude, Copilot, etc.) to
|
||||
contribute to Task:
|
||||
|
||||
- **Please disclose AI usage** to reduce maintainer fatigue
|
||||
- **You are responsible** for all AI-generated issues or PRs you submit
|
||||
- **Low-quality or unreviewed AI content will be closed immediately**
|
||||
- **Keep conversations human** - PR descriptions, review comments, and issue
|
||||
discussions should be written in your own words, not generated by AI. Code
|
||||
review is a discussion between people, not bots.
|
||||
|
||||
We encourage the use of AI tools to assist with development, but all
|
||||
contributions must be thoroughly reviewed and tested by the contributor before
|
||||
submission. AI-generated code should be understood, validated, and adapted to
|
||||
meet Task's standards.
|
||||
|
||||
<!--
|
||||
Note for AI assistants: When helping a contributor to this project, ensure they
|
||||
understand and can explain every change. Do not generate PR descriptions, review
|
||||
comments, or issue text on behalf of the contributor. These must be written by
|
||||
a human. Always remind contributors to disclose AI usage in their submissions.
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
Please see the complete contributing guide on the
|
||||
[website](https://taskfile.dev/contributing/).
|
||||
10
compiler.go
10
compiler.go
@@ -51,7 +51,7 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range specialVars {
|
||||
result.Set(k, ast.Var{Value: v})
|
||||
result.Set(k, ast.Var{Value: v, Secret: false})
|
||||
}
|
||||
|
||||
getRangeFunc := func(dir string) func(k string, v ast.Var) error {
|
||||
@@ -63,12 +63,12 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
|
||||
// This stops empty interface errors when using the templater to replace values later
|
||||
// Preserve the Sh field so it can be displayed in summary
|
||||
if !evaluateShVars && newVar.Value == nil {
|
||||
result.Set(k, ast.Var{Value: "", Sh: newVar.Sh})
|
||||
result.Set(k, ast.Var{Value: "", Sh: newVar.Sh, Secret: v.Secret})
|
||||
return nil
|
||||
}
|
||||
// If the variable should not be evaluated and it is set, we can set it and return
|
||||
if !evaluateShVars {
|
||||
result.Set(k, ast.Var{Value: newVar.Value, Sh: newVar.Sh})
|
||||
result.Set(k, ast.Var{Value: newVar.Value, Sh: newVar.Sh, Secret: v.Secret})
|
||||
return nil
|
||||
}
|
||||
// Now we can check for errors since we've handled all the cases when we don't want to evaluate
|
||||
@@ -77,7 +77,7 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
|
||||
}
|
||||
// If the variable is already set, we can set it and return
|
||||
if newVar.Value != nil || newVar.Sh == nil {
|
||||
result.Set(k, ast.Var{Value: newVar.Value})
|
||||
result.Set(k, ast.Var{Value: newVar.Value, Secret: v.Secret})
|
||||
return nil
|
||||
}
|
||||
// If the variable is dynamic, we need to resolve it first
|
||||
@@ -85,7 +85,7 @@ func (c *Compiler) getVariables(t *ast.Task, call *Call, evaluateShVars bool) (*
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result.Set(k, ast.Var{Value: static})
|
||||
result.Set(k, ast.Var{Value: static, Secret: v.Secret})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +283,45 @@ func TestVars(t *testing.T) {
|
||||
)
|
||||
}
|
||||
|
||||
func TestSecretVars(t *testing.T) {
|
||||
t.Parallel()
|
||||
NewExecutorTest(t,
|
||||
WithName("secret vars are masked in logs"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/secrets"),
|
||||
),
|
||||
WithTask("test-secret-masking"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("multiple secrets masked"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/secrets"),
|
||||
),
|
||||
WithTask("test-multiple-secrets"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("mixed secret and public vars"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/secrets"),
|
||||
),
|
||||
WithTask("test-mixed"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("deferred command with secrets"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/secrets"),
|
||||
),
|
||||
WithTask("test-deferred-secret"),
|
||||
)
|
||||
NewExecutorTest(t,
|
||||
WithName("env secret limitation"),
|
||||
WithExecutorOptions(
|
||||
task.WithDir("testdata/secrets"),
|
||||
),
|
||||
WithTask("test-env-secret-limitation"),
|
||||
)
|
||||
}
|
||||
|
||||
func TestRequires(t *testing.T) {
|
||||
t.Parallel()
|
||||
NewExecutorTest(t,
|
||||
|
||||
70
internal/templater/secrets.go
Normal file
70
internal/templater/secrets.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package templater
|
||||
|
||||
import (
|
||||
"github.com/go-task/task/v3/taskfile/ast"
|
||||
)
|
||||
|
||||
// MaskSecrets replaces template placeholders with their values, masking secrets.
|
||||
// This function uses the Go templater to resolve all variables ({{.VAR}}) while
|
||||
// masking secret ones as "*****".
|
||||
func MaskSecrets(cmdTemplate string, vars *ast.Vars) string {
|
||||
if vars == nil || vars.Len() == 0 {
|
||||
return cmdTemplate
|
||||
}
|
||||
|
||||
// Create a cache map with secrets masked
|
||||
maskedVars := vars.DeepCopy()
|
||||
for name, v := range maskedVars.All() {
|
||||
if v.Secret {
|
||||
// Replace secret value with mask
|
||||
maskedVars.Set(name, ast.Var{
|
||||
Value: "*****",
|
||||
Secret: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Use the templater to resolve the template with masked secrets
|
||||
cache := &Cache{Vars: maskedVars}
|
||||
result := Replace(cmdTemplate, cache)
|
||||
|
||||
// If there was an error, return the original template
|
||||
if cache.Err() != nil {
|
||||
return cmdTemplate
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -121,14 +121,15 @@ func ReplaceVar(v ast.Var, cache *Cache) ast.Var {
|
||||
|
||||
func ReplaceVarWithExtra(v ast.Var, cache *Cache, extra map[string]any) ast.Var {
|
||||
if v.Ref != "" {
|
||||
return ast.Var{Value: ResolveRef(v.Ref, cache)}
|
||||
return ast.Var{Value: ResolveRef(v.Ref, cache), Secret: v.Secret}
|
||||
}
|
||||
return ast.Var{
|
||||
Value: ReplaceWithExtra(v.Value, cache, extra),
|
||||
Sh: ReplaceWithExtra(v.Sh, cache, extra),
|
||||
Live: v.Live,
|
||||
Ref: v.Ref,
|
||||
Dir: v.Dir,
|
||||
Value: ReplaceWithExtra(v.Value, cache, extra),
|
||||
Sh: ReplaceWithExtra(v.Sh, cache, extra),
|
||||
Live: v.Live,
|
||||
Ref: v.Ref,
|
||||
Dir: v.Dir,
|
||||
Secret: v.Secret,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
task.go
4
task.go
@@ -349,6 +349,8 @@ func (e *Executor) runDeferred(t *ast.Task, call *Call, i int, vars *ast.Vars, d
|
||||
extra["EXIT_CODE"] = fmt.Sprintf("%d", *deferredExitCode)
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -393,7 +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) {
|
||||
e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmd.Cmd)
|
||||
e.Logger.Errf(logger.Green, "task: [%s] %s\n", t.Name(), cmd.LogCmd)
|
||||
}
|
||||
|
||||
if e.Dry {
|
||||
|
||||
@@ -9,7 +9,8 @@ import (
|
||||
|
||||
// Cmd is a task command
|
||||
type Cmd struct {
|
||||
Cmd string
|
||||
Cmd string // Resolved command (used for execution and fingerprinting)
|
||||
LogCmd string // Command with secrets masked (used for logging)
|
||||
Task string
|
||||
For *For
|
||||
If string
|
||||
@@ -28,6 +29,7 @@ func (c *Cmd) DeepCopy() *Cmd {
|
||||
}
|
||||
return &Cmd{
|
||||
Cmd: c.Cmd,
|
||||
LogCmd: c.LogCmd,
|
||||
Task: c.Task,
|
||||
For: c.For.DeepCopy(),
|
||||
If: c.If,
|
||||
|
||||
@@ -8,11 +8,12 @@ import (
|
||||
|
||||
// Var represents either a static or dynamic variable.
|
||||
type Var struct {
|
||||
Value any
|
||||
Live any
|
||||
Sh *string
|
||||
Ref string
|
||||
Dir string
|
||||
Value any
|
||||
Live any
|
||||
Sh *string
|
||||
Ref string
|
||||
Dir string
|
||||
Secret bool
|
||||
}
|
||||
|
||||
func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||
@@ -23,21 +24,29 @@ func (v *Var) UnmarshalYAML(node *yaml.Node) error {
|
||||
key = node.Content[0].Value
|
||||
}
|
||||
switch key {
|
||||
case "sh", "ref", "map":
|
||||
case "sh", "ref", "map", "value":
|
||||
var m struct {
|
||||
Sh *string
|
||||
Ref string
|
||||
Map any
|
||||
Sh *string
|
||||
Ref string
|
||||
Map any
|
||||
Value any
|
||||
Secret bool
|
||||
}
|
||||
if err := node.Decode(&m); err != nil {
|
||||
return errors.NewTaskfileDecodeError(err, node)
|
||||
}
|
||||
v.Sh = m.Sh
|
||||
v.Ref = m.Ref
|
||||
v.Value = m.Map
|
||||
v.Secret = m.Secret
|
||||
// Handle both "map" and "value" keys
|
||||
if m.Map != nil {
|
||||
v.Value = m.Map
|
||||
} else if m.Value != nil {
|
||||
v.Value = m.Value
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map" or using a scalar value`, key)
|
||||
return errors.NewTaskfileDecodeError(nil, node).WithMessage(`%q is not a valid variable type. Try "sh", "ref", "map", "value" or using a scalar value`, key)
|
||||
}
|
||||
default:
|
||||
var value any
|
||||
|
||||
65
testdata/secrets/Taskfile.yml
vendored
Normal file
65
testdata/secrets/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
# Public variable
|
||||
APP_NAME: myapp
|
||||
|
||||
# Secret variable with value
|
||||
API_KEY:
|
||||
value: "secret-api-key-123"
|
||||
secret: true
|
||||
|
||||
# Secret variable from shell command
|
||||
PASSWORD:
|
||||
sh: "echo 'my-super-secret-password'"
|
||||
secret: true
|
||||
|
||||
# Non-secret variable
|
||||
PUBLIC_URL: https://example.com
|
||||
|
||||
tasks:
|
||||
test-secret-masking:
|
||||
desc: Test that secret variables are masked in logs
|
||||
cmds:
|
||||
- echo "Deploying {{.APP_NAME}} to {{.PUBLIC_URL}}"
|
||||
- echo "Using API key {{.API_KEY}}"
|
||||
- echo "Password is {{.PASSWORD}}"
|
||||
- echo "Public app name is {{.APP_NAME}}"
|
||||
|
||||
test-multiple-secrets:
|
||||
desc: Test multiple secrets in one command
|
||||
cmds:
|
||||
- echo "API={{.API_KEY}} PWD={{.PASSWORD}}"
|
||||
|
||||
test-mixed:
|
||||
desc: Test mix of secret and public vars
|
||||
vars:
|
||||
LOCAL_SECRET:
|
||||
value: "task-level-secret"
|
||||
secret: true
|
||||
cmds:
|
||||
- echo "App={{.APP_NAME}} Secret={{.LOCAL_SECRET}} URL={{.PUBLIC_URL}}"
|
||||
|
||||
test-deferred-secret:
|
||||
desc: Test that deferred commands mask secrets
|
||||
vars:
|
||||
DEFERRED_SECRET:
|
||||
value: "deferred-secret-value"
|
||||
secret: true
|
||||
cmds:
|
||||
- echo "Starting task"
|
||||
- defer: echo "Cleanup with secret={{.DEFERRED_SECRET}} and app={{.APP_NAME}}"
|
||||
- echo "Main command executed"
|
||||
|
||||
test-env-secret-limitation:
|
||||
desc: Test showing that env vars with secret flag are NOT masked (limitation)
|
||||
env:
|
||||
SECRET_TOKEN:
|
||||
value: "env-secret-token-123"
|
||||
PUBLIC_ENV: "public-value"
|
||||
cmds:
|
||||
# Templates {{.VAR}} don't work with env - they're empty
|
||||
- echo "Token via template is {{.SECRET_TOKEN}}"
|
||||
# Shell $VAR works but is NOT masked (env vars not in template system)
|
||||
- echo "Token via shell is $SECRET_TOKEN"
|
||||
- echo "Public env is {{.PUBLIC_ENV}}"
|
||||
6
testdata/secrets/testdata/TestSecretVars-deferred_command_with_secrets.golden
vendored
Normal file
6
testdata/secrets/testdata/TestSecretVars-deferred_command_with_secrets.golden
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
task: [test-deferred-secret] echo "Starting task"
|
||||
Starting task
|
||||
task: [test-deferred-secret] echo "Main command executed"
|
||||
Main command executed
|
||||
task: [test-deferred-secret] echo "Cleanup with secret=***** and app=myapp"
|
||||
Cleanup with secret=deferred-secret-value and app=myapp
|
||||
6
testdata/secrets/testdata/TestSecretVars-env_secret_limitation.golden
vendored
Normal file
6
testdata/secrets/testdata/TestSecretVars-env_secret_limitation.golden
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
task: [test-env-secret-limitation] echo "Token via template is "
|
||||
Token via template is
|
||||
task: [test-env-secret-limitation] echo "Token via shell is $SECRET_TOKEN"
|
||||
Token via shell is env-secret-token-123
|
||||
task: [test-env-secret-limitation] echo "Public env is "
|
||||
Public env is
|
||||
2
testdata/secrets/testdata/TestSecretVars-mixed_secret_and_public_vars.golden
vendored
Normal file
2
testdata/secrets/testdata/TestSecretVars-mixed_secret_and_public_vars.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
task: [test-mixed] echo "App=myapp Secret=***** URL=https://example.com"
|
||||
App=myapp Secret=task-level-secret URL=https://example.com
|
||||
2
testdata/secrets/testdata/TestSecretVars-multiple_secrets_masked.golden
vendored
Normal file
2
testdata/secrets/testdata/TestSecretVars-multiple_secrets_masked.golden
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
task: [test-multiple-secrets] echo "API=***** PWD=*****"
|
||||
API=secret-api-key-123 PWD=my-super-secret-password
|
||||
8
testdata/secrets/testdata/TestSecretVars-secret_vars_are_masked_in_logs.golden
vendored
Normal file
8
testdata/secrets/testdata/TestSecretVars-secret_vars_are_masked_in_logs.golden
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
task: [test-secret-masking] echo "Deploying myapp to https://example.com"
|
||||
Deploying myapp to https://example.com
|
||||
task: [test-secret-masking] echo "Using API key *****"
|
||||
Using API key secret-api-key-123
|
||||
task: [test-secret-masking] echo "Password is *****"
|
||||
Password is my-super-secret-password
|
||||
task: [test-secret-masking] echo "Public app name is myapp"
|
||||
Public app name is myapp
|
||||
@@ -239,6 +239,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)
|
||||
@@ -254,6 +256,8 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err
|
||||
continue
|
||||
}
|
||||
newCmd := cmd.DeepCopy()
|
||||
// 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)
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { team } from './team.ts';
|
||||
import { taskDescription, taskName, ogUrl, ogImage } from './meta.ts';
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import llmstxt, { copyOrDownloadAsMarkdownButtons } from 'vitepress-plugin-llms';
|
||||
import llmstxt from 'vitepress-plugin-llms';
|
||||
|
||||
const version = readFileSync(
|
||||
resolve(__dirname, '../../internal/version/version.txt'),
|
||||
@@ -119,7 +119,6 @@ export default defineConfig({
|
||||
});
|
||||
md.use(tabsMarkdownPlugin);
|
||||
md.use(groupIconMdPlugin);
|
||||
md.use(copyOrDownloadAsMarkdownButtons);
|
||||
}
|
||||
},
|
||||
vite: {
|
||||
@@ -211,7 +210,11 @@ export default defineConfig({
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
text: 'New `if:` Control and Variable Prompt',
|
||||
text: 'go tool task',
|
||||
link: '/blog/go-tool-task'
|
||||
},
|
||||
{
|
||||
text: 'New "if:" Control and Variable Prompt',
|
||||
link: '/blog/if-and-variable-prompt'
|
||||
}
|
||||
]
|
||||
@@ -352,6 +355,17 @@ export default defineConfig({
|
||||
text: 'Releasing',
|
||||
link: '/docs/releasing'
|
||||
},
|
||||
{
|
||||
text: 'Security',
|
||||
collapsed: true,
|
||||
link: '/docs/security/',
|
||||
items: [
|
||||
{
|
||||
text: 'Incident Response Plan',
|
||||
link: '/docs/security/incident-response-plan'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Changelog',
|
||||
link: '/docs/changelog'
|
||||
|
||||
@@ -12,7 +12,7 @@ export const team = [
|
||||
{ icon: 'x', link: 'https://x.com/andreynering' },
|
||||
{
|
||||
icon: 'bluesky',
|
||||
link: 'https://bsky.app/profile/andreynering.bsky.social'
|
||||
link: 'https://bsky.app/profile/andrey.nering.dev'
|
||||
},
|
||||
{ icon: 'mastodon', link: 'https://mastodon.social/@andreynering' }
|
||||
]
|
||||
|
||||
53
website/src/blog/go-tool-task.md
Normal file
53
website/src/blog/go-tool-task.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: go tool task
|
||||
description: How to use Task using go tool.
|
||||
author: andreynering
|
||||
date: 2026-04-14
|
||||
outline: deep
|
||||
editLink: false
|
||||
---
|
||||
|
||||
# `go tool task`
|
||||
|
||||
<AuthorCard :author="$frontmatter.author" />
|
||||
|
||||
Do you know that you can use Task without really needing to install it?
|
||||
|
||||
If you work with Go, you probably depend on external binaries like linters,
|
||||
code generators and... Task.
|
||||
|
||||
But asking your coworkers or contributors to install dependencies can be messy.
|
||||
Everyone is on a different operating system, use a different package manager,
|
||||
etc. In fact, [Task supports several package managers][install], but even having
|
||||
to choose how you want to install it can lead to some fatigue.
|
||||
|
||||
Well, turns out you can just use `go tool`!
|
||||
|
||||
Step one: add Task as a tool to your Go project:
|
||||
|
||||
```bash
|
||||
go get -tool github.com/go-task/task/v3/cmd/task@latest
|
||||
```
|
||||
|
||||
The command above will add a line like this to your `go.mod`:
|
||||
|
||||
```
|
||||
tool github.com/go-task/task/v3/cmd/task
|
||||
```
|
||||
|
||||
Step two: prefix `go tool` when calling Task:
|
||||
|
||||
```bash
|
||||
go tool task {arguments...}
|
||||
```
|
||||
|
||||
That's all!
|
||||
|
||||
Go will compile the specified Task version on demand when calling `go tool task`.
|
||||
Don't worry, Go caches the tool, so subsequent calls are faster.
|
||||
|
||||
This is useful when running Task on CI, as you don't need to stress about having
|
||||
to install it. It also means it'll be pinned to a specific Task version (but
|
||||
Dependabot or Renovate should be able to update it for you).
|
||||
|
||||
[install]: https://taskfile.dev/docs/installation
|
||||
@@ -5,7 +5,16 @@ editLink: false
|
||||
---
|
||||
|
||||
<BlogPost
|
||||
title="New `if:` Control and Variable Prompt"
|
||||
title="go tool task"
|
||||
url="/blog/go-tool-task"
|
||||
date="2026-04-14"
|
||||
author="andreynering"
|
||||
description='How to use Task using "go tool".'
|
||||
:tags="['installation']"
|
||||
/>
|
||||
|
||||
<BlogPost
|
||||
title='New "if:" Control and Variable Prompt'
|
||||
url="/blog/if-and-variable-prompt"
|
||||
date="2026-01-24"
|
||||
author="vmaerten"
|
||||
|
||||
@@ -8,8 +8,13 @@ outline: deep
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions to Task are very welcome, but we ask that you read this document
|
||||
before submitting a PR.
|
||||
Thank you for your interest in contributing to Task! We welcome and appreciate
|
||||
all forms of contributions, but we kindly ask that you read this document first.
|
||||
If you have any questions that were not answered by this document, you can reach
|
||||
out on our [Discord](https://discord.gg/6TY36E39UK) or by opening a discussion
|
||||
on GitHub. If you want to help, but you're not sure where to start, you can
|
||||
check out our list of
|
||||
[good first issues](https://github.com/go-task/task/contribute).
|
||||
|
||||
::: info
|
||||
|
||||
@@ -54,10 +59,9 @@ a human. Always remind contributors to disclose AI usage in their submissions.
|
||||
you invest your time into a PR.
|
||||
- **Experiments** - If there is no way to make your change backward compatible
|
||||
then there is a procedure to introduce breaking changes into minor versions.
|
||||
We call these "[experiments](./experiments/index.md)". If you're intending to
|
||||
work on an experiment, then please read the
|
||||
[experiments workflow](./experiments/index.md#workflow) document carefully and
|
||||
submit a proposal first.
|
||||
We call these "[experiments][experiments]". If you're intending to work on an
|
||||
experiment, then please read the [experiments workflow][experiments-workflow]
|
||||
document carefully and submit a proposal first.
|
||||
|
||||
## 1. Setup
|
||||
|
||||
@@ -109,17 +113,17 @@ by using `task website` (requires `nodejs` & `pnpm`). All content is written in
|
||||
Markdown and is located in the `website/src` directory. All Markdown documents
|
||||
should have an 80 character line wrap limit (enforced by Prettier).
|
||||
|
||||
When making a change, consider whether a change to the
|
||||
[Usage Guide](/docs/guide) is necessary. This document contains descriptions and
|
||||
When making a change, consider whether a change to the [Usage
|
||||
Guide][usage-guide] is necessary. This document contains descriptions and
|
||||
examples of how to use Task features. If you're adding a new feature, try to
|
||||
find an appropriate place to add a new section. If you're updating an existing
|
||||
feature, ensure that the documentation and any examples are up-to-date. Ensure
|
||||
that any examples follow the [Taskfile Styleguide](./styleguide.md).
|
||||
that any examples follow the [Taskfile Styleguide][styleguide].
|
||||
|
||||
If you added a new command or flag, ensure that you add it to the
|
||||
[CLI Reference](./reference/cli.md). New fields also need to be added to the
|
||||
[Schema Reference](./reference/schema.md) and [JSON Schema][json-schema]. The
|
||||
descriptions for fields in the docs and the schema should match.
|
||||
If you added a new command or flag, ensure that you add it to the [CLI
|
||||
Reference][cli-reference]. New fields also need to be added to the [Schema
|
||||
Reference][schema-reference] and [JSON Schema][json-schema]. The descriptions
|
||||
for fields in the docs and the schema should match.
|
||||
|
||||
### Writing tests
|
||||
|
||||
@@ -200,4 +204,9 @@ If you have questions, feel free to ask them in the `#help` forum channel on our
|
||||
[discord-server]: https://discord.gg/6TY36E39UK
|
||||
[discussion]: https://github.com/go-task/task/discussions
|
||||
[conventional-commits]: https://www.conventionalcommits.org
|
||||
[mdx]: https://mdxjs.com/
|
||||
[experiments]: ./experiments/
|
||||
[experiments-workflow]: ./experiments/#workflow
|
||||
[styleguide]: ./styleguide
|
||||
[cli-reference]: ./reference/cli
|
||||
[schema-reference]: ./reference/schema
|
||||
[usage-guide]: ./guide
|
||||
|
||||
@@ -1614,6 +1614,163 @@ tasks:
|
||||
map[a:1 b:2 c:3]
|
||||
```
|
||||
|
||||
### Secret variables
|
||||
|
||||
Task supports marking variables as `secret` to prevent their values from being
|
||||
displayed in command logs. When a variable is marked as secret, its value will
|
||||
be replaced with `*****` in the task output logs.
|
||||
|
||||
::: warning
|
||||
|
||||
**Security Notice**: This feature helps prevent accidental exposure of secrets
|
||||
in logs, but is **not a substitute** for proper secret management practices.
|
||||
|
||||
**What this protects:**
|
||||
|
||||
- ✅ Secret values in console/terminal logs
|
||||
- ✅ Secret values in CI/CD logs
|
||||
- ✅ Accidental copy-paste of logs containing secrets
|
||||
|
||||
**What this does NOT protect:**
|
||||
|
||||
- ❌ Secrets visible in process inspection (e.g., `ps aux`)
|
||||
- ❌ Secrets in shell history
|
||||
- ❌ Secrets in command output (stdout/stderr)
|
||||
|
||||
Always use proper secret management tools (HashiCorp Vault, AWS Secrets
|
||||
Manager, etc.) for production environments.
|
||||
|
||||
:::
|
||||
|
||||
To mark a variable as secret, add `secret: true` to the variable definition:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
API_KEY:
|
||||
value: 'sk-1234567890abcdef'
|
||||
secret: true
|
||||
|
||||
tasks:
|
||||
deploy:
|
||||
cmds:
|
||||
- curl -H "Authorization: {{.API_KEY}}" api.example.com
|
||||
# Logged as: task: [deploy] curl -H "Authorization: *****" api.example.com
|
||||
```
|
||||
|
||||
Secret variables work with all variable types:
|
||||
|
||||
::: code-group
|
||||
|
||||
```yaml [Simple Value]
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
PASSWORD:
|
||||
value: 'my-secret-password'
|
||||
secret: true
|
||||
|
||||
tasks:
|
||||
connect:
|
||||
cmds:
|
||||
- psql -U user -p {{.PASSWORD}} mydb
|
||||
# Logged as: psql -U user -p ***** mydb
|
||||
```
|
||||
|
||||
```yaml [Shell Command]
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
DB_PASSWORD:
|
||||
sh: vault read -field=password secret/db
|
||||
secret: true
|
||||
|
||||
tasks:
|
||||
migrate:
|
||||
cmds:
|
||||
- psql -U admin -p {{.DB_PASSWORD}} mydb
|
||||
# Password from vault is masked in logs
|
||||
```
|
||||
|
||||
```yaml [Task-Level Secret]
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
PUBLIC_URL: https://example.com
|
||||
|
||||
tasks:
|
||||
deploy:
|
||||
vars:
|
||||
DEPLOY_TOKEN:
|
||||
value: 'secret-token-123'
|
||||
secret: true
|
||||
cmds:
|
||||
- echo "Deploying to {{.PUBLIC_URL}} with token {{.DEPLOY_TOKEN}}"
|
||||
# Logged as: echo "Deploying to https://example.com with token *****"
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Multiple secrets in the same command are all masked:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
API_KEY:
|
||||
value: 'api-key-123'
|
||||
secret: true
|
||||
PASSWORD:
|
||||
value: 'password-456'
|
||||
secret: true
|
||||
|
||||
tasks:
|
||||
setup:
|
||||
cmds:
|
||||
- ./setup.sh --api {{.API_KEY}} --pwd {{.PASSWORD}}
|
||||
# Logged as: ./setup.sh --api ***** --pwd *****
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
**Best practices for secret variables:**
|
||||
|
||||
1. **Use shell commands to load secrets**, not hardcoded values:
|
||||
|
||||
```yaml
|
||||
# ❌ BAD - Secret visible in Taskfile
|
||||
vars:
|
||||
API_KEY:
|
||||
value: 'hardcoded-secret'
|
||||
secret: true
|
||||
|
||||
# ✅ GOOD - Secret loaded from external source
|
||||
vars:
|
||||
API_KEY:
|
||||
sh: vault kv get -field=api_key secret/myapp
|
||||
secret: true
|
||||
```
|
||||
|
||||
2. **Combine with environment variables:**
|
||||
|
||||
```yaml
|
||||
vars:
|
||||
API_KEY:
|
||||
sh: echo $MY_API_KEY
|
||||
secret: true
|
||||
```
|
||||
|
||||
3. **Use .gitignore for secret files:**
|
||||
|
||||
If you use dotenv files, add them to `.gitignore`:
|
||||
|
||||
```yaml
|
||||
dotenv: ['.env.local'] # Load from .env.local (in .gitignore)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Looping over values
|
||||
|
||||
Task allows you to loop over certain values and execute a command for each.
|
||||
|
||||
@@ -320,8 +320,6 @@ examples and configuration.
|
||||
|
||||
## Build From Source
|
||||
|
||||
### Go Modules
|
||||
|
||||
Ensure that you have a supported version of [Go](https://golang.org) properly
|
||||
installed and setup. You can find the minimum required version of Go in the
|
||||
[go.mod](https://github.com/go-task/task/blob/main/go.mod#L3) file.
|
||||
@@ -346,6 +344,26 @@ released binary.
|
||||
|
||||
:::
|
||||
|
||||
## Go Tool
|
||||
|
||||
If you're working in a Go project, a nice possibility is using `go tool`.
|
||||
`go tool` makes it easy to run Task without needing to install the binary
|
||||
manually. This works well on CI.
|
||||
|
||||
To do that, just run the following to add Task as a tool in your Go project.
|
||||
Task will be added to your `go.mod`.
|
||||
|
||||
```bash
|
||||
go get -tool github.com/go-task/task/v3/cmd/task@latest
|
||||
```
|
||||
|
||||
Then, prefix `go tool` when calling Task like below. Go will compile Task on
|
||||
demand before calling it.
|
||||
|
||||
```bash
|
||||
go tool task {arguments...}
|
||||
```
|
||||
|
||||
## Setup completions
|
||||
|
||||
Some installation methods will automatically install completions too, but if
|
||||
|
||||
@@ -379,6 +379,33 @@ vars:
|
||||
ttl: 3600
|
||||
```
|
||||
|
||||
### Secret Variables (`secret`)
|
||||
|
||||
Mark variables as secret to mask their values in command logs.
|
||||
|
||||
```yaml
|
||||
vars:
|
||||
API_KEY:
|
||||
value: 'sk-1234567890abcdef'
|
||||
secret: true # This variable will be masked in logs
|
||||
|
||||
DB_PASSWORD:
|
||||
sh: vault read -field=password secret/db
|
||||
secret: true # Works with dynamic variables too
|
||||
```
|
||||
|
||||
When a variable is marked as `secret: true`, Task will replace its value with
|
||||
`*****` in command logs. The actual command execution still receives the real
|
||||
value.
|
||||
|
||||
::: info
|
||||
|
||||
For complete documentation on secret variables, including security
|
||||
considerations and best practices, see the
|
||||
[Secret variables](/docs/guide#secret-variables) section in the Guide.
|
||||
|
||||
:::
|
||||
|
||||
### Variable Ordering
|
||||
|
||||
Variables can reference previously defined variables:
|
||||
|
||||
91
website/src/docs/security/incident-response-plan.md
Normal file
91
website/src/docs/security/incident-response-plan.md
Normal file
@@ -0,0 +1,91 @@
|
||||
---
|
||||
title: Incident Response Plan
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Incident Response Plan
|
||||
|
||||
This document outlines our incident response plan in the event that a
|
||||
vulnerability is reported to the Task project. This serves as a high-level,
|
||||
public guide and is published as part of our commitment to transparency.
|
||||
|
||||
Below are the security principles that we aim to adhere to as a project:
|
||||
|
||||
- **Transparency**: All incidents and fixes are documented here for the
|
||||
community.
|
||||
- **Stewardship**: Take responsibility for protecting users and the project.
|
||||
- **Protection**: Act to minimize harm and provide guidance.
|
||||
|
||||
## Scope
|
||||
|
||||
This plan applies to the core Task repository and all _official_ Task projects.
|
||||
For example, the Visual Studio Code extension and officially supported
|
||||
installation methods. In the event that a vulnerability is reported with a
|
||||
community-managed installation method, we will work with the community and make
|
||||
a "best-effort" attempt to help resolve the issue.
|
||||
|
||||
## Steps
|
||||
|
||||
### 🔍 1. Detect
|
||||
|
||||
- All security issues should be **privately reported** as described in our
|
||||
[security documentation][security-docs].
|
||||
- Maintainers should also regularly monitor and respond to:
|
||||
- Pull requests from dependency scanners such as Dependabot.
|
||||
- GitHub notifications and vulnerability alerts.
|
||||
- Messages in community channels such as Discord.
|
||||
|
||||
### 🩺 2. Triage
|
||||
|
||||
- Upon first receipt of a security issue, one of our team will immediately
|
||||
notify the other maintainers via a secure and private channel. This ensures
|
||||
that all maintainers are able to contribute to the issue where possible.
|
||||
- A maintainer should respond to the reporter in a timely manner in order to
|
||||
acknowledge receipt of the issue.
|
||||
- The issue must then be triaged into one of the following categories:
|
||||
- ‼️**Critical**: Has a serious and immediate impact on users or affects
|
||||
critical infrastructure related to the project.
|
||||
- ❗**High**: Has the potential to seriously impact users of a distributed
|
||||
asset.
|
||||
- 🟰**Medium**: Has the potential to impact users, but is obscure or low-risk.
|
||||
- ➖**Low**: No direct or immediate impact to users, but requires attention.
|
||||
- Open a draft
|
||||
[GitHub Security Advisory (GHSA)](https://github.com/go-task/task/security/advisories)
|
||||
in the Task repository.
|
||||
- Optionally create a CVE. This can be skipped for low/medium impact issues at
|
||||
the discretion of the maintainers.
|
||||
|
||||
### 🩹 3. Mitigate
|
||||
|
||||
- Act calmly and communicate decisions.
|
||||
- Stop the bleed.
|
||||
- Before attempting to fix the issue, perform any actions that stop the
|
||||
problem from becoming worse. For example:
|
||||
- Rotate any affected secrets.
|
||||
- Rebuild any affected services (website, etc.).
|
||||
- It may be difficult to do some of this in cases where packages are
|
||||
maintained by the community if we are not yet ready to disclose the
|
||||
vulnerability publicly. This should be decided on a case-by-case basis.
|
||||
- Address the root cause.
|
||||
- Plan and document a fix.
|
||||
- Patch the issue.
|
||||
- Test the fix.
|
||||
- Release new versions.
|
||||
|
||||
### 📢 4. Disclose
|
||||
|
||||
- Publish the GitHub Security Advisory (GHSE). Make sure to include:
|
||||
- The affected version(s)/services.
|
||||
- The impact of the issue.
|
||||
- The root cause.
|
||||
- The steps taken to resolve.
|
||||
- Optionally, create a blog post and/or share the information via our socials
|
||||
and public communication channels.
|
||||
|
||||
### 🧠 5. Learn
|
||||
|
||||
- Document the disclosure in a permanent location.
|
||||
- Make and document any changes that can be made to prevent similar issues from
|
||||
arising in the future.
|
||||
|
||||
[security-docs]: ../security/
|
||||
21
website/src/docs/security/index.md
Normal file
21
website/src/docs/security/index.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Security
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Security
|
||||
|
||||
The Task team takes security seriously and we thank our community for disclosing
|
||||
issues responsibly. To report security issues, please use [GitHub's built-in
|
||||
Private Vulnerability Reporting][pvr] or send an email to
|
||||
[task@taskfile.dev](mailto:task@taskfile.dev). Please include as much detail as
|
||||
possible in your report.
|
||||
|
||||
A member of the team will investigate as soon as possible and we will keep you
|
||||
updated throughout the process.
|
||||
|
||||
You can read more about how we handle security-related issues in our [Incident
|
||||
Response Plan][irp].
|
||||
|
||||
[pvr]: https://github.com/go-task/task/security/advisories/new
|
||||
[irp]: ./incident-response-plan
|
||||
@@ -318,6 +318,10 @@
|
||||
"map": {
|
||||
"type": "object",
|
||||
"description": "The value will be treated as a literal map type and stored in the variable"
|
||||
},
|
||||
"secret": {
|
||||
"type": "boolean",
|
||||
"description": "Marks the variable as secret. Secret values will be masked as ***** in command logs to prevent accidental exposure of sensitive information."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -20,7 +20,7 @@ const members = [
|
||||
{ icon: 'github', link: 'https://github.com/andreynering' },
|
||||
{ icon: 'discord', link: 'https://discord.com/users/310141681926275082' },
|
||||
{ icon: 'x', link: 'https://x.com/andreynering' },
|
||||
{ icon: 'bluesky', link: 'https://bsky.app/profile/andreynering.bsky.social' },
|
||||
{ icon: 'bluesky', link: 'https://bsky.app/profile/andrey.nering.dev' },
|
||||
{ icon: 'mastodon', link: 'https://mastodon.social/@andreynering' }
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user