Port of upstream grok-build 1.0.14 ("--sandbox strict now restricts
writes to ~/.grok/sessions only"), shaped for Kigi.
The strict profile granted the whole ~/.kigi read-write, so a sandboxed
command could plant a global SessionStart hook or flip
`[sandbox].profile` in config.toml for the next launch. Landlock cannot
carve a child out of a write grant, so the home is now read-only
except an explicit allowlist of state subtrees (run, sessions,
worktrees, worktree_pool, tasks, memory, logs, debug, crash, memtrace,
trace-exports); config.toml, auth.json, sandbox.toml, hooks/, bin/ and
vendor/ stay read-only. A symlinked state dir under the home is
followed only to the default ~/.kigi/sessions.
What the process itself writes there after enforcement moves or is
minted first:
- kigi-shell `apply_sandbox` mints the device id, extracts the bundled
rg and the bundled skills BEFORE enforcement for every profile that
restricts home writes (`restricts_home_writes`, custom profiles
extending strict included).
- The sandbox audit log lives at ~/.kigi/sessions/sandbox-events.jsonl.
- Leader lock/socket default to ~/.kigi/run/, the leader stderr log to
~/.kigi/logs/leader.log; discovery scans run/ and the legacy root,
and the default target prefers the current socket.
Root-level caches (version.json, agent_id, slash-mru.json, pager.toml,
claude_import_state.json) degrade best-effort under strict; documented.
Claude-Session: https://claude.ai/code/session_01RUqV8HVGXcAETyYbZ2tPQ4
104 lines
2.7 KiB
Rust
104 lines
2.7 KiB
Rust
//! Sandbox event logger.
|
|
//!
|
|
//! Events (profile applied, violations, bypasses) are buffered in memory and
|
|
//! flushed as JSONL to `~/.kigi/sessions/sandbox-events.jsonl`.
|
|
|
|
use std::path::PathBuf;
|
|
use std::sync::Mutex;
|
|
|
|
use crate::types::{SandboxEvent, SandboxEventType, SandboxMetrics};
|
|
|
|
pub struct SandboxLogger {
|
|
events: Mutex<Vec<SandboxEvent>>,
|
|
metrics: SandboxMetrics,
|
|
}
|
|
|
|
impl SandboxLogger {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
events: Mutex::new(Vec::new()),
|
|
metrics: SandboxMetrics::default(),
|
|
}
|
|
}
|
|
|
|
pub fn log(&self, event: SandboxEvent) {
|
|
match &event.event_type {
|
|
SandboxEventType::FsViolation => self.metrics.inc_fs_violation(),
|
|
SandboxEventType::NetViolation => self.metrics.inc_net_violation(),
|
|
SandboxEventType::BypassGranted => self.metrics.inc_bypass_granted(),
|
|
SandboxEventType::BypassDenied => self.metrics.inc_bypass_denied(),
|
|
_ => {}
|
|
}
|
|
|
|
tracing::debug!(
|
|
event_type = ?event.event_type,
|
|
profile = %event.profile,
|
|
target = ?event.target,
|
|
operation = ?event.operation,
|
|
"sandbox event"
|
|
);
|
|
|
|
if let Ok(mut events) = self.events.lock() {
|
|
events.push(event);
|
|
}
|
|
}
|
|
|
|
pub fn metrics(&self) -> &SandboxMetrics {
|
|
&self.metrics
|
|
}
|
|
|
|
/// Drains the buffer.
|
|
pub fn take_events(&self) -> Vec<SandboxEvent> {
|
|
self.events
|
|
.lock()
|
|
.map(|mut events| std::mem::take(&mut *events))
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
pub fn flush_to_disk(&self) -> anyhow::Result<()> {
|
|
let events = self.take_events();
|
|
if events.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
let log_path = Self::log_file_path();
|
|
if let Some(parent) = log_path.parent() {
|
|
std::fs::create_dir_all(parent)?;
|
|
}
|
|
|
|
use std::io::Write;
|
|
let mut file = std::fs::OpenOptions::new()
|
|
.create(true)
|
|
.append(true)
|
|
.open(&log_path)?;
|
|
|
|
for event in &events {
|
|
if let Ok(json) = serde_json::to_string(event) {
|
|
writeln!(file, "{}", json)?;
|
|
}
|
|
}
|
|
|
|
tracing::debug!(
|
|
path = %log_path.display(),
|
|
count = events.len(),
|
|
"flushed sandbox events to disk"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Under `sessions/`: the only home subtree a strict sandbox leaves
|
|
/// writable, and this flush runs after enforcement.
|
|
fn log_file_path() -> PathBuf {
|
|
kigi_config::kigi_home()
|
|
.join("sessions")
|
|
.join("sandbox-events.jsonl")
|
|
}
|
|
}
|
|
|
|
impl Default for SandboxLogger {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|