Skip to content

Commit 93ab0d5

Browse files
fix(remote): redact credentialed URLs in ls-remote errors
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 577920d commit 93ab0d5

2 files changed

Lines changed: 25 additions & 2 deletions

File tree

cmd/entire/cli/checkpoint/remote/git.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,15 +462,17 @@ func lsRemote(ctx context.Context, dir, remote string, patterns ...string) ([]by
462462
disableTerminalPrompt(cmd)
463463
out, err := cmd.Output()
464464
if err != nil {
465-
return out, fmt.Errorf("git ls-remote: %w", formatGitCommandError(ctx, err))
465+
return out, fmt.Errorf("git ls-remote: %w", formatGitCommandError(ctx, err, remote))
466466
}
467467
return out, nil
468468
}
469469

470470
// formatGitCommandError enriches an exec error from git Output() so callers see
471471
// useful detail: context deadline expiry by name, and git's stderr (auth denied,
472472
// repository not found, DNS) which ExitError otherwise hides behind "exit status N".
473-
func formatGitCommandError(ctx context.Context, err error) error {
473+
// When remote is a URL it may carry credentials that git echoes into stderr;
474+
// those are redacted before the error is returned (same pattern as FetchBlobs).
475+
func formatGitCommandError(ctx context.Context, err error, remote string) error {
474476
if err == nil {
475477
return nil
476478
}
@@ -480,6 +482,9 @@ func formatGitCommandError(ctx context.Context, err error) error {
480482
var exitErr *exec.ExitError
481483
if errors.As(err, &exitErr) {
482484
if stderr := strings.TrimSpace(string(exitErr.Stderr)); stderr != "" {
485+
if remote != "" {
486+
stderr = strings.ReplaceAll(stderr, remote, RedactURL(remote))
487+
}
483488
// Collapse whitespace so multi-line git stderr stays one log/attr value.
484489
stderr = strings.Join(strings.Fields(stderr), " ")
485490
return fmt.Errorf("%w (%s)", err, stderr)

cmd/entire/cli/checkpoint/remote/git_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,3 +1274,21 @@ func TestIsNonInteractiveSSH(t *testing.T) {
12741274
assert.False(t, IsNonInteractiveSSH(context.Background()))
12751275
assert.True(t, IsNonInteractiveSSH(WithNonInteractiveSSH(context.Background())))
12761276
}
1277+
1278+
func TestFormatGitCommandError_RedactsRemoteURL(t *testing.T) {
1279+
t.Parallel()
1280+
1281+
remote := "https://user:hunter2@github.com/org/repo.git"
1282+
// Output() populates ExitError.Stderr (Run() does not).
1283+
cmd := exec.CommandContext(context.Background(), "sh", "-c",
1284+
fmt.Sprintf(`printf 'fatal: repository "%s" not found\n' >&2; exit 128`, remote))
1285+
_, err := cmd.Output()
1286+
require.Error(t, err)
1287+
1288+
formatted := formatGitCommandError(context.Background(), err, remote)
1289+
require.Error(t, formatted)
1290+
msg := formatted.Error()
1291+
assert.NotContains(t, msg, "hunter2")
1292+
assert.NotContains(t, msg, "user:hunter2")
1293+
assert.Contains(t, msg, RedactURL(remote))
1294+
}

0 commit comments

Comments
 (0)