Files
wehub-resource-sync e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

679 lines
18 KiB
Go

// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build linux
package isolation
import (
"fmt"
"os"
"os/exec"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func uidPtr(n uint32) *uint32 { return &n }
// Argv builder tests (platform-independent)
func TestBuildArgv_NamespaceFlags(t *testing.T) {
tests := []struct {
name string
shareNet bool
want string // substring that must appear
dontWant string // substring that must NOT appear
}{
{"share_net=true (default)", true, "", "--unshare-net"},
{"share_net=false", false, "--unshare-net", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
opts := basicWrapOpts()
opts.ShareNet = tt.shareNet
argv, err := buildArgv(opts, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
s := strings.Join(argv, " ")
if tt.want != "" && !strings.Contains(s, tt.want) {
t.Errorf("argv missing %q:\n %s", tt.want, s)
}
if tt.dontWant != "" && strings.Contains(s, tt.dontWant) {
t.Errorf("argv contains %q but should not:\n %s", tt.dontWant, s)
}
})
}
}
func TestBuildArgv_TmpSegment(t *testing.T) {
tests := []struct {
profile Profile
want string
dont string
}{
{ProfileStrict, "--tmpfs /tmp", "--bind /tmp /tmp"},
{ProfileBalanced, "--bind /tmp /tmp", "--tmpfs /tmp"},
}
for _, tt := range tests {
t.Run(string(tt.profile), func(t *testing.T) {
opts := basicWrapOpts()
opts.Profile = tt.profile
argv, err := buildArgv(opts, "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
s := strings.Join(argv, " ")
if !strings.Contains(s, tt.want) {
t.Errorf("%s: missing %q:\n %s", tt.profile, tt.want, s)
}
if tt.dont != "" && strings.Contains(s, tt.dont) {
t.Errorf("%s: should not contain %q:\n %s", tt.profile, tt.dont, s)
}
})
}
}
func TestBuildArgv_WorkspaceSegment(t *testing.T) {
ws := func(mode WorkspaceMode) WrapOptions {
opts := basicWrapOpts()
opts.Workspace.Mode = mode
return opts
}
t.Run("rw", func(t *testing.T) {
argv, err := buildArgv(ws(WorkspaceRW), "")
if err != nil {
t.Fatal(err)
}
s := strings.Join(argv, " ")
if !strings.Contains(s, "--bind /workspace /workspace") {
t.Error(s)
}
})
t.Run("ro", func(t *testing.T) {
argv, err := buildArgv(ws(WorkspaceRO), "")
if err != nil {
t.Fatal(err)
}
s := strings.Join(argv, " ")
if !strings.Contains(s, "--ro-bind /workspace /workspace") {
t.Error(s)
}
})
t.Run("overlay_with_persist", func(t *testing.T) {
opts := ws(WorkspaceOverlay)
opts.UpperDir = "/var/lib/execd/isolation/abc"
opts.WorkDir = "/var/lib/execd/isolation/abc-work"
argv, err := buildArgv(opts, "")
if err != nil {
t.Fatal(err)
}
s := strings.Join(argv, " ")
for _, want := range []string{
"--overlay",
"/workspace",
"/var/lib/execd/isolation/abc",
"/var/lib/execd/isolation/abc-work",
} {
if !strings.Contains(s, want) {
t.Errorf("missing %q", want)
}
}
})
t.Run("overlay_without_persist_tmpfs", func(t *testing.T) {
opts := ws(WorkspaceOverlay)
opts.UpperDir = "" // tmpfs upper
argv, err := buildArgv(opts, "")
if err != nil {
t.Fatal(err)
}
s := strings.Join(argv, " ")
if !strings.Contains(s, "--overlay-src") {
t.Error("missing --overlay-src")
}
})
}
func TestBuildArgv_EnvPassthrough(t *testing.T) {
t.Run("deny_with_keys", func(t *testing.T) {
opts := basicWrapOpts()
opts.EnvPassthrough = EnvSpec{Mode: EnvModeDeny, Keys: []string{"SECRET", "TOKEN"}}
argv, err := buildArgv(opts, "")
if err != nil {
t.Fatal(err)
}
s := strings.Join(argv, " ")
if !strings.Contains(s, "--unsetenv SECRET") {
t.Error(s)
}
if !strings.Contains(s, "--unsetenv TOKEN") {
t.Error(s)
}
})
t.Run("allow_with_clearenv", func(t *testing.T) {
opts := basicWrapOpts()
opts.EnvPassthrough = EnvSpec{Mode: EnvModeAllow, Keys: []string{"PATH", "HOME"}}
argv, err := buildArgv(opts, "")
if err != nil {
t.Fatal(err)
}
s := strings.Join(argv, " ")
if !strings.Contains(s, "--clearenv") {
t.Error("missing --clearenv")
}
})
t.Run("empty_mode_no_env_args", func(t *testing.T) {
opts := basicWrapOpts()
opts.EnvPassthrough = EnvSpec{} // empty mode
argv, err := buildArgv(opts, "")
if err != nil {
t.Fatal(err)
}
s := strings.Join(argv, " ")
if strings.Contains(s, "--clearenv") || strings.Contains(s, "--unsetenv") {
t.Error("should not have env args for empty mode")
}
})
}
func TestBuildArgv_ExtraWritable(t *testing.T) {
opts := basicWrapOpts()
opts.ExtraWritable = []string{"/data", "/tmp/custom"}
argv, err := buildArgv(opts, "")
if err != nil {
t.Fatal(err)
}
s := strings.Join(argv, " ")
for _, p := range opts.ExtraWritable {
// Each writable path generates "--bind $p $p"
if strings.Count(s, p) < 2 {
t.Errorf("missing bind for %q in:\n %s", p, s)
}
}
}
func TestBuildArgv_Binds(t *testing.T) {
t.Run("rw_src_to_dest", func(t *testing.T) {
opts := basicWrapOpts()
opts.Binds = []BindMount{{Source: "/host/data", Dest: "/container/data"}}
argv, err := buildArgv(opts, "")
require.NoError(t, err)
s := strings.Join(argv, " ")
assert.Contains(t, s, "--bind /host/data /container/data")
})
t.Run("readonly_uses_ro_bind", func(t *testing.T) {
opts := basicWrapOpts()
opts.Binds = []BindMount{{Source: "/host/ro", Dest: "/mnt/ro", ReadOnly: true}}
argv, err := buildArgv(opts, "")
require.NoError(t, err)
s := strings.Join(argv, " ")
assert.Contains(t, s, "--ro-bind /host/ro /mnt/ro")
})
t.Run("empty_dest_defaults_to_source", func(t *testing.T) {
opts := basicWrapOpts()
opts.Binds = []BindMount{{Source: "/host/same"}}
argv, err := buildArgv(opts, "")
require.NoError(t, err)
s := strings.Join(argv, " ")
assert.Contains(t, s, "--bind /host/same /host/same")
})
t.Run("validation_empty_source", func(t *testing.T) {
opts := basicWrapOpts()
opts.Binds = []BindMount{{Dest: "/mnt/x"}}
_, err := buildArgv(opts, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "bind.source is required")
})
t.Run("validation_relative_source", func(t *testing.T) {
opts := basicWrapOpts()
opts.Binds = []BindMount{{Source: "relative/path"}}
_, err := buildArgv(opts, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "must be an absolute path")
})
t.Run("validation_relative_dest", func(t *testing.T) {
opts := basicWrapOpts()
opts.Binds = []BindMount{{Source: "/host/a", Dest: "rel/dest"}}
_, err := buildArgv(opts, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "must be an absolute path")
})
}
func TestBuildArgv_Setpriv(t *testing.T) {
t.Run("default_uid_gid", func(t *testing.T) {
opts := basicWrapOpts()
argv, err := buildArgv(opts, "")
if err != nil {
t.Fatal(err)
}
s := strings.Join(argv, " ")
if !strings.Contains(s, "setpriv") {
t.Error("missing setpriv")
}
if !strings.Contains(s, "--clear-groups") {
t.Error("missing --clear-groups")
}
})
t.Run("explicit_uid_gid", func(t *testing.T) {
opts := basicWrapOpts()
u, g := uint32(1001), uint32(1002)
opts.Uid = &u
opts.Gid = &g
argv, err := buildArgv(opts, "")
if err != nil {
t.Fatal(err)
}
s := strings.Join(argv, " ")
if !strings.Contains(s, "--reuid=1001") {
t.Error("missing --reuid=1001")
}
if !strings.Contains(s, "--regid=1002") {
t.Error("missing --regid=1002")
}
})
}
func TestBuildArgv_Userns(t *testing.T) {
t.Run("userns_mode_uses_unshare_user", func(t *testing.T) {
opts := basicWrapOpts()
u, g := uint32(1000), uint32(1000)
opts.Uid = &u
opts.Gid = &g
opts.UidMode = UidModeUserns
argv, err := buildArgv(opts, "")
require.NoError(t, err)
s := strings.Join(argv, " ")
assert.Contains(t, s, "--unshare-user", "userns mode must include --unshare-user")
assert.Contains(t, s, "--disable-userns", "userns mode must include --disable-userns")
assert.Contains(t, s, "--uid 1000", "userns mode must include --uid")
assert.Contains(t, s, "--gid 1000", "userns mode must include --gid")
})
t.Run("userns_mode_setuid_bwrap_skips_disable_userns", func(t *testing.T) {
// --disable-userns is unsupported by the setuid build of bwrap.
bwrapIsSetuid = true
defer func() { bwrapIsSetuid = false }()
opts := basicWrapOpts()
opts.UidMode = UidModeUserns
argv, err := buildArgv(opts, "")
require.NoError(t, err)
s := strings.Join(argv, " ")
assert.Contains(t, s, "--unshare-user", "userns mode must still include --unshare-user")
assert.NotContains(t, s, "--disable-userns", "setuid bwrap must not include --disable-userns")
})
t.Run("userns_mode_no_setpriv", func(t *testing.T) {
opts := basicWrapOpts()
u, g := uint32(1000), uint32(1000)
opts.Uid = &u
opts.Gid = &g
opts.UidMode = UidModeUserns
argv, err := buildArgv(opts, "")
require.NoError(t, err)
s := strings.Join(argv, " ")
assert.NotContains(t, s, "setpriv", "userns mode must not include setpriv")
assert.NotContains(t, s, "--reuid", "userns mode must not include --reuid")
assert.NotContains(t, s, "--regid", "userns mode must not include --regid")
})
t.Run("setpriv_mode_no_unshare_user", func(t *testing.T) {
opts := basicWrapOpts()
u, g := uint32(1000), uint32(1000)
opts.Uid = &u
opts.Gid = &g
opts.UidMode = UidModeSetpriv
argv, err := buildArgv(opts, "")
require.NoError(t, err)
s := strings.Join(argv, " ")
assert.NotContains(t, s, "--unshare-user", "setpriv mode must not include --unshare-user")
assert.NotContains(t, s, "--disable-userns", "setpriv mode must not include --disable-userns")
assert.Contains(t, s, "setpriv", "setpriv mode must include setpriv")
assert.Contains(t, s, "--reuid=1000", "setpriv mode must include --reuid")
assert.Contains(t, s, "--regid=1000", "setpriv mode must include --regid")
})
t.Run("empty_uid_mode_defaults_to_setpriv", func(t *testing.T) {
opts := basicWrapOpts()
u, g := uint32(1000), uint32(1000)
opts.Uid = &u
opts.Gid = &g
// UidMode is empty string — should behave like setpriv.
argv, err := buildArgv(opts, "")
require.NoError(t, err)
s := strings.Join(argv, " ")
assert.NotContains(t, s, "--unshare-user")
assert.Contains(t, s, "setpriv")
})
t.Run("userns_namespace_flag_order", func(t *testing.T) {
opts := basicWrapOpts()
u := uint32(1000)
opts.Uid = &u
opts.UidMode = UidModeUserns
argv, err := buildArgv(opts, "")
require.NoError(t, err)
// --unshare-user must come before --unshare-pid (both in segment 1).
idxUser := indexOf(argv, "--unshare-user")
idxPid := indexOf(argv, "--unshare-pid")
assert.Greater(t, idxPid, idxUser,
"--unshare-user should appear before --unshare-pid")
// --uid/--gid should come after --unshare-ipc (still in segment 1),
// before segment 2 (--ro-bind).
idxUid := indexOf(argv, "--uid")
idxRoBind := indexOf(argv, "--ro-bind")
assert.Greater(t, idxRoBind, idxUid,
"--uid should appear before --ro-bind (segment 2)")
})
}
func TestBuildArgv_Validation_UidMode(t *testing.T) {
opts := basicWrapOpts()
opts.UidMode = "bogus"
_, err := buildArgv(opts, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "unknown uid mode")
}
func TestBuildArgv_Seccomp(t *testing.T) {
opts := basicWrapOpts()
argv, err := buildArgv(opts, "3") // fd number passed to --seccomp
require.NoError(t, err)
s := strings.Join(argv, " ")
assert.Contains(t, s, "--seccomp 3", "missing seccomp fd")
}
func TestBuildArgv_SegmentOrder(t *testing.T) {
opts := basicWrapOpts()
opts.Profile = ProfileStrict
opts.Workspace.Mode = WorkspaceOverlay
opts.UpperDir = "/tmp/upper"
opts.ExtraWritable = []string{"/data"}
opts.EnvPassthrough = EnvSpec{Mode: EnvModeDeny, Keys: []string{"TOKEN"}}
argv, err := buildArgv(opts, "3") // fd number passed to --seccomp
if err != nil {
t.Fatal(err)
}
// Expected segment order. We track by scanning argv for each marker
// and comparing the index of the first segment element.
type seg struct {
label string
match string // single argv element
}
order := []seg{
{"1.ns", "--unshare-pid"},
{"2.rootfs", "--ro-bind"},
{"3.tmp", "/tmp"},
{"4.run", "/run"},
{"5.dev", "--dev"},
{"6.proc", "--proc"},
{"7.workspace", "--overlay-src"},
{"8.extra_writable", "--bind"},
{"9.env", "--unsetenv"},
{"10.seccomp", "--seccomp"},
{"11.setpriv", "setpriv"},
}
lastIdx := -1
for _, s := range order {
idx := indexOf(argv, s.match)
if idx < 0 {
t.Errorf("segment %s (%q) not found in argv:\n %v", s.label, s.match, argv)
continue
}
if idx <= lastIdx {
t.Errorf("segment %s (%q) at %d: should be after index %d", s.label, s.match, idx, lastIdx)
}
lastIdx = idx
}
}
func TestBuildArgv_SegmentOrder_Userns(t *testing.T) {
opts := basicWrapOpts()
opts.Profile = ProfileStrict
opts.Workspace.Mode = WorkspaceOverlay
opts.UpperDir = "/tmp/upper"
opts.ExtraWritable = []string{"/data"}
opts.EnvPassthrough = EnvSpec{Mode: EnvModeDeny, Keys: []string{"TOKEN"}}
u := uint32(1000)
opts.Uid = &u
opts.UidMode = UidModeUserns
argv, err := buildArgv(opts, "3")
require.NoError(t, err)
type seg struct {
label string
match string
}
// In userns mode: --unshare-user comes first, no setpriv at the end.
order := []seg{
{"1.ns.userns", "--unshare-user"},
{"1.ns.pid", "--unshare-pid"},
{"2.rootfs", "--ro-bind"},
{"3.tmp", "/tmp"},
{"4.run", "/run"},
{"5.dev", "--dev"},
{"6.proc", "--proc"},
{"7.workspace", "--overlay-src"},
{"8.extra_writable", "--bind"},
{"9.env", "--unsetenv"},
{"10.seccomp", "--seccomp"},
}
lastIdx := -1
for _, s := range order {
idx := indexOf(argv, s.match)
if idx < 0 {
t.Errorf("segment %s (%q) not found in argv:\n %v", s.label, s.match, argv)
continue
}
if idx <= lastIdx {
t.Errorf("segment %s (%q) at %d: should be after index %d", s.label, s.match, idx, lastIdx)
}
lastIdx = idx
}
// setpriv must NOT appear in userns mode.
assert.Equal(t, -1, indexOf(argv, "setpriv"), "setpriv must not appear in userns mode")
}
func TestBuildArgv_Validation(t *testing.T) {
tests := []struct {
name string
opts WrapOptions
want string
}{
{"empty_workspace", WrapOptions{}, "workspace.path is required"},
{"bad_profile", WrapOptions{Workspace: WorkspaceSpec{Path: "/ws", Mode: WorkspaceRW}, Profile: "bogus"}, "unknown profile"},
{"bad_mode", WrapOptions{Profile: ProfileBalanced, Workspace: WorkspaceSpec{Path: "/ws", Mode: "bogus"}}, "unknown workspace mode"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := buildArgv(tt.opts, "")
if err == nil {
t.Fatal("expected error, got nil")
}
if !strings.Contains(err.Error(), tt.want) {
t.Errorf("error %q does not contain %q", err.Error(), tt.want)
}
})
}
}
// Env pattern match tests
func TestMatchEnvPattern(t *testing.T) {
tests := []struct {
testName string
envName string
pattern string
want bool
}{
{"exact", "PATH", "PATH", true},
{"suffix_wildcard_hit", "GITHUB_TOKEN", "*_TOKEN", true},
{"suffix_wildcard_miss", "PATH", "*_TOKEN", false},
{"prefix_wildcard_hit", "AWS_ACCESS_KEY_ID", "AWS_*", true},
{"prefix_wildcard_miss", "PATH", "AWS_*", false},
{"full_wildcard_hit", "MY_SECRET_KEY", "*SECRET*", true},
{"full_wildcard_miss", "PATH", "*SECRET*", false},
{"case_insensitive_exact", "PATH", "path", true},
{"case_insensitive_pattern", "GITHUB_TOKEN", "*_token", true},
}
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
got := matchEnvPattern(tt.envName, tt.pattern)
if got != tt.want {
t.Errorf("matchEnvPattern(%q, %q) = %v, want %v", tt.envName, tt.pattern, got, tt.want)
}
})
}
}
// WrapWithArgv test
func TestWrapWithArgv(t *testing.T) {
cmd := exec.Command("bash", "-c", "echo hello")
argv := []string{
"--unshare-pid", "--ro-bind", "/", "/",
"--tmpfs", "/tmp", "--tmpfs", "/run",
"--dev", "/dev", "--proc", "/proc",
"--bind", "/workspace", "/workspace",
"--", "setpriv", "--reuid=1000", "--regid=1000", "--clear-groups",
}
wrapWithArgv(cmd, "/usr/bin/bwrap", argv)
if cmd.Path != "/usr/bin/bwrap" {
t.Errorf("Path = %q, want /usr/bin/bwrap", cmd.Path)
}
if len(cmd.Args) < 3 {
t.Fatalf("too few args: %v", cmd.Args)
}
if cmd.Args[0] != "/usr/bin/bwrap" {
t.Errorf("Args[0] = %q, want /usr/bin/bwrap", cmd.Args[0])
}
// Original command args should be at the end.
n := len(cmd.Args)
if cmd.Args[n-1] != "echo hello" || cmd.Args[n-2] != "-c" || cmd.Args[n-3] != "bash" {
t.Errorf("original args not preserved at end: %v", cmd.Args)
}
}
// Profile / WorkspaceMode / EnvMode Valid tests
func TestProfile_Valid(t *testing.T) {
if !ProfileStrict.Valid() {
t.Error("strict should be valid")
}
if !ProfileBalanced.Valid() {
t.Error("balanced should be valid")
}
if Profile("bogus").Valid() {
t.Error("bogus should be invalid")
}
}
func TestWorkspaceMode_Valid(t *testing.T) {
for _, m := range []WorkspaceMode{WorkspaceRW, WorkspaceOverlay, WorkspaceRO} {
if !m.Valid() {
t.Errorf("%q should be valid", m)
}
}
if WorkspaceMode("bogus").Valid() {
t.Error("bogus should be invalid")
}
}
func TestEnvMode_Valid(t *testing.T) {
if !EnvModeDeny.Valid() {
t.Error("deny should be valid")
}
if !EnvModeAllow.Valid() {
t.Error("allow should be valid")
}
if EnvMode("bogus").Valid() {
t.Error("bogus should be invalid")
}
}
func TestUidMode_Valid(t *testing.T) {
if !UidModeSetpriv.Valid() {
t.Error("setpriv should be valid")
}
if !UidModeUserns.Valid() {
t.Error("userns should be valid")
}
if UidMode("bogus").Valid() {
t.Error("bogus should be invalid")
}
}
// Helpers
func basicWrapOpts() WrapOptions {
return WrapOptions{
Profile: ProfileBalanced,
ShareNet: true,
Workspace: WorkspaceSpec{Path: "/workspace", Mode: WorkspaceRW},
}
}
func indexOf(items []string, s string) int {
for i, item := range items {
if item == s {
return i
}
}
return -1
}
// Ensure unused import vars don't break compilation on non-test.
var _ = fmt.Sprintf
var _ = os.Getpid
var _ = uidPtr