Skip to content
Open
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The shell is supported on Linux, Windows and macOS.

- **`df` bypasses `AllowedPaths` for mount-table enumeration.** `df` delegates filesystem listing to `builtins/internal/diskstats`, which on Linux reads `/proc/self/mountinfo` directly via `os.Open` and then calls `unix.Statfs(2)` on every mount point returned by the kernel. On macOS it calls `unix.Getfsstat(2)`. The mount-point paths are kernel-controlled — never derived from user input — so the same trade-off as `ss` / `ip route` applies: operators cannot use `AllowedPaths` to hide individual mounts from `df`. `Statfs` returns metadata only (block / inode counts, filesystem type, block size); no file content is read.

- **`journalctl` bypasses `AllowedPaths` for trusted systemd target access.** The builtin delegates journal discovery and reads, disk-usage scans, vacuuming, machine-ID reads, and journald control-socket access to `internal/systemd`, which opens the configured paths directly with `os` APIs. `SystemdTargetConfig.JournalDirs`, `SystemdTargetConfig.MachineIDPath`, and `SystemdTargetConfig.JournalControlSocket` are fixed by the embedding application when constructing the runner and cannot be supplied or changed by shell scripts. Operators therefore cannot use `AllowedPaths` to block these accesses; authorization is enforced separately by `AllowedCommands`, exact `AllowedSystemServices` grants, and remediation mode for mutations. All configured target paths are trusted and must refer to the same host. See the trusted systemd target exception in `docs/RULES.md` for the backend requirements.
- **`journalctl` and `systemctl` bypass `AllowedPaths` for trusted systemd target access.** The builtins delegate journal discovery and reads, disk-usage scans, vacuuming, machine-ID reads, journald control-socket access, and restricted manager-bus operations to `internal/systemd`, which opens the configured paths directly with `os` APIs. `SystemdTargetConfig.JournalDirs`, `SystemdTargetConfig.MachineIDPath`, `SystemdTargetConfig.JournalControlSocket`, and `SystemdTargetConfig.ManagerBusSocket` are fixed by the embedding application when constructing the runner and cannot be supplied or changed by shell scripts. Operators therefore cannot use `AllowedPaths` to block these accesses; authorization is enforced separately by `AllowedCommands`, exact `AllowedSystemServices` unit/action grants, remediation mode for journal mutations, and remediation mode for every `systemctl` invocation. All configured target paths are trusted and must refer to the same host. On Linux, manager access pins the configured public system D-Bus socket through `/proc/self/fd`, authenticates with EXTERNAL, and verifies the systemd manager peer's machine ID before fixed manager-interface requests. See the trusted systemd target exception in `docs/RULES.md` for the complete backend requirements and indirect systemd effects.

## CRITICAL: Bug Fixes and Bash Compatibility

Expand Down
1 change: 1 addition & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ github.com/DataDog/datadog-agent/pkg/version,https://github.com/DataDog/datadog-
github.com/davecgh/go-spew,https://github.com/davecgh/go-spew,ISC,Copyright (c) 2012-2016 Dave Collins
github.com/ebitengine/purego,https://github.com/ebitengine/purego,Apache-2.0,Copyright 2022 The Ebitengine Authors
github.com/go-ole/go-ole,https://github.com/go-ole/go-ole,MIT,"Copyright (c) 2013-2017 Yasuhiro Matsumoto"
github.com/godbus/dbus/v5,https://github.com/godbus/dbus,BSD-2-Clause,"Copyright (c) 2013, Georg Reinke, Google"
github.com/google/uuid,https://github.com/google/uuid,BSD-3-Clause,Copyright (c) 2018 Google Inc.
github.com/inconshreveable/mousetrap,https://github.com/inconshreveable/mousetrap,Apache-2.0,Copyright 2014 Alan Shreve
github.com/klauspost/compress,https://github.com/klauspost/compress,BSD-3-Clause,"Copyright (c) 2012 The Go Authors; Copyright (c) 2019 Klaus Post"
Expand Down
51 changes: 44 additions & 7 deletions README.md

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions SHELL_FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The in-shell `help` command mirrors these feature categories: run `help` for a c
- ✅ `logrotate (-s SIZE|-f) [-n] [-v] FILE...` — remediation-mode helper that truncates log files to zero bytes through `AllowedPaths`; `--size SIZE` truncates only files at least SIZE bytes, `--force` explicitly truncates without a threshold, `--dry-run` reports without modifying files, and `--verbose` reports per-file actions. Symlinked write targets are rejected with `symlinks are not supported as write targets`; pass the real log path instead. This is not a full `logrotate(8)` replacement: no config parsing, rename-based rotation, retained copies, compression, state files, or rotate scripts.
- ✅ `sort [-rnhubfds] [-k KEYDEF] [-t SEP] [-c|-C] [FILE]...` — sort lines of text files; `-h`/`--human-numeric-sort` orders by SI suffix (none < K/k < M < G < T < P < E < Z < Y < R < Q) then by numeric value (single-letter suffixes only — `Ki`, `Mi`, etc. are not recognised); `-o`, `--compress-program`, and `-T` are rejected (filesystem write / exec)
- ✅ `ss [-tuaxlans4689Hoehs] [OPTION]...` — display network socket statistics; reads kernel socket state directly via `os.Open` (bypassing `AllowedPaths`) from: Linux: `/proc/net/`; macOS: sysctl; Windows: iphlpapi.dll; `-F`/`--filter` (GTFOBins file-read), `-p`/`--processes` (PID disclosure), `-K`/`--kill`, `-E`/`--events`, and `-N`/`--net` are rejected
- ✅ `systemctl [--system] [--no-pager] COMMAND [OPTION]... [UNIT]...` — remediation-mode-only bounded Linux system-manager inspection and control for exact, fully suffixed unit names; every invocation, including `--help`, bare/list inspection, and state predicates, fails before grant lookup or manager access in read-only mode; once enabled, a bare invocation is restricted `list-units`, which supports `--all`, `--type`, `--state`, and `--no-legend` but considers only units with exact `read` grants; by default it returns already-loaded units that are active, failed, or carrying a job, while `--all` may load valid read-granted candidates and includes inactive units (nonexistent names may be omitted); read-granted operations are `status`, fixed-property `show`, `is-active`, `is-failed`, and `is-enabled`; mutating operations are `start`, `stop`, `reload`, `restart`, `try-restart`, `reload-or-restart`, `try-reload-or-restart`, `reset-failed`, and `enable`/`disable` (optionally `--now`) with exact action grants; compound operations authorize every required action before any effect; runtime jobs use fixed `replace` mode, process multiple anchors sequentially in operand order, and may act on dependency-related units through normal systemd transaction semantics; enable/disable may follow `[Install] Alias=`, `Also=`, and template `DefaultInstance=` into auxiliary unit-file changes, then globally reload the manager, which may run generators and pick up unrelated host changes; granted unit payloads, install metadata, aliases, and dependency graphs are operator-trusted, so dedicated lifecycle verbs being absent does not make lifecycle effects impossible through a granted unit; every manager-backend operation has a fixed 30-second cap in addition to the runner deadline; accepts `.service`, `.timer`, `.socket`, and other valid unit types, with at most 32 operands and 64 KiB per returned field; `status` omits process command lines and logs, `show` has a fixed safe property allowlist with type-specific `Result` and service-only process fields, and every human-readable field is sanitized; arbitrary enumeration/properties, globs, implicit `.service`, user/machine/root/image targets, `clean`, standalone `daemon-reload`, `kill`, unit-file editing/linking/masking/presets, dedicated power-management verbs, asynchronous jobs, arbitrary job modes, and explicit dependency-expansion switches are unavailable; requires procfs descriptor links at `/proc/self/fd` and uses the configured public system D-Bus socket without executing host `systemctl`
- ✅ `ls [-1aAdFhlpRrSt] [--offset N] [--limit N] [FILE]...` — list directory contents; `--offset`/`--limit` are non-standard pagination flags (single-directory only, silently ignored with `-R` or multiple arguments, capped at 1,000 entries per call); offset operates on filesystem order (not sorted order) for O(n) memory
- ✅ `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; `CMD` shows only the process comm/executable name, never argv
Expand Down Expand Up @@ -122,12 +123,12 @@ The in-shell `help` command mirrors these feature categories: run `help` for a c
## Execution

- ✅ AllowedCommands — restricts which commands (builtins or external) may be executed; commands require the `rshell:` namespace prefix (e.g. `rshell:cat`); if not set, no commands are allowed
- ✅ AllowedSystemServices policy — one shared default-deny capability map for exact services with generic `read`, `clean`, `reload`, and `restart` actions; actionless grants are ignored, invalid services/actions are skipped, service names are case-sensitive, are not normalized, and cannot contain `:`, mutating actions require remediation mode, and allowing all commands does not bypass this policy; configure services through `interp.AllowedSystemServices` or CLI `--allowed-services SERVICE:ACTION[+ACTION...]`
- ✅ SystemdTargetConfig — journal-aware builtins use standard local paths by default; explicit `JournalDirs`, `MachineIDPath`, and `JournalControlSocket` paths support container mount layouts without falling back to local endpoints; target paths are trusted configuration and bypass `AllowedPaths`; explicit mounts must all refer to the same host
- ✅ AllowedSystemServices policy — one shared default-deny capability map for exact unit names with `read`, `clean`, `start`, `stop`, `reload`, `restart`, `reset-failed`, `enable`, and `disable` actions; actionless grants are ignored, invalid names/actions are skipped, names are case-sensitive, are not normalized, and cannot contain `:` or globs; all valid unit types are accepted; `read` remains usable by bounded `journalctl` queries in read-only mode, every non-read action requires remediation mode, and the entire `systemctl` builtin separately requires remediation mode while `list-units` sees only exact `read` grants; allowing all commands does not bypass this policy; configure grants through `interp.AllowedSystemServices` or CLI `--allowed-services UNIT:ACTION[+ACTION...]`
- ✅ SystemdTargetConfig — systemd-aware builtins use standard local paths by default; explicit `JournalDirs`, `MachineIDPath`, `JournalControlSocket`, and `ManagerBusSocket` paths support container mount layouts without falling back to local endpoints; target paths are trusted configuration and bypass `AllowedPaths`; explicit mounts must all refer to the same host; manager operations use the public system D-Bus socket (default `/run/dbus/system_bus_socket`), pin its inode, authenticate, and verify the systemd manager peer's machine ID, while the private `/run/systemd/private` endpoint is unsupported
- ✅ AllowedPaths filesystem sandboxing — restricts all file access (read and write) to specified directories. Entries may end with `:ro` or `:rw` to indicate read-only and read-write permissions, respectively; entries without a suffix default to read-only. In remediation mode, write operations are accepted only inside the most-specific matching `:rw` root. Cross-root symlink fallback is read-only to avoid TOCTOU on writes; on Unix, symlink components in write targets are rejected with `symlinks are not supported as write targets` via a no-follow `openat` walk
- ✅ Whole-run execution timeout — callers can bound a `Run()` call via `context.Context`, `interp.MaxExecutionTime`, or the CLI `--timeout` flag; the deadline applies to the entire script, not each individual command
- ✅ ProcPath — overrides the proc filesystem path used by `ps` (default `/proc`; Linux-only; useful for testing/container environments); `ps` does not read `/proc/<pid>/cmdline`
- ✅ RemediationMode — opt-in mode (`interp.WithMode(interp.ModeRemediation)` / `--mode remediation`) that enables file-target output redirections (`>`, `>>`, `2>`, `&>`, `&>>`) within `:rw` `AllowedPaths` and remediation-only builtins such as `truncate` and `logrotate`; targets outside the allowlist or inside read-only roots fail with `permission denied` (exit 1); symlinked write targets fail with `symlinks are not supported as write targets`; `/dev/null` always accepted; `<>` remains blocked
- ✅ RemediationMode — opt-in mode (`interp.WithMode(interp.ModeRemediation)` / `--mode remediation`) that enables file-target output redirections (`>`, `>>`, `2>`, `&>`, `&>>`) within `:rw` `AllowedPaths` and remediation-only builtins such as `truncate`, `logrotate`, and the entire restricted `systemctl` surface; targets outside the allowlist or inside read-only roots fail with `permission denied` (exit 1); symlinked write targets fail with `symlinks are not supported as write targets`; `/dev/null` always accepted; `<>` remains blocked
- ❌ External commands — blocked by default; requires an ExecHandler to be configured and the binary to be within AllowedPaths
- ❌ Background execution: `cmd &`
- ❌ Coprocesses: `coproc`
Expand Down
24 changes: 24 additions & 0 deletions analysis/symbols_builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,19 @@ var builtinPerCommandSymbols = map[string][]string{
// Note: builtins/internal/sizeparse symbols are exempt from this
// allowlist (internal packages are checked separately).
},
"systemctl": {
"context.Context", // 🟢 deadline/cancellation plumbing; pure interface, no side effects.
"fmt.Errorf", // 🟢 constructs bounded validation and backend errors in memory; no I/O.
"slices.SortFunc", // 🟢 deterministically sorts authorized selectors and bounded state values; pure in-memory operation.
"strconv.FormatInt", // 🟢 formats fixed numeric status properties; pure conversion.
"strconv.FormatUint", // 🟢 formats fixed numeric status properties; pure conversion.
"strings.LastIndexByte", // 🟢 locates a unit's final suffix separator; pure string inspection.
"strings.Map", // 🟢 sanitizes untrusted manager text at the output boundary; pure string transformation.
"strings.SplitSeq", // 🟢 streams bounded filter/property tokens without allocating an attacker-sized slice.
"strings.ToValidUTF8", // 🟢 replaces malformed manager output before display; pure string transformation.
"unicode.IsGraphic", // 🟢 identifies non-graphic manager output that must be sanitized; pure lookup.
"unicode/utf8.ValidString", // 🟢 validates exact unit selectors before authorization or backend access; pure inspection.
},
"logrotate": {
"context.Context", // 🟢 deadline/cancellation plumbing; pure interface, no side effects.
"errors.Is", // 🟢 error comparison; pure function, no I/O.
Expand Down Expand Up @@ -572,6 +585,7 @@ var callCtxAllFields = []string{
"ReadDir",
"ReadDirLimited",
"ReadlinkFile",
"ReadableSystemServices",
"RunCommand",
"RunCommandWithStdin",
"SetVar",
Expand Down Expand Up @@ -661,6 +675,11 @@ var builtinPerCommandCallContextFields = map[string][]string{
"AuthorizeSystemd",
"Systemd",
},
"systemctl": {
"AuthorizeSystemd",
"ReadableSystemServices",
"Systemd",
},
"logrotate": {
"PortableErr",
"TruncateToZeroIfAtLeast",
Expand Down Expand Up @@ -852,9 +871,13 @@ var builtinAllowedSymbols = []string{
"strings.HasSuffix", // 🟢 pure function for suffix matching; no I/O.
"strings.IndexByte", // 🟢 finds byte in string; pure function, no I/O.
"strings.Join", // 🟢 concatenates a slice of strings with a separator; pure function, no I/O.
"strings.LastIndexByte", // 🟢 finds the final byte occurrence in a string; pure function, no I/O.
"strings.Map", // 🟢 transforms runes in a string using a caller-supplied pure mapper; no I/O.
"strings.ReplaceAll", // 🟢 replaces all occurrences of a substring; pure function, no I/O.
"strings.Split", // 🟢 splits a string by separator into a slice; pure function, no I/O.
"strings.SplitSeq", // 🟢 iterates over string tokens without allocating a result slice; pure transformation.
"strings.ToLower", // 🟢 converts string to lowercase; pure function, no I/O.
"strings.ToValidUTF8", // 🟢 replaces invalid UTF-8 byte sequences; pure function, no I/O.
"strings.TrimPrefix", // 🟢 removes a leading prefix from a string; pure function, no I/O.
"strings.TrimSpace", // 🟢 removes leading/trailing whitespace; pure function.
"syscall.ByHandleFileInformation", // 🟢 Windows file info struct for extracting nlink; read-only type, no I/O.
Expand Down Expand Up @@ -883,6 +906,7 @@ var builtinAllowedSymbols = []string{
"unicode.Co", // 🟢 private-use character category range table; pure data, no I/O.
"unicode.Is", // 🟢 checks if rune belongs to a range table; pure function, no I/O.
"unicode.IsGraphic", // 🟢 reports whether rune is defined as a graphic character; pure function, no I/O.
"unicode/utf8.ValidString", // 🟢 reports whether a string contains valid UTF-8; pure function, no I/O.
"unicode.Me", // 🟢 enclosing mark category range table; pure data, no I/O.
"unicode.Mn", // 🟢 nonspacing mark category range table; pure data, no I/O.
"unicode.Range16", // 🟢 struct type for 16-bit Unicode ranges; pure data.
Expand Down
Loading
Loading