Skip to content

Commit a6cb60a

Browse files
authored
Treat suspicious as malicious in paranoid mode (#156)
* paranoid mode blocks suspicious packages * enable sandbox for paranoid flag * rm sandbox enabling for paranoid mode * update docs
1 parent 41e3b9b commit a6cb60a

File tree

4 files changed

+189
-2
lines changed

4 files changed

+189
-2
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,17 @@ This will:
171171
172172
## Usage
173173

174+
<details>
175+
<summary>Paranoid Mode</summary>
176+
177+
Use the `--paranoid` flag to treat suspicous (unverified) packages as malicious packages.
178+
179+
```bash
180+
pmg --paranoid npm install <package-name>
181+
```
182+
183+
</details>
184+
174185
<details>
175186
<summary>Silent Mode</summary>
176187

analyzer/malysis_query.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
1111
malysisv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/services/malysis/v1"
1212
drygrpc "github.com/safedep/dry/adapters/grpc"
13+
"github.com/safedep/pmg/config"
1314
"google.golang.org/grpc"
1415
)
1516

@@ -62,9 +63,15 @@ func (a *malysisQueryAnalyzer) Analyze(ctx context.Context,
6263
Data: res.GetReport(),
6364
}
6465

66+
cfg := config.Get()
6567
// Mark the package version to be confirmed if it is malicious (not confirmed)
6668
if res.GetReport().GetInference().GetIsMalware() {
6769
analysisResult.Action = ActionConfirm
70+
71+
// Treat suspicious package as malicious when `--paranoid` flag is set to true
72+
if cfg.Config.Paranoid {
73+
analysisResult.Action = ActionBlock
74+
}
6875
}
6976

7077
// This is a confirmed malicious package, we must always block it

analyzer/malysis_query_test.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package analyzer
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
malysisv1pb "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/malysis/v1"
8+
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
9+
malysisv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/services/malysis/v1"
10+
"github.com/safedep/pmg/config"
11+
"github.com/stretchr/testify/assert"
12+
"google.golang.org/grpc"
13+
)
14+
15+
// stubMalwareAnalysisServiceClient is a minimal stub implementing the Malysis gRPC client interface,
16+
// returning a preconfigured response for testing.
17+
type stubMalwareAnalysisServiceClient struct {
18+
resp *malysisv1.QueryPackageAnalysisResponse
19+
err error
20+
}
21+
22+
func (s *stubMalwareAnalysisServiceClient) QueryPackageAnalysis(ctx context.Context, req *malysisv1.QueryPackageAnalysisRequest, opts ...grpc.CallOption) (*malysisv1.QueryPackageAnalysisResponse, error) {
23+
return s.resp, s.err
24+
}
25+
26+
// helper to make a basic PackageVersion for tests
27+
func makePkgVersion(name, version string) *packagev1.PackageVersion {
28+
return &packagev1.PackageVersion{
29+
Package: &packagev1.Package{
30+
Name: name,
31+
},
32+
Version: version,
33+
}
34+
}
35+
36+
func TestMalysisQueryAnalyzer_DefaultAllowWhenNotMalicious(t *testing.T) {
37+
resp := &malysisv1.QueryPackageAnalysisResponse{
38+
AnalysisId: "analysis-1",
39+
Report: &malysisv1pb.Report{
40+
Inference: &malysisv1pb.Report_Inference{
41+
IsMalware: false,
42+
Summary: "No indicators of compromise",
43+
},
44+
},
45+
VerificationRecord: &malysisv1pb.VerificationRecord{
46+
IsMalware: false,
47+
},
48+
}
49+
an := &malysisQueryAnalyzer{
50+
client: &stubMalwareAnalysisServiceClient{resp: resp},
51+
}
52+
53+
pv := makePkgVersion("safe-pkg", "1.0.0")
54+
result, err := an.Analyze(context.Background(), pv)
55+
assert.NoError(t, err)
56+
assert.Equal(t, ActionAllow, result.Action)
57+
assert.Equal(t, "analysis-1", result.AnalysisID)
58+
assert.Equal(t, pv, result.PackageVersion)
59+
assert.NotEmpty(t, result.ReferenceURL)
60+
assert.Equal(t, "No indicators of compromise", result.Summary)
61+
}
62+
63+
func TestMalysisQueryAnalyzer_ConfirmOnSuspiciousWhenNotParanoid(t *testing.T) {
64+
// Ensure paranoid is disabled
65+
cfg := config.Get()
66+
origParanoid := cfg.Config.Paranoid
67+
cfg.Config.Paranoid = false
68+
defer func() { cfg.Config.Paranoid = origParanoid }()
69+
70+
// Setup: inference says suspicious/malicious (unverified)
71+
resp := &malysisv1.QueryPackageAnalysisResponse{
72+
AnalysisId: "analysis-2",
73+
Report: &malysisv1pb.Report{
74+
Inference: &malysisv1pb.Report_Inference{
75+
IsMalware: true,
76+
Summary: "Suspicious patterns detected",
77+
},
78+
},
79+
VerificationRecord: &malysisv1pb.VerificationRecord{
80+
IsMalware: false,
81+
},
82+
}
83+
an := &malysisQueryAnalyzer{
84+
client: &stubMalwareAnalysisServiceClient{resp: resp},
85+
}
86+
87+
pv := makePkgVersion("suspicious-pkg", "2.0.0")
88+
result, err := an.Analyze(context.Background(), pv)
89+
assert.NoError(t, err)
90+
assert.Equal(t, ActionConfirm, result.Action)
91+
assert.Equal(t, "analysis-2", result.AnalysisID)
92+
}
93+
94+
func TestMalysisQueryAnalyzer_BlockOnSuspiciousWhenParanoid(t *testing.T) {
95+
// Enable paranoid mode
96+
cfg := config.Get()
97+
origParanoid := cfg.Config.Paranoid
98+
cfg.Config.Paranoid = true
99+
defer func() { cfg.Config.Paranoid = origParanoid }()
100+
101+
// Setup: inference says suspicious/malicious (unverified)
102+
resp := &malysisv1.QueryPackageAnalysisResponse{
103+
AnalysisId: "analysis-3",
104+
Report: &malysisv1pb.Report{
105+
Inference: &malysisv1pb.Report_Inference{
106+
IsMalware: true,
107+
Summary: "Suspicious patterns detected",
108+
},
109+
},
110+
VerificationRecord: &malysisv1pb.VerificationRecord{
111+
IsMalware: false,
112+
},
113+
}
114+
an := &malysisQueryAnalyzer{
115+
client: &stubMalwareAnalysisServiceClient{resp: resp},
116+
}
117+
118+
pv := makePkgVersion("suspicious-pkg", "3.0.0")
119+
result, err := an.Analyze(context.Background(), pv)
120+
assert.NoError(t, err)
121+
assert.Equal(t, ActionBlock, result.Action, "Paranoid mode should block suspicious packages")
122+
}
123+
124+
func TestMalysisQueryAnalyzer_AlwaysBlockOnVerifiedMalware(t *testing.T) {
125+
// Paranoid on/off should not matter
126+
cfg := config.Get()
127+
origParanoid := cfg.Config.Paranoid
128+
cfg.Config.Paranoid = false
129+
defer func() { cfg.Config.Paranoid = origParanoid }()
130+
131+
resp := &malysisv1.QueryPackageAnalysisResponse{
132+
AnalysisId: "analysis-4",
133+
Report: &malysisv1pb.Report{
134+
Inference: &malysisv1pb.Report_Inference{
135+
IsMalware: false,
136+
Summary: "Inference not malicious, but verification is",
137+
},
138+
},
139+
VerificationRecord: &malysisv1pb.VerificationRecord{
140+
IsMalware: true,
141+
},
142+
}
143+
an := &malysisQueryAnalyzer{
144+
client: &stubMalwareAnalysisServiceClient{resp: resp},
145+
}
146+
147+
pv := makePkgVersion("verified-malware", "9.9.9")
148+
result, err := an.Analyze(context.Background(), pv)
149+
assert.NoError(t, err)
150+
assert.Equal(t, ActionBlock, result.Action, "Verified malware must be blocked always")
151+
}
152+
153+
// Implement the full client interface surface expected by malysisv1grpc.MalwareAnalysisServiceClient
154+
func (s *stubMalwareAnalysisServiceClient) AnalyzePackage(ctx context.Context, req *malysisv1.AnalyzePackageRequest, opts ...grpc.CallOption) (*malysisv1.AnalyzePackageResponse, error) {
155+
// Not used in these tests; return a nil response with no error
156+
return nil, nil
157+
}
158+
func (s *stubMalwareAnalysisServiceClient) GetAnalysisReport(ctx context.Context, req *malysisv1.GetAnalysisReportRequest, opts ...grpc.CallOption) (*malysisv1.GetAnalysisReportResponse, error) {
159+
// Not used in these tests
160+
return nil, nil
161+
}
162+
func (s *stubMalwareAnalysisServiceClient) InternalAnalyzePackage(ctx context.Context, req *malysisv1.InternalAnalyzePackageRequest, opts ...grpc.CallOption) (*malysisv1.InternalAnalyzePackageResponse, error) {
163+
// Not used in these tests
164+
return nil, nil
165+
}
166+
func (s *stubMalwareAnalysisServiceClient) ListPackageAnalysisRecords(ctx context.Context, req *malysisv1.ListPackageAnalysisRecordsRequest, opts ...grpc.CallOption) (*malysisv1.ListPackageAnalysisRecordsResponse, error) {
167+
// Not used in these tests
168+
return nil, nil
169+
}

config/config.template.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ transitive_depth: 5
1010
# Include dev dependencies in the dependency graph. Default is false.
1111
include_dev_dependencies: false
1212

13-
# Enable paranoid mode. In paranoid mode, PMG will use SafeDep Cloud credentials
14-
# to scan unknown packages for malware. This is slow and should be used with caution.
13+
# Enable paranoid mode. In paranoid mode, PMG will treat suspicious packages
14+
# as malicious packages
1515
paranoid: false
1616

1717
# Skip event logging. Default is false.

0 commit comments

Comments
 (0)