Skip to content

Commit 0ef7c80

Browse files
authored
Merge pull request #34 from rapidfort/sanitize-credentials
Sanitize credentials in logs when building Git contexts with BuildKit
2 parents 096523a + 6d412a4 commit 0ef7c80

File tree

3 files changed

+101
-6
lines changed

3 files changed

+101
-6
lines changed

src/internal/build/builder.go

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ func executeBuildah(config Config, ctx *Context) error {
291291
}
292292
}
293293

294-
logger.Info("Executing: buildah %s", strings.Join(args, " "))
294+
// Log the command being executed
295+
logger.Info("Executing: buildah %s", strings.Join(sanitizeCommandArgs(args), " "))
295296

296297
if err := cmd.Run(); err != nil {
297298
return fmt.Errorf("buildah build failed: %v", err)
@@ -576,11 +577,12 @@ func executeBuildKit(config Config, ctx *Context) error {
576577
if isGitContext {
577578
// Use Git URL for BuildKit native Git support
578579
// BuildKit requires Git URLs to be passed as --opt context=
579-
logger.Debug("Using Git context: %s", buildContext)
580+
logger.Debug("Using Git context: %s", logger.SanitizeGitURL(buildContext))
580581
args = append(args, "--opt", fmt.Sprintf("context=%s", buildContext))
581582
args = append(args, "--opt", fmt.Sprintf("dockerfile=%s", buildContext))
582583
} else {
583584
// Use local context
585+
logger.Debug("Using local context: %s", buildContext)
584586
args = append(args, "--local", fmt.Sprintf("context=%s", buildContext))
585587
args = append(args, "--local", fmt.Sprintf("dockerfile=%s", buildContext))
586588
}
@@ -755,7 +757,12 @@ func executeBuildKit(config Config, ctx *Context) error {
755757
}
756758

757759
// Log the command being executed
758-
logger.Info("Executing: buildctl %s", strings.Join(args, " "))
760+
logger.Info("Executing: buildctl %s", strings.Join(sanitizeCommandArgs(args), " "))
761+
762+
// BuildKit may log Git credentials in logs -- warn users accordingly
763+
if isGitContext && strings.Contains(buildContext, "@") {
764+
logger.Warning("BuildKit may expose Git credentials in build logs. Consider using SSH authentication instead of HTTPS tokens for better security.")
765+
}
759766

760767
// Execute build
761768
if err := cmd.Run(); err != nil {
@@ -1251,12 +1258,65 @@ func signImageWithCosign(image string, config Config) error {
12511258
}
12521259
}
12531260

1254-
logger.Debug("Executing: cosign %s", strings.Join(args, " "))
1261+
// Log the command being executed
1262+
logger.Debug("Executing: cosign %s", strings.Join(sanitizeCommandArgs(args), " "))
12551263

12561264
// Execute cosign
12571265
if err := cmd.Run(); err != nil {
12581266
return fmt.Errorf("cosign signing failed: %v", err)
12591267
}
12601268

12611269
return nil
1270+
}
1271+
1272+
// sanitizeCommandArgs removes credentials from Git URLs and sensitive build-args
1273+
func sanitizeCommandArgs(args []string) []string {
1274+
// List of build-arg names that contain sensitive data
1275+
sensitiveArgs := []string{
1276+
"GIT_PASSWORD",
1277+
"GIT_TOKEN",
1278+
"PASSWORD",
1279+
"TOKEN",
1280+
"API_KEY",
1281+
"SECRET",
1282+
"CREDENTIALS",
1283+
}
1284+
1285+
sanitized := make([]string, len(args))
1286+
for i, arg := range args {
1287+
if strings.HasPrefix(arg, "context=") || strings.HasPrefix(arg, "dockerfile=") {
1288+
// Handle --opt context=URL or --opt dockerfile=URL format
1289+
parts := strings.SplitN(arg, "=", 2)
1290+
if len(parts) == 2 {
1291+
sanitized[i] = parts[0] + "=" + logger.SanitizeGitURL(parts[1])
1292+
} else {
1293+
sanitized[i] = arg
1294+
}
1295+
} else if strings.HasPrefix(arg, "build-arg:") {
1296+
// Handle --opt build-arg:KEY=VALUE format
1297+
parts := strings.SplitN(arg, "=", 2)
1298+
if len(parts) == 2 {
1299+
argName := strings.TrimPrefix(parts[0], "build-arg:")
1300+
// Check if this is a sensitive build arg
1301+
isSensitive := false
1302+
for _, sensitive := range sensitiveArgs {
1303+
if strings.Contains(strings.ToUpper(argName), sensitive) {
1304+
isSensitive = true
1305+
break
1306+
}
1307+
}
1308+
if isSensitive {
1309+
sanitized[i] = parts[0] + "=***REDACTED***"
1310+
} else {
1311+
sanitized[i] = arg
1312+
}
1313+
} else {
1314+
sanitized[i] = arg
1315+
}
1316+
} else {
1317+
// For any other arg that might be a URL
1318+
sanitized[i] = logger.SanitizeGitURL(arg)
1319+
}
1320+
}
1321+
return sanitized
12621322
}

src/internal/build/context.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ func Prepare(gitConfig GitConfig, builder string) (*Context, error) {
4848

4949
// Check if context is a git URL
5050
if isGitURL(gitConfig.Context) {
51-
logger.Info("Detected git repository context: %s", gitConfig.Context)
52-
51+
logger.Info("Detected git repository context: %s", logger.SanitizeGitURL(gitConfig.Context))
52+
5353
// Normalize git:// URLs to https:// for known providers (GitHub, GitLab, etc)
5454
normalizedURL := normalizeGitURL(gitConfig.Context)
5555

src/pkg/logger/logger.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package logger
33
import (
44
"fmt"
55
"log"
6+
"net/url"
67
"os"
78
)
89

@@ -77,3 +78,37 @@ func Fatal(format string, args ...interface{}) {
7778
logFatal.Printf(format, args...)
7879
os.Exit(1)
7980
}
81+
82+
// SanitizeGitURL removes credentials from Git URLs for safe logging
83+
// Preserves username but redacts password/token
84+
func SanitizeGitURL(gitURL string) string {
85+
u, err := url.Parse(gitURL)
86+
if err != nil {
87+
// Not a valid URL, return as-is (might be SSH or local path)
88+
// return gitURL
89+
}
90+
91+
// If there's user info (credentials), redact the password but keep username
92+
if u.User != nil {
93+
username := u.User.Username()
94+
if _, hasPassword := u.User.Password(); hasPassword {
95+
// Manually reconstruct URL to avoid encoding **REDACTED**
96+
scheme := u.Scheme
97+
host := u.Host
98+
path := u.Path
99+
fragment := ""
100+
if u.Fragment != "" {
101+
fragment = "#" + u.Fragment
102+
}
103+
query := ""
104+
if u.RawQuery != "" {
105+
query = "?" + u.RawQuery
106+
}
107+
108+
return fmt.Sprintf("%s://%s:**REDACTED**@%s%s%s%s",
109+
scheme, username, host, path, query, fragment)
110+
}
111+
}
112+
113+
return u.String()
114+
}

0 commit comments

Comments
 (0)