fix: Suppress non-PMG error from Critical UI Error (#311)

* fix: Suppress non-PMG error from Critical UI Error

* fix: Code review fixes

---------

Co-authored-by: Sahil Bansal <bansalsahil315@gmail.com>
This commit is contained in:
Abhisek Datta
2026-05-29 14:49:53 +00:00
committed by GitHub
co-authored by Sahil Bansal
parent db1a5e58b3
commit 5018f8e2ff
25 changed files with 508 additions and 113 deletions
+10 -42
View File
@@ -1,54 +1,22 @@
package executor
import (
"fmt"
"github.com/safedep/dry/log"
"github.com/safedep/dry/usefulerror"
"github.com/safedep/pmg/config"
"github.com/safedep/pmg/errcodes"
"github.com/safedep/pmg/sandbox"
)
// WrapCommandExecutionError converts a package manager execution error into a
// user-facing error. It never attributes the failure to the sandbox: causation
// cannot be inferred from EPERM/EACCES returns alone, and a security tool
// should not make best-effort claims. Any observed sandbox denials are
// persisted to the violation cache for forensic review via
// `pmg sandbox violations list` and `pmg sandbox explain`.
func WrapCommandExecutionError(err error, result *sandbox.ExecutionResult, exitCode int) error {
if err == nil {
return nil
}
observed := observeAndPersistViolations(result, err)
humanError := "Failed to execute package manager command"
if exitCode >= 0 {
humanError = fmt.Sprintf("Package manager command exited with code: %d", exitCode)
}
help := "Check the package manager command and its arguments"
builder := usefulerror.NewUsefulError().
WithCode(errcodes.PackageManagerExecutionFailed).
WithHumanError(humanError).
WithHelp(help)
if observed > 0 {
builder = builder.WithAdditionalHelp(fmt.Sprintf(
"Sandbox observed %d denied operation(s) during this run. Run `pmg sandbox violations list` to investigate.",
observed,
))
}
return builder.Wrap(err)
}
// observeAndPersistViolations collects any sandbox violation report associated
// with the run and writes it to the violation cache. Returns the number of
// violations observed. Failures are logged and swallowed; observability MUST
// ObserveViolations collects any sandbox violation report associated with the
// run, persists it to the violation cache for forensic review (via
// `pmg sandbox violations list` / `pmg sandbox explain`), and returns the number
// of violations observed. Failures are logged and swallowed; observability MUST
// NOT affect command exit.
func observeAndPersistViolations(result *sandbox.ExecutionResult, runErr error) int {
//
// This is the sandbox package's only stake in command-failure handling. It
// deliberately does not classify or shape the failure: causation cannot be
// inferred from EPERM/EACCES returns alone, so attribution is left to the
// execution layer (see internal/runner classify).
func ObserveViolations(result *sandbox.ExecutionResult, runErr error) int {
if result == nil {
return 0
}
+6 -24
View File
@@ -6,11 +6,8 @@ import (
"os/exec"
"testing"
"github.com/safedep/dry/usefulerror"
"github.com/safedep/pmg/errcodes"
"github.com/safedep/pmg/sandbox"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type fakeViolationSandbox struct {
@@ -37,10 +34,7 @@ func (f *fakeViolationSandbox) BestEffortViolation(error) (*sandbox.ViolationRep
return f.report, nil
}
// WrapCommandExecutionError must never claim the sandbox blocked a command.
// Even when violations were observed, the user-facing error stays the package
// manager's native exit; a neutral breadcrumb points at the forensic command.
func TestWrapCommandExecutionErrorDoesNotAttributeFailureToSandbox(t *testing.T) {
func TestObserveViolationsCountsObservedViolations(t *testing.T) {
result := sandbox.NewExecutionResult(sandbox.WithExecutionResultSandbox(&fakeViolationSandbox{
report: &sandbox.ViolationReport{
SandboxName: sandbox.DriverSeatbelt,
@@ -58,29 +52,17 @@ func TestWrapCommandExecutionErrorDoesNotAttributeFailureToSandbox(t *testing.T)
},
}))
err := WrapCommandExecutionError(errors.New("npm failed"), result, 1)
usefulErr, ok := usefulerror.AsUsefulError(err)
require.True(t, ok)
assert.Equal(t, errcodes.PackageManagerExecutionFailed, usefulErr.Code())
assert.Equal(t, "Package manager command exited with code: 1", usefulErr.HumanError())
assert.NotContains(t, usefulErr.Help(), "./.env")
assert.Contains(t, usefulErr.AdditionalHelp(), "pmg sandbox violations list")
assert.Equal(t, 1, ObserveViolations(result, errors.New("npm failed")))
}
func TestWrapCommandExecutionErrorOmitsBreadcrumbWhenNoViolations(t *testing.T) {
func TestObserveViolationsReturnsZeroWhenNoReport(t *testing.T) {
result := sandbox.NewExecutionResult(sandbox.WithExecutionResultSandbox(&fakeViolationSandbox{
report: nil,
}))
err := WrapCommandExecutionError(errors.New("npm failed"), result, 1)
usefulErr, ok := usefulerror.AsUsefulError(err)
require.True(t, ok)
assert.Equal(t, errcodes.PackageManagerExecutionFailed, usefulErr.Code())
assert.NotContains(t, usefulErr.AdditionalHelp(), "pmg sandbox violations list")
assert.Equal(t, 0, ObserveViolations(result, errors.New("npm failed")))
}
func TestWrapCommandExecutionErrorReturnsNilOnNilError(t *testing.T) {
assert.NoError(t, WrapCommandExecutionError(nil, nil, 0))
func TestObserveViolationsReturnsZeroOnNilResult(t *testing.T) {
assert.Equal(t, 0, ObserveViolations(nil, errors.New("npm failed")))
}