Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.
Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
ptyctl, ptyctl-cli, third_party/ unchanged; proto package
xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
(templates re-encrypted)
Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
module & dc_log, heap-profile uploader, auth-diagnostics uploader,
session-analytics halves of feedback; local zero-egress observability
preserved in new kigi-log crate (unified log, --debug firehose,
subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
shell util
Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted
Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean
Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
(new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
fast-worktree); RSS measurement tests serialized via serial_test
Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
notices sustained; kigi-tools ported-code notices extended; README,
CONTRIBUTING, SECURITY, AGENTS.md rewritten
Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
12 KiB
Sandbox Mode
Sandbox mode restricts what the agent process and its spawned commands can access on your filesystem and network using OS-level kernel primitives (Landlock on Linux, Seatbelt on macOS). The kernel enforces these limits for the process lifetime.
Sandbox mode is off by default.
Quick Start
# Run with workspace sandbox (read everywhere, write to CWD + temp dirs + ~/.kigi/)
grok --sandbox workspace
# Read-only mode (read everywhere, write only to ~/.kigi/ + temp dirs)
grok --sandbox read-only
# Most restrictive profile (read CWD + system paths, write CWD + temp dirs + ~/.kigi/, no child network)
grok --sandbox strict
Built-in Profiles
| Profile | FS Read | FS Write | Child Network | Use Case |
|---|---|---|---|---|
off (default) |
Unrestricted | Unrestricted | Unrestricted | No sandbox |
workspace |
Everywhere | CWD + ~/.kigi/ + /tmp + /var/tmp |
Allowed | Normal development |
devbox |
Everywhere | All top-level dirs except /data |
Allowed | Disposable dev VMs |
read-only |
Everywhere | ~/.kigi/ + /tmp + /var/tmp |
Blocked¹ | Exploration, code review |
strict |
CWD + system paths | CWD + ~/.kigi/ + /tmp + /var/tmp |
Blocked¹ | Untrusted code |
¹ Child-network blocking is enforced on Linux only (via seccomp). On macOS it is a no-op — these profiles do not restrict child-process network there.
To block specific files (e.g. .env or credential paths) on top of a profile, define a custom profile with a deny list — it is kernel-enforced (read + write/rename) and supports glob patterns like **/*.pem.
Profile Details
workspace -- The recommended profile for everyday development. The agent can read any file on the system (for understanding dependencies, system libraries, etc.) but can only write to the current working directory, ~/.kigi/, and temp directories (/tmp, /var/tmp, plus the macOS temp dirs). Network access is allowed for tools like web_search and MCP servers.
devbox -- A reserved built-in profile for disposable development VMs. The agent can read everywhere and write to every top-level directory except /data and the virtual filesystems (/proc, /sys, /dev), including the home directory. Network access is allowed. --sandbox devbox runs the built-in profile, which shadows any [profiles.devbox] you define in sandbox.toml.
read-only -- Use when you want the agent to analyze code without modifying your project files. The agent can read everything but can only write to ~/.kigi/ (needed for session persistence) and temp directories. Child-process network access is blocked on Linux (no-op on macOS).
strict -- The most restrictive profile, for reviewing untrusted code. The agent can only read files within the current working directory and essential system paths. Writes are limited to CWD, ~/.kigi/, and temp directories. Child-process network access is blocked on Linux (no-op on macOS).
Custom Profiles
Create custom sandbox profiles in ~/.kigi/sandbox.toml (global) or .kigi/sandbox.toml (per-project):
[profiles.project]
# Start from a built-in profile, then add overrides
extends = "workspace"
restrict_network = true
# Paths the agent can read but NOT write/delete
read_only = ["/data"]
# Additional writable paths
read_write = ["/tmp/scratch"]
# Paths or globs to kernel-deny (read + write/rename, enforced; see notes below)
deny = ["/data/shared-secrets", "**/.env", "**/*.pem"]
Use the custom profile:
grok --sandbox project
A custom profile can't reuse a built-in name. --sandbox devbox always runs the built-in devbox profile, shadowing any [profiles.devbox] you define.
When the global and per-project files define the same custom profile name, the user-level definition takes precedence and the project definition is ignored. If those two definitions differ, Grok warns about the conflict at startup — on the welcome screen in the TUI, and on stderr for headless runs. Identical duplicate definitions do not produce a warning.
Custom Profile Fields
| Field | Type | Description |
|---|---|---|
extends |
String | Base built-in profile to inherit from (workspace, devbox, read-only, strict). Defaults to workspace when omitted |
restrict_network |
Boolean | Block network access for child processes |
read_only |
String[] | Additional read-only paths |
read_write |
String[] | Additional read-write paths |
deny |
String[] | Paths or globs to kernel-deny (read + write/rename; see notes). An entry with *, ?, or [ is a glob |
Note on
deny: A non-emptydenylist is kernel-enforced. Denied paths are read-denied and write/rename-denied via Seatbelt on macOS and a bwrap bind-over on Linux, so a denied path can neither be read (viabash,grep, or subagents) nor relocated out of the deny set and read elsewhere (themv secret x && cat xbypass is closed). On Linux, read-deny requiresbubblewrap: if it is missing (or any single deny path can't be bound), Grok refuses to start rather than run with denied paths exposed (devbox, which only write-denies/data, still falls back to Landlock). Writes to paths not indenyare controlled by what you grant inread_write.
Globs in
deny: An entry is a glob if it contains*,?, or[. Those characters always mean glob — to deny a literal file whose name contains them, name a parent directory instead. The supported, gitignore-style subset is:
*— any run of characters within one path segment (stops at/)?— exactly one character within a segment**— spans directories (as a whole path segment, e.g.**/,a/**);**/also matches zero directories, so**/.envmatches.envandsub/.env[abc]/[a-z]— character classes; a leading!or^negates ([!a]and[^a]both mean "nota")Brace alternation (
{a,b}), backslash-escapes, and the unusual class forms[]…](literal]first) and POSIX[[:…:]]are not supported, so the two platforms can never interpret a glob differently. A glob using an unsupported metacharacter, or one that is malformed, makes Grok refuse to start (fail closed) on both platforms — write*.pemand*.keyas separate entries rather than*.{pem,key}.Relative globs are anchored at the workspace; absolute globs (e.g.
/home/**/.ssh) at their literal prefix. Non-glob entries keep exact-path matching. Enforcement otherwise differs by platform:
- macOS is airtight: each glob becomes a Seatbelt regex applied at runtime, so matching files are denied even if created after Grok starts.
- Linux is best-effort: a mount namespace can't glob at runtime, so each glob is expanded to the files that exist at launch and those are bound over. Files created later that match a glob are not covered — name exact paths for anything that must be airtight on Linux. A glob that matches too many files, or whose tree is too deep/broad to walk, makes Grok refuse to start rather than under-enforce.
How It Works
The sandbox is applied to the entire grok process at startup using kernel primitives -- not per-command wrapping. This means all tool operations are covered:
read_file,search_replace,list_dir-- restricted by Landlock/Seatbelt in-processbashcommands,grep(rg) -- child processes inherit FS restrictions automatically- Network -- on Linux, child processes can be blocked via seccomp; on macOS this is a no-op
The sandbox is irreversible once applied. The agent cannot relax restrictions at runtime.
Resuming Sessions
The profile a session was started with is saved with the session and is fixed
for the life of the session. When you resume it (grok --resume <id>,
grok --continue, or grok -r), Grok restores that same profile automatically —
so a session started with --sandbox workspace won't silently come back under a
stricter default and break commands that previously worked.
Resuming will not change a session's sandbox:
- Omitting
--sandboxon resume uses the session's saved profile. - Passing
--sandbox <profile>that matches the saved profile is allowed. - Passing
--sandbox <profile>that differs from the saved profile is refused with an error — changing a resumed session's sandbox is a safety footgun (it could widen access the session was meant to be confined to, or break a session that relied on broader access). Start a new session to use a different profile.
Profile resolution order for a new session:
- An explicit
--sandbox <profile>flag orKIGI_SANDBOXenvironment variable - The
[sandbox] profilein your config off(no sandbox)
Platform Support
| Platform | Mechanism | Minimum Version |
|---|---|---|
| Linux | Landlock | Kernel 5.13 or later |
| macOS | Seatbelt | macOS (all versions) |
If the sandbox cannot be applied (e.g., unsupported kernel, missing entitlements), Grok logs a warning and continues without enforcement. The exception is an explicitly-requested custom profile: on both macOS and Linux, if it cannot be applied (unknown profile, malformed sandbox.toml, or — on Linux — bubblewrap unavailable for a non-empty deny), Grok refuses to start rather than run with its denied paths exposed.
Network Restrictions
On Linux, profiles with restrict_network block network access in child processes (bash commands, scripts) via seccomp. On macOS, network blocking is a no-op. Built-in tools that make HTTP requests in-process (web search, LLM API calls) are never affected -- the agent needs network access to function.
In practice, on Linux this means:
web_search,web_fetch, and the LLM API always have network accessbashcommands likecurl,wget, andnpm installare blocked whenrestrict_networkis enabled
Event Logging
Sandbox events are logged to ~/.kigi/sandbox-events.jsonl for debugging. Events include:
- Profile applied (which profile, timestamp)
- Violations (attempted access to denied paths)
When to Use Sandbox Mode
Use workspace when:
- Working on your own projects and you want basic write protection
- Running in shared environments where you want to limit the scope of changes
Define a custom profile with a deny list when:
- You need to block specific files (e.g.
.envor credential paths) on top of a base profile - You need kernel enforcement that covers
bash,grep, and subagents — not just theread_filetool
Use read-only when:
- Reviewing code you do not trust
- Exploring a codebase without risk of accidental modification
- Running code analysis or audits
Use strict when:
- Analyzing untrusted or third-party code
- Running in security-sensitive environments
- You want maximum isolation
Skip sandbox when:
- The agent needs to install dependencies (
npm install,pip install) - The agent needs to modify files outside the working directory
- You are working in a trusted environment and want maximum flexibility
Trade-offs
| Aspect | Without Sandbox | With Sandbox |
|---|---|---|
| Safety | Agent has full system access | Agent restricted to profile rules |
| Capability | Can do anything | Limited by profile |
| Performance | No overhead | Negligible overhead |
| Recovery | Must trust the agent | Kernel enforces boundaries |
The sandbox enforces limits at the OS level -- through Landlock or a mount namespace on Linux, and Seatbelt on macOS -- not a separate VM.