@@ -51,8 +51,10 @@ type Smith struct {
51
51
timeElapsedHandler func (t time.Time ) time.Duration
52
52
notifiedInfringements * lru.Cache
53
53
54
- detector detector.ProcessDetector
55
- classifier classifier.ProcessClassifier
54
+ detector detector.ProcessDetector
55
+ classifier classifier.ProcessClassifier
56
+ fileDetector detector.FileDetector
57
+ fileClassifier classifier.FileClassifier
56
58
}
57
59
58
60
// NewAgentSmith creates a new agent smith
@@ -135,6 +137,32 @@ func NewAgentSmith(cfg config.Config) (*Smith, error) {
135
137
return nil , err
136
138
}
137
139
140
+ // Initialize filesystem detection if enabled
141
+ var filesystemDetec detector.FileDetector
142
+ var filesystemClass classifier.FileClassifier
143
+ if cfg .FilesystemScanning != nil && cfg .FilesystemScanning .Enabled {
144
+ // Create filesystem detector config
145
+ fsConfig := detector.FileScanningConfig {
146
+ Enabled : cfg .FilesystemScanning .Enabled ,
147
+ ScanInterval : cfg .FilesystemScanning .ScanInterval .Duration ,
148
+ MaxFileSize : cfg .FilesystemScanning .MaxFileSize ,
149
+ WorkingArea : cfg .FilesystemScanning .WorkingArea ,
150
+ }
151
+
152
+ // Create independent filesystem classifier (no dependency on process classifier)
153
+ filesystemClass , err = cfg .Blocklists .FileClassifier ()
154
+ if err != nil {
155
+ log .WithError (err ).Error ("failed to create filesystem classifier" )
156
+ } else {
157
+ filesystemDetec , err = detector .NewfileDetector (fsConfig , filesystemClass )
158
+ if err != nil {
159
+ log .WithError (err ).Error ("failed to create filesystem detector" )
160
+ } else {
161
+ log .Info ("Filesystem detector created successfully with independent classifier" )
162
+ }
163
+ }
164
+ }
165
+
138
166
m := newAgentMetrics ()
139
167
res := & Smith {
140
168
EnforcementRules : map [string ]config.EnforcementRules {
@@ -150,8 +178,10 @@ func NewAgentSmith(cfg config.Config) (*Smith, error) {
150
178
151
179
wsman : wsman ,
152
180
153
- detector : detec ,
154
- classifier : class ,
181
+ detector : detec ,
182
+ classifier : class ,
183
+ fileDetector : filesystemDetec ,
184
+ fileClassifier : filesystemClass ,
155
185
156
186
notifiedInfringements : lru .New (notificationCacheSize ),
157
187
metrics : m ,
@@ -227,17 +257,34 @@ type classifiedProcess struct {
227
257
Err error
228
258
}
229
259
260
+ type classifiedFile struct {
261
+ F detector.File
262
+ C * classifier.Classification
263
+ Err error
264
+ }
265
+
230
266
// Start gets a stream of Infringements from Run and executes a callback on them to apply a Penalty
231
267
func (agent * Smith ) Start (ctx context.Context , callback func (InfringingWorkspace , []config.PenaltyKind )) {
232
268
ps , err := agent .detector .DiscoverProcesses (ctx )
233
269
if err != nil {
234
270
log .WithError (err ).Fatal ("cannot start process detector" )
235
271
}
236
272
273
+ // Start filesystem detection if enabled
274
+ var fs <- chan detector.File
275
+ if agent .fileDetector != nil {
276
+ fs , err = agent .fileDetector .DiscoverFiles (ctx )
277
+ if err != nil {
278
+ log .WithError (err ).Warn ("cannot start filesystem detector" )
279
+ }
280
+ }
281
+
237
282
var (
238
283
wg sync.WaitGroup
239
284
cli = make (chan detector.Process , 500 )
240
285
clo = make (chan classifiedProcess , 50 )
286
+ fli = make (chan detector.File , 100 )
287
+ flo = make (chan classifiedFile , 25 )
241
288
)
242
289
agent .metrics .RegisterClassificationQueues (cli , clo )
243
290
@@ -268,6 +315,25 @@ func (agent *Smith) Start(ctx context.Context, callback func(InfringingWorkspace
268
315
}()
269
316
}
270
317
318
+ // Filesystem classification workers (fewer than process workers)
319
+ if agent .fileClassifier != nil {
320
+ for i := 0 ; i < 5 ; i ++ {
321
+ wg .Add (1 )
322
+ go func () {
323
+ defer wg .Done ()
324
+ for file := range fli {
325
+ class , err := agent .fileClassifier .MatchesFile (file .Path )
326
+ if err == nil && class .Level == classifier .LevelNoMatch {
327
+ log .Infof ("File classification: no match - %s" , file .Path )
328
+ continue
329
+ }
330
+ log .Infof ("File classification result: %s (level: %s, err: %v)" , file .Path , class .Level , err )
331
+ flo <- classifiedFile {F : file , C : class , Err : err }
332
+ }
333
+ }()
334
+ }
335
+ }
336
+
271
337
defer log .Info ("agent smith main loop ended" )
272
338
273
339
// We want to fill the classifier in a Go routine seaparete from using the classification
@@ -288,6 +354,15 @@ func (agent *Smith) Start(ctx context.Context, callback func(InfringingWorkspace
288
354
// we're overfilling the classifier worker
289
355
agent .metrics .classificationBackpressureInDrop .Inc ()
290
356
}
357
+ case file , ok := <- fs :
358
+ if ! ok {
359
+ continue
360
+ }
361
+ select {
362
+ case fli <- file :
363
+ default :
364
+ // filesystem queue full, skip this file
365
+ }
291
366
}
292
367
}
293
368
}()
@@ -319,6 +394,32 @@ func (agent *Smith) Start(ctx context.Context, callback func(InfringingWorkspace
319
394
},
320
395
},
321
396
})
397
+ case fileClass := <- flo :
398
+ log .Infof ("Received classified file from flo channel" )
399
+ file , cl , err := fileClass .F , fileClass .C , fileClass .Err
400
+ if err != nil {
401
+ log .WithError (err ).WithFields (log .OWI (file .Workspace .OwnerID , file .Workspace .WorkspaceID , file .Workspace .InstanceID )).WithField ("path" , file .Path ).Error ("cannot classify filesystem file" )
402
+ continue
403
+ }
404
+
405
+ log .WithField ("path" , file .Path ).WithField ("severity" , cl .Level ).WithField ("message" , cl .Message ).
406
+ WithFields (log .OWI (file .Workspace .OwnerID , file .Workspace .WorkspaceID , file .Workspace .InstanceID )).
407
+ Info ("filesystem signature detected" )
408
+
409
+ _ , _ = agent .Penalize (InfringingWorkspace {
410
+ SupervisorPID : file .Workspace .PID ,
411
+ Owner : file .Workspace .OwnerID ,
412
+ InstanceID : file .Workspace .InstanceID ,
413
+ WorkspaceID : file .Workspace .WorkspaceID ,
414
+ GitRemoteURL : []string {file .Workspace .GitURL },
415
+ Infringements : []Infringement {
416
+ {
417
+ Kind : config .GradeKind (config .InfringementExec , common .Severity (cl .Level )), // Reuse exec for now
418
+ Description : fmt .Sprintf ("filesystem signature: %s" , cl .Message ),
419
+ CommandLine : []string {file .Path }, // Use file path as "command"
420
+ },
421
+ },
422
+ })
322
423
}
323
424
}
324
425
}
0 commit comments