Hide credentials in checkout preparation errors
This commit is contained in:
@@ -37,6 +37,29 @@ func TestVersionCommand(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCIDeliverRedactsRemoteCredentialsOnPreparationFailure(t *testing.T) {
|
||||
t.Setenv("NO_PROXY", "*")
|
||||
t.Setenv("no_proxy", "*")
|
||||
t.Setenv("GIT_CONFIG_GLOBAL", os.DevNull)
|
||||
t.Setenv("GIT_CONFIG_NOSYSTEM", "1")
|
||||
t.Setenv("GIT_TERMINAL_PROMPT", "0")
|
||||
repository := newCLIRepository(t)
|
||||
commit := cliGitOutput(t, repository, "rev-parse", "HEAD")
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}))
|
||||
defer server.Close()
|
||||
remote := strings.Replace(server.URL, "http://", "http://review-user:review-password@", 1) + "/repo.git"
|
||||
var stdout, stderr bytes.Buffer
|
||||
status := run(t.Context(), []string{"ci-deliver", "--repo", repository, "--remote", remote, "--commit", commit, "v1.2.3"}, &stdout, &stderr)
|
||||
if status != 1 || stdout.Len() != 0 || !strings.Contains(stderr.String(), "prepare tagged checkout") || !strings.Contains(stderr.String(), "repo.git") {
|
||||
t.Fatalf("status = %d, stdout = %q, stderr = %q", status, stdout.String(), stderr.String())
|
||||
}
|
||||
if strings.Contains(stderr.String(), "review-user") || strings.Contains(stderr.String(), "review-password") {
|
||||
t.Fatalf("CLI preparation diagnostic exposes credentials: %q", stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublishCommandReportsUnknownForPeeledOnlyRecovery(t *testing.T) {
|
||||
repository := newCLIRepository(t)
|
||||
writeRepositoryFile(t, repository, "docs/releases/v1.2.3.md", "# Example Product v1.2.3\n\nRelease note.\n")
|
||||
|
||||
@@ -244,6 +244,7 @@ the selected Git remote (default `origin`), requires its peeled commit to match
|
||||
the required full `--commit` object ID, checks out that commit detached, then
|
||||
verifies the tag, HEAD, and clean worktree. With `--prepared`, it performs those
|
||||
checks read-only against the existing checkout and does not fetch or check out.
|
||||
Preparation diagnostics mask userinfo in HTTP(S) remote URLs.
|
||||
In either mode, it loads the profile from the verified source. `--expected-module`
|
||||
optionally checks that the profile selects the module chosen by a launcher;
|
||||
module selection itself remains profile-owned. The command compares the selected
|
||||
|
||||
@@ -109,9 +109,16 @@ public publication regression using a local hook from another caller directory.
|
||||
|
||||
## R03. Checkout preparation reintroduces credentials after Git redaction
|
||||
|
||||
**Priority:** P2. **Status:** Pending. This is an A04 integration gap in the
|
||||
**Priority:** P2. **Status:** Complete. This addressed an A04 integration gap in the
|
||||
existing preparation workflow, separate from R01's URL-span parsing defect.
|
||||
|
||||
**Completion:** Fetch-failure errors render the remote with `DisplayURL`, while
|
||||
the fetch retains its original URL and wrapped Git cause. Public preparation
|
||||
and CLI regressions failed before the fix and now pass with local HTTP servers;
|
||||
the public test also verifies authentication with the configured credentials.
|
||||
The remaining owned Git URL renderers were reviewed, and the CLI guide describes
|
||||
the implemented diagnostic behavior. Combined verification below passed.
|
||||
|
||||
**Relevant code:** `PrepareTaggedCheckout` in
|
||||
[internal/git/prepare.go](../../internal/git/prepare.go), lines 18–20; the public
|
||||
[preparation facade](../../preparation.go); and error presentation in
|
||||
@@ -205,3 +212,20 @@ using the earlier Git environment behavior passed. Both audits left production
|
||||
code and existing tests unchanged. The full-suite, static, and compiled
|
||||
API/dependency results above record the audit baseline; completion notes record
|
||||
verification of the subsequent repairs.
|
||||
|
||||
## Completed remediation verification
|
||||
|
||||
All three findings are complete. Each fix includes regressions that reproduced
|
||||
the defect before the production change, and each unit has its own commit with
|
||||
the corresponding completion marker.
|
||||
|
||||
On Linux/amd64 with Go 1.26.8, combined verification passed with
|
||||
`GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off`, the prepared build cache, and
|
||||
`TMPDIR=/var/tmp` for test fixtures:
|
||||
|
||||
- `go test ./...`
|
||||
- `go test -race ./...`
|
||||
- `go run ./internal/apicheck`
|
||||
|
||||
Changed Go files passed formatting inspection. Local links and fragments in
|
||||
this document and the CLI guide resolved, and the diff passed whitespace checks.
|
||||
|
||||
@@ -17,7 +17,7 @@ func PrepareTaggedCheckout(ctx context.Context, repositoryDir, tag, remote, expe
|
||||
}
|
||||
ref := "refs/tags/" + tag
|
||||
if _, err := run(ctx, repositoryDir, nil, "fetch", "--no-tags", remote, ref+":"+ref); err != nil {
|
||||
return fmt.Errorf("fetch tag %s from remote %s: %w", tag, remote, err)
|
||||
return fmt.Errorf("fetch tag %s from remote %s: %w", tag, DisplayURL(remote), err)
|
||||
}
|
||||
source, err := InspectTaggedSource(ctx, repositoryDir, tag)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,17 +3,55 @@ package releasekit_test
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
releasekit "go.maximumdirect.net/releasekit"
|
||||
)
|
||||
|
||||
func TestPrepareTaggedCheckoutRedactsRemoteCredentials(t *testing.T) {
|
||||
t.Setenv("NO_PROXY", "*")
|
||||
t.Setenv("no_proxy", "*")
|
||||
t.Setenv("GIT_CONFIG_GLOBAL", os.DevNull)
|
||||
t.Setenv("GIT_CONFIG_NOSYSTEM", "1")
|
||||
t.Setenv("GIT_TERMINAL_PROMPT", "0")
|
||||
repo := newPublishFixture(t)
|
||||
var authenticated atomic.Bool
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user, password, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="fixture"`)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if user != "review-user" || password != "review-password" {
|
||||
t.Error("fetch did not preserve the configured credentials")
|
||||
}
|
||||
authenticated.Store(true)
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}))
|
||||
defer server.Close()
|
||||
remote := strings.Replace(server.URL, "http://", "http://review-user:review-password@", 1) + "/repo.git"
|
||||
err := releasekit.PrepareTaggedCheckout(t.Context(), releasekit.TaggedCheckoutRequest{
|
||||
RepositoryDir: repo.work, Tag: "v1.2.3", Remote: remote, ExpectedCommit: repo.commit,
|
||||
})
|
||||
var exit *exec.ExitError
|
||||
if !errors.As(err, &exit) || !authenticated.Load() || !strings.Contains(err.Error(), "v1.2.3") || !strings.Contains(err.Error(), "repo.git") {
|
||||
t.Fatalf("preparation failure = %v, authenticated = %t", err, authenticated.Load())
|
||||
}
|
||||
if strings.Contains(err.Error(), "review-user") || strings.Contains(err.Error(), "review-password") {
|
||||
t.Fatalf("preparation diagnostic exposes credentials: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareBuildDependenciesCancellationReleasesInheritedOutput(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
ready := filepath.Join(t.TempDir(), "descendant")
|
||||
|
||||
Reference in New Issue
Block a user