From f38051661b5a6ef281865f3006018b316add25ef Mon Sep 17 00:00:00 2001 From: Patrick Hermann Date: Wed, 7 Oct 2026 14:01:59 +0000 Subject: [PATCH] fix: random secret names for registry and MinIO credentials #403 named the docker config secret (crossplane, helm, oci, flux push) and the mc host secret (hugo export) after a SHA-256 prefix of their content. Secret names are not secret: they show up in --progress plain output and in traces. An unsalted hash of the base64 user:token, or of https://:@, lets a guessed credential be confirmed offline. Same finding as #411 for sops set. The names are now crypto/rand.Text() per call. Co-Authored-By: Claude Opus 5.5 --- crossplane/push.go | 11 +++++------ flux/push.go | 11 +++++------ helm/push.go | 11 +++++------ hugo/export.go | 8 ++++---- oci/push.go | 11 +++++------ 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/crossplane/push.go b/crossplane/push.go index f25ed22a..ee83479c 100644 --- a/crossplane/push.go +++ b/crossplane/push.go @@ -2,10 +2,9 @@ package main import ( "context" - "crypto/sha256" + "crypto/rand" "dagger/crossplane/internal/dagger" reg "dagger/crossplane/registry" - "encoding/hex" ) // Push Crossplane Package @@ -51,9 +50,9 @@ func (m *Crossplane) Push( // dockerConfigSecret wraps a docker config.json as a Secret, so it is mounted // instead of passed as an op argument, which --progress plain prints (#318). -// The name is derived from the content, so different credentials never share -// one secret name within a session. +// The name is random per call: secret names can appear in traces, and a name +// derived from the content (an unsalted hash of the base64 credential) lets a +// guessed password be confirmed offline. func dockerConfigSecret(configJSON string) *dagger.Secret { - sum := sha256.Sum256([]byte(configJSON)) - return dag.SetSecret("docker-config-"+hex.EncodeToString(sum[:8]), configJSON) + return dag.SetSecret("docker-config-"+rand.Text(), configJSON) } diff --git a/flux/push.go b/flux/push.go index cc642729..28d7b0b4 100644 --- a/flux/push.go +++ b/flux/push.go @@ -2,10 +2,9 @@ package main import ( "context" - "crypto/sha256" + "crypto/rand" "dagger/flux/internal/dagger" reg "dagger/flux/registry" - "encoding/hex" "fmt" ) @@ -149,9 +148,9 @@ func (m *Flux) PushArtifacts( // dockerConfigSecret wraps a docker config.json as a Secret, so it is mounted // instead of passed as an op argument, which --progress plain prints (#318). -// The name is derived from the content, so different credentials never share -// one secret name within a session. +// The name is random per call: secret names can appear in traces, and a name +// derived from the content (an unsalted hash of the base64 credential) lets a +// guessed password be confirmed offline. func dockerConfigSecret(configJSON string) *dagger.Secret { - sum := sha256.Sum256([]byte(configJSON)) - return dag.SetSecret("docker-config-"+hex.EncodeToString(sum[:8]), configJSON) + return dag.SetSecret("docker-config-"+rand.Text(), configJSON) } diff --git a/helm/push.go b/helm/push.go index 2e7b7d24..75c6bd8a 100644 --- a/helm/push.go +++ b/helm/push.go @@ -2,10 +2,9 @@ package main import ( "context" - "crypto/sha256" + "crypto/rand" "dagger/helm/internal/dagger" reg "dagger/helm/registry" - "encoding/hex" "fmt" ) @@ -66,9 +65,9 @@ func (m *Helm) Push( // dockerConfigSecret wraps a docker config.json as a Secret, so it is mounted // instead of passed as an op argument, which --progress plain prints (#318). -// The name is derived from the content, so different credentials never share -// one secret name within a session. +// The name is random per call: secret names can appear in traces, and a name +// derived from the content (an unsalted hash of the base64 credential) lets a +// guessed password be confirmed offline. func dockerConfigSecret(configJSON string) *dagger.Secret { - sum := sha256.Sum256([]byte(configJSON)) - return dag.SetSecret("docker-config-"+hex.EncodeToString(sum[:8]), configJSON) + return dag.SetSecret("docker-config-"+rand.Text(), configJSON) } diff --git a/hugo/export.go b/hugo/export.go index 2ec40b32..32118a63 100644 --- a/hugo/export.go +++ b/hugo/export.go @@ -2,9 +2,8 @@ package main import ( "context" - "crypto/sha256" + "crypto/rand" "dagger/hugo/internal/dagger" - "encoding/hex" "fmt" "strconv" "strings" @@ -169,8 +168,9 @@ func (m *Hugo) SyncMinioBucket( } mcHostURL := fmt.Sprintf("https://%s:%s@%s", accessKeyStr, secretKeyStr, endpoint) // # pragma: allowlist secret - mcHostSum := sha256.Sum256([]byte(mcHostURL)) - mcHost := dag.SetSecret("mc-host-"+hex.EncodeToString(mcHostSum[:8]), mcHostURL) + // Random name: secret names can appear in traces, and a hash of the URL + // would let guessed MinIO keys be confirmed offline. + mcHost := dag.SetSecret("mc-host-"+rand.Text(), mcHostURL) var repoContent *dagger.Directory repoContent = dag.Directory() diff --git a/oci/push.go b/oci/push.go index 21c25e2d..ab5e4f49 100644 --- a/oci/push.go +++ b/oci/push.go @@ -2,10 +2,9 @@ package main import ( "context" - "crypto/sha256" + "crypto/rand" "dagger/oci/internal/dagger" reg "dagger/oci/registry" - "encoding/hex" "fmt" ) @@ -153,9 +152,9 @@ func (m *Oci) PushArtifacts( // dockerConfigSecret wraps a docker config.json as a Secret, so it is mounted // instead of passed as an op argument, which --progress plain prints (#318). -// The name is derived from the content, so different credentials never share -// one secret name within a session. +// The name is random per call: secret names can appear in traces, and a name +// derived from the content (an unsalted hash of the base64 credential) lets a +// guessed password be confirmed offline. func dockerConfigSecret(configJSON string) *dagger.Secret { - sum := sha256.Sum256([]byte(configJSON)) - return dag.SetSecret("docker-config-"+hex.EncodeToString(sum[:8]), configJSON) + return dag.SetSecret("docker-config-"+rand.Text(), configJSON) }