~james/core

core/migrator/git/runner_test.go -rw-r--r-- 4.5 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package git

import (
	"context"
	"errors"
	"os"
	"path/filepath"
	"slices"
	"strings"
	"testing"
)

func TestExtractHostFromPrompt(t *testing.T) {
	tests := []struct {
		prompt   string
		wantHost string
	}{
		{"Username for 'https://github.com': ", "github.com"},
		{"Password for 'https://x-access-token@github.com': ", "github.com"},
		{"Password for 'https://github.com.evil/repo.git': ", "github.com.evil"},
		{"Password for 'https://x-tangled-token@knot.example.com:443': ", "knot.example.com"},
		{"Password for 'https://knot.example.com': ", "knot.example.com"},
		{"random string without url", ""},
	}

	for _, tt := range tests {
		got := ExtractHostFromPrompt(tt.prompt)
		if got != tt.wantHost {
			t.Errorf("ExtractHostFromPrompt(%q) = %q, want %q", tt.prompt, got, tt.wantHost)
		}
	}
}

func TestHandleAskpassExactHostMatching(t *testing.T) {
	u, err := HandleAskpass("Username for 'https://github.com': ", "github.com", "mytoken")
	if err != nil || u != "x-access-token" {
		t.Fatalf("source username failed: %v, %q", err, u)
	}

	p, err := HandleAskpass("Password for 'https://x-access-token@github.com': ", "github.com", "mytoken")
	if err != nil || p != "mytoken" {
		t.Fatalf("source password failed: %v, %q", err, p)
	}

	_, err = HandleAskpass("Password for 'https://github.com.evil': ", "github.com", "mytoken")
	if err == nil {
		t.Fatal("expected error for github.com.evil subdomain mismatch")
	}

	_, err = HandleAskpass("Password for 'https://attacker.com': ", "github.com", "mytoken")
	if err == nil {
		t.Fatal("expected error for attacker.com mismatch")
	}
}

func TestCreateAskpassScriptExcludesSecrets(t *testing.T) {
	dir := t.TempDir()
	scriptPath, err := CreateAskpassScript(dir)
	if err != nil {
		t.Fatal(err)
	}

	content, err := os.ReadFile(scriptPath)
	if err != nil {
		t.Fatal(err)
	}

	secretToken := "supersecret12345"
	if strings.Contains(string(content), secretToken) {
		t.Fatal("askpass script contains secret token!")
	}
	if strings.Contains(string(content), "Bearer") {
		t.Fatal("askpass script contains Bearer string!")
	}
	if !strings.Contains(string(content), "askpass") {
		t.Fatal("askpass script does not invoke askpass command")
	}
}

func TestRedactSecrets(t *testing.T) {
	secret1 := "ghp_tokensecret98765"
	secret2 := "capability-secret-token"
	logMsg := "clone failed with ghp_tokensecret98765 and serving failed with capability-secret-token"
	redacted := RedactSecrets(logMsg, secret1, secret2)
	if strings.Contains(redacted, secret1) || strings.Contains(redacted, secret2) {
		t.Fatalf("secret was not redacted: %s", redacted)
	}
	if !strings.Contains(redacted, "[REDACTED]") {
		t.Fatalf("missing [REDACTED] in %s", redacted)
	}
}

func TestDiskLimitEnforcement(t *testing.T) {
	dir := t.TempDir()
	watchDir := filepath.Join(dir, "scratch")
	_ = os.MkdirAll(watchDir, 0700)

	runner := RealCommandRunner{
		MaxDiskBytes: 100,
		WatchDir:     watchDir,
	}

	testFile := filepath.Join(watchDir, "bigfile.txt")
	_, err := runner.Run(context.Background(), dir, nil, "sh", "-c", "head -c 10000 /dev/zero > '"+testFile+"' && sleep 1")
	if err == nil {
		t.Fatal("expected disk limit error, got nil")
	}
	if !strings.Contains(err.Error(), "scratch disk limit exceeded") {
		t.Fatalf("expected ErrDiskLimitExceeded, got: %v", err)
	}
}

func TestClassifyGitErrorKeepsRepoNotFoundRetryable(t *testing.T) {
	// github answers a repo the token cannot see with both phrases at once
	output := "remote: Repository not found.\nfatal: Authentication failed for 'https://github.com/alice/nope.git/'"
	err := ClassifyGitError(output, errors.New("exit status 128"))
	if !errors.Is(err, ErrRepoNotFound) {
		t.Fatalf("error = %v, want ErrRepoNotFound", err)
	}
	if errors.Is(err, ErrAuthRequired) {
		t.Fatal("a missing repository masqueraded as authorization required; the batch token would be scrubbed")
	}

	err = ClassifyGitError("fatal: Authentication failed for 'https://github.com/alice/repo.git/'", nil)
	if !errors.Is(err, ErrAuthRequired) || errors.Is(err, ErrRepoNotFound) {
		t.Fatalf("error = %v, want ErrAuthRequired only", err)
	}
}

func TestHardenedGitEnvTrustsTheProcessCertFile(t *testing.T) {
	t.Setenv("SSL_CERT_FILE", "")
	for _, kv := range HardenedGitEnv("", "", t.TempDir(), "", "") {
		if strings.HasPrefix(kv, "GIT_SSL_CAINFO=") {
			t.Fatalf("set %s with no SSL_CERT_FILE", kv)
		}
	}

	t.Setenv("SSL_CERT_FILE", "/etc/local-root.crt")
	env := HardenedGitEnv("", "", t.TempDir(), "", "")
	if !slices.Contains(env, "GIT_SSL_CAINFO=/etc/local-root.crt") {
		t.Fatalf("env %q doesn't point git at SSL_CERT_FILE", env)
	}
}