mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
Add comprehensive event logging system (#82)
* Add comprehensive event logging system with OS-specific location and rotation Features: - Event logging for security-relevant events (malware detection, installations) - OS-specific default log locations (~/.pmg/logs/ on macOS/Linux, %LOCALAPPDATA%\pmg\logs\ on Windows) - Automatic 7-day log rotation with daily log files (YYYYMMDD-pmg.log format) - Support for custom log files via --log flag - Thread-safe JSON logging with zero external dependencies Implementation: - New internal/eventlog package with comprehensive logging functionality - Integration with guard.go to log malware detections and blocks - Integration with main.go for initialization and cleanup - Log file naming: YYYYMMDD-pmg.log (e.g., 20251216-pmg.log) - Fail-safe design - PMG continues if logging fails Event Types: - malware_blocked: Malicious package blocked from installation - malware_confirmed: User proceeded with flagged package - install_allowed: Clean package installation allowed - install_started: Package manager command initiated - error: Error events Testing: - Comprehensive test suite with 6 passing tests - Verified with real malware detection (e.g., @postman/tunnel-agent) - Works with all package managers (npm, pip, etc.) Technical Details: - Thread-safe with mutex protection - JSON format for easy parsing - Automatic cleanup of logs >7 days old - Background cleanup goroutine - Uses only Go standard library (encoding/json, os, path/filepath, sync, time) * Add update command support and improve event logging robustness Features: - Add support for npm/pnpm/bun/yarn update/upgrade/ci commands - These commands now scan packages for malware before updating - Closes security gap where update commands bypassed PMG protection Improvements: - Make event logging more defensive (graceful failure when not initialized) - Add nil check for packageManager in guard to prevent test failures - Add comprehensive tests for update commands Testing: - All 33+ unit tests passing - Integration tests verified with real malware detection - Tested with npm update, npm ci, npm upgrade, pnpm update, yarn upgrade Files changed: - packagemanager/npm.go: Added update/upgrade/ci to InstallCommands - packagemanager/npm_test.go: Added 4 new test cases for update commands - guard/guard.go: Added nil check for packageManager - internal/eventlog/eventlog.go: Made logging more defensive * Address review feedback: use log.Warnf instead of silently failing Replace silent error handling in cleanupOldLogs with log.Warnf to avoid completely swallowing errors when reading log directory. Fixes reviewer feedback from abhisek. * Remove update/upgrade command support, keep logging improvements - Remove update/upgrade/ci commands from InstallCommands for npm, pnpm, bun, yarn - Remove special handling for update/upgrade/ci commands in ParseCommand - Remove update command test cases and restore original test - Preserve logging improvements (nil check in guard.go, defensive check in eventlog.go) All tests passing.
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/safedep/dry/log"
|
||||
"github.com/safedep/pmg/analyzer"
|
||||
"github.com/safedep/pmg/extractor"
|
||||
"github.com/safedep/pmg/internal/eventlog"
|
||||
"github.com/safedep/pmg/internal/ui"
|
||||
"github.com/safedep/pmg/packagemanager"
|
||||
)
|
||||
@@ -79,6 +80,11 @@ func NewPackageManagerGuard(config PackageManagerGuardConfig,
|
||||
|
||||
func (g *packageManagerGuard) Run(ctx context.Context, args []string, parsedCommand *packagemanager.ParsedCommand) error {
|
||||
log.Debugf("Running package manager guard with args: %v", args)
|
||||
|
||||
// Log the installation start
|
||||
if g.packageManager != nil {
|
||||
eventlog.LogInstallStarted(g.packageManager.Name(), args)
|
||||
}
|
||||
|
||||
if g.config.InsecureInstallation {
|
||||
log.Debugf("Bypassing block for unconfirmed malicious packages due to PMG_INSECURE_INSTALLATION")
|
||||
@@ -150,6 +156,7 @@ func (g *packageManagerGuard) Run(ctx context.Context, args []string, parsedComm
|
||||
for _, result := range analysisResults {
|
||||
if result.Action == analyzer.ActionBlock {
|
||||
blockConfig.MalwarePackages = append(blockConfig.MalwarePackages, result)
|
||||
g.logMalwareDetection(result, true)
|
||||
return g.blockInstallation(blockConfig)
|
||||
}
|
||||
|
||||
@@ -167,12 +174,32 @@ func (g *packageManagerGuard) Run(ctx context.Context, args []string, parsedComm
|
||||
if !confirmed {
|
||||
blockConfig.ShowReference = false
|
||||
blockConfig.MalwarePackages = confirmableMalwarePackages
|
||||
for _, pkg := range confirmableMalwarePackages {
|
||||
g.logMalwareDetection(pkg, true)
|
||||
}
|
||||
return g.blockInstallation(blockConfig)
|
||||
}
|
||||
|
||||
// User confirmed installation despite warning
|
||||
for _, pkg := range confirmableMalwarePackages {
|
||||
g.logMalwareDetection(pkg, false)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("No malicious packages found, continuing execution")
|
||||
|
||||
// Log successful installation allowance
|
||||
if len(parsedCommand.InstallTargets) > 0 {
|
||||
for _, target := range parsedCommand.InstallTargets {
|
||||
eventlog.LogInstallAllowed(
|
||||
target.PackageVersion.GetPackage().GetName(),
|
||||
target.PackageVersion.GetVersion(),
|
||||
target.PackageVersion.GetPackage().GetEcosystem().String(),
|
||||
len(packagesToAnalyze),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
g.clearStatus()
|
||||
return g.continueExecution(ctx, parsedCommand)
|
||||
}
|
||||
@@ -388,12 +415,65 @@ func (g *packageManagerGuard) handleManifestInstallation(ctx context.Context, pa
|
||||
if !confirmed {
|
||||
blockConfig.ShowReference = false
|
||||
blockConfig.MalwarePackages = confirmableMalwarePackages
|
||||
for _, pkg := range confirmableMalwarePackages {
|
||||
g.logMalwareDetection(pkg, true)
|
||||
}
|
||||
return g.blockInstallation(blockConfig)
|
||||
}
|
||||
|
||||
// User confirmed installation despite warning
|
||||
for _, pkg := range confirmableMalwarePackages {
|
||||
g.logMalwareDetection(pkg, false)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("No malicious packages found in manifest files, continuing execution")
|
||||
|
||||
// Log successful installation allowance for manifest-based installations
|
||||
if len(packages) > 0 {
|
||||
firstPkg := packages[0]
|
||||
eventlog.LogInstallAllowed(
|
||||
firstPkg.GetPackage().GetName(),
|
||||
firstPkg.GetVersion(),
|
||||
firstPkg.GetPackage().GetEcosystem().String(),
|
||||
len(packagesToAnalyze),
|
||||
)
|
||||
}
|
||||
|
||||
g.clearStatus()
|
||||
return g.continueExecution(ctx, parsedCommand)
|
||||
}
|
||||
|
||||
// logMalwareDetection logs malware detection events
|
||||
func (g *packageManagerGuard) logMalwareDetection(result *analyzer.PackageVersionAnalysisResult, blocked bool) {
|
||||
if result == nil || result.PackageVersion == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pkg := result.PackageVersion.GetPackage()
|
||||
if pkg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
details := map[string]interface{}{
|
||||
"analysis_id": result.AnalysisID,
|
||||
"reference_url": result.ReferenceURL,
|
||||
"summary": result.Summary,
|
||||
}
|
||||
|
||||
if blocked {
|
||||
eventlog.LogMalwareBlocked(
|
||||
pkg.GetName(),
|
||||
result.PackageVersion.GetVersion(),
|
||||
pkg.GetEcosystem().String(),
|
||||
result.Summary,
|
||||
details,
|
||||
)
|
||||
} else {
|
||||
eventlog.LogMalwareConfirmed(
|
||||
pkg.GetName(),
|
||||
result.PackageVersion.GetVersion(),
|
||||
pkg.GetEcosystem().String(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user