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
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe("security", () => {

it("sandbox escape blocked", () => {
const bash = new Bash();
assert.notEqual(bash.executeSync("exec /bin/bash").exitCode, 0);
assert.notEqual(bash.executeSync("exec /usr/bin/gcc").exitCode, 0);
assert.notEqual(bash.executeSync("cat /proc/self/maps 2>&1").exitCode, 0);
assert.ok(!bash.executeSync("cat /etc/passwd 2>&1").stdout.includes("root:x:0:0"));
assert.notEqual(
Expand Down
5 changes: 4 additions & 1 deletion crates/bashkit-js/__test__/security.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,11 @@ test("WB: stderr truncation on massive error output", (t) => {
test("WB: exec cannot escape sandbox (TM-ESC-001)", (t) => {
const bash = new Bash();
// exec runs commands within VFS sandbox — external binaries don't exist
const r = bash.executeSync("exec /bin/bash");
const r = bash.executeSync("exec /usr/bin/gcc");
t.not(r.exitCode, 0, "exec of external binary must fail in sandbox");
// /bin/bash is a rootfs stub that re-enters the in-process interpreter
const inner = bash.executeSync("exec /bin/bash -c 'cat /etc/passwd'");
t.false(inner.stdout.includes("root:x:0:0"), "host passwd must not leak");
});

test("WB: /proc filesystem not accessible (TM-ESC-003)", (t) => {
Expand Down
16 changes: 16 additions & 0 deletions crates/bashkit/docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,21 @@ Chrono's validated strftime implementation, plus GNU `%N`, `%3N`, `%6N`, and
| `mktemp` | `-d`, `-p`, `-t` | Create temporary files |
| `realpath` | `PATH` | Resolve path |
| `pushd`/`popd`/`dirs` | standard flags | Directory stack |
| `arch` | (none) | Virtual machine name (`x86_64`) |
| `sum` | `-r`, `-s` | BSD and System V checksums, GNU output format |
| `shasum` | `-a 1/224/256/384/512` | Perl `shasum` front end over the `sha*sum` builtins |
| `egrep`/`fgrep` | `grep` flags | `grep -E` / `grep -F` |
| `link`/`unlink` | `FILE1 FILE2` / `FILE` | Single-file link (a symlink, L-FS-001) and remove |
| `chgrp` | `GROUP FILE...` | Change group (virtual, like `chown`) |
| `nohup`/`nice` | `COMMAND...`, `nice -n N` | Run the command; no signal or priority effect in a sandbox |
| `flock` | `FILE CMD`, `FILE -c CMD`, `-n FD` | Creates the lock file and runs the command; locks are uncontended in one session |
| `getconf` | `NAME`, `-a` | POSIX config values, consistent with `nproc` and `/proc/meminfo` |
| `tty` | `-s` | "not a tty" unless the embedder sets `tty(0, true)`, like `[ -t 0 ]` |
| `sync`/`hostid` | (none) | No-op / fixed virtual host id |
| `groups`/`logname` | `[USER]` | Virtual user only |
| `users`/`who` | (none) | Empty: no login sessions |
| `uptime` | `-p`, `-s` | Derived from the virtual clock |
| `free` | `-b`, `-k`, `-m`, `-g`, `-h`, `-t` | Fixed virtual memory, consistent with `/proc/meminfo` |

### Not Implemented

Expand Down Expand Up @@ -225,6 +240,7 @@ Chrono's validated strftime implementation, plus GNU `%N`, `%3N`, `%6N`, and
| `2>` | ✅ | `cmd 2> file` | Stderr redirect |
| `2>&1` | ✅ | `cmd 2>&1` | Stderr to stdout |
| `&>` | ✅ | `cmd &> file` | Both to file |
| `N>`/`N>>` (N≥3) | ✅ | `{ cmd >&3; } 3>file` | Opens fd N only; stdout untouched |

### Control Flow

Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ Scripts may attempt to break out of the sandbox to access the host system.

| Threat | Attack Example | Mitigation | Status |
|--------|---------------|------------|--------|
| Shell escape (TM-ESC-005) | `exec /bin/bash` | Not implemented (exit 127) | MITIGATED |
| Shell escape (TM-ESC-005) | `exec /bin/bash` | Re-enters the in-process interpreter; host binaries are unreachable (exit 127) | MITIGATED |
| External commands (TM-ESC-006) | `./malicious` | Runs in VFS sandbox, no host shell | MITIGATED |
| Background proc (TM-ESC-007) | `malicious &` | Background not implemented | MITIGATED |
| eval injection (TM-ESC-008) | `eval "$input"` | Sandboxed eval (builtins only) | MITIGATED |
Expand Down
11 changes: 10 additions & 1 deletion crates/bashkit/src/builtins/checksum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,20 @@ async fn read_operand(ctx: &Context<'_>, file: &str) -> std::result::Result<Vec<
}

async fn checksum_execute<D: Digest>(ctx: &Context<'_>, cmd: &str) -> Result<ExecResult> {
checksum_execute_args::<D>(ctx, cmd, ctx.args).await
}

/// [`checksum_execute`] over explicit arguments (`shasum` strips `-a N`).
pub(super) async fn checksum_execute_args<D: Digest>(
ctx: &Context<'_>,
cmd: &str,
args: &[String],
) -> Result<ExecResult> {
let mut files: Vec<&str> = Vec::new();
let mut end_of_options = false;
let mut opts = CheckOpts::default();

for arg in ctx.args {
for arg in args {
if end_of_options || arg == "-" || !arg.starts_with('-') {
files.push(arg);
continue;
Expand Down
Loading
Loading