docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment guidelines: delete redundant restatements, decorative banners, change narration, and end-of-line comments; keep and tighten the crucial ones (invariants, bug rationale, SAFETY blocks, ported-source attribution). No functional code changed. Every edit is proven comment-only against the prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a separate doctest-fence check. Where removing a comment made rustfmt or clippy want to re-lay-out adjacent code, the minimal triggering comment is restored so code tokens stay byte-identical. Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy --workspace --all-targets (0 warnings). Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for these guidelines (flags banners, end-of-line comments, change narration, and commented-out code).
This commit is contained in:
@@ -15,7 +15,8 @@ pub unsafe fn install_child_network_filter() -> std::io::Result<()> {
|
||||
|
||||
const SECCOMP_RET_ALLOW: u32 = 0x7fff_0000;
|
||||
const SECCOMP_RET_ERRNO: u32 = 0x0005_0000;
|
||||
const EPERM_VAL: u32 = 1; // libc::EPERM
|
||||
// libc::EPERM
|
||||
const EPERM_VAL: u32 = 1;
|
||||
|
||||
macro_rules! bpf_stmt {
|
||||
($code:expr, $k:expr) => {
|
||||
@@ -39,7 +40,8 @@ pub unsafe fn install_child_network_filter() -> std::io::Result<()> {
|
||||
};
|
||||
}
|
||||
|
||||
const NR_OFFSET: u32 = 0; // seccomp_data.nr offset
|
||||
// seccomp_data.nr offset
|
||||
const NR_OFFSET: u32 = 0;
|
||||
|
||||
let blocked_syscalls: &[i64] = &[
|
||||
SYS_connect,
|
||||
@@ -54,24 +56,22 @@ pub unsafe fn install_child_network_filter() -> std::io::Result<()> {
|
||||
let mut filter: Vec<sock_filter> = Vec::new();
|
||||
let total_checks = blocked_syscalls.len();
|
||||
|
||||
// 1. Load syscall number
|
||||
filter.push(bpf_stmt!(BPF_LD | BPF_W | BPF_ABS, NR_OFFSET));
|
||||
|
||||
// 2. Check each blocked syscall
|
||||
for (i, &syscall) in blocked_syscalls.iter().enumerate() {
|
||||
let remaining = total_checks - i - 1;
|
||||
filter.push(bpf_jump!(
|
||||
BPF_JMP | BPF_JEQ | BPF_K,
|
||||
syscall,
|
||||
remaining as u8 + 1, // match: jump to ERRNO
|
||||
0 // no match: check next
|
||||
// match: jump to ERRNO
|
||||
remaining as u8 + 1,
|
||||
// no match: check next
|
||||
0
|
||||
));
|
||||
}
|
||||
|
||||
// 3. Default: ALLOW
|
||||
filter.push(bpf_stmt!(BPF_RET | BPF_K, SECCOMP_RET_ALLOW));
|
||||
|
||||
// 4. Blocked: ERRNO(EPERM)
|
||||
filter.push(bpf_stmt!(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EPERM_VAL));
|
||||
|
||||
let prog = sock_fprog {
|
||||
|
||||
@@ -25,7 +25,7 @@ pub(crate) fn is_glob(entry: &str) -> bool {
|
||||
|
||||
/// Split a profile's raw deny entries into exact paths (handled by the literal /
|
||||
/// subpath kernel-deny flow) and glob patterns. Non-glob entries are returned
|
||||
/// unchanged so their exact-path enforcement is preserved with no regression.
|
||||
/// `unchanged` so their exact-path enforcement is preserved with no regression.
|
||||
#[cfg(all(feature = "enforce", unix))]
|
||||
pub(crate) fn partition_deny_entries(deny: &[PathBuf]) -> (Vec<PathBuf>, Vec<String>) {
|
||||
let mut exact = Vec::new();
|
||||
@@ -170,12 +170,15 @@ fn glob_tail_to_regex(tail: &str) -> String {
|
||||
chars.next();
|
||||
if chars.peek() == Some(&'/') {
|
||||
chars.next();
|
||||
out.push_str("(.*/)?"); // `**/` spans zero or more dirs
|
||||
// `**/` spans zero or more dirs
|
||||
out.push_str("(.*/)?");
|
||||
} else {
|
||||
out.push_str(".*"); // `**` spans anything, incl. `/`
|
||||
// `**` spans anything, incl. `/`
|
||||
out.push_str(".*");
|
||||
}
|
||||
} else {
|
||||
out.push_str("[^/]*"); // `*` stops at a path separator
|
||||
// `*` stops at a path separator
|
||||
out.push_str("[^/]*");
|
||||
}
|
||||
}
|
||||
'?' => out.push_str("[^/]"),
|
||||
@@ -622,7 +625,8 @@ mod tests {
|
||||
}
|
||||
for p in &patterns {
|
||||
if validate_deny_glob(p).is_err() {
|
||||
continue; // rejected patterns aren't enforced on either platform
|
||||
// rejected patterns aren't enforced on either platform
|
||||
continue;
|
||||
}
|
||||
let regexes = glob_to_seatbelt_regexes(Path::new("/ws"), p);
|
||||
assert_eq!(regexes.len(), 1, "expected one regex for {p:?}");
|
||||
@@ -732,7 +736,8 @@ mod tests {
|
||||
let _g = TmpTree(ws.clone());
|
||||
std::fs::create_dir_all(ws.join("sub/dir")).unwrap();
|
||||
std::fs::write(ws.join("sub/dir/key.pem"), "x").unwrap();
|
||||
std::fs::write(ws.join(".env"), "x").unwrap(); // hidden + usually gitignored
|
||||
// hidden + usually gitignored
|
||||
std::fs::write(ws.join(".env"), "x").unwrap();
|
||||
std::fs::write(ws.join("readable.txt"), "x").unwrap();
|
||||
let globs = vec!["**/*.pem".to_string(), "**/.env".to_string()];
|
||||
let out = expand_deny_globs(&ws, &globs, 64, 4096, 200_000).expect("should expand");
|
||||
|
||||
@@ -60,11 +60,9 @@ struct GlobalSandboxState {
|
||||
logger: SandboxLogger,
|
||||
applied: bool,
|
||||
}
|
||||
/// Whether child subprocesses should have network blocked via seccomp.
|
||||
pub fn should_restrict_child_network() -> bool {
|
||||
RESTRICT_CHILD_NETWORK.load(Ordering::Relaxed)
|
||||
}
|
||||
/// Whether bash commands should be auto-approved when the sandbox is active.
|
||||
pub fn should_auto_allow_bash() -> bool {
|
||||
AUTO_ALLOW_BASH.load(Ordering::Relaxed) && is_active()
|
||||
}
|
||||
@@ -75,7 +73,6 @@ pub fn set_auto_allow_bash(enabled: bool) {
|
||||
pub fn set_configured_profile(name: impl Into<String>) {
|
||||
let _ = CONFIGURED_PROFILE.set(name.into());
|
||||
}
|
||||
/// Resolved sandbox profile from startup, or `None` if `set_configured_profile` was never called.
|
||||
pub fn configured_profile_name() -> Option<&'static str> {
|
||||
CONFIGURED_PROFILE.get().map(|s| s.as_str())
|
||||
}
|
||||
@@ -83,15 +80,13 @@ pub fn configured_profile_name() -> Option<&'static str> {
|
||||
pub fn is_active() -> bool {
|
||||
SANDBOX.get().is_some_and(|s| s.applied)
|
||||
}
|
||||
/// The active sandbox profile name, or `None` if sandbox is not applied.
|
||||
pub fn profile_name() -> Option<&'static str> {
|
||||
SANDBOX
|
||||
.get()
|
||||
.filter(|s| s.applied)
|
||||
.map(|s| s.profile.as_str())
|
||||
}
|
||||
/// Log a sandbox violation. Immediately flushed to disk.
|
||||
/// No-op if sandbox is not active.
|
||||
/// Log a sandbox violation, flushing it to disk immediately.
|
||||
pub fn log_violation(target: &str, operation: &str) {
|
||||
if let Some(state) = SANDBOX.get() {
|
||||
state.logger.log(SandboxEvent::fs_violation(
|
||||
@@ -102,7 +97,6 @@ pub fn log_violation(target: &str, operation: &str) {
|
||||
let _ = state.logger.flush_to_disk();
|
||||
}
|
||||
}
|
||||
/// Flush sandbox events to disk. No-op if not initialized.
|
||||
pub fn flush() {
|
||||
if let Some(state) = SANDBOX.get()
|
||||
&& let Err(e) = state.logger.flush_to_disk()
|
||||
@@ -110,7 +104,6 @@ pub fn flush() {
|
||||
tracing::warn!(error = % e, "Failed to flush sandbox events to disk");
|
||||
}
|
||||
}
|
||||
/// Violation metrics, or `None` if sandbox is not active.
|
||||
pub fn metrics() -> Option<&'static SandboxMetrics> {
|
||||
SANDBOX.get().map(|s| s.logger.metrics())
|
||||
}
|
||||
@@ -122,7 +115,6 @@ pub struct SandboxManager {
|
||||
applied: bool,
|
||||
}
|
||||
impl SandboxManager {
|
||||
/// Create a sandbox manager. Does not apply until `apply()` is called.
|
||||
pub fn new(profile: ProfileName, _workspace: &Path) -> Self {
|
||||
let net_restricted = profile.restricts_network();
|
||||
Self {
|
||||
@@ -210,24 +202,19 @@ impl SandboxManager {
|
||||
applied: self.applied,
|
||||
});
|
||||
}
|
||||
/// Check whether the current platform supports sandboxing.
|
||||
#[cfg(all(feature = "enforce", unix))]
|
||||
pub fn support_info() -> nono::SupportInfo {
|
||||
Sandbox::support_info()
|
||||
}
|
||||
/// Whether the sandbox was successfully applied.
|
||||
pub fn is_applied(&self) -> bool {
|
||||
self.applied
|
||||
}
|
||||
/// Whether child subprocesses should have network blocked.
|
||||
pub fn restrict_child_network(&self) -> bool {
|
||||
self.applied && self.net_restricted
|
||||
}
|
||||
/// The active profile name.
|
||||
pub fn profile(&self) -> &ProfileName {
|
||||
&self.profile
|
||||
}
|
||||
/// Access the sandbox event logger (before `install()`).
|
||||
pub fn logger(&self) -> &SandboxLogger {
|
||||
&self.logger
|
||||
}
|
||||
@@ -480,7 +467,7 @@ pub fn bwrap_reexec_for_profile(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serial_test::serial;
|
||||
/// Save, set/remove, and auto-restore an env var on drop.
|
||||
/// Restores the previous value of the env var on drop.
|
||||
struct EnvGuard {
|
||||
key: &'static str,
|
||||
prev: Option<String>,
|
||||
@@ -612,8 +599,8 @@ mod tests {
|
||||
set_configured_profile("read-only");
|
||||
assert_eq!(configured_profile_name(), Some("read-only"));
|
||||
}
|
||||
/// Create a temp workspace whose `.kigi/sandbox.toml` contains `toml_body`.
|
||||
/// Returns the workspace path (caller removes it).
|
||||
/// Returns a workspace whose `.kigi/sandbox.toml` holds `toml_body`; the
|
||||
/// caller is responsible for removing it.
|
||||
#[cfg(all(feature = "enforce", unix))]
|
||||
fn temp_workspace_with_sandbox_toml(tag: &str, toml_body: &str) -> PathBuf {
|
||||
let nanos = std::time::SystemTime::now()
|
||||
@@ -626,9 +613,8 @@ mod tests {
|
||||
std::fs::write(kigi.join("sandbox.toml"), toml_body).unwrap();
|
||||
ws
|
||||
}
|
||||
/// Create a temp workspace defining a `denytest` profile (extends `workspace`)
|
||||
/// with the given `deny` list. `deny_toml` is the raw TOML array body
|
||||
/// (e.g. `"\".env\""`).
|
||||
/// Defines a `denytest` profile extending `workspace`. `deny_toml` is the
|
||||
/// raw TOML array body, e.g. `"\".env\""`.
|
||||
#[cfg(all(feature = "enforce", unix))]
|
||||
fn temp_workspace_with_deny(tag: &str, deny_toml: &str) -> PathBuf {
|
||||
temp_workspace_with_sandbox_toml(
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
//! Sandbox event logger.
|
||||
//!
|
||||
//! Records sandbox events (profile applied, violations, bypasses) for
|
||||
//! telemetry and debugging. Events are kept in memory and can be flushed
|
||||
//! to a JSONL file at `~/.kigi/sandbox-events.jsonl`.
|
||||
//! Events (profile applied, violations, bypasses) are buffered in memory and
|
||||
//! flushed as JSONL to `~/.kigi/sandbox-events.jsonl`.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::types::{SandboxEvent, SandboxEventType, SandboxMetrics};
|
||||
|
||||
/// Logger that collects sandbox events and maintains violation counters.
|
||||
pub struct SandboxLogger {
|
||||
events: Mutex<Vec<SandboxEvent>>,
|
||||
metrics: SandboxMetrics,
|
||||
@@ -23,7 +21,6 @@ impl SandboxLogger {
|
||||
}
|
||||
}
|
||||
|
||||
/// Record an event, updating metrics counters as appropriate.
|
||||
pub fn log(&self, event: SandboxEvent) {
|
||||
match &event.event_type {
|
||||
SandboxEventType::FsViolation => self.metrics.inc_fs_violation(),
|
||||
@@ -46,12 +43,11 @@ impl SandboxLogger {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the metrics counters.
|
||||
pub fn metrics(&self) -> &SandboxMetrics {
|
||||
&self.metrics
|
||||
}
|
||||
|
||||
/// Take all accumulated events, draining the internal buffer.
|
||||
/// Drains the buffer.
|
||||
pub fn take_events(&self) -> Vec<SandboxEvent> {
|
||||
self.events
|
||||
.lock()
|
||||
@@ -59,8 +55,6 @@ impl SandboxLogger {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Flush accumulated events to the JSONL log file.
|
||||
/// Each event is written as a single JSON line.
|
||||
pub fn flush_to_disk(&self) -> anyhow::Result<()> {
|
||||
let events = self.take_events();
|
||||
if events.is_empty() {
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// ── Kigi state directory ────────────────────────────────────────────────────
|
||||
// Kigi state directory
|
||||
|
||||
/// Kigi state directory — always writable (`$KIGI_SHARE_DIR` or `~/.kigi`).
|
||||
pub(crate) fn kigi_home() -> PathBuf {
|
||||
kigi_config::kigi_home()
|
||||
}
|
||||
|
||||
// ── Device files & directories ──────────────────────────────────────────────
|
||||
// Device files & directories
|
||||
|
||||
/// Device files that need write access for normal tool operation.
|
||||
///
|
||||
@@ -27,22 +27,30 @@ pub(crate) fn kigi_home() -> PathBuf {
|
||||
/// `/dev/pts` is a directory (PTY slaves on Linux) so it uses `allow_path`.
|
||||
#[cfg(all(feature = "enforce", unix))]
|
||||
pub(crate) const DEVICE_FILES: &[&str] = &[
|
||||
"/dev/null", // output sink — used by virtually every CLI tool
|
||||
"/dev/zero", // zero source — used by memory allocators
|
||||
"/dev/random", // entropy — used by crypto/TLS
|
||||
"/dev/urandom", // entropy — used by crypto/TLS
|
||||
"/dev/tty", // controlling terminal — used by git, ssh, gpg
|
||||
"/dev/ptmx", // PTY allocation — used by terminal spawning
|
||||
"/dev/fd", // file descriptor access (symlink to /proc/self/fd on Linux)
|
||||
// output sink — used by virtually every CLI tool
|
||||
"/dev/null",
|
||||
// zero source — used by memory allocators
|
||||
"/dev/zero",
|
||||
// entropy — used by crypto/TLS
|
||||
"/dev/random",
|
||||
// entropy — used by crypto/TLS
|
||||
"/dev/urandom",
|
||||
// controlling terminal — used by git, ssh, gpg
|
||||
"/dev/tty",
|
||||
// PTY allocation — used by terminal spawning
|
||||
"/dev/ptmx",
|
||||
// file descriptor access (symlink to /proc/self/fd on Linux)
|
||||
"/dev/fd",
|
||||
];
|
||||
|
||||
/// Device directories that need write access.
|
||||
#[cfg(all(feature = "enforce", unix))]
|
||||
pub(crate) const DEVICE_DIRS: &[&str] = &[
|
||||
"/dev/pts", // PTY slaves (Linux)
|
||||
// PTY slaves (Linux)
|
||||
"/dev/pts",
|
||||
];
|
||||
|
||||
// ── Temporary directories ───────────────────────────────────────────────────
|
||||
// Temporary directories
|
||||
|
||||
/// Temporary directories that need write access.
|
||||
///
|
||||
@@ -77,7 +85,7 @@ pub(crate) fn temp_writable_paths() -> Vec<PathBuf> {
|
||||
paths
|
||||
}
|
||||
|
||||
// ── Essential writable paths ────────────────────────────────────────────────
|
||||
// Essential writable paths
|
||||
|
||||
/// Writable directory paths for profiles that allow workspace writes (workspace, devbox, strict).
|
||||
/// Device files are handled separately via `allow_file` in `to_capability_set_with_config`.
|
||||
|
||||
@@ -367,10 +367,11 @@ impl ProfileName {
|
||||
"/run",
|
||||
// NSS/SSSD (and similar) under /var — needed beyond resolv.conf alone
|
||||
"/var",
|
||||
// macOS-specific paths (filtered by exists() below)
|
||||
"/System", // Security framework, dylibs, TLS certificates
|
||||
"/Library", // System-wide frameworks
|
||||
"/private", // Real path behind /etc, /tmp, /var symlinks
|
||||
// macOS-specific paths, filtered by exists() below:
|
||||
// /System (security framework, dylibs, TLS certs),
|
||||
// /Library (system-wide frameworks), /private (real path
|
||||
// behind the /etc, /tmp, /var symlinks).
|
||||
"/System", "/Library", "/private",
|
||||
]
|
||||
.iter()
|
||||
.map(PathBuf::from)
|
||||
|
||||
@@ -4,14 +4,13 @@ use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
/// A recorded sandbox event for telemetry and debugging.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SandboxEvent {
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub event_type: SandboxEventType,
|
||||
pub profile: String,
|
||||
|
||||
// Context fields — present on ProfileApplied/ApplyFailed
|
||||
// Only ProfileApplied and ApplyFailed carry these.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub workspace: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -27,7 +26,6 @@ pub struct SandboxEvent {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deny_paths: Option<Vec<String>>,
|
||||
|
||||
// Violation/error fields
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub operation: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -61,7 +59,6 @@ impl SandboxEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a "profile applied" event with full context.
|
||||
pub fn profile_applied(
|
||||
profile: &str,
|
||||
workspace: &std::path::Path,
|
||||
@@ -108,7 +105,6 @@ impl SandboxEvent {
|
||||
event
|
||||
}
|
||||
|
||||
/// Create an "apply failed" event with context.
|
||||
pub fn apply_failed(
|
||||
profile: &str,
|
||||
workspace: &std::path::Path,
|
||||
@@ -130,7 +126,6 @@ impl SandboxEvent {
|
||||
event
|
||||
}
|
||||
|
||||
/// Create a filesystem violation event.
|
||||
pub fn fs_violation(profile: &str, target: &str, operation: &str) -> Self {
|
||||
let mut event = Self::base(SandboxEventType::FsViolation, profile);
|
||||
event.operation = Some(operation.to_string());
|
||||
@@ -138,7 +133,6 @@ impl SandboxEvent {
|
||||
event
|
||||
}
|
||||
|
||||
/// Create a network violation event.
|
||||
pub fn net_violation(profile: &str, target: &str) -> Self {
|
||||
let mut event = Self::base(SandboxEventType::NetViolation, profile);
|
||||
event.operation = Some("connect".to_string());
|
||||
@@ -157,7 +151,6 @@ pub enum SandboxEventType {
|
||||
BypassDenied,
|
||||
}
|
||||
|
||||
/// Counters for sandbox activity, used for telemetry dashboards.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SandboxMetrics {
|
||||
pub fs_violations: AtomicU64,
|
||||
|
||||
Reference in New Issue
Block a user