Files
pmg/main.go
T
EapolSniperandGitHub 698bd3dd13 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.
2025-12-31 16:05:31 +05:30

131 lines
4.0 KiB
Go

package main
import (
"fmt"
"os"
"strconv"
"github.com/safedep/dry/log"
"github.com/safedep/pmg/cmd/npm"
"github.com/safedep/pmg/cmd/pypi"
"github.com/safedep/pmg/cmd/setup"
"github.com/safedep/pmg/cmd/version"
"github.com/safedep/pmg/config"
"github.com/safedep/pmg/internal/analytics"
"github.com/safedep/pmg/internal/eventlog"
"github.com/safedep/pmg/internal/ui"
appVersion "github.com/safedep/pmg/internal/version"
"github.com/spf13/cobra"
)
var (
debug bool
silent bool
verbose bool
logFile string
globalConfig config.Config
)
func main() {
cmd := &cobra.Command{
Use: "pmg",
TraverseChildren: true,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// Always set this first because we will override the log
// level if debug or verbose is set
if logFile != "" {
os.Setenv("APP_LOG_FILE", logFile)
os.Setenv("APP_LOG_LEVEL", "info")
}
// Set the log level when debug is enabled
if debug {
os.Setenv("APP_LOG_LEVEL", "debug")
}
// Skip stdout logging when debugging is not enabled
if !debug {
os.Setenv("APP_LOG_SKIP_STDOUT_LOGGER", "true")
}
if silent && verbose {
fmt.Println("pmg: --silent and --verbose cannot be used together")
os.Exit(1)
}
if silent {
ui.SetVerbosityLevel(ui.VerbosityLevelSilent)
} else if verbose {
ui.SetVerbosityLevel(ui.VerbosityLevelVerbose)
}
// Check for PMG_INSECURE_INSTALLATION environment variable
if val := os.Getenv("PMG_INSECURE_INSTALLATION"); val != "" {
if boolVal, err := strconv.ParseBool(val); err == nil {
globalConfig.InsecureInstallation = boolVal
}
}
log.InitZapLogger("pmg", "cli")
// Initialize event logging (silently fail if it can't be initialized)
if logFile != "" {
// If a custom log file is specified, use it for event logging too
_ = eventlog.InitializeWithFile(logFile)
} else {
// Otherwise use the default log directory
_ = eventlog.Initialize()
}
cmd.SetContext(globalConfig.Inject(cmd.Context()))
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return cmd.Help()
}
return fmt.Errorf("pmg: %s is not a valid command", args[0])
},
}
cmd.PersistentFlags().StringVar(&logFile, "log", "", "Log file to write to")
cmd.PersistentFlags().BoolVar(&silent, "silent", false, "Silent mode for invisible experience")
cmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "Verbose mode for more information")
cmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug logging (defaults to stdout)")
cmd.PersistentFlags().BoolVar(&globalConfig.Transitive, "transitive", true, "Resolve transitive dependencies")
cmd.PersistentFlags().IntVar(&globalConfig.TransitiveDepth, "transitive-depth", 5,
"Maximum depth of transitive dependencies to resolve")
cmd.PersistentFlags().BoolVar(&globalConfig.IncludeDevDependencies, "include-dev-dependencies", false,
"Include dev dependencies in the dependency graph (slows down resolution)")
cmd.PersistentFlags().BoolVar(&globalConfig.DryRun, "dry-run", false, "Dry run skips execution of package manager")
cmd.PersistentFlags().BoolVar(&globalConfig.Paranoid, "paranoid", false, "Perform active scanning of unknown packages (slow)")
cmd.AddCommand(npm.NewNpmCommand())
cmd.AddCommand(npm.NewPnpmCommand())
cmd.AddCommand(npm.NewBunCommand())
cmd.AddCommand(npm.NewYarnCommand())
cmd.AddCommand(pypi.NewPipCommand())
cmd.AddCommand(pypi.NewPip3Command())
cmd.AddCommand(pypi.NewUvCommand())
cmd.AddCommand(pypi.NewPoetryCommand())
cmd.AddCommand(version.NewVersionCommand())
cmd.AddCommand(setup.NewSetupCommand())
cmd.AddCommand(setup.NewRemoveCommand())
// Print Banner on --help / -h
cmd.SetHelpFunc(func(command *cobra.Command, args []string) {
fmt.Print(ui.GeneratePMGBanner(appVersion.Version, appVersion.Commit))
fmt.Println(command.UsageString())
})
defer analytics.Close()
defer eventlog.Close()
analytics.TrackCommandRun()
analytics.TrackCI()
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}