mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
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:
co-authored by
Sahil Bansal
parent
db1a5e58b3
commit
5018f8e2ff
@@ -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,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")))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user