Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
dc38576
empty
AlexandreYang May 19, 2026
dbef982
[vuln-hunt 2026-05-19-gpt-5.5-cyber-2] signal-handling: test pipeline…
AlexandreYang May 19, 2026
d8dad6b
[vuln-hunt 2026-05-19-gpt-5.5-cyber-2] heredoc: add scenario coverage
AlexandreYang May 19, 2026
1928976
[vuln-hunt 2026-05-19-gpt-5.5-cyber-2] grep: test numeric overflow re…
AlexandreYang May 19, 2026
1c83a99
[vuln-hunt 2026-05-19-gpt-5.5-cyber-2] function: add composition scen…
AlexandreYang May 19, 2026
6d77f25
[vuln-hunt 2026-05-19-gpt-5.5-cyber-2] cut: add sandbox and special-f…
AlexandreYang May 19, 2026
343b7d6
[vuln-hunt 2026-05-19-gpt-5.5-cyber-2] strings: add vuln hunt coverage
AlexandreYang May 19, 2026
d022afd
[vuln-hunt 2026-05-19-gpt-5.5-cyber-2] until: add composition scenarios
AlexandreYang May 19, 2026
ba84b09
[vuln-hunt 2026-05-19-gpt-5.5-cyber-2] pwd: add symlink cwd coverage
AlexandreYang May 19, 2026
3e72f2d
[vuln-hunt 2026-05-19-gpt-5.5-cyber-2] brace group: add vuln hunt cov…
AlexandreYang May 20, 2026
8a3f455
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] negation: test blocked probes
AlexandreYang May 20, 2026
c245faf
test: add procnet reader vuln hunt tripwires
AlexandreYang May 20, 2026
e6373f3
test: cover procnet socket entry cap
AlexandreYang May 20, 2026
726facc
test: add callctx openfile vuln hunt tripwires
AlexandreYang May 20, 2026
338723d
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] field_splitting: test blocked …
AlexandreYang May 20, 2026
ebb369c
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] testcmd: blocked tests
AlexandreYang May 20, 2026
897999b
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] until_clause: blocked tests
AlexandreYang May 20, 2026
fbb803b
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] empty_script: blocked tests
AlexandreYang May 20, 2026
656a842
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] ping: test blocked attacks
AlexandreYang May 20, 2026
10642d2
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] ls: test blocked attacks
AlexandreYang May 20, 2026
80e5834
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] simple_command: test blocked a…
AlexandreYang May 20, 2026
ae3ff66
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] environment: test blocked attacks
AlexandreYang May 20, 2026
0f72b99
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] redirections: test blocked att…
AlexandreYang May 20, 2026
ee382e3
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] case_clause: test blocked attacks
AlexandreYang May 20, 2026
a390972
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] help: blocked tests
AlexandreYang May 20, 2026
b3b3a2f
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] allowed_paths: blocked tests
AlexandreYang May 20, 2026
60832e4
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] cd: blocked tests
AlexandreYang May 20, 2026
266c50f
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] allowedpaths: blocked tests
AlexandreYang May 20, 2026
2982a15
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] ss: blocked tests
AlexandreYang May 20, 2026
0226518
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] cmd_separator: blocked tests
AlexandreYang May 20, 2026
483ad7b
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] pipe: blocked tests
AlexandreYang May 20, 2026
4592bec
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] input_processing: blocked tests
AlexandreYang May 20, 2026
edf47cb
[vuln-hunt 2026-05-20-gpt-5.5-cyber-3] subshell: blocked tests
AlexandreYang May 20, 2026
549e790
test: add xargs vuln hunt regressions
AlexandreYang May 20, 2026
d6e6ff9
test: add heredoc vuln hunt regressions
AlexandreYang May 20, 2026
cd80d3e
test: add inline var vuln hunt regressions
AlexandreYang May 20, 2026
59d942a
test: add ip vuln hunt regressions
AlexandreYang May 20, 2026
750a72a
test: add line continuation vuln hunt regressions
AlexandreYang May 20, 2026
72cbfba
test: add tail vuln hunt regressions
AlexandreYang May 20, 2026
917b6a2
test: add errors vuln hunt regressions
AlexandreYang May 20, 2026
79cdc48
test: add uniq vuln hunt regressions
AlexandreYang May 20, 2026
dd7e834
test: add function vuln hunt regressions
AlexandreYang May 20, 2026
141334e
test: add command substitution vuln hunt regressions
AlexandreYang May 20, 2026
9983fb5
test: add brace group vuln hunt regressions
AlexandreYang May 20, 2026
088c0b1
test: add cat vuln hunt regressions
AlexandreYang May 20, 2026
6941425
test: add break vuln hunt regressions
AlexandreYang May 20, 2026
19b2a5d
test: add parser lexer vuln hunt regressions
AlexandreYang May 20, 2026
049b0e0
test: add df mount enumeration vuln hunt regressions
AlexandreYang May 20, 2026
0c8edc6
test: add signal handling vuln hunt regressions
AlexandreYang May 20, 2026
e2e7bb4
test: add uname vuln hunt regressions
AlexandreYang May 20, 2026
15fb3b0
test: add allowed commands vuln hunt regressions
AlexandreYang May 20, 2026
b13ac9a
test: add sort vuln hunt regressions
AlexandreYang May 20, 2026
e3e5d77
test: add while clause vuln hunt regressions
AlexandreYang May 20, 2026
6911a18
test: add executor context cancellation regression
AlexandreYang May 20, 2026
424c8fd
test: add tr vuln hunt regressions
AlexandreYang May 20, 2026
1f5096d
test: add pwd vuln hunt regressions
AlexandreYang May 20, 2026
de15796
test: add globbing vuln hunt regressions
AlexandreYang May 20, 2026
8d29c55
test: add find vuln hunt regressions
AlexandreYang May 20, 2026
72b0d86
test: add wc timeout regression
AlexandreYang May 20, 2026
a2fcfbd
test: add false vuln hunt tripwires
AlexandreYang May 20, 2026
cf2750e
test: add printf vuln hunt tripwires
AlexandreYang May 20, 2026
6dbf0f0
test: add builtin import allowlist tripwires
AlexandreYang May 20, 2026
6699b7f
test: add blocked commands vuln hunt tripwires
AlexandreYang May 20, 2026
4962d56
test: add blocked redirects vuln hunt tripwires
AlexandreYang May 20, 2026
757f529
test: add for clause vuln hunt tripwires
AlexandreYang May 20, 2026
9cff33f
test: add readonly vuln hunt tripwires
AlexandreYang May 20, 2026
6968529
test: add comments vuln hunt tripwires
AlexandreYang May 20, 2026
b4b783f
test: add heredoc dash vuln hunt tripwires
AlexandreYang May 21, 2026
93ab0ce
test: add logic ops vuln hunt tripwires
AlexandreYang May 21, 2026
27a4eca
test: add continue vuln hunt tripwires
AlexandreYang May 21, 2026
43c2e6f
test: add output buffer vuln hunt tripwires
AlexandreYang May 21, 2026
a8df567
test: add var expansion vuln hunt tripwires
AlexandreYang May 21, 2026
710b093
test: add true vuln hunt tripwires
AlexandreYang May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions allowedpaths/sandbox_vuln_hunt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2026-present Datadog, Inc.

// vuln-hunt 2026-05-20-gpt-5.5-cyber-3 (target: allowedpaths-sandbox)

package allowedpaths

import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestVulnHuntSubsystemAllowedPathsSandbox_OpenReadOnlyAndTraversalBlocked(t *testing.T) {
parent := t.TempDir()
allowed := filepath.Join(parent, "allowed")
outside := filepath.Join(parent, "outside")
siblingPrefix := filepath.Join(parent, "allowed-sibling")
require.NoError(t, os.Mkdir(allowed, 0o755))
require.NoError(t, os.Mkdir(outside, 0o755))
require.NoError(t, os.Mkdir(siblingPrefix, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(allowed, "safe.txt"), []byte("safe"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(outside, "secret.txt"), []byte("secret"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(siblingPrefix, "secret.txt"), []byte("sibling"), 0o644))

sb, _, err := New([]string{allowed})
require.NoError(t, err)
defer sb.Close()

f, err := sb.Open("safe.txt", allowed, os.O_RDONLY, 0)
require.NoError(t, err)
data, err := io.ReadAll(f)
require.NoError(t, err)
require.NoError(t, f.Close())
assert.Equal(t, "safe", string(data))

for _, flag := range []int{os.O_WRONLY, os.O_RDWR, os.O_CREATE, os.O_TRUNC, os.O_APPEND, os.O_WRONLY | os.O_CREATE} {
f, err := sb.Open("safe.txt", allowed, flag, 0o644)
assert.Nil(t, f)
assert.ErrorIs(t, err, os.ErrPermission, "flag %d", flag)
}

for _, path := range []string{
filepath.Join(outside, "secret.txt"),
filepath.Join("..", "outside", "secret.txt"),
filepath.Join(siblingPrefix, "secret.txt"),
} {
f, err := sb.Open(path, allowed, os.O_RDONLY, 0)
assert.Nil(t, f, "path %q", path)
assert.ErrorIs(t, err, os.ErrPermission, "path %q", path)
}
}

func TestVulnHuntSubsystemAllowedPathsSandbox_CrossRootSymlinkTerminalSemantics(t *testing.T) {
dir1 := t.TempDir()
dir2 := t.TempDir()
outside := t.TempDir()
require.NoError(t, os.Mkdir(filepath.Join(dir1, "sub"), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(dir1, "sub", "file.txt"), []byte("data"), 0o644))
require.NoError(t, os.Symlink("file.txt", filepath.Join(dir1, "sub", "leaf.lnk")))
require.NoError(t, os.Symlink(filepath.Join(dir1, "sub"), filepath.Join(dir2, "bridge")))
require.NoError(t, os.Symlink(filepath.Join(outside, "secret.txt"), filepath.Join(dir2, "escape.lnk")))

sb, _, err := New([]string{dir1, dir2})
require.NoError(t, err)
defer sb.Close()

f, err := sb.Open(filepath.Join("bridge", "file.txt"), dir2, os.O_RDONLY, 0)
require.NoError(t, err)
data, err := io.ReadAll(f)
require.NoError(t, err)
require.NoError(t, f.Close())
assert.Equal(t, "data", string(data))

info, err := sb.Lstat(filepath.Join("bridge", "leaf.lnk"), dir2)
require.NoError(t, err)
assert.NotZero(t, info.Mode()&fs.ModeSymlink)
target, err := sb.Readlink(filepath.Join("bridge", "leaf.lnk"), dir2)
require.NoError(t, err)
assert.Equal(t, "file.txt", target)

f, err = sb.Open("escape.lnk", dir2, os.O_RDONLY, 0)
assert.Nil(t, f)
assert.Error(t, err)
}

func TestVulnHuntSubsystemAllowedPathsSandbox_ReadDirCapsAndLimitedBounds(t *testing.T) {
dir := t.TempDir()
for i := range 5 {
require.NoError(t, os.WriteFile(filepath.Join(dir, fmt.Sprintf("f%02d", i)), nil, 0o644))
}
sb, _, err := New([]string{dir})
require.NoError(t, err)
defer sb.Close()

entries, err := sb.readDirN(".", dir, 3)
assert.Nil(t, entries)
assert.Error(t, err)
assert.Contains(t, err.Error(), "too many entries")

entries, truncated, err := sb.ReadDirLimited(".", dir, -100, 2)
require.NoError(t, err)
assert.True(t, truncated)
assert.Len(t, entries, 2)

entries, truncated, err = sb.ReadDirLimited(".", dir, 100, 2)
require.NoError(t, err)
assert.False(t, truncated)
assert.Empty(t, entries)
}

func TestVulnHuntSubsystemAllowedPathsSandbox_HostPrefixAndNullDeviceBoundaries(t *testing.T) {
hostPrefix, pods, containers := setupContainerDirsForVulnHunt(t)
outside := filepath.Join(hostPrefix, "etc", "secret")
require.NoError(t, os.MkdirAll(outside, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(outside, "shadow"), []byte("secret"), 0o644))
require.NoError(t, os.Symlink("/etc/secret/shadow", filepath.Join(containers, "escape.log")))

sb, _, err := New([]string{pods, containers})
require.NoError(t, err)
defer sb.Close()
sb.SetHostPrefix(hostPrefix)

f, err := sb.Open("app.log", containers, os.O_RDONLY, 0)
require.NoError(t, err)
require.NoError(t, f.Close())
f, err = sb.Open("escape.log", containers, os.O_RDONLY, 0)
assert.Nil(t, f)
assert.Error(t, err)

info, err := sb.Stat(os.DevNull, containers)
require.NoError(t, err)
assert.False(t, info.IsDir())
f, err = sb.Open(os.DevNull, containers, os.O_RDONLY, 0)
assert.Nil(t, f)
assert.ErrorIs(t, err, os.ErrPermission)
}

func TestVulnHuntSubsystemAllowedPathsSandbox_NilAndEmptySandboxesFailClosed(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "file.txt"), []byte("data"), 0o644))

var nilSB *Sandbox
assert.Nil(t, nilSB.Paths())
assert.NoError(t, nilSB.Close())
_, err := nilSB.Open("file.txt", dir, os.O_RDONLY, 0)
assert.ErrorIs(t, err, os.ErrPermission)
_, err = nilSB.Stat("file.txt", dir)
assert.ErrorIs(t, err, os.ErrPermission)

emptySB, _, err := New(nil)
require.NoError(t, err)
defer emptySB.Close()
_, err = emptySB.Open("file.txt", dir, os.O_RDONLY, 0)
assert.ErrorIs(t, err, os.ErrPermission)
_, err = emptySB.ReadDir(".", dir)
assert.ErrorIs(t, err, os.ErrPermission)
}

func TestVulnHuntSubsystemAllowedPathsSandbox_PortableErrorsKeepOperationAndPath(t *testing.T) {
err := PortablePathError(&os.PathError{Op: "openat", Path: "x", Err: fs.ErrPermission})
var pe *os.PathError
require.True(t, errors.As(err, &pe))
assert.Equal(t, "openat", pe.Op)
assert.Equal(t, "x", pe.Path)
assert.Equal(t, "permission denied", pe.Err.Error())
}

func setupContainerDirsForVulnHunt(t *testing.T) (hostPrefix, pods, containers string) {
t.Helper()
root := t.TempDir()
hostPrefix = root
pods = filepath.Join(root, "var", "log", "pods")
containers = filepath.Join(root, "var", "log", "containers")
require.NoError(t, os.MkdirAll(pods, 0o755))
require.NoError(t, os.MkdirAll(containers, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(pods, "app.log"), []byte("log line"), 0o644))
require.NoError(t, os.Symlink("/var/log/pods/app.log", filepath.Join(containers, "app.log")))
return hostPrefix, pods, containers
}
118 changes: 118 additions & 0 deletions analysis/builtin_import_allowlist_cyber3_vuln_hunt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2026-present Datadog, Inc.

// Tripwire tests added by vuln-hunt campaign 2026-05-20-gpt-5.5-cyber-3 /
// builtin-import-allowlist.

package analysis

import (
"os"
"path/filepath"
"sort"
"strings"
"testing"
)

func TestVulnHuntSubsystemBuiltinImportAllowlist_AliasedUnlistedSymbolRejected(t *testing.T) {
root := repoRoot(t)
tmp := t.TempDir()
copyDir(t, filepath.Join(root, "builtins"), filepath.Join(tmp, "builtins"))

writeGoFile(t,
filepath.Join(tmp, "builtins", "echo", "alias_symbol_rejected.go"),
"echo",
[]string{`o "os"`},
"var _ = o.Setenv\n",
)

var globalErrs []string
checkAllowedSymbols(t, builtinsVerifyCfg(tmp, &globalErrs))
if !errContains(globalErrs, "os.Setenv") || !errContains(globalErrs, "not in the allowlist") {
t.Fatalf("expected aliased os.Setenv to be rejected by global builtin allowlist, got: %v", globalErrs)
}

var perCmdErrs []string
cfg := builtinsPerCmdVerifyCfg(tmp, &perCmdErrs)
checkPerBuiltinAllowedSymbols(t, cfg)
if !errContains(perCmdErrs, "os") || !errContains(perCmdErrs, "not in the allowlist") {
t.Fatalf("expected aliased os.Setenv to be rejected by per-command allowlist, got: %v", perCmdErrs)
}
}

func TestVulnHuntSubsystemBuiltinImportAllowlist_DotImportRejected(t *testing.T) {
root := repoRoot(t)
tmp := t.TempDir()
copyDir(t, filepath.Join(root, "builtins"), filepath.Join(tmp, "builtins"))

writeGoFile(t,
filepath.Join(tmp, "builtins", "echo", "dot_import_rejected.go"),
"echo",
[]string{`. "fmt"`},
"var _ = Sprintf\n",
)

var errs []string
checkAllowedSymbols(t, builtinsVerifyCfg(tmp, &errs))
if !errContains(errs, "blank/dot import") {
t.Fatalf("expected dot import to be rejected, got: %v", errs)
}
}

func TestVulnHuntSubsystemBuiltinImportAllowlist_ParseErrorsFailClosed(t *testing.T) {
root := repoRoot(t)
tmp := t.TempDir()
copyDir(t, filepath.Join(root, "builtins"), filepath.Join(tmp, "builtins"))

badPath := filepath.Join(tmp, "builtins", "echo", "parse_error.go")
if err := os.WriteFile(badPath, []byte("package echo\nfunc broken(\n"), 0o644); err != nil {
t.Fatal(err)
}

var errs []string
checkAllowedSymbols(t, builtinsVerifyCfg(tmp, &errs))
if !errContains(errs, "parse error") || !errContains(errs, "parse_error.go") {
t.Fatalf("expected parse error to fail closed, got: %v", errs)
}
}

func TestVulnHuntSubsystemBuiltinImportAllowlist_PlatformSpecificInternalFilesChecked(t *testing.T) {
root := repoRoot(t)
cfg := internalCheckConfig()
files, err := cfg.CollectFiles(filepath.Join(root, "builtins", "internal"))
if err != nil {
t.Fatal(err)
}

seen := make(map[string]bool, len(files))
for _, path := range files {
rel, err := filepath.Rel(filepath.Join(root, "builtins", "internal"), path)
if err != nil {
t.Fatal(err)
}
seen[filepath.ToSlash(rel)] = true
}

required := []string{
"diskstats/diskstats_darwin.go",
"procnetsocket/procnetsocket_linux.go",
"winnet/winnet_windows.go",
"winpoll/winpoll_windows.go",
}
for _, rel := range required {
if !seen[rel] {
t.Fatalf("platform-specific internal file %s was not collected; got %s", rel, strings.Join(mapKeys(seen), ", "))
}
}
}

func mapKeys(m map[string]bool) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
Loading
Loading