railiance-forge/tools/receive-secret-scan/batch_test.go
tegwick ea65606c12
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Batch small receive inputs within the unchanged scan budget
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a14bfc-9643-7872-aa12-98326dcf25e7
2026-10-18 13:25:08 +02:00

323 lines
9.7 KiB
Go

package main
import (
"bufio"
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
func TestMain(m *testing.M) {
if code, handled := internalProcess(os.Args[1:]); handled {
os.Exit(code)
}
if len(os.Args) == 2 && os.Args[1] == "--test-storage-refusal" {
storage, err := newVolatileStorage()
if err == nil {
storage.close()
os.Exit(3)
}
json.NewEncoder(os.Stdout).Encode(map[string]string{"status": "unverified", "reason": err.Error()})
os.Exit(0)
}
if len(os.Args) == 2 && os.Args[1] == "--test-storage-parent" {
storage, err := newVolatileStorage()
if err != nil {
os.Exit(3)
}
if os.WriteFile(filepath.Join(storage.path, "input"), []byte("synthetic private test input"), 0600) != nil {
os.Exit(3)
}
json.NewEncoder(os.Stdout).Encode(map[string]any{"path": storage.path, "watchdog": storage.command.Process.Pid})
child := exec.Command("/proc/self/exe", "--test-storage-reader", storage.path)
child.Stdout = os.Stdout
if runChild(child) != nil {
os.Exit(4)
}
os.Exit(0)
}
if len(os.Args) == 3 && os.Args[1] == "--test-storage-reader" {
f, err := os.Open(filepath.Join(os.Args[2], "input"))
if err != nil {
os.Exit(3)
}
defer f.Close()
fmt.Fprintln(os.Stdout, os.Getpid())
time.Sleep(time.Minute)
os.Exit(0)
}
os.Exit(m.Run())
}
func TestVolatileStorageLifecycle(t *testing.T) {
storage, err := newVolatileStorage()
if err != nil {
t.Fatal(err)
}
if !storage.live() {
t.Fatal("watchdog did not remain live")
}
if err = os.WriteFile(filepath.Join(storage.path, "input"), []byte("synthetic input"), 0600); err != nil {
t.Fatal("cannot write private fixture")
}
if err = storage.close(); err != nil {
t.Fatal(err)
}
if _, err = os.Lstat(storage.path); !os.IsNotExist(err) {
t.Fatal("private storage retained")
}
}
func TestVolatileIdentityRefusesReplacement(t *testing.T) {
storage, err := newVolatileStorage()
if err != nil {
t.Fatal(err)
}
// Keep the original inode alive so a new directory cannot reuse it.
original := storage.path + "-original"
if os.Rename(storage.path, original) != nil || os.Mkdir(storage.path, 0700) != nil {
t.Fatal("cannot prepare replacement")
}
defer os.RemoveAll(original)
defer os.RemoveAll(storage.path)
if err = storage.close(); err == nil {
t.Fatal("replacement directory accepted")
}
if _, err = os.Stat(storage.path); err != nil {
t.Fatal("replacement directory was removed")
}
}
func TestReceiverDeathCleansStorageAndStopsReader(t *testing.T) {
cmd := exec.Command("/proc/self/exe", "--test-storage-parent")
pipe, err := cmd.StdoutPipe()
if err != nil || cmd.Start() != nil {
t.Fatal("cannot start disposable parent")
}
defer cmd.Process.Kill()
reader := bufio.NewReader(pipe)
line, err := reader.ReadBytes('\n')
var receipt struct {
Path string
Watchdog int
}
if err != nil || json.Unmarshal(line, &receipt) != nil || receipt.Path == "" {
t.Fatal("disposable parent did not prepare storage")
}
var childPID int
if _, err = fmt.Fscanln(reader, &childPID); err != nil || childPID < 2 {
t.Fatal("disposable reader did not open input")
}
if cmd.Process.Kill() != nil {
t.Fatal("cannot kill disposable parent")
}
cmd.Wait()
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
_, pathErr := os.Lstat(receipt.Path)
stat, procErr := os.ReadFile(fmt.Sprintf("/proc/%d/stat", childPID))
terminal := os.IsNotExist(procErr)
if procErr == nil {
// Zombies are terminal; the private PID namespace reaps them.
fields := strings.Fields(string(stat)[strings.LastIndex(string(stat), ")")+1:])
terminal = len(fields) > 0 && fields[0] == "Z"
}
if os.IsNotExist(pathErr) && terminal {
return
}
time.Sleep(10 * time.Millisecond)
}
t.Fatal("receiver loss retained private input or live reader")
}
func TestSmallInputParity(t *testing.T) {
scanner := os.Getenv("FORGE_TEST_GITLEAKS")
if scanner == "" {
t.Skip("requires explicitly admitted disposable Gitleaks binary")
}
f, err := pinScanner(scanner)
if err != nil {
t.Fatal(err)
}
defer f.Close()
storage, err := newVolatileStorage()
if err != nil {
t.Fatal(err)
}
defer func() {
if err := storage.close(); err != nil {
t.Error(err)
}
}()
config := t.TempDir()
write(t, filepath.Join(config, "scanner.toml"), []byte("[extend]\nuseDefault = true\n"))
write(t, filepath.Join(config, "ignore"), nil)
privateKey := key(t)
var random [32]byte
if _, err := rand.Read(random[:]); err != nil {
t.Fatal("cannot generate synthetic token")
}
canary := []byte("glpat-" + hex.EncodeToString(random[:]))
for _, tc := range []struct {
name string
input []byte
want bool
}{
{"empty", nil, false},
{"clean", []byte("reviewed clean input\n"), false},
{"exact-limit", bytes.Repeat([]byte("x"), directoryInputBytes), false},
{"private-key", privateKey, true},
{"exact-limit-private-key", append(bytes.Repeat([]byte("\n"), directoryInputBytes-len(privateKey)), privateKey...), true},
{"binary-private-key", append([]byte{0, 1, 0}, privateKey...), true},
{"token", canary, true},
{"binary-token", append([]byte{0, 0}, canary...), true},
{"inline-suppression", append([]byte("# gitleaks:allow\n"), privateKey...), true},
} {
t.Run(tc.name, func(t *testing.T) {
stdin, e := scan(context.Background(), f, config, tc.input)
if e != nil || stdin != tc.want {
t.Fatal("stdin reference outcome differs", e)
}
name := strings.Repeat("a", 40)
blocked, e := scanSmall(context.Background(), f, config, storage, []scanInput{{name, tc.input}})
if e != nil || (blocked != "") != stdin || blocked != "" && blocked != name {
t.Fatal("directory and original stdin detection differ", e)
}
})
}
t.Run("sixty-four-distinct-inputs", func(t *testing.T) {
var inputs []scanInput
for i := 0; i < directoryBatchInputs; i++ {
inputs = append(inputs, scanInput{fmt.Sprintf("%040x", i), []byte("clean")})
}
inputs[63].content = privateKey
blocked, e := scanSmall(context.Background(), f, config, storage, inputs)
if e != nil || blocked != inputs[63].name {
t.Fatal("last batch input was not detected", e)
}
})
t.Run("expired-context", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, e := scanSmall(ctx, f, config, storage, []scanInput{{strings.Repeat("b", 40), []byte("clean")}})
if e == nil || e.Error() != "scan-deadline-exceeded" {
t.Fatal("expired scan did not fail closed")
}
})
t.Run("invalid-inputs", func(t *testing.T) {
name := strings.Repeat("c", 40)
for _, inputs := range [][]scanInput{
{{"../escape", []byte("clean")}},
{{name, bytes.Repeat([]byte("x"), directoryInputBytes+1)}},
{{name, []byte("clean")}, {name, []byte("clean")}},
} {
if _, e := scanSmall(context.Background(), f, config, storage, inputs); e == nil {
t.Fatal("invalid batch input accepted")
}
if _, e := os.Lstat(filepath.Join(storage.path, "payload")); !os.IsNotExist(e) {
t.Fatal("invalid batch left input files")
}
}
})
t.Run("scanner-failure", func(t *testing.T) {
write(t, filepath.Join(config, "scanner.toml"), []byte("invalid = ["))
if _, e := scanSmall(context.Background(), f, config, storage, []scanInput{{strings.Repeat("d", 40), []byte("clean")}}); e == nil {
t.Fatal("scanner failure accepted")
}
})
}
func TestBatchAndLargerInputPushBoundaries(t *testing.T) {
adapter, scanner := binary(t)
for _, tc := range []struct {
name string
size int
secret bool
}{
{"exact-limit-key", directoryInputBytes, true},
{"larger-stdin-key", directoryInputBytes + 1, true},
{"larger-stdin-clean", 70 * 1024, false},
} {
t.Run(tc.name, func(t *testing.T) {
f := setup(t, adapter, scanner)
content := []byte("reviewed clean input\n")
want := 0
if tc.secret {
content = key(t)
want = 1
}
content = append(content, bytes.Repeat([]byte("\n"), tc.size-len(content))...)
f.commit(t, "input.bin", content, "disposable boundary fixture")
f.push(t, want, "HEAD:refs/heads/main")
})
}
t.Run("multiple-ref-name-detection", func(t *testing.T) {
f := setup(t, adapter, scanner)
var random [32]byte
if _, err := rand.Read(random[:]); err != nil {
t.Fatal("fixture generation failed")
}
out := f.push(t, 1, "HEAD:refs/heads/clean-new", "HEAD:refs/heads/glpat-"+hex.EncodeToString(random[:]))
if !bytes.Contains(out, []byte(`"reason":"ref-name-detection"`)) {
t.Fatal("multi-ref name was not scanned")
}
})
}
func TestBatchRefusesUnverifiedReportsAndDiagnostics(t *testing.T) {
known := map[string]string{"payload/ref-0": "ref-0"}
for _, tc := range []struct {
code int
raw string
}{
{0, "payload/ref-0\n"}, {20, ""}, {0, "\n"},
{20, "payload/ref-0"}, {20, "payload/ref-0\n\n"},
{20, "payload/ref-unknown\n"}, {20, "../payload/ref-0\n"},
{20, "payload/ref-0\r\n"}, {2, ""},
} {
if _, err := batchFindings(tc.code, []byte(tc.raw), known); err == nil {
t.Fatal("unverified report accepted")
}
}
for _, tc := range []struct {
code int
raw string
}{
{0, ""}, {20, "payload/ref-0\n"}, {20, "payload/ref-0\npayload/ref-0\n"},
} {
if _, err := batchFindings(tc.code, []byte(tc.raw), known); err != nil {
t.Fatal("valid report refused")
}
}
for _, tc := range []struct {
code int
text string
}{
{0, "12:03PM WRN incomplete scan\n"}, {20, "12:03PM WRN incomplete scan\n"},
{20, "12:03PM WRN leaks found: 1\n12:03PM WRN incomplete scan\n"},
{0, "12:03PM WRN leaks found: 1\n"}, {2, ""},
} {
if validateBatchDiagnostics(tc.code, []byte(tc.text)) == nil {
t.Fatal("unverified diagnostic accepted")
}
}
for _, tc := range []struct {
code int
text string
}{
{0, ""}, {20, ""}, {20, "12:03PM WRN leaks found: 1\n"},
} {
if validateBatchDiagnostics(tc.code, []byte(tc.text)) != nil {
t.Fatal("valid diagnostic refused")
}
}
}