package interceptors import ( "net/http" "net/url" "testing" "time" packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1" "github.com/safedep/pmg/analyzer" pmgconfig "github.com/safedep/pmg/config" "github.com/safedep/pmg/proxy" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func setTrustedPackagesForTest(t *testing.T, pkgs []pmgconfig.TrustedPackage) { t.Helper() orig := pmgconfig.Get().Config.TrustedPackages pmgconfig.Get().Config.TrustedPackages = pkgs require.NoError(t, pmgconfig.PreprocessPackageRefs(&pmgconfig.Get().Config), "setTrustedPackagesForTest: preprocess") t.Cleanup(func() { pmgconfig.Get().Config.TrustedPackages = orig assert.NoError(t, pmgconfig.PreprocessPackageRefs(&pmgconfig.Get().Config)) }) } func TestFastAllow_TrustedReturnsAllow(t *testing.T) { setTrustedPackagesForTest(t, []pmgconfig.TrustedPackage{{Purl: "pkg:npm/trusted-pkg"}}) b := &baseRegistryInterceptor{} ctx := makeTestRequestContext("https://registry.npmjs.org/trusted-pkg/-/trusted-pkg-1.0.0.tgz") resp, ok := b.fastAllow(ctx, packagev1.Ecosystem_ECOSYSTEM_NPM, "trusted-pkg", "1.0.0") require.True(t, ok) assert.Equal(t, proxy.ActionAllow, resp.Action) } func TestFastAllow_UntrustedReturnsFalse(t *testing.T) { setTrustedPackagesForTest(t, nil) b := &baseRegistryInterceptor{} ctx := makeTestRequestContext("https://registry.npmjs.org/x/-/x-1.0.0.tgz") resp, ok := b.fastAllow(ctx, packagev1.Ecosystem_ECOSYSTEM_NPM, "x", "1.0.0") assert.False(t, ok) assert.Nil(t, resp) } func TestFastAllow_InsecureReturnsAllow(t *testing.T) { orig := pmgconfig.Get().InsecureInstallation pmgconfig.Get().InsecureInstallation = true t.Cleanup(func() { pmgconfig.Get().InsecureInstallation = orig }) b := &baseRegistryInterceptor{} ctx := makeTestRequestContext("https://registry.npmjs.org/any-pkg/-/any-pkg-1.0.0.tgz") resp, ok := b.fastAllow(ctx, packagev1.Ecosystem_ECOSYSTEM_NPM, "any-pkg", "1.0.0") require.True(t, ok) assert.Equal(t, proxy.ActionAllow, resp.Action) } func TestBaseRegistryInterceptor_HandleAnalysisResult(t *testing.T) { tests := []struct { name string ecosystem packagev1.Ecosystem packageName string packageVersion string analysisResult *analyzer.PackageVersionAnalysisResult userConfirms bool expectedAction proxy.ResponseAction expectedBlockCode int expectedBlockReason proxy.BlockReason }{ { name: "ActionBlock - malicious package", ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, packageName: "malicious-pkg", packageVersion: "1.0.0", analysisResult: &analyzer.PackageVersionAnalysisResult{ Action: analyzer.ActionBlock, Summary: "Contains known malware", ReferenceURL: "https://example.com/malware-report", }, expectedAction: proxy.ActionBlock, expectedBlockCode: http.StatusForbidden, expectedBlockReason: proxy.BlockReasonMalware, }, { name: "ActionConfirm - user confirms installation", ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, packageName: "suspicious-pkg", packageVersion: "2.0.0", analysisResult: &analyzer.PackageVersionAnalysisResult{ Action: analyzer.ActionConfirm, Summary: "Suspicious behavior detected", ReferenceURL: "https://example.com/suspicious-report", }, userConfirms: true, expectedAction: proxy.ActionAllow, expectedBlockCode: 0, expectedBlockReason: proxy.BlockReasonNone, }, { name: "ActionConfirm - user declines installation", ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, packageName: "suspicious-pkg", packageVersion: "2.0.0", analysisResult: &analyzer.PackageVersionAnalysisResult{ Action: analyzer.ActionConfirm, Summary: "Suspicious behavior detected", ReferenceURL: "https://example.com/suspicious-report", }, userConfirms: false, expectedAction: proxy.ActionBlock, expectedBlockCode: http.StatusForbidden, expectedBlockReason: proxy.BlockReasonUserDeclined, }, // Note: Timeout test case is skipped as it would require waiting 5 minutes // The timeout behavior is covered by the implementation but not tested here // to keep tests fast { name: "ActionAllow - safe package", ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, packageName: "safe-pkg", packageVersion: "3.0.0", analysisResult: &analyzer.PackageVersionAnalysisResult{ Action: analyzer.ActionAllow, Summary: "Package is safe", ReferenceURL: "https://example.com/safe-report", }, expectedAction: proxy.ActionAllow, expectedBlockCode: 0, expectedBlockReason: proxy.BlockReasonNone, }, { name: "ActionUnknown - default to allow", ecosystem: packagev1.Ecosystem_ECOSYSTEM_NPM, packageName: "unknown-pkg", packageVersion: "4.0.0", analysisResult: &analyzer.PackageVersionAnalysisResult{ Action: analyzer.ActionUnknown, Summary: "Unknown action", ReferenceURL: "https://example.com/unknown-report", }, expectedAction: proxy.ActionAllow, expectedBlockCode: 0, expectedBlockReason: proxy.BlockReasonNone, }, { name: "ActionBlock - pypi ecosystem", ecosystem: packagev1.Ecosystem_ECOSYSTEM_PYPI, packageName: "malicious-pypi-pkg", packageVersion: "5.0.0", analysisResult: &analyzer.PackageVersionAnalysisResult{ Action: analyzer.ActionBlock, Summary: "Malicious PyPI package", ReferenceURL: "https://example.com/pypi-malware", }, expectedAction: proxy.ActionBlock, expectedBlockCode: http.StatusForbidden, expectedBlockReason: proxy.BlockReasonMalware, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { confirmationChan := make(chan *ConfirmationRequest, 1) base := &baseRegistryInterceptor{ confirmationChan: confirmationChan, } parsedURL, _ := url.Parse("https://registry.npmjs.org/test") ctx := &proxy.RequestContext{ URL: parsedURL, Method: "GET", Headers: make(http.Header), RequestID: "test-request-id", StartTime: time.Now(), Data: make(map[string]interface{}), } if tt.analysisResult.Action == analyzer.ActionConfirm { go func() { req := <-confirmationChan req.ResponseChan <- tt.userConfirms close(req.ResponseChan) }() } response, err := base.handleAnalysisResult( ctx, tt.ecosystem, tt.packageName, tt.packageVersion, tt.analysisResult, ) assert.NoError(t, err) assert.Equal(t, tt.expectedAction, response.Action) assert.Equal(t, tt.expectedBlockCode, response.BlockCode) assert.Equal(t, tt.expectedBlockReason, response.BlockReason) assert.Empty(t, response.BlockMessage) switch tt.expectedBlockReason { case proxy.BlockReasonNone: assert.Nil(t, response.BlockContext) case proxy.BlockReasonMalware, proxy.BlockReasonUserDeclined: require.NotNil(t, response.BlockContext) assert.Equal(t, tt.ecosystem, response.BlockContext.Ecosystem) assert.Equal(t, tt.packageName, response.BlockContext.PackageName) assert.Equal(t, tt.packageVersion, response.BlockContext.PackageVersion) assert.Equal(t, tt.analysisResult.Summary, response.BlockContext.MalwareSummary) assert.Equal(t, tt.analysisResult.ReferenceURL, response.BlockContext.MalwareReferenceURL) default: require.NotNil(t, response.BlockContext) assert.Equal(t, tt.ecosystem, response.BlockContext.Ecosystem) assert.Equal(t, tt.packageName, response.BlockContext.PackageName) assert.Equal(t, tt.packageVersion, response.BlockContext.PackageVersion) } }) } }