Go 209 lines
package gates
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// initRepo creates a fresh git repo in t.TempDir() with one seed commit
// so HEAD~N..HEAD ranges resolve. Returns the workdir path.
func initRepo(t *testing.T) string {
t.Helper()
dir := t.TempDir()
runOrFail(t, dir, "git", "init", "-q", "-b", "main")
runOrFail(t, dir, "git", "config", "user.email", "[email protected]")
runOrFail(t, dir, "git", "config", "user.name", "test")
if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte("# seed\n"), 0o644); err != nil {
t.Fatal(err)
}
runOrFail(t, dir, "git", "add", "README.md")
runOrFail(t, dir, "git", "commit", "-q", "-m", "seed")
return dir
}
func runOrFail(t *testing.T, dir, name string, args ...string) {
t.Helper()
cmd := exec.Command(name, args...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("%s %s: %v\n%s", name, strings.Join(args, " "), err, out)
}
}
func writeAndCommit(t *testing.T, dir, rel, body, msg string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, rel), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
runOrFail(t, dir, "git", "add", rel)
runOrFail(t, dir, "git", "commit", "-q", "-m", msg)
}
func TestCheckAttribution_CleanTree(t *testing.T) {
dir := initRepo(t)
res, err := CheckAttribution(dir, Options{ScanFiles: true, ScanCommits: true, Range: "HEAD~0..HEAD"})
if err != nil {
t.Fatalf("CheckAttribution: %v", err)
}
if len(res.Findings) != 0 {
t.Errorf("clean tree has findings: %+v", res.Findings)
}
}
func TestCheckAttribution_FileHitDetectedByDetector(t *testing.T) {
dir := initRepo(t)
// A simulated leak in a tracked file: a Co-Authored-By line.
leaked := "feat\n\n" + "Co-" + "Authored-" + "By: Whoever <[email protected]>\n"
writeAndCommit(t, dir, "NOTES.md", leaked, "add notes")
res, err := CheckAttribution(dir, Options{ScanFiles: true})
if err != nil {
t.Fatalf("CheckAttribution: %v", err)
}
if len(res.Findings) == 0 {
t.Fatal("expected a file hit for the seeded trailer")
}
hit := res.Findings[0]
if hit.Path != "NOTES.md" {
t.Errorf("hit path = %q, want NOTES.md", hit.Path)
}
if hit.Commit != "" {
t.Errorf("file hit must not carry a commit SHA, got %q", hit.Commit)
}
}
func TestCheckAttribution_BodyHitOnCommitMessage(t *testing.T) {
dir := initRepo(t)
// Author a commit whose MESSAGE carries a Co-Authored-By:Claude
// trailer; the working-tree change itself is innocuous.
if err := os.WriteFile(filepath.Join(dir, "x.md"), []byte("x\n"), 0o644); err != nil {
t.Fatal(err)
}
runOrFail(t, dir, "git", "add", "x.md")
runOrFail(t, dir, "git", "commit", "-q", "-m",
"feat: x\n\n"+"Co-"+"Authored-"+"By: Claude <[email protected]>")
res, err := CheckAttribution(dir, Options{ScanCommits: true, Range: "HEAD~1..HEAD"})
if err != nil {
t.Fatalf("CheckAttribution: %v", err)
}
if len(res.Findings) == 0 {
t.Fatal("expected a commit-body hit")
}
hit := res.Findings[0]
if hit.Commit == "" {
t.Errorf("body hit must carry a commit SHA, got empty")
}
if !strings.Contains(strings.ToLower(hit.Excerpt), "claude") {
t.Errorf("hit excerpt should name claude, got %q", hit.Excerpt)
}
}
func TestCheckAttribution_BodyScanIgnoresPolicyDiscussion(t *testing.T) {
dir := initRepo(t)
// A docs commit whose SUBJECT and BODY mention the forbidden tokens
// in prose — not as an actual trailer. Strict trailer-anchored
// patterns must let this pass.
if err := os.WriteFile(filepath.Join(dir, "docs.md"), []byte("policy\n"), 0o644); err != nil {
t.Fatal(err)
}
runOrFail(t, dir, "git", "add", "docs.md")
runOrFail(t, dir, "git", "commit", "-q", "-m",
"docs: discuss the Co-"+"Authored-"+"By trailer policy\n\n"+
"The claude and anthropic tokens used to leak via the trailer; "+
"the noreply@anthropic email is also blocked.")
res, err := CheckAttribution(dir, Options{ScanCommits: true, Range: "HEAD~1..HEAD"})
if err != nil {
t.Fatalf("CheckAttribution: %v", err)
}
if len(res.Findings) != 0 {
t.Errorf("body scan flagged a policy-discussion commit: %+v", res.Findings)
}
}
func TestCheckAttribution_RangeFallbackEmitsNotice(t *testing.T) {
// Fresh repo has no origin/main remote — exercise the fallback.
dir := initRepo(t)
res, err := CheckAttribution(dir, Options{ScanCommits: true})
if err != nil {
t.Fatalf("CheckAttribution: %v", err)
}
if len(res.Notices) == 0 {
t.Fatal("expected a fallback notice")
}
if !strings.Contains(res.Notices[0], "HEAD~10..HEAD") {
t.Errorf("notice = %q, want HEAD~10..HEAD mention", res.Notices[0])
}
}
func TestCheckAttribution_NoCommitsSkipsBodyScan(t *testing.T) {
dir := initRepo(t)
// Stage a body-leak commit; ScanCommits=false should skip it.
runOrFail(t, dir, "git", "commit", "--allow-empty", "-q", "-m",
"feat: x\n\n"+"Co-"+"Authored-"+"By: Claude <[email protected]>")
res, err := CheckAttribution(dir, Options{ScanFiles: true, ScanCommits: false})
if err != nil {
t.Fatalf("CheckAttribution: %v", err)
}
for _, f := range res.Findings {
if f.Commit != "" {
t.Errorf("body finding leaked through ScanCommits=false: %+v", f)
}
}
}
func TestCheckAttribution_PathsOverrideLimitsScope(t *testing.T) {
dir := initRepo(t)
// Seed a file with a real trailer; the override paths point at a
// different (clean) file so the scan returns nothing.
leaked := "feat\n\n" + "Co-" + "Authored-" + "By: Whoever <[email protected]>\n"
writeAndCommit(t, dir, "DIRTY.md", leaked, "add dirty")
writeAndCommit(t, dir, "CLEAN.md", "just text\n", "add clean")
res, err := CheckAttribution(dir, Options{
ScanFiles: true,
Paths: []string{"CLEAN.md"},
})
if err != nil {
t.Fatalf("CheckAttribution: %v", err)
}
if len(res.Findings) != 0 {
t.Errorf("override scope leaked DIRTY.md hit: %+v", res.Findings)
}
}
func TestCheckAttribution_ExcludeSkipsPath(t *testing.T) {
dir := initRepo(t)
leaked := "feat\n\n" + "Co-" + "Authored-" + "By: Whoever <[email protected]>\n"
writeAndCommit(t, dir, "DIRTY.md", leaked, "add dirty")
res, err := CheckAttribution(dir, Options{
ScanFiles: true,
Excludes: []string{"DIRTY.md"},
})
if err != nil {
t.Fatalf("CheckAttribution: %v", err)
}
if len(res.Findings) != 0 {
t.Errorf("exclude failed to skip DIRTY.md: %+v", res.Findings)
}
}
func TestCheckAttribution_RobotEmojiInBody(t *testing.T) {
dir := initRepo(t)
robot := string([]rune{0x1F916})
body := "feat: thing\n\n" + robot + " " + "Generated" + " with the assistant\n"
if err := os.WriteFile(filepath.Join(dir, "x.md"), []byte("x\n"), 0o644); err != nil {
t.Fatal(err)
}
runOrFail(t, dir, "git", "add", "x.md")
runOrFail(t, dir, "git", "commit", "-q", "-m", body)
res, err := CheckAttribution(dir, Options{ScanCommits: true, Range: "HEAD~1..HEAD"})
if err != nil {
t.Fatalf("CheckAttribution: %v", err)
}
if len(res.Findings) == 0 {
t.Fatal("expected a body hit for robot-emoji Generated signature")
}
}