package security import ( "os" "strings" "testing" "time" "github.com/jfraeys/fetch_ml/internal/manifest" ) // TestManifestFilenameNonce verifies that manifest filenames include a cryptographic nonce // to prevent information disclosure in multi-tenant environments where predictable // filenames could be enumerated. func TestManifestFilenameNonce(t *testing.T) { t.Run("FilenameIncludesNonce", func(t *testing.T) { // Generate multiple filenames and verify they are unique filenames := make(map[string]bool) for i := 0; i < 10; i++ { filename, err := manifest.GenerateManifestFilename() if err != nil { t.Fatalf("GenerateManifestFilename failed: %v", err) } // Verify format: run_manifest_.json if !strings.HasPrefix(filename, "run_manifest_") { t.Errorf("filename %q missing required prefix 'run_manifest_'", filename) } if !strings.HasSuffix(filename, ".json") { t.Errorf("filename %q missing required suffix '.json'", filename) } // Extract and verify nonce nonce := manifest.ParseManifestFilename(filename) if nonce == "" { t.Errorf("failed to parse nonce from filename %q", filename) } if len(nonce) != 32 { t.Errorf("nonce length = %d, want 32 hex chars", len(nonce)) } // Verify uniqueness (no collisions in 10 generations) if filenames[filename] { t.Errorf("duplicate filename generated: %q", filename) } filenames[filename] = true } }) t.Run("ManifestWrittenWithNonce", func(t *testing.T) { // Generate a nonce for the manifest nonce, err := manifest.GenerateManifestNonce() if err != nil { t.Fatalf("GenerateManifestNonce failed: %v", err) } // Create a manifest with nonce in Environment created := time.Now().UTC() m := manifest.NewRunManifest("run-test-nonce", "task-nonce", "job-nonce", created) m.CommitID = "deadbeef" m.Environment = &manifest.ExecutionEnvironment{ ConfigHash: "abc123", ManifestNonce: nonce, } dir := t.TempDir() if err := m.WriteToDir(dir); err != nil { t.Fatalf("WriteToDir failed: %v", err) } // List files in directory entries, err := os.ReadDir(dir) if err != nil { t.Fatalf("ReadDir failed: %v", err) } // Find manifest file var manifestFile string for _, entry := range entries { if strings.HasPrefix(entry.Name(), "run_manifest_") && strings.HasSuffix(entry.Name(), ".json") { manifestFile = entry.Name() break } } if manifestFile == "" { t.Fatal("no manifest file found with expected naming pattern") } // Verify nonce is present in filename parsedNonce := manifest.ParseManifestFilename(manifestFile) if parsedNonce == "" { t.Errorf("manifest file %q does not contain a valid nonce", manifestFile) } if parsedNonce != nonce { t.Errorf("nonce mismatch: got %q, want %q", parsedNonce, nonce) } if len(parsedNonce) != 32 { t.Errorf("nonce length = %d, want 32 hex chars", len(parsedNonce)) } // Verify file can be loaded back loaded, err := manifest.LoadFromDir(dir) if err != nil { t.Fatalf("LoadFromDir failed: %v", err) } if loaded.RunID != m.RunID { t.Errorf("loaded RunID = %q, want %q", loaded.RunID, m.RunID) } }) t.Run("NonceUniqueness", func(t *testing.T) { // Generate many nonces to check for collisions (statistical test) nonces := make(map[string]int) iterations := 100 for i := 0; i < iterations; i++ { nonce, err := manifest.GenerateManifestNonce() if err != nil { t.Fatalf("GenerateManifestNonce failed: %v", err) } nonces[nonce]++ } // Check for any collisions collisions := 0 for nonce, count := range nonces { if count > 1 { t.Errorf("nonce collision detected: %q appeared %d times", nonce, count) collisions++ } } if collisions > 0 { t.Fatalf("detected %d nonce collisions in %d iterations", collisions, iterations) } t.Logf("Generated %d unique nonces with no collisions", iterations) }) }