mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
* feat: Add support for environment variable protection for sandbox * chore: Update dangerous env var list * fix: Split profiles for improved environment protection * fix: pipx sandbox profile separation * chore: Show sandbox scrub info on error exit * fix: Code review fixes * test: Add e2e for sandbox environment scrubbing
113 lines
3.3 KiB
Go
113 lines
3.3 KiB
Go
package runner
|
|
|
|
import (
|
|
"errors"
|
|
"os/exec"
|
|
"testing"
|
|
|
|
"github.com/safedep/dry/usefulerror"
|
|
"github.com/safedep/pmg/errcodes"
|
|
"github.com/safedep/pmg/internal/pty"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestExtractExit(t *testing.T) {
|
|
t.Run("direct non-zero exit resolves the real code", func(t *testing.T) {
|
|
err := exec.Command("sh", "-c", "exit 2").Run()
|
|
require.Error(t, err)
|
|
|
|
code, signaled, resolved := extractExit(err)
|
|
assert.Equal(t, 2, code)
|
|
assert.False(t, signaled)
|
|
assert.True(t, resolved)
|
|
})
|
|
|
|
t.Run("direct signal termination resolves to 128+signum", func(t *testing.T) {
|
|
err := exec.Command("sh", "-c", "kill -INT $$").Run()
|
|
require.Error(t, err)
|
|
|
|
code, signaled, resolved := extractExit(err)
|
|
assert.Equal(t, 130, code) // 128 + SIGINT(2)
|
|
assert.True(t, signaled)
|
|
assert.True(t, resolved)
|
|
})
|
|
|
|
t.Run("pty exit error reads its fields directly", func(t *testing.T) {
|
|
code, signaled, resolved := extractExit(&pty.ExitError{Code: 1})
|
|
assert.Equal(t, 1, code)
|
|
assert.False(t, signaled)
|
|
assert.True(t, resolved)
|
|
|
|
code, signaled, resolved = extractExit(&pty.ExitError{Code: 143, Signaled: true})
|
|
assert.Equal(t, 143, code)
|
|
assert.True(t, signaled)
|
|
assert.True(t, resolved)
|
|
})
|
|
|
|
t.Run("non-exit error is unresolved", func(t *testing.T) {
|
|
_, _, resolved := extractExit(errors.New("failed to launch binary"))
|
|
assert.False(t, resolved)
|
|
})
|
|
}
|
|
|
|
func TestDecideExit(t *testing.T) {
|
|
runErr := errors.New("npm failed")
|
|
|
|
t.Run("plain child exit becomes a transparent ChildExitError", func(t *testing.T) {
|
|
err := decideExit(runErr, 1, false, true, "npm", 0)
|
|
|
|
var ce *ChildExitError
|
|
require.True(t, errors.As(err, &ce))
|
|
assert.Equal(t, 1, ce.ExitCode())
|
|
assert.True(t, ce.Transparent())
|
|
assert.False(t, ce.IsSignaled())
|
|
assert.Equal(t, "npm", ce.PMName)
|
|
assert.Equal(t, 0, ce.ScrubbedEnvCount())
|
|
})
|
|
|
|
t.Run("scrubbed env count is carried for the exit hint", func(t *testing.T) {
|
|
err := decideExit(runErr, 1, false, true, "npm", 3)
|
|
|
|
var ce *ChildExitError
|
|
require.True(t, errors.As(err, &ce))
|
|
assert.Equal(t, 3, ce.ScrubbedEnvCount())
|
|
})
|
|
|
|
t.Run("signaled child exit is transparent and signaled", func(t *testing.T) {
|
|
err := decideExit(runErr, 130, true, true, "npm", 0)
|
|
|
|
var ce *ChildExitError
|
|
require.True(t, errors.As(err, &ce))
|
|
assert.True(t, ce.IsSignaled())
|
|
assert.Equal(t, 130, ce.ExitCode())
|
|
})
|
|
|
|
t.Run("unresolved exit is a visible launch failure", func(t *testing.T) {
|
|
err := decideExit(runErr, -1, false, false, "npm", 0)
|
|
|
|
usefulErr, ok := usefulerror.AsUsefulError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, errcodes.PackageManagerExecutionFailed, usefulErr.Code())
|
|
assert.Equal(t, "Failed to execute package manager command", usefulErr.HumanError())
|
|
|
|
var ce *ChildExitError
|
|
assert.False(t, errors.As(err, &ce))
|
|
})
|
|
}
|
|
|
|
// classify is pure: a child that produced its own exit status is always a
|
|
// transparent passthrough. Sandbox denials are persisted by the caller and
|
|
// never reach classify, so they cannot make the exit loud (issue #309).
|
|
func TestClassifyTransparentChildExit(t *testing.T) {
|
|
childErr := exec.Command("sh", "-c", "exit 1").Run()
|
|
require.Error(t, childErr)
|
|
|
|
err := classify(childErr, "npm", 0)
|
|
|
|
var ce *ChildExitError
|
|
require.True(t, errors.As(err, &ce))
|
|
assert.Equal(t, 1, ce.ExitCode())
|
|
assert.Equal(t, "npm", ce.PMName)
|
|
}
|