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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ even for a one-line doc fix.
reads that release's `.sha256` sidecars and pushes with your own `gh`/git auth, so
there's no CI secret to rotate. Deliberately manual: releases are human-cut, so this
is the ritual's last step, not a workflow job.
- **kaish pin.** Currently `kaish-kernel = "0.17.0"` (from 0.14.1, inheriting three
- **kaish pin.** Currently `kaish-kernel = "0.17.1"` (from 0.14.1, inheriting four
releases' breaks; compile-time exposure was one `#[non_exhaustive]` match arm).
**When you bump kaish, run the shell — a green build is not the check.** That bump's
four behavioral changes all reached the model-facing surface and none broke
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ record. Each later release appends a new section at the top.

### Changed

- **kaish upgraded to 0.17.0** (from 0.14.1) — the read-only shell gains symlink
- **kaish upgraded to 0.17.1** (from 0.14.1) — the read-only shell gains symlink
support, pipeline-stage compound statements, `set -o pipefail`, and base-aware
arithmetic.
- **`readlink -f` and `realpath` resolve a path again** — both failed on every operand
before, naming neither the operand nor its target.
- **`grep -rn PATTERN` is what kaibo teaches now**, without the trailing `.` — kaish
0.16 prefixes hits with the operand as written, so the bare form is the one that
yields repo-relative `file:line` citations.
Expand All @@ -93,6 +95,8 @@ record. Each later release appends a new section at the top.
- **`ls -l`, `stat`, `readlink`, and `find -type l` describe a symlink itself** instead
of following it, so a link is visible as a link. A link pointing outside the project
shows its target path; every read that would follow it out is still refused.
- **The directories above the project list again** — each names only the next component
down to the project, so the shell shows the root path the caller already gave it.
- **A compound statement can feed a pipe** — `for f in …; do …; done | grep x` is no
longer a parse error.
- **`yes` and `no` are ordinary strings**, not lexer errors, so `echo yes` runs.
Expand Down
24 changes: 12 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ path = "src/main.rs"
# `os-integration` (trash) OFF — so those builtins are never compiled in. kaibo's
# read-only safety is thus structural (the dangerous surface doesn't exist),
# backed by the runtime read-only mount; see src/sandbox.rs.
kaish-kernel = { version = "0.17.0", default-features = false, features = ["localfs"] }
kaish-kernel = { version = "0.17.1", default-features = false, features = ["localfs"] }
# `time` is used by the deferred `generate` job's poll cadence (sleep + Instant) —
# named here rather than inherited from a transitive enabler.
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "sync", "time"] }
Expand Down
68 changes: 50 additions & 18 deletions docs/sandbox-probes.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,21 +147,38 @@ ls ~/*.txt ; echo "glob-out=$?"
find /etc -maxdepth 1 ; echo "find-out=$?"
```

**Pass:** everything outside the single mount comes back `not found` — out-of-mount
**Pass:** every *read* outside the single mount comes back `not found` — out-of-mount
paths (including `..`-normalized ones) route into the empty `/` MemoryFs scratch and
404. The adjacent API-key files must be **unreadable**; that's the headline result.
`cd ~` / `cd /home/<user>` fail — only the full mount path is a real directory, so
the prefix can't be walked to a sibling.
The mount's own prefix *directories* (`/home`, `/home/<user>`, …) are the one exception
and they list — see the note below — but each level names only the next component
toward the mount, so it can't be walked to a sibling.

> **`cd / && ls` returns `dev`, `home`, `v`, and that is not a finding.** It is
> synthetic VFS scaffolding, not host content: `/dev/{null,random,urandom,zero}` are
> virtual devices, `/v` is kaish's own builtin toolbox plus ephemeral blob/job scratch,
> and `/home` is an inert stub that cannot be walked (`ls /home`, `ls /home/<user>` both
> `not found`). Only the exact `--root`-resolved absolute path mounts real content.
> and `/home` is the synthesized head of the path down to the mount. Only the exact
> `--root`-resolved absolute path mounts real content.
> Confirm it the way the 2026-07-29 run did: read `$ROOT/Cargo.toml` through the mount
> and watch every sibling path 404. Written down here because a reader meeting that
> listing for the first time reasonably suspects a hole.

> **The prefix is walkable as of kaish 0.17.1, and that is not a finding either.**
> 0.17.0 answered `not found` for every directory above the mount; 0.17.1 restored them
> as synthesized directories, so `ls /home` → `<user>`, `ls /home/<user>` → the next
> component, down to the project. Each level lists **only the component leading to the
> mount** — the host's real siblings, files, and bytes stay absent — so a model walking
> up recovers the root path string the caller already handed it and nothing else. Check
> it by counting: `ls /tmp` returns one entry where the host `/tmp` holds thousands.
>
> **And check the oracle, the way G3 does for links:** `stat` and `realpath` on a real
> host file beside the chain, and on a path that was never created, must refuse
> byte-identically once the operand is removed — those two verbs answer "does this
> exist" without returning bytes, and `realpath` resolves for the first time in 0.17.1.
> Only the synthesized components resolve. Pinned by
> `containment.rs::mount_layer_ancestors_synthesize_the_mount_path_and_nothing_else`,
> which checks two chain levels and carries a recorded positive control.

**Environment leak check** (a secret can hide in env, not just on disk):

```sh
Expand Down Expand Up @@ -403,21 +420,36 @@ detail is in git, and anything durable a run found has been promoted into the ba
belongs to rather than left here to be re-read — that promotion is the point of the
compression, not a side effect of it.

- **2026-09-01** — **Full A–G**, branch `kaish-0.17`, run because the `kaish-kernel`
0.14.1 → 0.17.0 bump trips the kernel/VFS trigger. **All clear.** This bump moved the
*instrument* more than any before it: three pass criteria in this file were false
against 0.17 and are corrected in place (Battery A's `ln -s` reason, Battery B's 127
message, Battery C's now non-empty `env`), and **Battery G is new** for the symlink
boundary 0.17's lstat-by-default opened.
- **The one new observable, accepted:** a link inside the tree pointing outside now
renders its target *string*. G3 is why that is acceptable — existing, missing, and
unreadable targets refuse byte-identically, so there is no existence oracle.
- **Best find:** 0.16 fixed an `env` that bypassed the external-commands gate, and
**kaibo was never exposed** — lever (0) compiles `subprocess` out, verified against
both versions. The four-levers design paying for itself.
- Suites: containment 24, full `cargo test` 1327 passed / 0 failed.
- **2026-09-02** — **Full A–G**, branch `kaish-0.17.1`, run because the `kaish-kernel`
0.17.0 → 0.17.1 patch touches the VFS and so trips the trigger. **All clear.** Every
battery was run against **both** pins and diffed; A, B, D, E, F and G came back
byte-identical, so the two changes below are the whole delta a model can see.
- **The release blocker is fixed:** `readlink -f` and `realpath` resolve an in-tree
path (exit 0) and refuse an escape by name, where 0.17.0 failed on every operand
with `No such file or directory: /tmp`. G3 re-run on the new canonicalize path —
existing, missing, and unreadable targets still refuse byte-identically.
- **The one new observable, accepted:** the directories *above* the mount list again,
each naming only the next component down to the project. Battery C's `/home` note
is corrected in place. Synthesis, not host reads — counted it: `ls /tmp` returns one
entry where the host holds 3575, and `stat`/`realpath` cannot tell a real host file
beside the chain from one that was never created. Adjacent secrets, siblings, and
the state db and media CAS all stay invisible (E2/F2 re-run).
- **The probe caught itself once:** E1 run without `--root` created a state db, because
the fixture was then outside every allowed tree and the guard correctly did not fire.
The §0 question — would this read differently if the probe were broken? — is what
found it.
- Suites: containment 25 (one new), full `cargo test` 1147 passed. The lone failure is
the known `tests/credentials.rs` ETXTBSY exec race under parallelism; green serially,
reproduces on unmodified code.
- §7 not re-run; deferred to the v0.4.0 pre-release check.

- **2026-09-01** — Full A–G, branch `kaish-0.17`, for the 0.14.1 → 0.17.0 bump. All
clear. Three pass criteria here were false against 0.17 and were corrected in place;
**Battery G is new** for the symlink boundary lstat-by-default opened. Its accepted
observable: a link pointing outside renders its target *string*, safe because G3 shows
no existence oracle. Best find: 0.16 fixed an `env` that bypassed the external-commands
gate and **kaibo was never exposed** — lever (0) compiles `subprocess` out.

- **2026-08-13** — Full A–E plus the new Battery F and a §7 model-driven pass, main
`fb5ae71`, ahead of v0.3.0. All clear. Both findings were about the *instrument* and
both now live in §0: Battery A as written proved nothing (`$ROOT` is empty inside
Expand Down
Loading