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
8 changes: 6 additions & 2 deletions crates/bashkit-js/__test__/security.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,13 @@ test("WB: recursive function depth limited (TM-DOS-020)", (t) => {
test("WB: fork bomb pattern blocked (TM-DOS-021)", (t) => {
const bash = new Bash({ maxCommands: 100 });
const r = bash.executeSync(":(){ :|:& };:");
// Should fail — no real process forking and command limit
// Background jobs run concurrently, so like real bash the top-level `:`
// returns 0 once it has backgrounded the bomb. The bomb itself must hit a
// limit (job slots, function depth or command budget) and terminate.
t.true(
r.exitCode !== 0 || r.error !== undefined,
r.exitCode !== 0 ||
r.error !== undefined ||
/limit|fork: retry/.test(r.stderr),
"fork bomb pattern must not run indefinitely",
);
});
Expand Down
10 changes: 7 additions & 3 deletions crates/bashkit/docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ for sandbox security reasons. See the compliance spec for details.
| `cut` | `-d DELIM`, `-f FIELDS` | Extract fields |
| `tr` | `-d`, character ranges | Translate/delete chars |
| `date` | `+FORMAT`, `-u`, `-R`, `-I`, `-r`, `-d`/`--date` (relative, compound, epoch) | Display/format date with sandboxed IANA timezone support |
| `wait` | `[JOB_ID...]` | Wait for background jobs |
| `wait` | `[-n] [-p VAR] [-f] [ID...]` (PID or `%jobspec`) | Wait for background jobs |
| `jobs` | `-l`, `-p` | List running background jobs |
| `ps` | `-f`, `aux`, `-o`, `-p` | Virtual process list (shell + jobs) |
| `pgrep`/`pkill` | `-f`, `-l`, `-a`, `-c`, `-x`, `-SIGNAL` | Match jobs by command |
| `disown`, `fg`, `bg` | `[JOBSPEC]` | `fg` waits; `bg` is a no-op (jobs already run) |
| `curl` | `-s`, `-o`, `-X`, `-d`/`--data`, `--data-raw`, `--data-binary`, `--data-urlencode`, `-G`/`--get`, `-H`, `-I`, `-f`, `-L`, `-w`, `--compressed`, `-u`, `-A`, `-e`, `-v`, `-m` | HTTP client (requires http_client feature) |
| `wget` | `-q`, `-O`, `--spider`, `--header`, `-U`, `--post-data`, `-t` | Download files (requires http_client feature) |
| `time` | `[-p] [-f FORMAT] [-o FILE] [-a] [-v] [--] PIPELINE` | Reserved-word timing; elapsed/status/Bashkit counters are truthful, host CPU/RSS fields say `unavailable` |
Expand Down Expand Up @@ -164,7 +168,7 @@ Chrono's validated strftime implementation, plus GNU `%N`, `%3N`, `%6N`, and
|---------|-------------------|-------|
| `ln` | `-s`, `-f` | Create links |
| `chown` | `OWNER[:GROUP] FILE` | Change ownership (virtual) |
| `kill` | `-SIGNAL PID` | Send signals (virtual) |
| `kill` | `-l`, `-s SIG`, `-n NUM`, `-SIG`, `PID`/`%job` | Terminate background jobs (exit 128+signal); stop/continue signals are ignored |
| `trap` | `COMMAND SIGNAL...`, `-p`, `-l` | Signal/event handlers |
| `type` | `NAME...` | Describe command type |
| `which` | `NAME...` | Locate a command |
Expand Down Expand Up @@ -465,7 +469,7 @@ cargo test --test spec_tests -- bash_comparison_tests --ignored
- [x] Byte inspection (`strings`, `od`, `xxd`, `hexdump`)
- [x] `basename`/`dirname` builtins
- [x] `date` builtin
- [x] Background execution (`&`, `wait`) - parsed, runs synchronously
- [x] Background execution (`&`, `wait`) - jobs run concurrently on the session's task (no stop/continue)
- [x] Network (`curl`, `wget`) - full HTTP implementation with security mitigations
- [x] `timeout` builtin - stub, requires interpreter-level integration
- [x] Process substitution (`<(cmd)`, `>(cmd)`)
Expand Down
3 changes: 2 additions & 1 deletion crates/bashkit/docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ through configurable limits.
| Builtin parser depth (TM-DOS-027) | Deeply nested awk/jq | `MAX_AWK_PARSER_DEPTH` (100) + `MAX_JQ_JSON_DEPTH` (100) | MITIGATED |
| Collect dirs recursion (TM-DOS-049) | Deep VFS tree | Mitigated by `max_path_depth` | MITIGATED |
| Python read-only self-mount recursion (TM-DOS-118) | `bash.mount("/", bash.fs(), read_only=True)` hides the live filesystem identity behind a wrapper | Python validates the resolved filesystem identity before wrapping it | MITIGATED |
| find traversal amplification (TM-DOS-121) | `find -L` over a symlink cycle, unbounded `-exec ... {} +` batches, or `*`-heavy `-name` patterns | Canonical-path loop detection with a 40-hop symlink cap, budget-charged directory listings, capped output, batches flushed every 4096 paths, linear-time glob matching |
| find traversal amplification (TM-DOS-121) | `find -L` over a symlink cycle, unbounded `-exec ... {} +` batches, or `*`-heavy `-name` patterns | Canonical-path loop detection with a 40-hop symlink cap, budget-charged directory listings, capped output, batches flushed every 4096 paths, linear-time glob matching | MITIGATED |
| Background job flooding (TM-DOS-122) | `while :; do sleep 99 & done` holds unbounded concurrent interpreters | `max_background_jobs` (64 default, 16 hardened) caps live jobs; extra `&` fails like bash's fork EAGAIN; jobs share the session budget, timeout and cancellation; every job is reaped when `exec()` returns | MITIGATED |

**Parser and Arithmetic:**

Expand Down
48 changes: 0 additions & 48 deletions crates/bashkit/src/builtins/fileops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -992,54 +992,6 @@ impl Builtin for Chown {
}
}

/// The kill builtin - send signal to process (no-op in VFS).
///
/// Usage: kill [-s SIGNAL] [-SIGNAL] PID...
///
/// Since there are no real processes in the virtual environment, kill is a no-op
/// that accepts the command syntax for compatibility.
pub struct Kill;

#[async_trait]
impl Builtin for Kill {
async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
if let Some(r) = super::check_help_version(
ctx.args,
"Usage: kill [-s SIGNAL | -SIGNAL] PID...\nSend a signal to a process.\n\n -s SIGNAL\tspecify the signal to send\n -l, -L\tlist signal names\n --help\tdisplay this help and exit\n --version\toutput version information and exit\n",
Some("kill (bashkit) 0.1"),
) {
return Ok(r);
}

let mut pids: Vec<&str> = Vec::new();

for arg in ctx.args {
if arg == "-l" || arg == "-L" {
// List signal names
return Ok(ExecResult::ok(
"HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM\n"
.to_string(),
));
}
if arg.starts_with('-') {
continue; // skip signal spec
}
pids.push(arg);
}

if pids.is_empty() {
return Ok(ExecResult::err(
"kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ...\n"
.to_string(),
2,
));
}

// In VFS, no real processes exist — just succeed silently
Ok(ExecResult::ok(String::new()))
}
}

/// The mktemp builtin - create temporary files or directories.
///
/// Argument surface is generated from uutils/coreutils' `uu_app()` via
Expand Down
Loading
Loading