a23eb6b92c
Pure whitespace cleanup surfaced when 'make check' ran gofmt over the tree. Mostly struct-field column alignment in internal/safety/banner.go (SessionInfo) and the var(...) flag block in cmd/gnoma/main.go after --dangerously-allow-anywhere was added without realignment. Verified zero substantive changes via 'git diff --ignore-all-space --ignore-blank-lines'.
158 lines
4.0 KiB
Go
158 lines
4.0 KiB
Go
package safety
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"testing"
|
|
)
|
|
|
|
func TestScanCWDForSensitive_Matches(t *testing.T) {
|
|
dir := t.TempDir()
|
|
// Sensitive files we expect to flag.
|
|
sensitive := []string{
|
|
".env",
|
|
".env.local",
|
|
"id_rsa",
|
|
"private.pem",
|
|
"aws_credentials",
|
|
".netrc",
|
|
"vault.kdbx",
|
|
}
|
|
// Non-sensitive control files.
|
|
control := []string{
|
|
".envrc", // direnv config, not a credential
|
|
"main.go",
|
|
"README.md",
|
|
"secret_handler.go", // source code, not data
|
|
}
|
|
for _, f := range sensitive {
|
|
if err := os.WriteFile(filepath.Join(dir, f), []byte("x"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, f := range control {
|
|
if err := os.WriteFile(filepath.Join(dir, f), []byte("x"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// Sensitive directory.
|
|
if err := os.MkdirAll(filepath.Join(dir, ".ssh"), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
matches := ScanCWDForSensitive(dir)
|
|
|
|
wantNames := append([]string{}, sensitive...)
|
|
wantNames = append(wantNames, ".ssh")
|
|
sort.Strings(wantNames)
|
|
|
|
gotNames := make([]string, 0, len(matches))
|
|
for _, m := range matches {
|
|
gotNames = append(gotNames, filepath.Base(m.Path))
|
|
}
|
|
sort.Strings(gotNames)
|
|
|
|
if len(gotNames) != len(wantNames) {
|
|
t.Errorf("matched %d files (%v), want %d (%v)", len(gotNames), gotNames, len(wantNames), wantNames)
|
|
}
|
|
for i, n := range wantNames {
|
|
if i >= len(gotNames) || gotNames[i] != n {
|
|
t.Errorf("match[%d] = %q, want %q (got=%v want=%v)", i, gotNames[i], n, gotNames, wantNames)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScanCWDForSensitive_EmptyDir(t *testing.T) {
|
|
dir := t.TempDir()
|
|
matches := ScanCWDForSensitive(dir)
|
|
if len(matches) != 0 {
|
|
t.Errorf("empty dir matched %v, want none", matches)
|
|
}
|
|
}
|
|
|
|
func TestScanCWDForSensitive_PrecisionNoFalsePositives(t *testing.T) {
|
|
dir := t.TempDir()
|
|
// Files that look credential-y but conventionally hold no
|
|
// secrets — must NOT be flagged.
|
|
control := []string{
|
|
".envrc", // direnv config
|
|
"secret_handler.go", // source code
|
|
".env.example", // template
|
|
".env.sample", // template
|
|
".env.template", // template
|
|
".env.dist", // template
|
|
".env.default", // template
|
|
"env.local.example", // template
|
|
}
|
|
for _, name := range control {
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte("x"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
matches := ScanCWDForSensitive(dir)
|
|
if len(matches) != 0 {
|
|
names := make([]string, 0, len(matches))
|
|
for _, m := range matches {
|
|
names = append(names, filepath.Base(m.Path))
|
|
}
|
|
t.Errorf("precision regression: none of %v should flag, got %v", control, names)
|
|
}
|
|
}
|
|
|
|
func TestScanCWDForSensitive_RealEnvFilesStillMatch(t *testing.T) {
|
|
dir := t.TempDir()
|
|
// Real env files (non-template) must still be flagged.
|
|
real := []string{
|
|
".env",
|
|
".env.local",
|
|
".env.production",
|
|
".env.staging",
|
|
"env.local",
|
|
"env.local.production",
|
|
}
|
|
for _, name := range real {
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte("API_KEY=secret"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
matches := ScanCWDForSensitive(dir)
|
|
if len(matches) != len(real) {
|
|
got := make([]string, 0, len(matches))
|
|
for _, m := range matches {
|
|
got = append(got, filepath.Base(m.Path))
|
|
}
|
|
t.Errorf("expected %d real env files flagged, got %d (%v)", len(real), len(matches), got)
|
|
}
|
|
}
|
|
|
|
func TestScanCWDForSensitive_BoundedScan(t *testing.T) {
|
|
dir := t.TempDir()
|
|
// Populate just over the scan limit. The function should not panic
|
|
// or hang. Result count is at most scanLimit (matches may be 0 if
|
|
// the entries beyond the cap happen to be sensitive — that's OK,
|
|
// the bound is a safety knob, not a correctness one).
|
|
for i := 0; i < scanLimit+10; i++ {
|
|
if err := os.WriteFile(filepath.Join(dir, "file"+itoa(i)), []byte("x"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
_ = ScanCWDForSensitive(dir) // mustn't panic
|
|
}
|
|
|
|
// itoa avoids importing strconv just for one use.
|
|
func itoa(n int) string {
|
|
if n == 0 {
|
|
return "0"
|
|
}
|
|
var buf [20]byte
|
|
i := len(buf)
|
|
for n > 0 {
|
|
i--
|
|
buf[i] = byte('0' + n%10)
|
|
n /= 10
|
|
}
|
|
return string(buf[i:])
|
|
}
|