Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions SHELL_FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Blocked features are rejected before execution with exit code 2.
- ✅ `ping [-c N] [-W DURATION] [-i DURATION] [-q] [-4|-6] [-h] HOST` — send ICMP echo requests to a network host and report round-trip statistics; `-f` (flood), `-b` (broadcast), `-s` (packet size), `-I` (interface), `-p` (pattern), and `-R` (record route) are blocked; count/wait/interval are clamped to safe ranges with a warning; multicast, unspecified (`0.0.0.0`/`::`), and broadcast addresses (IPv4 last-octet `.255`) are rejected — note: directed broadcasts on non-standard subnets (e.g. `.127` on a `/25`) are not blocked without subnet-mask knowledge
- ✅ `ps [-e|-A] [-f] [-p PIDLIST]` — report process status; default shows current-session processes; `-e`/`-A` shows all; `-f` adds UID/PPID/STIME columns; `-p` selects by PID list
- ✅ `printf FORMAT [ARGUMENT]...` — format and print data to stdout; supports `%s`, `%b`, `%c`, `%d`, `%i`, `%o`, `%u`, `%x`, `%X`, `%e`, `%E`, `%f`, `%F`, `%g`, `%G`, `%%`; format reuse for excess arguments; `%n` rejected (security risk); `-v` rejected
- ✅ `python [-c CODE] [-h] [SCRIPT | -] [ARG ...]` — execute Python 3.4 source code using the gpython pure-Go interpreter (no CPython required); `-c CODE` runs inline code; a positional `SCRIPT` argument runs a file (via AllowedPaths sandbox); `-` reads from stdin; security sandbox removes all OS exec/write/spawn functions, replaces `open()` with a read-only AllowedPaths-aware version, and blocks `tempfile`/`glob` modules; source files and stdin are capped at 1 MiB; stdlib is limited to `math`, `sys`, `os` (read-only), `time`, `binascii`; no `subprocess`, `socket`, `ctypes`, or f-strings (Python 3.4 syntax only)
- ✅ `sed [-n] [-e SCRIPT] [-E|-r] [SCRIPT] [FILE]...` — stream editor for filtering and transforming text; uses RE2 regex engine; `-i`/`-f` rejected; `e`/`w`/`W`/`r`/`R` commands blocked
- ✅ `strings [-a] [-n MIN] [-t o|d|x] [-o] [-f] [-s SEP] [FILE]...` — print printable character sequences in files (default min length 4); offsets via `-t`/`-o`; filename prefix via `-f`; custom separator via `-s`
- ✅ `tail [-n N|-c N] [-q|-v] [-z] [FILE]...` — output the last part of files (default: last 10 lines); supports `+N` offset mode; `-f`/`--follow` is rejected
Expand Down
11 changes: 11 additions & 0 deletions analysis/symbols_builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,15 @@ var builtinPerCommandSymbols = map[string][]string{
"strings.Split", // 🟢 splits a string by separator into a slice; pure function, no I/O.
"strings.TrimSpace", // 🟢 removes leading/trailing whitespace; pure function.
},
"python": {
"context.Context", // 🟢 deadline/cancellation plumbing; pure interface, no side effects.
"io.LimitReader", // 🟢 caps source-code reads at 1 MiB to prevent memory exhaustion; no I/O side effects.
"io.ReadAll", // 🟠 reads all bytes from a LimitReader-wrapped source; bounded by maxSourceBytes (1 MiB).
"io.Reader", // 🟢 interface type; no side effects.
"os.O_RDONLY", // 🟢 read-only file flag constant; cannot open files by itself.
// Note: builtins/internal/pyruntime symbols are exempt from this allowlist
// (internal packages are not checked by the builtinAllowedSymbols test).
},
"printf": {
"context.Context", // 🟢 deadline/cancellation plumbing; pure interface, no side effects.
"errors.As", // 🟢 error type assertion; pure function, no I/O.
Expand Down Expand Up @@ -420,8 +429,10 @@ var builtinAllowedSymbols = []string{
"github.com/prometheus-community/pro-bing.Statistics", // 🟢 ping round-trip statistics struct; pure data type, no I/O.
"golang.org/x/sys/unix.SysctlRaw", // 🟠 macOS: reads kernel socket tables (read-only, no exec, no filesystem).
"io.EOF", // 🟢 sentinel error value; pure constant.
"io.LimitReader", // 🟢 wraps a Reader with a byte-count limit; prevents reading unbounded data; no I/O side effects.
"io.MultiReader", // 🟢 combines multiple Readers into one sequential Reader; no I/O side effects.
"io.NopCloser", // 🟢 wraps a Reader with a no-op Close; no side effects.
"io.ReadAll", // 🟠 reads all bytes from a Reader; only safe when combined with io.LimitReader to bound allocation.
"io.ReadCloser", // 🟢 interface type; no side effects.
"io.ReadSeeker", // 🟢 interface type combining Reader and Seeker; no side effects.
"io.Reader", // 🟢 interface type; no side effects.
Expand Down
14 changes: 13 additions & 1 deletion analysis/symbols_builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,19 @@ func internalCheckConfig() allowedSymbolsConfig {
return collectSubdirGoFiles(dir, nil, nil)
},
ExemptImport: func(importPath string) bool {
return importPath == "github.com/DataDog/rshell/builtins"
// builtins package: the framework types used by all internal helpers.
if importPath == "github.com/DataDog/rshell/builtins" {
return true
}
// gpython: trusted third-party Python interpreter used exclusively by
// builtins/internal/pyruntime/. All gpython symbols are exempt because
// listing every py.* symbol would be impractical and offer no real
// security benefit — the entire gpython library is a deliberate,
// code-reviewed dependency.
if strings.HasPrefix(importPath, "github.com/go-python/gpython/") {
return true
}
return false
},
ListName: "internalAllowedSymbols",
MinFiles: 1,
Expand Down
44 changes: 44 additions & 0 deletions analysis/symbols_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@ package analysis
// symbols it is allowed to use. Every symbol listed here must also appear in
// internalAllowedSymbols (which acts as the global ceiling).
var internalPerPackageSymbols = map[string][]string{
"pyruntime": {
"bufio.NewReader", // 🟢 wraps an io.Reader with buffering for readline support; no write capability.
"bufio.NewScanner", // 🟢 creates a line scanner on a file for readline(); no write capability.
"bufio.Reader", // 🟢 type reference for buffered reader; no write capability.
"bufio.Scanner", // 🟢 type reference for line-by-line scanner on goFile; no write capability.
"bytes.SplitAfter", // 🟢 splits byte slice after delimiter; pure function, no I/O.
"context.Background", // 🟢 returns the background context used for Open calls within Python open(); no side effects.
"context.Context", // 🟢 deadline/cancellation interface; no side effects.
"errors.Is", // 🟢 checks whether an error in a chain matches a target; pure function, no I/O.
"fmt.Errorf", // 🟢 error formatting; pure function, no I/O.
"fmt.Fprintf", // 🟢 formats and writes to a writer; used only for error output to stderr.
"fmt.Sprintf", // 🟢 string formatting; pure function, no I/O.
"io.EOF", // 🟢 sentinel error value for end-of-file; read-only constant, no I/O.
"io.LimitReader", // 🟢 wraps a reader with a byte cap to prevent memory exhaustion; pure wrapper, no I/O by itself.
"io.ReadAll", // 🟠 reads all bytes from a reader; always bounded by io.LimitReader in this package.
"io.ReadWriteCloser", // 🟢 type reference for sandbox file handle; no write capability (write mode is blocked).
"io.Reader", // 🟢 type reference for stdin reader; no write capability.
"io.Writer", // 🟢 type reference for stdout/stderr writers; used only for output, not file writes.
"os.FileMode", // 🟢 file mode type; used only as argument type in the Open callback signature.
"os.IsNotExist", // 🟢 checks whether an error indicates file-not-found; pure predicate, no I/O.
"os.O_RDONLY", // 🟢 read-only file flag; pure constant.
"strings.ContainsRune", // 🟢 checks if a rune appears in a string (used to detect binary mode 'b'); pure function, no I/O.
"strings.NewReader", // 🟢 creates an in-memory io.Reader from a string (empty stdin fallback); pure function, no I/O.
},
"loopctl": {
"strconv.Atoi", // 🟢 string-to-int conversion; pure function, no I/O.
},
Expand Down Expand Up @@ -129,6 +153,26 @@ var internalPerPackageSymbols = map[string][]string{
// via iphlpapi.dll. Usage is limited to two call sites; no unsafe pointer
// arithmetic occurs after the DLL call. All buffer parsing uses encoding/binary.
var internalAllowedSymbols = []string{
// pyruntime
"bufio.NewReader", // 🟢 pyruntime: wraps an io.Reader with buffering for readline support; no write capability.
"bufio.NewScanner", // 🟢 pyruntime: creates a line scanner on a file for readline(); no write capability.
"bufio.Reader", // 🟢 pyruntime: buffered reader type reference; no write capability.
"bufio.Scanner", // 🟢 pyruntime: line-by-line scanner type reference; no write capability.
"bytes.SplitAfter", // 🟢 pyruntime: splits byte slice after delimiter; pure function, no I/O.
"context.Background", // 🟢 pyruntime: returns background context for sandbox open() calls; no side effects.
"errors.Is", // 🟢 pyruntime: checks error chain membership; pure function, no I/O.
"fmt.Fprintf", // 🟢 pyruntime: writes formatted error messages to stderr; no file-write capability.
"io.EOF", // 🟢 pyruntime: end-of-file sentinel; read-only constant.
"io.LimitReader", // 🟢 pyruntime/procsyskernel: wraps a reader with a byte cap; pure wrapper, no I/O by itself.
"io.ReadAll", // 🟠 pyruntime/procsyskernel: reads all bytes from a bounded reader; always used with LimitReader.
"io.ReadWriteCloser", // 🟢 pyruntime: sandbox file handle type; write mode is blocked at runtime.
"io.Reader", // 🟢 pyruntime: stdin reader type reference; no write capability.
"io.Writer", // 🟢 pyruntime: stdout/stderr writer type reference; no file-write capability.
"os.FileMode", // 🟢 pyruntime: file mode type used in sandbox Open callback signature; pure type.
"os.IsNotExist", // 🟢 pyruntime: file-not-found predicate; pure function, no I/O.
"strings.ContainsRune", // 🟢 pyruntime: checks mode string for binary flag; pure function, no I/O.
"strings.NewReader", // 🟢 pyruntime: creates in-memory reader from string (empty stdin fallback); pure function.
// procinfo
"bufio.NewScanner", // 🟢 procinfo: line-by-line reading of /proc files; no write capability.
"github.com/DataDog/rshell/builtins/internal/procpath.Default", // 🟢 procinfo/procnet: canonical /proc filesystem root path constant; pure constant, no I/O.
"bytes.NewReader", // 🟢 procinfo: wraps a byte slice as an in-memory io.Reader; no I/O side effects.
Expand Down
10 changes: 9 additions & 1 deletion analysis/symbols_interp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,15 @@ func internalPerPackageCheckConfig() perBuiltinConfig {
PerCommandSymbols: internalPerPackageSymbols,
TargetDir: "builtins/internal",
ExemptImport: func(importPath string) bool {
return importPath == "github.com/DataDog/rshell/builtins"
if importPath == "github.com/DataDog/rshell/builtins" {
return true
}
// gpython: exempt from per-package checks (same rationale as
// internalCheckConfig — listing every py.* symbol is impractical).
if strings.HasPrefix(importPath, "github.com/go-python/gpython/") {
return true
}
return false
},
SkipDirs: map[string]bool{},
}
Expand Down
Loading
Loading