Synced from monorepo

Changes:
- Non-blocking coding-data sharing upsell banner
- Consolidate remediation in Doctor
- Auto mode defers fail-closed gate asks to the classifier
- Coalesce marketplace list fetches
- Allow removing a marketplace source by name
- Contain hung git marketplace sources (timeouts, non-blocking refresh, unbrick modal)
- Label failed workspace RPCs with error_kind
- Drop redundant explicit tonic/prost deps from xai-grok-shell
- Report real exit codes for completed background shells
- Narrow the date-rollover reminder to date-bearing templates
- Wire toolOverrides through the session and agent
- Security: Bash(git:*) allowlist matches whole command chain by prefix
- Split prompt-trigger telemetry and record classifier provenance
- Raise connectors-manager timeout to 60s
- Auto classifier honors recorded approvals for repeat actions
- Apply doctor fixes in the TUI
- Auto-mode classifier timeouts prompt instead of silently denying
- Scope subagent completion drains to the owning session
- Add the toolOverrides wire types
- Set client_identifier=grok-agent-sdk
- Accept both spellings of the workspace-teleport kill switch
- Persist one-shot occurrence journal
- Stop turns that poll the exact same tool call 16x in a row
- Copy compaction checkpoint files when forking sessions
- Auto-focus permission prompt from scrollback
- Esc cancels the running turn in non-vim and minimal modes
- List Ctrl+Z undo and redo in keyboard shortcuts
- Out-of-process macOS mic capture
- Show active auth mode on session-info
- Install the npm binary under $GROK_HOME
- Remove hover/click dead zones between dashboard items
- Route startup warnings to doctor
- Document [feedback.user] author identity config
- Extend bang command timeout
- Close combine-queued edit-hold race
- Integrate relocation recovery
- Expose privacy notice rollout flag
- Break harness discovery ref cycle so connections can idle-evict
- Shift/Alt+Enter inserts newline when editing a queued prompt
- Gate project Claude permissions on folder trust
- Echo response.create.event_id on response.created
- Toast when session creation fails from disk full
- Add shared test process lifecycle
- Enable dynamic workflows by default
- Add relocation transaction state machine
- Add shared test sandbox
- Surface auth failures on model-switch compact
- Persist durable scheduler expiry
- Confirm before removing extensions-modal items
- Re-run compact and prompt after login when compact hit expired auth
- Recap sends hosted tools under backend search
This commit is contained in:
grokkybara[bot] 2026-07-22 19:18:53 +01:00
commit a5727c5960
482 changed files with 37627 additions and 13402 deletions

View file

@ -245,16 +245,44 @@ pub fn repo_configs_present(cwd: &Path) -> bool {
}
/// Display-only: which repo-local trust-sensitive config KINDS are present for
/// `cwd` (`mcp`, `plugins`, `lsp`, `envrc`, `claude`, `hooks`, `agents`, `roles`,
/// `personas`, `workflows`), deduped in cheap→expensive marker order. Single
/// source with [`repo_configs_present`] (which is
/// `cwd` (`mcp`, `plugins`, `permission`, `lsp`, `envrc`, `claude`, `hooks`,
/// `agents`, `roles`, `personas`, `workflows`), deduped in cheap→expensive
/// marker order. Single source with [`repo_configs_present`] (which is
/// `!repo_config_kinds(cwd).is_empty()`), so a folder that the gate fired on
/// always has a non-empty, accurate kind list — no `[plugins].paths` / `.claude`
/// / `.grok/agents` / subdir-launch gaps. NOT itself the trust gate.
/// always has a non-empty, accurate kind list — no `[plugins].paths` /
/// `[permission]` / `.claude` / `.grok/agents` / subdir-launch gaps. NOT itself
/// the trust gate.
pub fn repo_config_kinds(cwd: &Path) -> Vec<&'static str> {
collect_repo_config_kinds(cwd, false)
}
/// Whether a project `.grok/config.toml` `[permission]` value would contribute
/// rules to the permission resolver. Mirrors the compact/verbose shapes that
/// `permission::resolution` loads: non-empty `allow`/`deny`/`ask` string arrays,
/// or a non-empty verbose `rules` array. Empty arrays / empty tables do not gate
/// (same as empty `[mcp_servers]` / empty `[plugins].paths`).
fn config_toml_permission_contributes(permission_value: &TomlValue) -> bool {
let Some(table) = permission_value.as_table() else {
// Non-table `[permission]` fails config load elsewhere; treat as a
// marker so a malicious non-table still trips the gate rather than
// resolving trusted.
return true;
};
for key in ["deny", "allow", "ask"] {
if table
.get(key)
.and_then(|v| v.as_array())
.is_some_and(|a| !a.is_empty())
{
return true;
}
}
table
.get("rules")
.and_then(|v| v.as_array())
.is_some_and(|a| !a.is_empty())
}
fn path_present_or_uncertain(path: &Path) -> bool {
match std::fs::symlink_metadata(path) {
Ok(_) => true,
@ -302,11 +330,13 @@ fn collect_repo_config_kinds(cwd: &Path, first_only: bool) -> Vec<&'static str>
if !crate::project_config::find_mcp_json_files_in(&chain.dirs).is_empty() {
hit!("mcp");
}
// Project `.grok/config.toml` declaring repo-controlled code-exec: a
// non-empty `[mcp_servers]` table OR a non-empty `[plugins].paths` array.
// `[plugins].paths` loads as auto-trusted ConfigPath plugins, so a clone
// whose ONLY repo-local config is `[plugins].paths` must still be gated
// (else it resolves Trusted and the paths merge runs ungated => RCE).
// Project `.grok/config.toml` declaring repo-controlled code-exec or
// permission policy: a non-empty `[mcp_servers]` table, a non-empty
// `[plugins].paths` array, OR a contributing `[permission]` section.
// `[plugins].paths` loads as auto-trusted ConfigPath plugins; `[permission]`
// allow/deny/ask rules auto-approve or block tools — a clone whose ONLY
// repo-local config is either must still be gated (else it resolves Trusted
// and the loader runs ungated).
for path in crate::project_config::find_project_configs_in(&chain.dirs) {
let Ok(root) = xai_grok_config::load_config_file(&path) else {
continue;
@ -320,12 +350,18 @@ fn collect_repo_config_kinds(cwd: &Path, first_only: bool) -> Vec<&'static str>
.and_then(|v| v.get("paths"))
.and_then(|v| v.as_array())
.is_some_and(|a| !a.is_empty());
let has_permission = root
.get("permission")
.is_some_and(config_toml_permission_contributes);
if has_mcp_servers {
hit!("mcp");
}
if has_plugin_paths {
hit!("plugins");
}
if has_permission {
hit!("permission");
}
}
// Project `.grok/lsp.json`.
if cwd.join(".grok").join("lsp.json").is_file() {
@ -795,6 +831,48 @@ mod tests {
assert!(!repo_configs_present(tmp.path()));
}
#[test]
fn repo_configs_present_detects_grok_config_permission() {
// A repo whose ONLY repo-local config is a contributing `[permission]`
// section (no MCP/plugins/hooks) must still be gated: those allow rules
// auto-approve tool calls, so an ungated clone loads the attacker's
// policy. Also covers subdir launch (cwd→git-root walk).
let tmp = repo_tmp();
let grok = tmp.path().join(".grok");
std::fs::create_dir_all(&grok).unwrap();
std::fs::write(
grok.join("config.toml"),
"[permission]\nallow = [\"Bash(*)\"]\n",
)
.unwrap();
assert!(repo_configs_present(tmp.path()));
assert!(
repo_config_kinds(tmp.path()).contains(&"permission"),
"permission-only repo must report the permission kind"
);
let subdir = tmp.path().join("crates").join("inner");
std::fs::create_dir_all(&subdir).unwrap();
assert!(
repo_configs_present(&subdir),
"permission-only config at git root must gate subdir launches"
);
}
#[test]
fn repo_configs_present_false_for_empty_permission() {
// Empty allow/deny/ask arrays contribute no rules, so they must not
// trip the gate (mirrors empty `[mcp_servers]` / empty `[plugins].paths`).
let tmp = repo_tmp();
let grok = tmp.path().join(".grok");
std::fs::create_dir_all(&grok).unwrap();
std::fs::write(
grok.join("config.toml"),
"[permission]\nallow = []\ndeny = []\n",
)
.unwrap();
assert!(!repo_configs_present(tmp.path()));
}
#[test]
fn repo_config_kinds_matches_gate_and_reports_all_kinds() {
// SSOT guard: `repo_config_kinds` (full scan) must agree with the gate