Synced from monorepo

Changes:
- Classify clipboard delivery confidence
- Add durable session update append
- Scope the xAI session bearer to first-party memory embedding endpoints
- Persist subagent outputs to disk and bound long-lived agent state
- Add MiniSweAgent:bash for mini-swe-agent parity
- Revert taking local sessions off the persistent shell
- Contextual tip recommending grok wrap on SSH sessions
- Voice STT bearer from model BYOK env_key/api_key
- Define exact website policies for sandbox
- Gate unsafe shell environments
- Shared pin hoist; single require_sha gate for marketplace plugins
- Server-signed is-managed claim (closes sidecar-removal downgrade)
- Optional require_sha pin for remote plugin installs
- Show session title and last exchange in the exit resume hint
- Gate shell output redirects
- Warn when fail_closed is present but not a boolean
- Add canonical text editing core (ratatui-textarea)
- Keep execution state out of goal scratch
- Add acknowledged persistence primitives
- Inherit child network restrictions in sandbox
- Fail closed when hook matchers fail to recompile
- Add MCP setup preferences for plugin MCPs
- Gate sourced shell scripts
- Gate file-typed project hooks
- grok wrap: restore terminal modes on child death
- Harden owner-only permissions on auth and MCP credentials
- Create crash dump files with owner-only permissions
- Write the agent_id cache owner-only (0600)
- SessionMetrics mode skips Mixpanel profile sync
- Dashboard: slim live-tail peek
- Yank full queued prompt text, not (+N lines)
- Defeat clock-rollback on the signed managed-config cache
- Stop early session/cancel from overtaking the prompt and wedging the turn slot
- Self-heal a diverged agent entrypoint on startup
- Add matched inference expectations in test-support
- Add AuthSingleFlight cancel/successor gap tests
- Remove consumer from external OTEL allowlist and pin scrub coverage
- Enable /copy in minimal mode
- Surface capacity and API-key detail on 429 errors
- Single-flight interactive auth
- Fix PageUp/PageDown skipping lines behind sticky prompt header
This commit is contained in:
grokkybara[bot] 2026-07-17 14:19:50 +01:00
commit 98c3b2438a
225 changed files with 18836 additions and 7156 deletions

View file

@ -29,6 +29,25 @@ struct RawSource {
branch: Option<String>,
}
/// Whether remote plugin installs/updates must pin a full commit sha.
///
/// `[marketplace] require_sha = true` in config.toml, or
/// `GROK_MARKETPLACE_REQUIRE_SHA=1`. Tighten-only: either source can enable,
/// neither can override the other off. Defaults off so existing unpinned
/// catalogs keep installing.
pub fn load_require_sha(config: &toml::Value) -> bool {
env_require_sha()
|| config
.get("marketplace")
.and_then(|m| m.get("require_sha"))
.and_then(|v| v.as_bool())
.unwrap_or(false)
}
pub fn env_require_sha() -> bool {
xai_grok_config::env_bool("GROK_MARKETPLACE_REQUIRE_SHA").unwrap_or(false)
}
/// Reads `[marketplace].sources` array. Returns empty vec if not configured.
pub fn load_sources(config: &toml::Value) -> Vec<MarketplaceSource> {
let Some(marketplace) = config.get("marketplace") else {
@ -310,6 +329,34 @@ mod tests {
assert!(load_sources(&config).is_empty());
}
/// Drives the shipped composition: config alone, env alone, and the
/// tighten-only rule (falsy env cannot relax config-set true).
#[test]
fn require_sha_policy_composition() {
// Process-global env: serialize against any other env-touching test.
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _guard = ENV_LOCK.lock().unwrap();
let empty: toml::Value = toml::from_str("").unwrap();
let enabled: toml::Value = toml::from_str("[marketplace]\nrequire_sha = true\n").unwrap();
// SAFETY: single-threaded within the lock; restored before release.
unsafe { std::env::remove_var("GROK_MARKETPLACE_REQUIRE_SHA") };
assert!(!load_require_sha(&empty), "absent everywhere → off");
assert!(load_require_sha(&enabled), "config alone can enable");
unsafe { std::env::set_var("GROK_MARKETPLACE_REQUIRE_SHA", "1") };
assert!(load_require_sha(&empty), "env alone can enable");
unsafe { std::env::set_var("GROK_MARKETPLACE_REQUIRE_SHA", "0") };
assert!(
load_require_sha(&enabled),
"a falsy env must not relax config-set policy (tighten-only)"
);
unsafe { std::env::remove_var("GROK_MARKETPLACE_REQUIRE_SHA") };
}
#[test]
fn missing_sources_key_returns_empty() {
let config: toml::Value = toml::from_str("[marketplace]\n").unwrap();

View file

@ -62,7 +62,9 @@ pub fn install_from_marketplace(
subdir: None,
};
match git_install::install_from_source(&source, registry) {
// Local copy from the synced source checkout: the pin gate governs remote
// fetches only (see install_from_remote_url's security doc).
match git_install::install_from_source(&source, registry, false) {
Ok(result) => {
let repo_key = result.repo_key.clone();
let installed_path = registry.install_dir().join(&repo_key);
@ -93,7 +95,7 @@ pub fn install_from_marketplace(
registry.remove(&key);
registry.save()?;
// Retry — registry no longer has the key.
match git_install::install_from_source(&source, registry) {
match git_install::install_from_source(&source, registry, false) {
Ok(result) => {
let repo_key = result.repo_key.clone();
let installed_path = registry.install_dir().join(&repo_key);
@ -119,6 +121,19 @@ pub fn install_from_marketplace(
///
/// Clones the plugin repo and installs it via the standard git install
/// pipeline; pins to `git_sha` if set, otherwise uses `git_ref` or HEAD.
///
/// # Security
///
/// Marketplace plugins are **not cryptographically signed**. A remote install
/// without `git_sha` tracks a mutable ref (branch/tag/HEAD) and can be
/// substituted by anyone who can push that ref. Prefer publishing `sha` in
/// `plugin-index.json` and installing with that pin.
///
/// `require_sha` (from [`crate::config::load_require_sha`]) fails such installs
/// closed. It covers every path that fetches plugin code from a remote git URL
/// (marketplace `remote_url` entries, direct installs, git updates). It does
/// NOT cover plugins vendored inside a marketplace source itself — those come
/// from the synced source checkout, whose branch is not yet pinnable.
pub fn install_from_remote_url(
url: &str,
git_ref: Option<&str>,
@ -127,6 +142,7 @@ pub fn install_from_remote_url(
plugin_name: &str,
provenance: MarketplaceProvenance,
registry: &mut InstallRegistry,
require_sha: bool,
) -> Result<MarketplaceInstallResult, InstallError> {
let subdir = subdir
.map(|s| {
@ -137,6 +153,8 @@ pub fn install_from_remote_url(
})
})
.transpose()?;
// No-fetch short-circuit before the pin gate: re-install of an already-present
// plugin must not refuse just because the catalog entry is unpinned.
if let Some((existing_key, _)) = find_installed_marketplace_plugin(
registry,
&provenance.source_url_or_path,
@ -153,7 +171,14 @@ pub fn install_from_remote_url(
subdir,
};
match git_install::install_from_source(&source, registry) {
// Single pin gate lives in install_from_source; pass plugin_name so refusals
// name the catalog entry rather than the bare URL.
match git_install::install_from_source_with_label(
&source,
registry,
require_sha,
Some(plugin_name),
) {
Ok(result) => {
let repo_key = result.repo_key.clone();
let installed_path = registry.install_dir().join(&repo_key);
@ -176,7 +201,12 @@ pub fn install_from_remote_url(
let _ = std::fs::remove_file(&old_path);
registry.remove(&key);
registry.save()?;
match git_install::install_from_source(&source, registry) {
match git_install::install_from_source_with_label(
&source,
registry,
require_sha,
Some(plugin_name),
) {
Ok(result) => {
let repo_key = result.repo_key.clone();
let installed_path = registry.install_dir().join(&repo_key);
@ -203,6 +233,7 @@ pub fn update_from_marketplace_entry_transactional(
entry: &MarketplaceEntry,
mut provenance: MarketplaceProvenance,
registry: &mut InstallRegistry,
require_sha: bool,
) -> Result<MarketplaceUpdateResult, InstallError> {
let plugin_relative_path =
MarketplaceRelativePath::parse(&entry.relative_path).map_err(|e| {
@ -272,12 +303,11 @@ pub fn update_from_marketplace_entry_transactional(
remove_path_if_exists(&backup_path)?;
let stage_result = if let Some(url) = entry.remote_url.as_deref() {
clone_repo_to_path(
url,
entry.remote_ref.as_deref(),
entry.remote_sha.as_deref(),
&staging_path,
)
// Catalog pins published as `ref` still need hoisting for the verified clone path.
let (git_ref, git_sha) =
git_install::hoist_pin_slots(entry.remote_ref.as_deref(), entry.remote_sha.as_deref());
git_install::ensure_pinned(require_sha, git_sha, &entry.name, url)?;
clone_repo_to_path(url, git_ref, git_sha, &staging_path)
} else {
let source_path = plugin_relative_path
.join_under(marketplace_root)
@ -793,6 +823,144 @@ mod tests {
static TEST_HOME: OnceLock<tempfile::TempDir> = OnceLock::new();
static TEST_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn require_sha_rejects_unpinned_remote_install() {
with_test_registry(|registry| {
let err = install_from_remote_url(
"https://example.com/plugin.git",
Some("main"),
None, // no sha
None,
"plugins/demo",
MarketplaceProvenance {
source_url_or_path: "https://example.com/market.git".into(),
source_display_name: "test".into(),
plugin_subdir: "plugins/demo".into(),
},
registry,
true, // require_sha
)
.unwrap_err();
assert!(
matches!(err, InstallError::UnpinnedRemoteRefused { .. }),
"expected the typed refusal, got: {err}"
);
let err = install_from_remote_url(
"https://example.com/plugin.git",
None,
Some("main"),
None,
"plugins/demo",
MarketplaceProvenance {
source_url_or_path: "https://example.com/market.git".into(),
source_display_name: "test".into(),
plugin_subdir: "plugins/demo".into(),
},
registry,
true,
)
.unwrap_err();
assert!(
matches!(err, InstallError::UnpinnedRemoteRefused { .. }),
"a non-hex 'pin' must be refused up front, got: {err}"
);
});
}
#[test]
fn require_sha_already_installed_skips_pin_gate() {
if !git_available() {
eprintln!("skipping: `git` binary not available in test sandbox");
return;
}
with_test_registry(|registry| {
let repo = tempfile::tempdir().unwrap();
run_git(repo.path(), &["init", "--initial-branch=main", "--quiet"]);
write_root_plugin(repo.path(), "acme", "1.0.0");
run_git(repo.path(), &["add", "-A"]);
run_git(repo.path(), &["commit", "-m", "v1", "--quiet"]);
let url = format!("file://{}", repo.path().display());
let provenance = MarketplaceProvenance {
source_url_or_path: "https://example.com/marketplace.git".into(),
source_display_name: "Test".into(),
plugin_subdir: "acme".into(),
};
// Unpinned first install (policy off) so the registry is populated.
match install_from_remote_url(
&url,
Some("main"),
None,
None,
"acme",
provenance.clone(),
registry,
false,
)
.unwrap()
{
MarketplaceInstallResult::Installed { .. } => {}
MarketplaceInstallResult::AlreadyInstalled { repo_key } => {
panic!("expected fresh Installed, got AlreadyInstalled {repo_key}")
}
}
// No-fetch re-install under require_sha must not refuse unpinned catalog entries.
match install_from_remote_url(
&url,
Some("main"),
None,
None,
"acme",
provenance,
registry,
true,
)
.unwrap()
{
MarketplaceInstallResult::AlreadyInstalled { .. } => {}
MarketplaceInstallResult::Installed { repo_key } => {
panic!("expected AlreadyInstalled, got Installed {repo_key}")
}
}
});
}
#[test]
fn require_sha_rejects_unpinned_remote_update() {
with_test_registry(|registry| {
let marketplace = tempfile::tempdir().unwrap();
write_plugin(marketplace.path(), "demo", "1.0.0", "old");
let repo_key = install_test_plugin(registry, marketplace.path(), "demo");
let mut entry = crate::scan_marketplace(marketplace.path())
.entries
.into_iter()
.find(|p| p.relative_path == "plugins/demo")
.unwrap();
entry.remote_url = Some("https://example.com/plugin.git".into());
entry.remote_sha = None;
let err = update_from_marketplace_entry_transactional(
marketplace.path(),
&entry,
provenance(marketplace.path(), "plugins/demo"),
registry,
true, // require_sha
)
.unwrap_err();
assert!(
matches!(err, InstallError::UnpinnedRemoteRefused { .. }),
"expected the typed refusal, got: {err}"
);
assert!(
registry.install_dir().join(&repo_key).exists(),
"a refused update must leave the existing install in place"
);
});
}
fn with_test_registry<T>(f: impl FnOnce(&mut InstallRegistry) -> T) -> T {
let _guard = TEST_LOCK.lock().unwrap();
let home = TEST_HOME.get_or_init(|| tempfile::tempdir().unwrap());
@ -893,6 +1061,7 @@ mod tests {
&entry,
provenance(marketplace.path(), "plugins/demo"),
registry,
false, // require_sha off: pin policy has its own tests
)
.unwrap();
@ -933,6 +1102,7 @@ mod tests {
&entry,
provenance(marketplace.path(), "plugins/demo"),
registry,
false, // require_sha off: pin policy has its own tests
)
.unwrap();
@ -963,6 +1133,7 @@ mod tests {
&entry,
provenance(marketplace.path(), "plugins/demo"),
registry,
false, // require_sha off: pin policy has its own tests
);
unsafe { std::env::remove_var("XAI_GROK_TEST_FAIL_REGISTRY_SAVE_AFTER_SERIALIZE") };
@ -1015,6 +1186,7 @@ mod tests {
&entry,
provenance(marketplace.path(), "plugins/demo"),
registry,
false, // require_sha off: pin policy has its own tests
);
assert!(matches!(result, Err(InstallError::InstallFailed { .. })));
@ -1049,6 +1221,7 @@ mod tests {
"acme",
provenance,
registry,
false, // require_sha off: pin policy has its own tests
);
assert!(matches!(result, Err(InstallError::InstallFailed { .. })));
});
@ -1135,6 +1308,7 @@ mod tests {
"acme",
provenance.clone(),
registry,
false, // require_sha off: pin policy has its own tests
)
.unwrap()
{
@ -1177,6 +1351,7 @@ mod tests {
&entry,
provenance,
registry,
false, // require_sha off: pin policy has its own tests
)
.unwrap();
@ -1240,6 +1415,7 @@ mod tests {
&entry,
provenance.clone(),
registry,
false, // require_sha off: pin policy has its own tests
);
assert!(
matches!(result, Err(InstallError::InstallFailed { .. })),
@ -1291,6 +1467,7 @@ mod tests {
"acme",
provenance.clone(),
registry,
false, // require_sha off: pin policy has its own tests
)
.unwrap()
{
@ -1308,6 +1485,7 @@ mod tests {
"acme",
provenance,
registry,
false, // require_sha off: pin policy has its own tests
)
.unwrap()
{

View file

@ -16,7 +16,8 @@ pub mod scanner;
pub mod types;
pub use config::{
load_extra_sources_from_settings, load_extra_sources_from_settings_in, load_sources,
env_require_sha, load_extra_sources_from_settings, load_extra_sources_from_settings_in,
load_require_sha, load_sources,
};
pub use error::MarketplaceError;
pub use scanner::scan_marketplace;