Commit Graph
161 Commits
Author SHA1 Message Date
zeekayandHanzo Dev dbf0a3c699 ci: build and release on Hanzo Git runners
fmt, clippy -D warnings and tests on hanzo-build-macos-arm64 with a
cargo check -p vm-linux leg on hanzo-build-linux-arm64; v* tags build
the codesigned CLI and the guest OS image and upload them as release
assets with gh.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 11:49:14 -07:00
zeekayandHanzo Dev 51a68b9311 Rebrand shuru as Hanzo VM
Crates keep their history: vm (lib, was shuru-vm), hanzo-vm (CLI package
and binary, was shuru-cli), vm-guest, vm-proto, vm-proxy, vm-sdk,
vm-store, vm-darwin, vm-linux; all at 0.1.0, Apache-2.0, hanzoai/vm.
Config is vm.json, data lives in ~/.hanzo/vm (HANZO_VM_HOME), env vars
are HANZO_VM_*, the guest runs as /usr/bin/vm-guest, and assets download
from hanzoai/vm releases. The TypeScript SDK is @hanzo/vm. NOTICE
credits shuru as the origin. prepare-rootfs.sh builds the image natively
on arm64 Linux with mke2fs -d and re-execs in Docker only on macOS.
Includes rustfmt over the tree and fixes for clippy 1.98 lints.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 11:49:10 -07:00
zeekayandHanzo Dev daf752f91e chore: remove upstream site, agent skills, and GitHub CI
The www/ Astro site, skills/, .claude/, GitHub workflows and changelogs
belong to the upstream project; CI moves to .hanzo/workflows.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 11:49:03 -07:00
harshdoesdev 890069a21f Bump shuru-cli version to 0.7.0 2026-08-05 05:57:15 +05:30
SuperHQ DeveloperandGitHub 47a718907f Merge pull request #41 from superhq-ai/rfc-0002-refreshable-secrets
Refreshable secrets, and head-scoped substitution
2026-08-05 05:55:37 +05:30
harshdoesdev 6c4e3a7b7c feat(proxy): scope secret substitution to the request head
Substitution was a blind byte replace over the whole guest to upstream
stream, which failed two ways. A placeholder straddling two reads went
upstream unsubstituted, reproduced on an 80KB upload where 3 of 2000 leaked.
And substituting in a body changed its length while leaving the framing that
declared it, so any request carrying a secret in its body hung or was closed.
Measured across framings: with a secret the same length as the placeholder
every shape returned 200, and with a differing length only the header case
did. Since placeholders are a fixed 30 bytes and no real credential is 30
bytes, body substitution has never worked for anyone, and no comparable
sandbox proxy offers it.

Substitution now applies to the request line and any header value, scoped by
position rather than by a list of header names, so an unknown vendor header
works with no configuration and a credential in a query parameter is covered.
Bodies stream through untouched, so no declared length is ever invalidated.

This is deterministic rather than timed. An unterminated head is
unambiguously an unfinished request, where an arbitrary byte offset is not,
so no flush heuristic is needed at any throughput.

Framing is read but never rewritten, only to find where the next head begins
on a reused connection. Anything RFC 9112 6.3 flags as a possible smuggling
attempt, both Content-Length and Transfer-Encoding, conflicting lengths, an
unparseable head, CONNECT or Upgrade, stops substitution and degrades to a
byte tunnel. An intermediary that never re-frames cannot desynchronise its
upstream by re-framing wrongly.

Also cheaper than before: only heads are scanned, not every byte of every
body.

A live relay additionally re-resolves its secrets every 5s, so a rotation
reaches a connection that is already open instead of waiting for it to
reconnect.

RFC 0002 is rewritten to match, and records the evidence, the rejected
options, and a separate defect found on the way: substitution is one
directional, so an upstream that reflects a request returns the real
credential to the guest.

Verified end to end on real VMs: header substitution with a length-changing
secret, an unknown custom header, a 40KB POST body forwarded untouched, five
requests substituted on one reused connection, a placeholder in a body
carried through verbatim with a 200 instead of a hang, and no placeholder
leaked in any case.
2026-08-05 05:52:32 +05:30
harshdoesdev 1502a052d2 docs: clarify when to use a command backed secret 2026-08-04 23:31:16 +05:30
harshdoesdev 6619ad5488 feat(proxy): refresh secrets from a provider command
Secrets resolved from the host environment are frozen when the VM boots, so
a credential with a short lifetime (a GitHub App installation token lasts an
hour) strands any task that outlives it. A secret can now name a command
that mints its value instead:

    "GITHUB_TOKEN": {
      "command": ["./scripts/mint-installation-token.sh"],
      "hosts": ["api.github.com", "github.com"]
    }

The command prints {"version":1,"value":"...","expires_at":"..."} and is
re-run about a minute before the value it minted expires. Following
credential_process and git credential helpers, the provider reports its own
expiry rather than the config guessing a ttl; `ttl` covers providers that
cannot. argv is never passed to a shell, and the working directory is the
folder holding shuru.json.

- resolution moves to a SecretResolver behind the existing per-connection
  call site, so a rotation is picked up by the next connection to the domain
  and the placeholder in the guest is untouched
- single flight: the cache lock is held across the provider run, so a burst
  of connections to one expired secret mints once
- command backed secrets fail closed, and the proxy warns with the provider
  stderr; forwarding a placeholder upstream only yields a 401 that reads
  like a scope problem. env backed secrets keep skipping substitution on a
  missing variable, so existing configs behave as before
- values live in memory only, and neither stdout nor a parsed value reaches
  a log, an error message, or a Debug impl

Breaking for direct consumers of shuru-proxy and shuru-sdk:
SecretConfig.from is now Option<String>. Use SecretConfig::from_env or
SecretConfig::from_command. Bumps shuru-proxy to 0.3.0, shuru-sdk to 0.4.0.

Implements docs/rfcs/0002-refreshable-secrets.md, closes #40.
2026-08-04 23:19:10 +05:30
harshdoesdev 774b8c114a docs: add RFC 0002 for refreshable secrets
Spec for backing a secret with a command instead of a host env var, so
short lived credentials (GitHub App installation tokens) survive a long
running sandbox session.

- provider contract follows credential_process: argv array, JSON on
  stdout with an optional expires_at, no shell in the path of a secret
- resolution stays per TLS connection, which is where the substitution
  list is already captured, so the diff is a resolver with a cache
  rather than a lock free read in the relay loop
- command backed secrets fail closed; env backed secrets keep the
  current skip behaviour so nothing that works today starts failing

Closes nothing yet; tracks superhq-ai/shuru#40.
2026-08-04 23:04:01 +05:30
harshdoesdev edf1821895 Bump shuru-cli version to 0.6.5 2026-07-29 04:40:39 +05:30
harshdoesdev 342daa8dbd feat: direct disk mode by default, sub-500ms boots, checkpoint sync fix
Boot path:
- direct clonefile disk is the default; CAS NBD is opt-in via
  SHURU_STORAGE=cas, and .idx checkpoints force NBD automatically (also
  fixes SHURU_STORAGE=direct booting a zero placeholder disk from a .idx
  checkpoint)
- vsock readiness poll at 10ms granularity, same 10s budget
- unused virtio balloon device removed from the VM configuration
- cmdline gains init=/usr/bin/shuru-init so no-initramfs boots land on the
  right init
- SHURU_BOOT_TRACE=1 prints per-stage host boot timings

Correctness:
- checkpoint create now syncs the guest before stopping the VM; late guest
  writes previously vanished from saved checkpoints and journaled images
  replayed recovery on every boot from them

Guest:
- boot stage logs carry monotonic timestamps
- PID 1 stdio hardened against a missing console
2026-07-29 04:40:39 +05:30
harshdoesdev 3a118cf56d feat(sdk): add dial_guest ingress primitive
Add a host-to-guest ingress primitive so external code can reach a TCP
port inside a running sandbox without binding a local listener.

- shuru-vm: factor the vsock forward handshake into open_forward_stream
  and expose Sandbox::connect_forward, returning the connected stream
  directly (works with or without --allow-net; no guest changes).
- shuru-sdk: add AsyncSandbox::dial_guest and a GuestStream wrapper.

This is the seam the neko tunnel builds on: dial_guest(port) yields a
byte stream to the guest service that can be bridged onto any transport.

Bump shuru-vm and shuru-sdk to 0.3.7.
2026-07-24 23:32:14 +05:30
harshdoesdev 4fd80d7654 Bump shuru-cli version to 0.6.4 2026-07-24 23:00:24 +05:30
Harsh SinghandGitHub 67afa21236 Merge pull request #39 from kujhawk94/dns-resolver-pr
Allow configuring the DNS resolver
2026-07-24 22:57:49 +05:30
Christopher Brull 577f097c03 Allow configuring the DNS resolver 2026-07-23 06:13:54 +00:00
harshdoesdev 42b6b3dc0e style: format unrelated files 2026-04-24 22:11:20 +05:30
harshdoesdev b22834e0e2 Bump shuru-cli version to 0.6.3 2026-04-24 21:24:02 +05:30
Harsh SinghandGitHub cfb1984188 Merge pull request #33 from anew-bhav/fix/cas-checkpoint-disk-size-validation
fix(store): flip disk_size vs fallback size validation direction
2026-04-23 03:43:36 +05:30
Anubhav JainandHanzo Dev 2536c50c0e fix(store): flip disk_size vs fallback size validation direction
check_size_against_backend enforced disk_size <= fb.size(), but the
virtual disk is intentionally larger than the flat file (sparse
expansion). Correct invariant is fb.size() <= disk_size — flat file
cannot exceed the virtual disk it backs.

This caused all checkpoints created with default disk-size (4GB) to
fail on next boot with 'index disk_size exceeds fallback file size',
making checkpoints broken by default.

Also updates the test to cover the real-world case: flat file smaller
than virtual disk must be valid, not an error.

Co-authored-by: Hanzo Dev <[email protected]>
2026-04-23 02:48:52 +05:30
harshdoesdev f195685d5b chore: bump shuru-cli to 0.6.2, shuru-linux to 0.2.1, shuru-store to 0.1.3 2026-04-22 00:48:53 +05:30
Harsh SinghandGitHub 467153fc09 Merge pull request #31 from homanp/fix/virtiofs-open-flags
linux: complete open flag check for read-only virtiofs mounts
2026-04-22 00:40:39 +05:30
Ismail Pelaseyed fed4174602 linux: include O_TRUNC in virtiofs read-only open flag check
The read-only mount check for FUSE OPEN was not including O_TRUNC
in the set of flags that indicate a mutating operation.
2026-04-21 16:27:19 +02:00
Harsh SinghandGitHub 3405ae318e Merge pull request #30 from homanp/fix/cas-index-size-validation
store: validate chunk index fields on load
2026-04-21 19:25:11 +05:30
Ismail Pelaseyed 37a03a6639 store: remove unnecessary Debug derive from ChunkIndex
Use match instead of unwrap_err() in tests so ChunkIndex does not
need to derive Debug.
2026-04-21 15:51:44 +02:00
Ismail PelaseyedandHanzo Dev d72d7baa94 store: validate chunk index fields on load
ChunkIndex::load() now checks that num_chunks is consistent with
disk_size, and callers validate disk_size against the actual backing
store size before use. Prevents out-of-bounds reads when an index
file contains invalid length fields.

Co-authored-by: Hanzo Dev <[email protected]>
2026-04-21 15:46:12 +02:00
Harsh SinghandGitHub 8f6ed738e5 Merge pull request #29 from homanp/deps/bump-tar-0.4.45
deps: bump tar from 0.4.44 to 0.4.45
2026-04-21 19:11:18 +05:30
Ismail PelaseyedandHanzo Dev 08fc784e78 deps: bump tar from 0.4.44 to 0.4.45
Co-authored-by: Hanzo Dev <[email protected]>
2026-04-21 15:36:46 +02:00
harshdoesdev c341fad00b readme: use GitHub alert syntax for callouts (WARNING/NOTE) 2026-04-17 07:14:43 +05:30
harshdoesdev 2e67ad311a chore: bump shuru-cli to 0.6.1, shuru-vm to 0.3.6, shuru-linux to 0.2.0 2026-04-17 07:05:20 +05:30
harshdoesdev 488ead3a73 linux: in-process virtio-fs passthrough for --mount
Implements the FUSE wire protocol directly over a virtio-fs request queue
and forwards each op to the host filesystem via libc/std::fs. No external
daemon (virtiofsd) or crate (fuse-backend-rs) required, matching the
hand-rolled virtio style of the other devices.

Ops covered: INIT, LOOKUP, FORGET (single + batch), GETATTR, SETATTR,
OPEN/READ/WRITE/RELEASE, OPENDIR/READDIR(PLUS)/RELEASEDIR, CREATE,
UNLINK/MKDIR/RMDIR/RENAME/RENAME2, STATFS, FLUSH, FSYNC/FSYNCDIR,
ACCESS, xattr stubs. Anything else returns ENOSYS.

Mounts plumb through VmCreateConfig.mounts and land as one virtio-mmio
virtio-fs device per mount. Read-only flag is honored at the op layer
(SETATTR/WRITE/CREATE/UNLINK/... return EROFS).

Verified on AWS a1.metal:
  - --mount ./dir:/workspace      -> guest reads/ls host files, guest
                                     writes stay in the tmpfs overlay.
  - --mount ./dir:/workspace:rw   -> guest writes land on host with
                                     correct contents.
2026-04-17 07:03:06 +05:30
harshdoesdev 2b277fc9eb linux: add PL031 RTC so guest boots with correct wall clock
Without an RTC device, the guest clock stayed at Unix epoch (1970-01-01),
which caused every TLS handshake inside the sandbox to fail with "certificate
not yet valid". macOS gets this for free from Virtualization.framework; Linux
needs us to expose the device ourselves.

Minimal read-only PL031: RTCDR/RTCLR return host wall clock seconds, the
counter is always enabled, no alarm IRQ. The guest kernel already ships with
CONFIG_RTC_DRV_PL031 and CONFIG_RTC_HCTOSYS, so the system clock is seeded at
boot from /dev/rtc0.

Verified on AWS a1.metal (us-east-1): guest date matches host, HTTPS returns
200 where it used to error with "cert not yet valid".
2026-04-17 06:49:01 +05:30
harshdoesdev 0788b44238 ci: split publish-crates into darwin/linux/cross jobs 2026-04-17 05:40:32 +05:30
harshdoesdev 17ba7ec6e9 ci: skip verify-build for shuru-linux publish on macOS runner 2026-04-17 05:39:28 +05:30
harshdoesdev 3f362a2ca5 chore: bump shuru-cli to 0.6.0, shuru-vm to 0.3.5 2026-04-17 05:35:29 +05:30
harshdoesdev bee475d321 Launch experimental Linux ARM64 support
- shuru-linux backend wired into shuru-vm unconditionally on Linux targets
- install.sh detects Linux ARM64 and installs the matching tarball
- shuru upgrade picks the right tarball per host (darwin or linux aarch64)
- Release workflow builds both darwin-aarch64 and linux-aarch64 CLI tarballs
- Crates-publish workflow includes shuru-linux
- www: Linux section in install box, /linux setup guide, blog post, banner
- README and CHANGELOG updated for the 0.6.0 launch
2026-04-17 05:32:55 +05:30
harshdoesdev 744a21cfbd guest: explicit strip_components on download instead of auto-strip
The guest's tar extractor unconditionally stripped the first path component,
which silently dropped flat tarballs (e.g. OpenAI Codex's single-binary
release) on the floor while the download reported success. Callers had no
way to opt out.

Replace with an explicit `strip_components: u32` field on `DownloadRequest`,
mirroring `tar --strip-components=N`. Default 0. Callers spell out the value
that matches their archive.

- shuru-proto 0.3.2 -> 0.3.3: DownloadRequest gains strip_components.
- shuru-guest 0.3.4 -> 0.3.5: use req.strip_components instead of hardcoded 1.
- shuru-vm   0.3.3 -> 0.3.4: sandbox.download() takes strip_components.
- shuru-sdk  0.3.5 -> 0.3.6: AsyncSandbox::download() takes strip_components.
- shuru-cli  0.5.8 -> 0.5.9: release bump so a new shuru-os rootfs ships.
2026-04-15 03:55:18 +05:30
harshdoesdev 4bcffb87d9 chore: bump shuru-proxy to 0.2.4, shuru-sdk to 0.3.5, shuru-cli to 0.5.8 2026-04-14 20:54:44 +05:30
harshdoesdev adbcd36728 proxy: bound DNS-pinned IP set with 1024-entry LRU 2026-04-14 20:51:14 +05:30
Harsh SinghandGitHub 97e85e8cd3 Merge pull request #28 from vessux/fix/network-egress-hardening
Harden network egress when domain allowlist is active
2026-04-14 20:35:06 +05:30
Jakub Kovaľ 8970685092 Harden network egress when domain allowlist is active
The `--allow-host` domain allowlist only blocked DNS resolution, not
direct TCP connections. A guest that knew a target IP could bypass the
allowlist entirely (e.g. `curl --resolve` with a hardcoded IP).

This commit closes three egress gaps:

1. DNS-pinning: when DNS resolves an allowed domain, record the
   resulting IPs in a shared set. Reject TCP connections to IPs not
   in that set. This blocks hardcoded-IP bypass.

2. SNI filtering: on TLS connections, verify the SNI hostname matches
   an allowed domain before proxying. This prevents accessing
   non-allowed services that share an IP with an allowed domain
   (e.g. Cloudflare-fronted sites).

3. ICMP drop: filter ICMP frames at the device level before they
   reach smoltcp, which otherwise auto-replies to echo requests for
   any IP (due to any_ip=true).

All three checks are only active when `network.allow` is non-empty
(i.e. a domain allowlist is configured). Without an allowlist,
behavior is unchanged.
2026-04-14 10:59:13 +02:00
harshdoesdev 00f1ef6b79 chore: bump shuru-cli to 0.5.7 2026-04-14 07:45:20 +05:30
harshdoesdev bae566c39d chore: bump shuru-guest and shuru-sdk to 0.3.4 2026-04-14 07:38:13 +05:30
harshdoesdev 8ffa509568 guest+sdk: watch merged overlay directly, parallelize SDK reads
shuru-guest now watches the overlay mount directly instead of the upper
dir. Modern kernels (Linux >= 5.11) propagate inotify events from the
upper through the merged view, so we get proper CREATE/MODIFY/REMOVE
events on the caller's namespace paths — no whiteout char device
shenanigans, no recursive-watch race compensation, no path rewriting.
Also filters out Access events (open/read/close) since only content
changes matter.

shuru-sdk wraps the VM sandbox in Arc and fans ReadFile/ReadDir onto
worker threads. Each call already opens its own vsock connection on the
host side and the guest spawns a thread per connection, so the
previously single-threaded dispatch was serializing concurrent reads
for no reason. Bursts of file status probes now pipeline.
2026-04-14 07:35:53 +05:30
harshdoesdev 89e03f5497 Add dynamic port forwarding, SO_REUSEADDR, and OVERFLOW watch event
- Add AsyncSandbox::add_port_forward() for runtime port forwarding
- Use socket2 SO_REUSEADDR on port forward listeners to prevent
  bind failures when forking from checkpoints
- Add OVERFLOW watch_kind for inotify queue overflow detection
- Guest sends OVERFLOW event on notify Flag::Rescan
2026-04-11 15:40:44 +05:30
harshdoesdev 4fd8aa88af Add default env config to SandboxConfig
Adds `env: HashMap<String, String>` to SandboxConfig, merged into the
base environment for all commands. Config env is applied first, then
proxy placeholders override, then caller extra_env overrides both.
2026-04-11 07:02:05 +05:30
harshdoesdev daa45dbfeb fix: extract after download should strip leading path in the download proc 2026-04-10 02:42:39 +05:30
Harsh SinghandGitHub 33980eb818 Add Star History section to README
Added Star History section with a chart for tracking stars.
2026-04-08 23:18:17 +05:30
Harsh SinghandGitHub 5dd451180f Update GitHub Sponsors username in FUNDING.yml 2026-04-08 15:01:37 +05:30
harshdoesdev e84df11cda fix: handle NBD_OPT_INFO in handshake for macOS 26.3 compatibility 2026-04-07 18:12:20 +05:30
harshdoesdev ee052a99ce fix: add shuru-store to crates.io publish workflow 2026-04-05 21:55:45 +05:30