Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
60ac370
feat(df): add df builtin for disk space usage reporting
julesmcrt Apr 30, 2026
9c867c4
fix(df): address CI failures and Codex review feedback
julesmcrt Apr 30, 2026
64586bf
fix(df): tighten Windows pentest skips and relax fuzz contract
julesmcrt Apr 30, 2026
06f3f3e
fix(df): drop platform-specific YAML scenarios that fail on Windows
julesmcrt Apr 30, 2026
d44c17e
fix(df): address Codex round-2 GNU-compat review
julesmcrt Apr 30, 2026
69e3ca9
fix(df): align tests with new pseudo-FS classification and exit-1 path
julesmcrt Apr 30, 2026
d2bc41e
fix(df): pre-stat filter, dedup bind-mounts, document AllowedPaths ex…
julesmcrt Apr 30, 2026
2afb4bb
fix(df): FUSE remote subtypes, README sandbox-bypass note, humanBytes…
julesmcrt Apr 30, 2026
07ff0f0
fix(df): pseudo-is-local, dedup by device + shortest mount, no comma-…
julesmcrt Apr 30, 2026
1a9d850
fix(df): saturating block multiply + last-flag-wins for -h/-H
julesmcrt Apr 30, 2026
37fd206
fix(df): -k joins last-flag-wins, reject overlapping -t/-x
julesmcrt Apr 30, 2026
f062ed7
fix(df): GNU header alignment for human Avail and inode-POSIX IUse%
julesmcrt Apr 30, 2026
30babd7
fix(df): GNU layout for -P combos; reject nonstandard --kibibytes
julesmcrt May 1, 2026
161dc72
chore: gitignore .claude/scheduled_tasks.lock
julesmcrt May 1, 2026
8bf221b
fix(df): pseudo filter independent of -t; enable df in fuzz runner
julesmcrt May 4, 2026
bb6f851
fix(df): clean -k rendering in df --help
julesmcrt May 4, 2026
02750e5
fix(df): drop single-space POSIX layout, always use GNU aligned columns
julesmcrt May 4, 2026
689ea90
fix(df): percentUsed handles denominator overflow
julesmcrt May 4, 2026
c15a7a5
fix(df): reject explicit values for unit flags
julesmcrt May 4, 2026
4442436
fix(df): lowercase k for SI kilo suffix
julesmcrt May 4, 2026
0399295
fix(df): reject explicit values on all boolean flags
julesmcrt May 4, 2026
73932ea
fix(df): NUL-byte sentinel rejects all explicit-value forms of no-arg…
julesmcrt May 5, 2026
ca30987
fix(df): use f_frsize for Linux statfs block counts
julesmcrt May 5, 2026
f33df40
fix(df): preserve kernel mount-table order, drop alphabetical sort
julesmcrt May 5, 2026
c8b06f3
fix(df): clear NoOptDefVal NUL sentinel before printing --help
julesmcrt May 5, 2026
b0525ad
fix(df): classify mounts as remote by source shape, not just type
julesmcrt May 5, 2026
27149a5
fix(df): seed columns with GNU's minimum widths
julesmcrt May 5, 2026
18140fa
fix(df): keep squashfs visible in default listing
julesmcrt May 5, 2026
79d2373
[iter 1] fix(df): replace control characters in mount names before pr…
julesmcrt May 6, 2026
4ef1242
fix(df): address review feedback (humanBytes, printHelp, tests, gitig…
julesmcrt May 6, 2026
078c039
Merge remote-tracking branch 'origin/main' into jules.macret/host-rem…
julesmcrt May 6, 2026
af7543a
test(df): add 21 scenario tests covering flags, errors, and composition
julesmcrt May 7, 2026
1a3884c
Merge remote-tracking branch 'origin/main' into jules.macret/host-rem…
julesmcrt May 7, 2026
d78c0e5
fix(df): preserve non-zero usage when sum-overflow scaling truncates
julesmcrt May 7, 2026
2c30254
test(df): drop skip_assert_against_bash where bash and rshell already…
julesmcrt May 7, 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
13 changes: 13 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ jobs:
- pkg: ./builtins/tests/ps/
name: ps
corpus_path: builtins/tests/ps
- pkg: ./builtins/df/
name: df
# df fuzz tests live in builtins/df/ (not builtins/tests/df/)
# because the test-helper functions (firstLine, requireSupported)
# are defined in df_test.go and only visible to files in the
# same directory.
corpus_path: builtins/df
- pkg: ./builtins/internal/diskstats/
name: diskstats
# The mountinfo parser is the most security-sensitive parser
# in df. Fuzzing it directly is much faster than going
# through the shell runner.
corpus_path: builtins/internal/diskstats
- pkg: ./builtins/tests/xargs/
name: xargs
corpus_path: builtins/tests/xargs
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ The shell is supported on Linux, Windows and macOS.

- **`ss` and `ip route` bypass `AllowedPaths` for `/proc/net/*` reads.** Both builtins delegate `/proc/net/` I/O to internal packages (`builtins/internal/procnetsocket` for `ss`, `builtins/internal/procnetroute` for `ip route`) that call `os.Open` directly on kernel pseudo-filesystem paths (e.g. `/proc/net/tcp`, `/proc/net/route`). These paths are hardcoded in the implementation and are never derived from user input, so `AllowedPaths` restrictions do not apply to them. As a consequence, operators cannot use `AllowedPaths` to block `ss` from enumerating local sockets or `ip route` from reading the routing table. This is an intentional trade-off: the paths are non-user-controllable, so there is no sandbox-escape risk, but the operator loses the ability to deny these reads via sandbox configuration.

- **`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.
Comment thread
julesmcrt marked this conversation as resolved.

## CRITICAL: Bug Fixes and Bash Compatibility

- **ALWAYS prioritise fixing the shell implementation to match bash behaviour over changing tests to match the current (incorrect) shell output.** Never "fix" a failing test by updating its expected output to match broken shell behaviour β€” fix the shell instead.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Every access path is default-deny:

**AllowedPaths** restricts all file operations to specified directories using Go's `os.Root` API (`openat` syscalls), making it immune to symlink traversal, TOCTOU races, and `..` escape attacks. Configured directories that cannot be opened (missing, not a directory, no permission) are skipped with a diagnostic message; by default these messages are flushed once to the runner's stderr at construction time. Callers that need to keep stderr clean of sandbox diagnostics can route them to a dedicated sink with `WarningsWriter(io.Writer)` or retrieve them programmatically via `Runner.Warnings()`.

> **Note:** The `ss` and `ip route` builtins bypass `AllowedPaths` for their `/proc/net/*` reads. Both builtins open kernel pseudo-filesystem paths (e.g. `/proc/net/tcp`, `/proc/net/route`) directly with `os.Open` rather than going through the sandboxed opener. These paths are hardcoded in the implementation and are never derived from user input, so there is no sandbox-escape risk. However, operators cannot use `AllowedPaths` to block `ss` from enumerating local sockets or `ip route` from reading the routing table β€” these reads succeed regardless of the configured path policy.
> **Note:** The `ss`, `ip route`, and `df` builtins bypass `AllowedPaths` for their kernel-state reads. `ss` and `ip route` open `/proc/net/*` paths directly; `df` reads `/proc/self/mountinfo` (Linux) or calls `getfsstat(2)` (macOS), then issues `unix.Statfs(2)` against every kernel-reported mount point. These paths are hardcoded β€” never derived from user input β€” and `Statfs` returns metadata only (block / inode counts, filesystem type, block size). There is no sandbox-escape risk, but operators cannot use `AllowedPaths` to block `ss` from enumerating local sockets, `ip route` from reading the routing table, or `df` from reporting mount-table capacity β€” these reads succeed regardless of the configured path policy.

**ProcPath** (Linux-only) overrides the proc filesystem root used by the `ps` builtin (default `/proc`). This is a privileged option set at runner construction time by trusted caller code β€” scripts cannot influence it. Access to the proc path is intentionally not subject to `AllowedPaths` restrictions, since proc is a read-only virtual filesystem that does not expose host data under the normal file hierarchy.

Expand Down
1 change: 1 addition & 0 deletions SHELL_FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The in-shell `help` command mirrors these feature categories: run `help` for a c
- βœ… `cat [-AbeEnstTuv] [FILE]...` β€” concatenate files to stdout; supports line numbering, blank squeezing, and non-printing character display
- βœ… `continue` β€” skip to the next iteration of the innermost `for` loop
- βœ… `cut [-b LIST|-c LIST|-f LIST] [-d DELIM] [-s] [-n] [--complement] [--output-delimiter=STRING] [FILE]...` β€” remove sections from each line of files
- βœ… `df [-hHkPTialx] [-t TYPE] [-x TYPE] [--total] [--no-sync]` β€” report file system disk space usage (Linux/macOS only; on Windows `df` exits 1 with `df: not supported on this platform` because mount enumeration goes through `/proc/self/mountinfo` on Linux and `getfsstat(2)` on macOS, neither of which has a Windows equivalent); Linux reads `/proc/self/mountinfo` directly via `os.Open`, bypassing `AllowedPaths`; positional `FILE` operands and `--sync`, `-B`, `--output` are not supported; mount table capped at 100 000 entries
- βœ… `du [-asScSLP0bhkm] [-d N] [--apparent-size|--si] [FILE]...` β€” estimate file space usage; recursion capped at depth 256 and hardlink-dedup tracking capped at 2²⁰ entries; `--files0-from`, `--exclude-from`/`-X`, `--exclude` are rejected (data-exfiltration / file-driven control); `-B`/`--block-size`, `-t`/`--threshold`, `-x`/`--one-file-system`, `--inodes`, `--time`, `-l`/`--count-links` are not implemented
- βœ… `echo [-neE] [ARG]...` β€” write arguments to stdout; `-n` suppresses trailing newline, `-e` enables backslash escapes, `-E` disables them (default)
- βœ… `exit [N]` β€” exit the shell with status N (default 0)
Expand Down
14 changes: 14 additions & 0 deletions analysis/symbols_builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ var builtinPerCommandSymbols = map[string][]string{
"strings.IndexByte", // 🟒 finds byte in string; pure function, no I/O.
"strings.Split", // 🟒 splits a string by separator into a slice; pure function, no I/O.
},
"df": {
"context.Context", // 🟒 deadline/cancellation plumbing; pure interface, no side effects.
"errors.Is", // 🟒 error comparison via chain; pure function, no I/O.
"errors.New", // 🟒 creates a sentinel error (unitFlag.Set rejects explicit values); pure function, no I/O.
"fmt.Sprintf", // 🟒 string formatting; pure function, no I/O.
"math.Ceil", // 🟒 ceiling of a float64; pure function, no I/O. Used for GNU-compatible round-up of human-readable sizes.
"strconv.FormatUint", // 🟒 uint-to-string conversion; pure function, no I/O.
"strings.Builder", // 🟒 efficient string concatenation; pure in-memory buffer, no I/O.
"strings.Repeat", // 🟒 returns a string of n repetitions; pure function, no I/O.
// Note: builtins/internal/diskstats symbols are exempt from this
// allowlist (internal packages are not checked by the
// builtinAllowedSymbols test).
},
"echo": {
"context.Context", // 🟒 deadline/cancellation plumbing; pure interface, no side effects.
"strings.Builder", // 🟒 efficient string concatenation; pure in-memory buffer, no I/O.
Expand Down Expand Up @@ -583,6 +596,7 @@ var builtinAllowedSymbols = []string{
"slices.Reverse", // 🟒 reverses a slice in-place; pure function, no I/O.
"slices.SortFunc", // 🟒 sorts a slice with a comparison function; pure function, no I/O.
"slices.SortStableFunc", // 🟒 stable sort with a comparison function; pure function, no I/O.
"strings.Repeat", // 🟒 returns a string of n repetitions; pure function, no I/O.
"strconv.Atoi", // 🟒 string-to-int conversion; pure function, no I/O.
"strconv.ErrRange", // 🟒 sentinel error value for overflow; pure constant.
"strconv.FormatBool", // 🟒 bool-to-string conversion; pure function, no I/O.
Expand Down
Loading
Loading