From 0cae0bf0db91ab0ea582c729615f75a89da54065 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Wed, 23 Sep 2026 17:03:28 -0400 Subject: [PATCH 1/3] feat(hooks): add kefka wasm commands Update kefka and expose its uutils and WASM programs in the shared hook shell. Give WASI directory handles so these commands can resolve paths within the sandbox. Assisted-by: GPT-6 via Codex Signed-off-by: Xe Iaso --- cmd/objgitd/hooks.go | 4 ++++ cmd/objgitd/hooks_test.go | 8 ++++++- docs/architecture/hooks.md | 4 +++- docs/plans/kefka-uutils.md | 31 ++++++++++++++++++++++++++ docs/usage/hooks.md | 14 +++++++----- go.mod | 4 ++-- go.sum | 6 +++-- internal/mountfs/mountfs.go | 44 +++++++++++++++++++++++++++++++++++-- 8 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 docs/plans/kefka-uutils.md diff --git a/cmd/objgitd/hooks.go b/cmd/objgitd/hooks.go index 31d23a3..b4d9d46 100644 --- a/cmd/objgitd/hooks.go +++ b/cmd/objgitd/hooks.go @@ -29,6 +29,8 @@ import ( "mvdan.cc/sh/v3/syntax" "tangled.org/xeiaso.net/kefka/command/registry" "tangled.org/xeiaso.net/kefka/command/registry/coreutils" + "tangled.org/xeiaso.net/kefka/command/registry/uutils" + "tangled.org/xeiaso.net/kefka/command/registry/wasmprog" ) // receivePackHook names the hook that a push runs, and so its script at @@ -280,6 +282,8 @@ func newHookShell(tree *object.Tree, changes hookChanges, env []string, stdin io reg := registry.New() coreutils.Register(reg) + wasmprog.Register(reg) + uutils.Register(reg) if err := reg.Chdir(fsys, "/src"); err != nil { return nil, fmt.Errorf("chdir /src: %w", err) } diff --git a/cmd/objgitd/hooks_test.go b/cmd/objgitd/hooks_test.go index f9ffb4d..872fda4 100644 --- a/cmd/objgitd/hooks_test.go +++ b/cmd/objgitd/hooks_test.go @@ -81,6 +81,11 @@ func TestReceivePackHook(t *testing.T) { // read-only error, so WROTE_SRC must never print. hook := strings.Join([]string{ "cat README.md", + "cd sub && cat note.txt", + "printf hi | base32", + `printf '{"value":42}' | jq -r '.value'`, + "cp /src/README.md /tmp/copied && cat /tmp/copied", + "if cp /src/README.md /src/copied; then echo WROTE_SRC_CP; fi", `echo "cwd=$PWD"`, "echo built > /tmp/out", "cat /tmp/out", @@ -88,6 +93,7 @@ func TestReceivePackHook(t *testing.T) { "echo WROTE_SRC", }, "\n") + "\n" writeFile(t, filepath.Join(work, "README.md"), "hello from repo\n") + writeFile(t, filepath.Join(work, "sub", "note.txt"), "from subdir\n") writeFile(t, filepath.Join(work, ".objgit", "hooks", "receive-pack"), hook) runGit(t, work, "add", ".") runGit(t, work, "commit", "-m", "with hook") @@ -102,7 +108,7 @@ func TestReceivePackHook(t *testing.T) { t.Fatalf("hook did not run; logs:\n%s", logs) } // /src is readable and /tmp is writable; their output streamed to the client. - for _, want := range []string{"hello from repo", "cwd=/src", "built"} { + for _, want := range []string{"hello from repo", "from subdir", "NBUQ====", "42", "cwd=/src", "built"} { if !strings.Contains(pushOut, want) { t.Errorf("push output missing streamed hook output %q; output:\n%s", want, pushOut) } diff --git a/docs/architecture/hooks.md b/docs/architecture/hooks.md index 6faba85..0c93255 100644 --- a/docs/architecture/hooks.md +++ b/docs/architecture/hooks.md @@ -57,7 +57,7 @@ carries its own hook. The script runs in a **kefka** virtual shell (`tangled.org/xeiaso.net/kefka`). kefka is _not_ an OS sandbox. It is an `mvdan.cc/sh` interpreter wired to a `billy.Filesystem`, plus a fixed registry -of commands, which here is coreutils only. +of Go coreutils, WASM programs, and WASM-backed uutils. The sandbox filesystem is an `internal/mountfs` composite of two mounts: @@ -71,6 +71,8 @@ A write outside `/tmp` fails, and a redirect into `/src` aborts the script. `internal/kefkash` vendors the unexported `billysh` handler wiring from kefka. Its `OpenHandler` is adapted to permit writes, so `/tmp` redirections work. The filesystem is what enforces the read-only `/src`. +`internal/mountfs` also provides read-only directory handles for the WASI +programs, which need to open `/` and directories below it to resolve paths. `newHookShell` builds this sandbox, and `hookEnv` builds its environment. `loadHookChanges` gives both of them the file changes of the update. diff --git a/docs/plans/kefka-uutils.md b/docs/plans/kefka-uutils.md new file mode 100644 index 0000000..400e36b --- /dev/null +++ b/docs/plans/kefka-uutils.md @@ -0,0 +1,31 @@ +# Update kefka and add uutils to the hook shell + +## Context + +`objgitd` pins kefka to a May 2026 commit and registers only its Go +coreutils. The current upstream `main` commit (August 23, 2026) moves common +coreutils to a WASM-backed uutils registry and exposes WASM programs through +`wasmprog`. Hooks and SSH `sh` share +`newHookShell`, so that function is the registration point for both. + +## Changes + +1. Pin kefka to the current `main` commit and update the module sums. +2. Register wasmprog and uutils after coreutils, following kefka's own CLI. + The uutils registry provides commands such as `cat` and `ls` and adds + `base32`; wasmprog provides `jq`, `python3`, `qjs`, and `rg`. +3. Let `mountfs` open read-only directory handles. WASI needs them to + resolve paths beneath `/`, including `/src` and its subdirectories. +4. Exercise a uutils-only command, `jq`, relative paths after `cd`, and the + read-only `/src` and writable `/tmp` mounts in the existing push test. +5. Update the hook documentation to describe the available commands. + +## Verification + +Run `go build ./...`, the hook and SSH protocol tests, then `go test ./...`. + +The upstream August commit is not on the public Go module proxy yet. At the +time of this update, the proxy returned 404, and a direct all-refs fetch from +Tangled failed because its `Xe/uutils` ref could not be fetched. The module +was downloaded from a local clone of upstream `main` with the same commit +hash. A clean builder needs the proxy or the upstream fetch to recover. diff --git a/docs/usage/hooks.md b/docs/usage/hooks.md index 3d03456..4a7a890 100644 --- a/docs/usage/hooks.md +++ b/docs/usage/hooks.md @@ -47,11 +47,12 @@ Hooks run in [kefka](https://xeiaso.net/blog/2026/dancing-mad-sandboxing/), a virtual `bash` interpreter. **This is not a container, VM, or OS sandbox.** It is safe because of what it _cannot_ reach, not because of kernel isolation: -- **No system binaries.** Only kefka's built-in commands exist — roughly the - POSIX coreutils: `cat`, `ls`, `echo`, `printf`, `head`, `tail`, `cut`, `sort`, - `uniq`, `wc`, `tr`, `grep`, `sha256sum`, `base64`, `mkdir`, `cp`, `mv`, `rm`, - `touch`, `date`, `sleep`, `seq`, `expr`, and so on. There is no `git`, no - package manager, no compiler, no `curl`. +- **No system binaries.** Kefka provides Go commands, WASM-backed uutils, and + the WASM programs `jq`, `python3` (also `python`), `qjs`, and `rg`. Available + coreutils include `cat`, `ls`, `printf`, `head`, `tail`, `cut`, `sort`, + `uniq`, `wc`, `tr`, `sha256sum`, `base64`, `base32`, `mkdir`, `cp`, `mv`, + `rm`, `touch`, `date`, `sleep`, `seq`, and `expr`. There is no `git`, + package manager, compiler, or `curl`. - **No network.** - **No host filesystem.** The only files a hook can see are the two mounts below. @@ -218,5 +219,6 @@ Hook failure is logged, but it cannot undo the accepted push. - No writable working tree: `/src` is strictly read-only and `/tmp` is the only scratch space. - No way to reject a push from a hook (it runs after the fact). -- No system tooling, network, or arbitrary executables — only kefka built-ins. +- No system tooling, network, or arbitrary executables — only kefka's + registered commands. - Hook output reaches the pusher only when the client negotiated sideband. diff --git a/go.mod b/go.mod index d6e8466..4cd0663 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.20.0 golang.org/x/term v0.43.0 mvdan.cc/sh/v3 v3.13.1 - tangled.org/xeiaso.net/kefka v0.0.6-0.20260528192045-e0a84e40ceb8 + tangled.org/xeiaso.net/kefka v0.0.6-0.20260823145432-0592fe1f1777 ) require ( @@ -60,13 +60,13 @@ require ( github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/sergi/go-diff v1.4.0 // indirect + github.com/tetratelabs/wazero v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/mod v0.36.0 // indirect golang.org/x/net v0.54.0 // indirect golang.org/x/sys v0.44.0 // indirect golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect - golang.org/x/text v0.37.0 // indirect golang.org/x/tools v0.45.0 // indirect google.golang.org/protobuf v1.36.8 honnef.co/go/tools v0.7.0 // indirect diff --git a/go.sum b/go.sum index 3bee081..2a489e6 100644 --- a/go.sum +++ b/go.sum @@ -120,6 +120,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA= +github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= github.com/tigrisdata/storage-go v0.6.0 h1:1Rxzxp/GAUqwFln8qMXYOQIa47IsT2aBNuxYc55o6D4= github.com/tigrisdata/storage-go v0.6.0/go.mod h1:l3u7N9LDIhv4lfpkEBJYzolWJ/SBb6WiBexgy/uq6iQ= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -164,5 +166,5 @@ honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= mvdan.cc/sh/v3 v3.13.1 h1:DP3TfgZhDkT7lerUdnp6PTGKyxxzz6T+cOlY/xEvfWk= mvdan.cc/sh/v3 v3.13.1/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0= -tangled.org/xeiaso.net/kefka v0.0.6-0.20260528192045-e0a84e40ceb8 h1:R008zBlWio3SXRZUaEDDDzzEf82X6e6KLvqhNddXprI= -tangled.org/xeiaso.net/kefka v0.0.6-0.20260528192045-e0a84e40ceb8/go.mod h1:p474Le4nfwuwnuAWowRhXMsTYfkpOMscchuaFvDa2zM= +tangled.org/xeiaso.net/kefka v0.0.6-0.20260823145432-0592fe1f1777 h1:6w5rTDGF/4nAs1gafl7NO0KaZEZNWXutE/Phi4G/IH4= +tangled.org/xeiaso.net/kefka v0.0.6-0.20260823145432-0592fe1f1777/go.mod h1:h2ZITASpfUqC6PzLUfYTTO9AKPAft45UvCyxe/LAN2k= diff --git a/internal/mountfs/mountfs.go b/internal/mountfs/mountfs.go index c854f96..b408f8f 100644 --- a/internal/mountfs/mountfs.go +++ b/internal/mountfs/mountfs.go @@ -13,6 +13,7 @@ import ( "path" "sort" "strings" + "syscall" "time" "github.com/go-git/go-billy/v6" @@ -69,11 +70,50 @@ func (f *FS) OpenFile(filename string, flag int, perm fs.FileMode) (billy.File, return nil, pathErr("open", filename, err) } if isRoot || isMount { - return nil, pathErr("open", filename, billy.ErrNotSupported) + return f.openDir(filename, flag) } - return sub.OpenFile(rel, flag, perm) + file, err := sub.OpenFile(rel, flag, perm) + if err == nil || flag&(os.O_WRONLY|os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC) != 0 { + return file, err + } + // Some mounted filesystems cannot open directories. WASI needs directory + // handles to resolve paths beneath the preopened root. + if info, statErr := sub.Stat(rel); statErr == nil && info.IsDir() { + return f.openDir(filename, flag) + } + return nil, err +} + +func (f *FS) openDir(filename string, flag int) (billy.File, error) { + if flag&(os.O_WRONLY|os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC) != 0 { + return nil, pathErr("open", filename, billy.ErrReadOnly) + } + return &dirFile{fsys: f, name: path.Clean("/" + filename)}, nil } +// dirFile is a read-only handle for a virtual or mounted directory. The WASI +// adapter uses its name to stat and list entries through the composite FS. +type dirFile struct { + fsys *FS + name string +} + +func (d *dirFile) Name() string { return d.name } +func (d *dirFile) Stat() (fs.FileInfo, error) { return d.fsys.Stat(d.name) } +func (d *dirFile) Close() error { return nil } +func (d *dirFile) Read([]byte) (int, error) { return 0, syscall.EISDIR } +func (d *dirFile) ReadAt([]byte, int64) (int, error) { + return 0, syscall.EISDIR +} +func (d *dirFile) Write([]byte) (int, error) { return 0, billy.ErrReadOnly } +func (d *dirFile) WriteAt([]byte, int64) (int, error) { + return 0, billy.ErrReadOnly +} +func (d *dirFile) Seek(int64, int) (int64, error) { return 0, syscall.EISDIR } +func (d *dirFile) Truncate(int64) error { return billy.ErrReadOnly } + +var _ billy.File = (*dirFile)(nil) + func (f *FS) Stat(filename string) (fs.FileInfo, error) { sub, rel, isRoot, isMount, err := f.route(filename) if err != nil { From 38223f221196443bf79c6c710935203aff07ddc5 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Wed, 23 Sep 2026 17:54:18 -0400 Subject: [PATCH 2/3] fix(ci): pin kefka to v0.0.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the upstream tag for the existing kefka commit. The tag and recorded checksums let clean module caches resolve the dependency through Go’s normal proxy and direct fallback. Assisted-by: GPT-6 via Codex Signed-off-by: Xe Iaso --- docs/plans/kefka-uutils.md | 13 ++++++------- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/plans/kefka-uutils.md b/docs/plans/kefka-uutils.md index 400e36b..51cb950 100644 --- a/docs/plans/kefka-uutils.md +++ b/docs/plans/kefka-uutils.md @@ -3,14 +3,14 @@ ## Context `objgitd` pins kefka to a May 2026 commit and registers only its Go -coreutils. The current upstream `main` commit (August 23, 2026) moves common +coreutils. Upstream `v0.0.6` (August 23, 2026) moves common coreutils to a WASM-backed uutils registry and exposes WASM programs through `wasmprog`. Hooks and SSH `sh` share `newHookShell`, so that function is the registration point for both. ## Changes -1. Pin kefka to the current `main` commit and update the module sums. +1. Pin kefka to `v0.0.6` and update the module sums. 2. Register wasmprog and uutils after coreutils, following kefka's own CLI. The uutils registry provides commands such as `cat` and `ls` and adds `base32`; wasmprog provides `jq`, `python3`, `qjs`, and `rg`. @@ -24,8 +24,7 @@ coreutils to a WASM-backed uutils registry and exposes WASM programs through Run `go build ./...`, the hook and SSH protocol tests, then `go test ./...`. -The upstream August commit is not on the public Go module proxy yet. At the -time of this update, the proxy returned 404, and a direct all-refs fetch from -Tangled failed because its `Xe/uutils` ref could not be fetched. The module -was downloaded from a local clone of upstream `main` with the same commit -hash. A clean builder needs the proxy or the upstream fetch to recover. +The tag points to commit `0592fe1f17775d60fd533cdbf06e4b716b3243c8`. +The public Go module proxy initially returned 404, but Go's normal direct +fallback fetched the tag. `go mod download` passed with an empty module cache +and the `v0.0.6` checksums in `go.sum`. diff --git a/go.mod b/go.mod index 4cd0663..12361a6 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.20.0 golang.org/x/term v0.43.0 mvdan.cc/sh/v3 v3.13.1 - tangled.org/xeiaso.net/kefka v0.0.6-0.20260823145432-0592fe1f1777 + tangled.org/xeiaso.net/kefka v0.0.6 ) require ( diff --git a/go.sum b/go.sum index 2a489e6..83272e8 100644 --- a/go.sum +++ b/go.sum @@ -166,5 +166,5 @@ honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= mvdan.cc/sh/v3 v3.13.1 h1:DP3TfgZhDkT7lerUdnp6PTGKyxxzz6T+cOlY/xEvfWk= mvdan.cc/sh/v3 v3.13.1/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0= -tangled.org/xeiaso.net/kefka v0.0.6-0.20260823145432-0592fe1f1777 h1:6w5rTDGF/4nAs1gafl7NO0KaZEZNWXutE/Phi4G/IH4= -tangled.org/xeiaso.net/kefka v0.0.6-0.20260823145432-0592fe1f1777/go.mod h1:h2ZITASpfUqC6PzLUfYTTO9AKPAft45UvCyxe/LAN2k= +tangled.org/xeiaso.net/kefka v0.0.6 h1:umz6a3ZwnSEZb72xmSUaMXTprxTJFntSy1LHXveAc4g= +tangled.org/xeiaso.net/kefka v0.0.6/go.mod h1:h2ZITASpfUqC6PzLUfYTTO9AKPAft45UvCyxe/LAN2k= From 0c093832dd798f1ae659490534836c45719cc59f Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Wed, 23 Sep 2026 18:13:35 -0400 Subject: [PATCH 3/3] fix(hooks): support kefka v0.0.8 Upgrade kefka and preserve mount-qualified names on opened files. The newer uutils coreutils binary stats files after open, which otherwise makes head fail on files under /src. Assisted-by: GPT-6 via Codex Signed-off-by: Xe Iaso --- .objgit/hooks/receive-pack | 4 ++-- docs/architecture/hooks.md | 1 + docs/plans/kefka-uutils.md | 13 +++++++------ docs/usage/hooks.md | 2 +- go.mod | 2 +- go.sum | 4 ++-- internal/mountfs/mountfs.go | 16 ++++++++++++++-- 7 files changed, 28 insertions(+), 14 deletions(-) diff --git a/.objgit/hooks/receive-pack b/.objgit/hooks/receive-pack index 04c173b..5fc22df 100755 --- a/.objgit/hooks/receive-pack +++ b/.objgit/hooks/receive-pack @@ -8,8 +8,8 @@ # # Execution environment (see docs/usage/hooks.md): # * It runs in a kefka virtual shell, NOT a real OS shell. Only kefka's -# built-in commands are available (coreutils: cat, ls, echo, printf, head, -# tail, cut, sort, uniq, wc, sha256sum, grep, ...). There is no PATH to +# registered commands are available (cat, ls, printf, head, tail, cut, +# sort, uniq, wc, sha256sum, jo, jq, rg, ...). There is no PATH to # system binaries, no network, and no `git`. # * /src is a READ-ONLY checkout of the pushed commit. The working directory # starts here. diff --git a/docs/architecture/hooks.md b/docs/architecture/hooks.md index 0c93255..693dbba 100644 --- a/docs/architecture/hooks.md +++ b/docs/architecture/hooks.md @@ -73,6 +73,7 @@ Its `OpenHandler` is adapted to permit writes, so `/tmp` redirections work. The filesystem is what enforces the read-only `/src`. `internal/mountfs` also provides read-only directory handles for the WASI programs, which need to open `/` and directories below it to resolve paths. +Open files report their full mounted path so WASI can stat them after open. `newHookShell` builds this sandbox, and `hookEnv` builds its environment. `loadHookChanges` gives both of them the file changes of the update. diff --git a/docs/plans/kefka-uutils.md b/docs/plans/kefka-uutils.md index 51cb950..eda564f 100644 --- a/docs/plans/kefka-uutils.md +++ b/docs/plans/kefka-uutils.md @@ -3,19 +3,20 @@ ## Context `objgitd` pins kefka to a May 2026 commit and registers only its Go -coreutils. Upstream `v0.0.6` (August 23, 2026) moves common +coreutils. Upstream `v0.0.8` moves common coreutils to a WASM-backed uutils registry and exposes WASM programs through `wasmprog`. Hooks and SSH `sh` share `newHookShell`, so that function is the registration point for both. ## Changes -1. Pin kefka to `v0.0.6` and update the module sums. +1. Pin kefka to `v0.0.8` and update the module sums. 2. Register wasmprog and uutils after coreutils, following kefka's own CLI. The uutils registry provides commands such as `cat` and `ls` and adds - `base32`; wasmprog provides `jq`, `python3`, `qjs`, and `rg`. + `base32`; wasmprog provides `jo`, `jq`, `python3`, `qjs`, and `rg`. 3. Let `mountfs` open read-only directory handles. WASI needs them to - resolve paths beneath `/`, including `/src` and its subdirectories. + resolve paths beneath `/`, including `/src` and its subdirectories. Return + mount-qualified file names so WASI can stat opened files. 4. Exercise a uutils-only command, `jq`, relative paths after `cd`, and the read-only `/src` and writable `/tmp` mounts in the existing push test. 5. Update the hook documentation to describe the available commands. @@ -24,7 +25,7 @@ coreutils to a WASM-backed uutils registry and exposes WASM programs through Run `go build ./...`, the hook and SSH protocol tests, then `go test ./...`. -The tag points to commit `0592fe1f17775d60fd533cdbf06e4b716b3243c8`. +The tag points to commit `e1236bb201d5013afcfd3b71ce7259e2d42c7830`. The public Go module proxy initially returned 404, but Go's normal direct fallback fetched the tag. `go mod download` passed with an empty module cache -and the `v0.0.6` checksums in `go.sum`. +and the `v0.0.8` checksums in `go.sum`. diff --git a/docs/usage/hooks.md b/docs/usage/hooks.md index 4a7a890..18e5031 100644 --- a/docs/usage/hooks.md +++ b/docs/usage/hooks.md @@ -48,7 +48,7 @@ virtual `bash` interpreter. **This is not a container, VM, or OS sandbox.** It i safe because of what it _cannot_ reach, not because of kernel isolation: - **No system binaries.** Kefka provides Go commands, WASM-backed uutils, and - the WASM programs `jq`, `python3` (also `python`), `qjs`, and `rg`. Available + the WASM programs `jo`, `jq`, `python3` (also `python`), `qjs`, and `rg`. Available coreutils include `cat`, `ls`, `printf`, `head`, `tail`, `cut`, `sort`, `uniq`, `wc`, `tr`, `sha256sum`, `base64`, `base32`, `mkdir`, `cp`, `mv`, `rm`, `touch`, `date`, `sleep`, `seq`, and `expr`. There is no `git`, diff --git a/go.mod b/go.mod index 12361a6..a0885be 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.20.0 golang.org/x/term v0.43.0 mvdan.cc/sh/v3 v3.13.1 - tangled.org/xeiaso.net/kefka v0.0.6 + tangled.org/xeiaso.net/kefka v0.0.8 ) require ( diff --git a/go.sum b/go.sum index 83272e8..e09b6f7 100644 --- a/go.sum +++ b/go.sum @@ -166,5 +166,5 @@ honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= mvdan.cc/sh/v3 v3.13.1 h1:DP3TfgZhDkT7lerUdnp6PTGKyxxzz6T+cOlY/xEvfWk= mvdan.cc/sh/v3 v3.13.1/go.mod h1:lXJ8SexMvEVcHCoDvAGLZgFJ9Wsm2sulmoNEXGhYZD0= -tangled.org/xeiaso.net/kefka v0.0.6 h1:umz6a3ZwnSEZb72xmSUaMXTprxTJFntSy1LHXveAc4g= -tangled.org/xeiaso.net/kefka v0.0.6/go.mod h1:h2ZITASpfUqC6PzLUfYTTO9AKPAft45UvCyxe/LAN2k= +tangled.org/xeiaso.net/kefka v0.0.8 h1:RbiOJn+b+zxa4gNVkW1mSIBj0Qjbcji2eo7wdBf5Gu8= +tangled.org/xeiaso.net/kefka v0.0.8/go.mod h1:h2ZITASpfUqC6PzLUfYTTO9AKPAft45UvCyxe/LAN2k= diff --git a/internal/mountfs/mountfs.go b/internal/mountfs/mountfs.go index b408f8f..0a4c272 100644 --- a/internal/mountfs/mountfs.go +++ b/internal/mountfs/mountfs.go @@ -73,8 +73,13 @@ func (f *FS) OpenFile(filename string, flag int, perm fs.FileMode) (billy.File, return f.openDir(filename, flag) } file, err := sub.OpenFile(rel, flag, perm) - if err == nil || flag&(os.O_WRONLY|os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC) != 0 { - return file, err + if err == nil { + // The mounted filesystem may return only the basename from Name(). + // WASI uses it to stat the open file against this composite filesystem. + return namedFile{File: file, name: path.Clean("/" + filename)}, nil + } + if flag&(os.O_WRONLY|os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC) != 0 { + return nil, err } // Some mounted filesystems cannot open directories. WASI needs directory // handles to resolve paths beneath the preopened root. @@ -84,6 +89,13 @@ func (f *FS) OpenFile(filename string, flag int, perm fs.FileMode) (billy.File, return nil, err } +type namedFile struct { + billy.File + name string +} + +func (f namedFile) Name() string { return f.name } + func (f *FS) openDir(filename string, flag int) (billy.File, error) { if flag&(os.O_WRONLY|os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC) != 0 { return nil, pathErr("open", filename, billy.ErrReadOnly)