191 lines
5.5 KiB
Go
191 lines
5.5 KiB
Go
package output
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestSanitizeRedactsPasswordTokenAndKeyFields(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := Sanitize("DB_PASSWORD=hunter2\naccess_token: abc123\napi-key = quoted-value\nplain=safe\n", nil)
|
|
want := "DB_PASSWORD=[REDACTED]\naccess_token: [REDACTED]\napi-key = [REDACTED]\nplain=safe\n"
|
|
if got != want {
|
|
t.Errorf("Sanitize() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestSanitizeRedactsSecretFileContents(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
secretFile := filepath.Join(physicalTempDir(t), "provider-token")
|
|
if err := os.WriteFile(secretFile, []byte("top-secret-value\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
secrets, err := SecretValuesFromFiles([]string{secretFile})
|
|
if err != nil {
|
|
t.Fatalf("SecretValuesFromFiles() error = %v", err)
|
|
}
|
|
got := Sanitize("request failed for top-secret-value", secrets)
|
|
if got != "request failed for [REDACTED]" {
|
|
t.Errorf("Sanitize() = %q, want redacted secret", got)
|
|
}
|
|
}
|
|
|
|
func TestSecretValuesFromFilesRedactsNestedJSONScalars(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
secretFile := filepath.Join(physicalTempDir(t), "pi-auth.json")
|
|
contents := `{
|
|
"providers": {
|
|
"dummy-provider": {
|
|
"auth": {
|
|
"key": "dummy-canary-json-key",
|
|
"tokens": {
|
|
"access": "dummy-canary-json-access",
|
|
"refresh": "dummy-canary-json-refresh"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
if err := os.WriteFile(secretFile, []byte(contents), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
secrets, err := SecretValuesFromFiles([]string{secretFile})
|
|
if err != nil {
|
|
t.Fatalf("SecretValuesFromFiles() error = %v", err)
|
|
}
|
|
got := Sanitize(
|
|
"unlabelled dummy-canary-json-key dummy-canary-json-access dummy-canary-json-refresh",
|
|
secrets,
|
|
)
|
|
for _, canary := range []string{
|
|
"dummy-canary-json-key",
|
|
"dummy-canary-json-access",
|
|
"dummy-canary-json-refresh",
|
|
} {
|
|
if strings.Contains(got, canary) {
|
|
t.Fatalf("Sanitize() exposed nested JSON scalar %q: %q", canary, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSecretValuesFromFilesRedactsEveryDotenvBundleValue(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
secretFile := filepath.Join(physicalTempDir(t), "thothii.secrets")
|
|
contents := "MODEL_API_KEY=dummy-canary-bundle-model\n" +
|
|
"DWH_PASSWORD='dummy-canary-bundle-dwh'\n" +
|
|
"SESSION_TOKEN=\"dummy-canary-bundle-session\"\n"
|
|
if err := os.WriteFile(secretFile, []byte(contents), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
secrets, err := SecretValuesFromFiles([]string{secretFile})
|
|
if err != nil {
|
|
t.Fatalf("SecretValuesFromFiles() error = %v", err)
|
|
}
|
|
got := Sanitize(
|
|
"unlabelled dummy-canary-bundle-model dummy-canary-bundle-dwh dummy-canary-bundle-session",
|
|
secrets,
|
|
)
|
|
for _, canary := range []string{
|
|
"dummy-canary-bundle-model",
|
|
"dummy-canary-bundle-dwh",
|
|
"dummy-canary-bundle-session",
|
|
} {
|
|
if strings.Contains(got, canary) {
|
|
t.Fatalf("Sanitize() exposed dotenv bundle scalar %q: %q", canary, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSecretValuesFromFilesAcceptsOpenSSHPrivateKey(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
secretFile := filepath.Join(physicalTempDir(t), "git-ssh-key")
|
|
contents := "-----BEGIN OPENSSH PRIVATE KEY-----\n" +
|
|
"ZmFrZS1rZXktcGF5bG9hZA==\n" +
|
|
"-----END OPENSSH PRIVATE KEY-----\n"
|
|
if err := os.WriteFile(secretFile, []byte(contents), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
secrets, err := SecretValuesFromFiles([]string{secretFile})
|
|
if err != nil {
|
|
t.Fatalf("SecretValuesFromFiles() error = %v, want OpenSSH key accepted", err)
|
|
}
|
|
if len(secrets) == 0 {
|
|
t.Fatal("SecretValuesFromFiles() returned no values for OpenSSH key")
|
|
}
|
|
}
|
|
|
|
func TestSanitizeRecognizesQuotedCredentialKeys(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got := Sanitize(`{"key":"dummy-canary-quoted-key","safe":"visible"}`, nil)
|
|
if strings.Contains(got, "dummy-canary-quoted-key") || !strings.Contains(got, `"safe":"visible"`) {
|
|
t.Fatalf("Sanitize() = %q, want only the quoted credential field redacted", got)
|
|
}
|
|
}
|
|
|
|
func TestSecretValuesFromFilesRejectsMalformedJSONAndExcessiveScalars(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("malformed", func(t *testing.T) {
|
|
secretFile := filepath.Join(physicalTempDir(t), "malformed-auth.json")
|
|
if err := os.WriteFile(secretFile, []byte(`{"auth":{"key":"dummy-canary-malformed"}`), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := SecretValuesFromFiles([]string{secretFile}); err == nil {
|
|
t.Fatal("SecretValuesFromFiles() error = nil, want malformed-JSON failure")
|
|
}
|
|
})
|
|
|
|
t.Run("scalar bound", func(t *testing.T) {
|
|
secretFile := filepath.Join(physicalTempDir(t), "many-auth-values.json")
|
|
values := make([]string, 1025)
|
|
for index := range values {
|
|
values[index] = `"dummy-canary-value"`
|
|
}
|
|
if err := os.WriteFile(secretFile, []byte("["+strings.Join(values, ",")+"]"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := SecretValuesFromFiles([]string{secretFile}); err == nil {
|
|
t.Fatal("SecretValuesFromFiles() error = nil, want scalar-count failure")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSecretValuesFromFilesRejectsOversizedFiles(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
secretFile := filepath.Join(physicalTempDir(t), "oversized-token")
|
|
if err := os.WriteFile(secretFile, make([]byte, 64*1024+1), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := SecretValuesFromFiles([]string{secretFile}); err == nil {
|
|
t.Fatal("SecretValuesFromFiles() error = nil, want oversized-file error")
|
|
}
|
|
}
|
|
|
|
func physicalTempDir(t *testing.T) string {
|
|
t.Helper()
|
|
root, err := filepath.EvalSymlinks(os.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
directory, err := os.MkdirTemp(root, "tht-output-test-")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = os.RemoveAll(directory) })
|
|
return directory
|
|
}
|