Synced from monorepo

Synced from monorepo

Changes:
- Workspace server: report `/ready` as failed with dwell on hub connect failure
- Refresh OIDC token for the Grok agent in the shell
- ACP terminal output recorder
- Cross-platform provider auth commands in the shell
- Default `/resume` to Grok sessions with a hint for hidden external sessions
- Resume sessions by title with `--resume`
- Limit app-builder archive size
- Data-driven tag labels for slash commands
- Doctor fixes for tmux
- Custom provider gateways and subprocess environment policy in the shell
- `/tutorial` — opt-in onboarding tour of Grok Build
- Soft and required CLI version checks in the shell
- Privacy banner env overrides survive live settings updates
- Add remote flag to override the image-edit model
- Return profile fields from auth info even when the access token is expired
- Add edit control on queued prompt rows
- Keep fail-closed policy when clearing orphans with no team
- Setting to disable the Ctrl+Space/F8 voice shortcut
- Pass `--raw` to pw-record so Linux dictation works on older PipeWire
- Validate git URLs when adding marketplace entries
- Stop shipping stale tool-doc parameter and tool names
- Re-point dashboard attach after `/fork` only when the parent was attached
- Surface Grok Computer media-generation results as file-path chunks
- Clear web background-task tray on kill and keep the task description
- Show privacy upsell banner in agent view until acted on
- Add tools-server client callback surface
- Protect persistent global hook sources

Source-Revision: 95d84f443eddcbed6cbfd6eed22e2eafe6b3939d
This commit is contained in:
grokkybara[bot] 2026-07-23 17:12:33 +00:00
commit 69f0ba880a
286 changed files with 22939 additions and 9624 deletions

View file

@ -11,13 +11,15 @@ use std::path::{Path, PathBuf};
#[cfg(all(feature = "enforce", unix))]
use crate::deny::{
apply_deny_globs_to_capability_set, apply_deny_paths_to_capability_set, effective_deny_paths,
partition_deny_entries,
apply_deny_globs_to_capability_set, apply_deny_paths_to_capability_set,
apply_write_deny_paths_to_capability_set, effective_deny_paths, partition_deny_entries,
};
use crate::hook_write_deny::profile_hook_write_deny;
use crate::paths::grok_home;
#[cfg(all(feature = "enforce", unix))]
use crate::paths::{DEVICE_DIRS, DEVICE_FILES};
use crate::paths::{essential_writable_paths, essential_writable_paths_minimal};
use xai_grok_config::GlobalHookSource;
/// A resolved sandbox profile ready to be converted to a `CapabilitySet`.
#[derive(Debug, Clone)]
@ -30,12 +32,18 @@ pub struct SandboxProfile {
pub read_write: Vec<PathBuf>,
/// Paths denied entirely (overrides read_only/read_write)
pub deny: Vec<PathBuf>,
/// Typed direct global hook sources (write-denied, still readable).
pub write_deny: Vec<GlobalHookSource>,
/// Whether to grant read access to the entire filesystem by default
pub default_read: bool,
/// Whether child processes should have network blocked
pub restrict_network: bool,
}
fn resolve_write_deny(profile: &ProfileName) -> anyhow::Result<Vec<GlobalHookSource>> {
profile_hook_write_deny(profile)
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct ProfileConfig {
#[serde(default)]
@ -280,6 +288,27 @@ impl ProfileName {
}
}
// Direct global-hook write-deny (macOS Seatbelt; Linux via bwrap).
if !profile.write_deny.is_empty() {
let mut pairs: Vec<(PathBuf, bool)> = profile
.write_deny
.iter()
.map(|s| (s.path.clone(), s.is_dir()))
.collect();
#[cfg(unix)]
{
let files =
xai_grok_config::validated_hook_json_files_for_sources(&profile.write_deny)
.map_err(|e| anyhow::anyhow!("hook JSON alias validation failed: {e}"))?;
for f in files {
if !pairs.iter().any(|(p, _)| p == &f) {
pairs.push((f, false));
}
}
}
apply_write_deny_paths_to_capability_set(&mut caps, &pairs, &profile.read_write)?;
}
// Kernel deny (read+write): macOS Seatbelt rules; Linux via bwrap bind-over.
// The effective deny set is the profile's own `deny` (custom profiles only;
// built-ins carry an empty `deny`). An empty set means there is nothing to
@ -325,6 +354,7 @@ impl ProfileName {
read_only: vec![],
read_write: essential_writable_paths(workspace),
deny: vec![],
write_deny: resolve_write_deny(self)?,
default_read: true,
restrict_network: false,
}),
@ -363,6 +393,7 @@ impl ProfileName {
read_only: vec![],
read_write,
deny: vec![],
write_deny: vec![],
default_read: true,
restrict_network: false,
})
@ -373,6 +404,7 @@ impl ProfileName {
read_only: vec![],
read_write: essential_writable_paths_minimal(),
deny: vec![],
write_deny: resolve_write_deny(self)?,
default_read: true,
restrict_network: true,
}),
@ -398,6 +430,7 @@ impl ProfileName {
.chain(std::iter::once(home.join("Library")))
.filter(|p| p.exists())
.chain(std::iter::once(workspace.to_path_buf()))
.chain(std::iter::once(grok_home()))
.collect();
Ok(SandboxProfile {
@ -405,6 +438,7 @@ impl ProfileName {
read_only: system_read,
read_write: essential_writable_paths(workspace),
deny: vec![],
write_deny: resolve_write_deny(self)?,
default_read: false,
restrict_network: true,
})
@ -422,7 +456,7 @@ impl ProfileName {
})?;
// Start from the base profile if `extends` is set
let mut profile = if let Some(base_name) = &profile_config.extends {
let (base, mut profile) = if let Some(base_name) = &profile_config.extends {
let base: ProfileName = base_name.parse().map_err(|e: String| {
anyhow::anyhow!("Profile '{name}' extends invalid base: {e}")
})?;
@ -438,10 +472,10 @@ impl ProfileName {
cannot extend other custom profiles (only built-ins)"
);
}
base.resolve(workspace, config)?
let resolved = base.resolve(workspace, config)?;
(base, resolved)
} else {
// Default: start from workspace
Self::Workspace.resolve(workspace, config)?
(Self::Workspace, Self::Workspace.resolve(workspace, config)?)
};
profile.name = name.clone();
@ -466,6 +500,10 @@ impl ProfileName {
profile.deny.push(PathBuf::from(path_str));
}
if matches!(base, Self::Devbox) {
profile.write_deny.clear();
}
Ok(profile)
}
}
@ -528,8 +566,26 @@ mod tests {
assert_eq!(p.to_string(), "my-custom");
}
/// Hosts with a retargetable `$GROK_HOME/hooks` symlink (fail-closed under
/// write-deny) cannot resolve enforcing profiles against the real home.
fn skip_if_host_hook_write_deny_unresolvable() -> bool {
if !crate::hook_write_deny::profile_enforces_hook_write_deny(&ProfileName::Workspace) {
return false;
}
match crate::hook_write_deny::resolve_hook_write_deny_snapshot() {
Ok(_) => false,
Err(e) => {
eprintln!("skipping profile resolve test: host hook write-deny unresolvable ({e})");
true
}
}
}
#[test]
fn built_in_network_restriction_values() {
if skip_if_host_hook_write_deny_unresolvable() {
return;
}
let workspace = std::env::current_dir().unwrap();
let config = SandboxConfig::default();
@ -593,6 +649,9 @@ mod tests {
#[test]
fn custom_network_restriction_inherits_and_overrides_base() {
if skip_if_host_hook_write_deny_unresolvable() {
return;
}
let workspace = std::env::current_dir().unwrap();
let config = network_inheritance_config();
@ -611,6 +670,9 @@ mod tests {
#[test]
#[cfg(all(feature = "enforce", unix))]
fn strict_allowlist_includes_run_and_var_when_present() {
if skip_if_host_hook_write_deny_unresolvable() {
return;
}
// Regression: /run (resolv realpath) + /var (NSS/SSSD) when present.
let workspace = std::env::temp_dir();
let profile = ProfileName::Strict
@ -636,6 +698,9 @@ mod tests {
#[test]
#[cfg(all(feature = "enforce", unix))]
fn base_profile_capability_set_builds() {
if skip_if_host_hook_write_deny_unresolvable() {
return;
}
// A base profile with no `deny` builds a CapabilitySet without erroring.
let workspace = std::env::current_dir().unwrap();
let config = SandboxConfig::default();
@ -646,6 +711,9 @@ mod tests {
#[test]
#[cfg(all(feature = "enforce", unix))]
fn custom_profile_from_config() {
if skip_if_host_hook_write_deny_unresolvable() {
return;
}
let workspace = std::env::current_dir().unwrap();
let config = SandboxConfig {
profiles: HashMap::from([(
@ -901,6 +969,9 @@ read_write = ["/tmp/ci-artifacts"]
#[test]
#[cfg(all(feature = "enforce", unix))]
fn strict_capability_set_builds_without_openable_dev_tty() {
if skip_if_host_hook_write_deny_unresolvable() {
return;
}
let workspace = std::env::current_dir().unwrap();
let result = ProfileName::Strict.to_capability_set(&workspace);
assert!(