Files
pmg/proxy/interceptors/confirmation.go
T
779deeb23d feat: Add Support for Proxy Based Npm Interceptor (#87)
* feat: Add experimental proxy based npm interceptor

* refactor: Analysis cache

* ci: Add E2E for npm proxy

* fix: Handle dry-run in proxy flow

* fix: Handle special case for scope package name

* fix: Misc fixes

* fix: Code review fixes

* fix: Code review fixes

* refactor: Reusable code into base registry interceptor

* Pause npm process during user confirmation (#90)

* pause npm process when prompting user for confirmation

* disable progress bar

* fix logging and close chan on return

* update use of deprecated field

* refactor: Separation of concerns for handling process state

* fix: Safe permission for cert file

* fix: Handle nil check for interaction hook

* fix: Add test for base registry

* Fix goreleaser for windows build (#93)

* introduce platform specific process control

* rename common.go to common_flow.go

* feat: Add support for pause resume on windows

* fix: Code review fixes

* test: Add confirmation handler tests

---------

Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
2026-01-07 13:22:08 +05:30

96 lines
3.6 KiB
Go

package interceptors
import (
packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
"github.com/safedep/dry/log"
"github.com/safedep/pmg/analyzer"
"github.com/safedep/pmg/guard"
)
// ConfirmationRequest represents a request for user confirmation on a suspicious package
type ConfirmationRequest struct {
PackageVersion *packagev1.PackageVersion
AnalysisResult *analyzer.PackageVersionAnalysisResult
// ResponseChan is used to send the user's response back.
ResponseChan chan bool
}
// ConfirmationHook is a set of hooks that can be used to customize the confirmation process.
type ConfirmationHook struct {
// BeforeInteraction is called before the user interaction is started.
BeforeInteraction func([]*analyzer.PackageVersionAnalysisResult) error
// AfterInteraction is called after the user interaction is finished.
AfterInteraction func([]*analyzer.PackageVersionAnalysisResult, bool) error
}
// HandleConfirmationRequests processes confirmation requests sequentially
// This function should be run in a goroutine and will process requests
// from the confirmation channel one at a time, blocking on user input.
//
// The function will exit when the confirmation channel is closed.
func HandleConfirmationRequests(confirmationChan chan *ConfirmationRequest,
interaction guard.PackageManagerGuardInteraction, hooks *ConfirmationHook) {
if hooks == nil {
hooks = &ConfirmationHook{}
}
for req := range confirmationChan {
func() {
// The default response is false ie. user did not confirm the installation.
// The code here falls through and eventually sets this flag to true if user
// confirms the installation.
responseVal := false
// We must make sure to close the response channel to prevent goroutine leaks.
// Idiomatic go suggests that the writer should close the channel.
defer func() {
req.ResponseChan <- responseVal
close(req.ResponseChan)
}()
packageName := req.PackageVersion.GetPackage().GetName()
log.Debugf("Processing confirmation request for package %s", packageName)
// Hook to allow the caller to customize the confirmation process.
// Hook failures are non-fatal and will not break the confirmation process.
if hooks.BeforeInteraction != nil {
if err := hooks.BeforeInteraction([]*analyzer.PackageVersionAnalysisResult{req.AnalysisResult}); err != nil {
log.Errorf("Error before interaction for package %s: %v", packageName, err)
}
}
// Call the user interaction handler to get confirmation
// This blocks waiting for stdin input
confirmed, confirmationErr := interaction.GetConfirmationOnMalware([]*analyzer.PackageVersionAnalysisResult{req.AnalysisResult})
// Must guarantee to call the after interaction hook regardless of the confirmation error.
if hooks.AfterInteraction != nil {
if err := hooks.AfterInteraction([]*analyzer.PackageVersionAnalysisResult{req.AnalysisResult}, confirmed); err != nil {
log.Errorf("Error after interaction for package %s: %v", packageName, err)
}
}
if confirmationErr != nil {
log.Errorf("Error getting confirmation for package %s: %v", packageName, confirmationErr)
return
}
// Set the response value to the user's confirmation
responseVal = confirmed
}()
}
log.Debugf("Confirmation handler exiting (channel closed)")
}
// NewConfirmationRequest creates a new confirmation request with a response channel
func NewConfirmationRequest(pkgVersion *packagev1.PackageVersion, result *analyzer.PackageVersionAnalysisResult) *ConfirmationRequest {
return &ConfirmationRequest{
PackageVersion: pkgVersion,
AnalysisResult: result,
ResponseChan: make(chan bool, 1),
}
}