Skip to content
Merged
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
63 changes: 63 additions & 0 deletions allowedsymbols/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# allowedsymbols

Static enforcement of symbol-level import restrictions for the rshell interpreter.

## Purpose

rshell is a sandboxed shell. Any Go package it imports is a potential escape vector. This package maintains **explicit allowlists** of every `importpath.Symbol` that each subsystem is permitted to use, and enforces those lists via Go AST analysis run as tests.

If a symbol is not on the list, the code cannot compile it through CI — adding new capabilities requires a deliberate, reviewed allowlist entry.

## Permanently Banned Packages

Some packages may never be imported regardless of the symbol, declared in `symbols_common.go`:

## Allowlists

Each subsystem has its own allowlist file:

| File | Governs |
|------|---------|
| `symbols_builtins.go` | `builtins/` — builtin command implementations |
| `symbols_interp.go` | `interp/` — interpreter core |
| `symbols_allowedpaths.go` | `allowedpaths/` — filesystem sandbox |
| `symbols_internal.go` | `builtins/internal/` — shared internal helpers |

### Two-layer system for builtins

Builtins use two complementary lists:

- **`builtinAllowedSymbols`** — global ceiling: every symbol any builtin may use.
- **`builtinPerCommandSymbols`** — per-command sublists: each builtin directory (`cat/`, `grep/`, …) declares only the symbols it actually needs.

Every symbol in a per-command list must be present in the global ceiling. Every symbol in the global ceiling must appear in at least one per-command list. This keeps each builtin's surface area minimal and auditable in isolation.

The same two-layer pattern applies to `builtins/internal/` via `internalAllowedSymbols` and `internalPerPackageSymbols`.

## Safety Legend

Each allowlist entry carries an inline comment prefixed with a safety emoji:

| Emoji | Meaning | Examples |
|-------|---------|---------|
| 🟢 | **Pure** — no side effects (pure functions, constants, types, interfaces) | `strings.Split`, `fmt.Sprintf`, `io.Reader`, AST types |
| 🟠 | **Read-only I/O** — reads from filesystem, OS state, or kernel; or delegates writes to an `io.Writer` | `os.Open`, `os.ReadFile`, `time.Now`, `net.Interfaces`, `syscall.Getsid` |
| 🔴 | **Privileged** — network I/O, unsafe memory, or native code loading | `net.DefaultResolver`, `pro-bing.NewPinger`, `unsafe.Pointer`, `syscall.MustLoadDLL` |

## Enforcement

The tests in this package use Go's `go/parser` and `go/ast` to walk source files and verify:

1. No permanently banned package is imported.
2. Every imported package is in the allowlist.
3. Every `pkg.Symbol` reference is explicitly listed.
4. Every symbol in an allowlist is actually used (no dead entries).
5. Every builtin subdirectory has a per-command entry.

Verification tests additionally inject banned imports or unlisted symbols into a temporary copy of the repo and assert the checker catches them.

## Adding a New Symbol

1. Add a line to the appropriate allowlist (and the per-command sublist if it's a builtin).
2. Prefix the comment with the correct safety emoji.
3. Run `go test ./allowedsymbols/` to verify the entry is valid and used.
80 changes: 40 additions & 40 deletions allowedsymbols/symbols_allowedpaths.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,44 +17,44 @@ package allowedsymbols
//
// The permanently banned packages (reflect, unsafe) apply here too.
var allowedpathsAllowedSymbols = []string{
"errors.As", // error type assertion; pure function, no I/O.
"errors.Is", // error comparison; pure function, no I/O.
"errors.New", // creates a simple error value; pure function, no I/O.
"fmt.Errorf", // formatted error creation; pure function, no I/O.
"io.EOF", // sentinel error value; pure constant.
"io.ReadWriteCloser", // combined interface type; no side effects.
"io/fs.DirEntry", // interface type for directory entries; no side effects.
"io/fs.ErrExist", // sentinel error for "already exists"; pure constant.
"io/fs.ErrNotExist", // sentinel error for "does not exist"; pure constant.
"io/fs.ErrPermission", // sentinel error for permission denied; pure constant.
"io/fs.FileInfo", // interface type for file metadata; no side effects.
"io/fs.FileMode", // file permission bits type; pure type.
"io/fs.ReadDirFile", // read-only directory handle interface; no write capability.
"os.DevNull", // platform null device path constant; pure constant.
"os.ErrPermission", // sentinel error for permission denied; pure constant.
"os.FileMode", // file permission bits type; pure type.
"os.Getgid", // returns the numeric group id of the caller; read-only syscall.
"os.Getgroups", // returns supplementary group ids; read-only syscall.
"os.Getuid", // returns the numeric user id of the caller; read-only syscall.
"os.O_RDONLY", // read-only file flag constant; pure constant.
"os.OpenRoot", // opens a directory as a root for sandboxed file access; needed for sandbox.
"os.PathError", // error type wrapping path and operation; pure type.
"os.Root", // sandboxed directory root type; core of the filesystem sandbox.
"os.Stat", // returns file info for a path; needed for sandbox path validation.
"path/filepath.Abs", // returns absolute path; pure path computation.
"path/filepath.IsAbs", // checks if path is absolute; pure function, no I/O.
"path/filepath.Join", // joins path elements; pure function, no I/O.
"path/filepath.Rel", // returns relative path; pure path computation.
"path/filepath.Separator", // OS path separator constant; pure constant.
"slices.SortFunc", // sorts a slice with a comparison function; pure function, no I/O.
"strings.Compare", // compares two strings lexicographically; pure function, no I/O.
"strings.EqualFold", // case-insensitive string comparison; pure function, no I/O.
"strings.HasPrefix", // pure function for prefix matching; no I/O.
"syscall.ByHandleFileInformation", // Windows file identity structure; pure type for file metadata.
"syscall.EISDIR", // "is a directory" errno constant; pure constant.
"syscall.Errno", // system call error number type; pure type.
"syscall.GetFileInformationByHandle", // Windows API for file identity (vol serial + file index); read-only syscall.
"syscall.Handle", // Windows file handle type; pure type alias.
"syscall.O_NONBLOCK", // non-blocking open flag; prevents blocking on FIFOs during access checks. Pure constant.
"syscall.Stat_t", // file stat structure type; pure type for Unix file metadata.
"errors.As", // 🟢 error type assertion; pure function, no I/O.
"errors.Is", // 🟢 error comparison; pure function, no I/O.
"errors.New", // 🟢 creates a simple error value; pure function, no I/O.
"fmt.Errorf", // 🟢 formatted error creation; pure function, no I/O.
"io.EOF", // 🟢 sentinel error value; pure constant.
"io.ReadWriteCloser", // 🟢 combined interface type; no side effects.
"io/fs.DirEntry", // 🟢 interface type for directory entries; no side effects.
"io/fs.ErrExist", // 🟢 sentinel error for "already exists"; pure constant.
"io/fs.ErrNotExist", // 🟢 sentinel error for "does not exist"; pure constant.
"io/fs.ErrPermission", // 🟢 sentinel error for permission denied; pure constant.
"io/fs.FileInfo", // 🟢 interface type for file metadata; no side effects.
"io/fs.FileMode", // 🟢 file permission bits type; pure type.
"io/fs.ReadDirFile", // 🟢 read-only directory handle interface; no write capability.
"os.DevNull", // 🟢 platform null device path constant; pure constant.
"os.ErrPermission", // 🟢 sentinel error for permission denied; pure constant.
"os.FileMode", // 🟢 file permission bits type; pure type.
"os.Getgid", // 🟠 returns the numeric group id of the caller; read-only syscall.
"os.Getgroups", // 🟠 returns supplementary group ids; read-only syscall.
"os.Getuid", // 🟠 returns the numeric user id of the caller; read-only syscall.
"os.O_RDONLY", // 🟢 read-only file flag constant; pure constant.
"os.OpenRoot", // 🟠 opens a directory as a root for sandboxed file access; needed for sandbox.
"os.PathError", // 🟢 error type wrapping path and operation; pure type.
"os.Root", // 🟠 sandboxed directory root type; core of the filesystem sandbox.
"os.Stat", // 🟠 returns file info for a path; needed for sandbox path validation.
"path/filepath.Abs", // 🟢 returns absolute path; pure path computation.
"path/filepath.IsAbs", // 🟢 checks if path is absolute; pure function, no I/O.
"path/filepath.Join", // 🟢 joins path elements; pure function, no I/O.
"path/filepath.Rel", // 🟢 returns relative path; pure path computation.
"path/filepath.Separator", // 🟢 OS path separator constant; pure constant.
"slices.SortFunc", // 🟢 sorts a slice with a comparison function; pure function, no I/O.
"strings.Compare", // 🟢 compares two strings lexicographically; pure function, no I/O.
"strings.EqualFold", // 🟢 case-insensitive string comparison; pure function, no I/O.
"strings.HasPrefix", // 🟢 pure function for prefix matching; no I/O.
"syscall.ByHandleFileInformation", // 🟢 Windows file identity structure; pure type for file metadata.
"syscall.EISDIR", // 🟢 "is a directory" errno constant; pure constant.
"syscall.Errno", // 🟢 system call error number type; pure type.
"syscall.GetFileInformationByHandle", // 🟠 Windows API for file identity (vol serial + file index); read-only syscall.
"syscall.Handle", // 🟢 Windows file handle type; pure type alias.
"syscall.O_NONBLOCK", // 🟢 non-blocking open flag; prevents blocking on FIFOs during access checks. Pure constant.
"syscall.Stat_t", // 🟢 file stat structure type; pure type for Unix file metadata.
}
Loading
Loading