Sampler / inference (PRD F3):
- kimi_compat.rs: single adaptation point for the Kimi chat/completions
dialect (thinking-field mapping, model_id stripping, empty-content
tool-call message fix, stream_options.include_usage), with kimi-cli
source citations
- Rate-limit handling reworked for Kimi/Moonshot semantics; UA kigi/{version}
- /models replaces the xAI models-v2 endpoint everywhere; idle model
refresh carries X-Msh-* device headers only (X-XAI-Token-Auth and
x-grok-client-mode/CLIENT_MODE_HEADER machinery deleted)
Cloud-surface excision (PRD §5, zero-egress):
- remote/ conversations lane, cli-chat-proxy-types crate, prod/ dir,
share command, credit bar: deleted (single local session lane;
paginate() replaces merge_and_paginate)
- Subscription/tier gate stack deleted end-to-end: AppView
gate/tier/team/ZDR fields, app/subscription.rs watch loop,
dispatch/billing.rs paywall + SuperGrok upsell, free-usage-exhausted
chain, tier-restricted commands, GateInfo, RemoteSettings gate fields,
SettingsUpdateNotification gate fields
- /privacy + coding-data-sharing setting deleted (backed by a dead xAI
RPC; Kigi is zero-egress — nothing to share or retain remotely)
Auth UX correctness (user-reported):
- Device-flow fixtures now mirror the live Kimi payload shape
(https://www.kimi.com/code/authorize_device?user_code=..., verified
against auth.kimi.com); the fabricated auth.kimi.com/device?code=...
URLs are gone
- open_browser_detached is a no-op under cfg(test): unit tests drove
wiremock fixture URLs into the real browser (root cause of the
"garbage mock link" ABCD-1234 tabs)
- Welcome/pager-minimal rebrand: Grok Build -> Kigi, grok.com ->
kimi.com, "Sign in to Grok" -> "Sign in to Kimi"
294 lines
11 KiB
Rust
294 lines
11 KiB
Rust
//! Extended KEYED managed-config scenarios. Harness + seam/serial constraints:
|
|
//! `signed_managed_config/common.rs`.
|
|
//!
|
|
//! Placement rule: new keyed scenarios land HERE; `signed_managed_config.rs`
|
|
//! stays fixed to the review-cited security claims (verify-persists /
|
|
//! reject-persists-nothing / sidecar-deletion-refuses).
|
|
|
|
#[path = "signed_managed_config/common.rs"]
|
|
mod common;
|
|
|
|
#[cfg(unix)]
|
|
use common::skip_as_root;
|
|
use common::{
|
|
MANAGED, REQUIREMENTS_FAIL_CLOSED, TEST_EXPIRES_AT, TEST_KEY_ID, dk_identity, forged_dk_body,
|
|
install_test_key, reset, sign_envelope, signed_dk_body, spawn_mock, test_home, write_dk_config,
|
|
};
|
|
use kigi_config::signed_policy::{self, SignedPayload};
|
|
use serial_test::serial;
|
|
|
|
/// The healthy fail-closed starting state the tamper/heal scenarios mutate;
|
|
/// the mock keeps serving the same body, so a healing sync can refetch it.
|
|
async fn sync_fail_closed_policy(home: &std::path::Path, kp: &ring::signature::Ed25519KeyPair) {
|
|
let url = spawn_mock(signed_dk_body(
|
|
kp,
|
|
"dep-42",
|
|
Some(MANAGED),
|
|
Some(REQUIREMENTS_FAIL_CLOSED),
|
|
));
|
|
write_dk_config(home, &url, "dep-key-1");
|
|
kigi_shell::managed_config::sync()
|
|
.await
|
|
.expect("initial sync should succeed");
|
|
assert!(kigi_shell::managed_config::managed_policy_gate().is_ok());
|
|
}
|
|
|
|
/// The signed-empty deployment response: a `{}` body (no legacy fields) whose
|
|
/// envelope binds ABSENCE to `deployment_id` — what the server serves for a
|
|
/// provisioned key with no config row.
|
|
fn signed_dk_empty_body(kp: &ring::signature::Ed25519KeyPair, deployment_id: &str) -> String {
|
|
let payload = SignedPayload {
|
|
version: kigi_config::signed_policy::SIGNED_PAYLOAD_VERSION,
|
|
deployment_id: Some(deployment_id.to_owned()),
|
|
team_id: None,
|
|
managed_config: None,
|
|
requirements: None,
|
|
fail_closed: false,
|
|
expires_at: TEST_EXPIRES_AT,
|
|
key_id: TEST_KEY_ID.into(),
|
|
};
|
|
serde_json::json!({ "signatures": [sign_envelope(kp, &payload)] }).to_string()
|
|
}
|
|
|
|
/// The marker principal for an applied signed-EMPTY dk response comes from the
|
|
/// VERIFIED payload's deployment_id (the `{}` body carries none), so the gate's
|
|
/// cross-tenant binding holds even on an unprovisioned dk machine.
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn empty_dk_response_marker_binds_the_verified_deployment_id() {
|
|
let home = test_home().clone();
|
|
reset(&home);
|
|
let (kp, _pubkey) = install_test_key();
|
|
|
|
let url = spawn_mock(signed_dk_empty_body(&kp, "dep-42"));
|
|
write_dk_config(&home, &url, "dep-key-1");
|
|
// No team auth: the empty dk body is applied (converges), not fallen through.
|
|
|
|
let wrote = kigi_shell::managed_config::sync()
|
|
.await
|
|
.expect("signed-empty dk sync should succeed");
|
|
assert!(!wrote, "nothing to write for an empty row");
|
|
|
|
let marker = std::fs::read_to_string(home.join("managed_config_cache.json")).unwrap();
|
|
let v: serde_json::Value = serde_json::from_str(&marker).unwrap();
|
|
assert_eq!(
|
|
v["principal"].as_str(),
|
|
Some("dep-42"),
|
|
"the marker must bind the VERIFIED deployment id: {marker}"
|
|
);
|
|
assert!(
|
|
home.join("managed_config.sig.json").exists(),
|
|
"the absence envelope is persisted"
|
|
);
|
|
assert!(kigi_shell::managed_config::managed_policy_gate().is_ok());
|
|
}
|
|
|
|
/// A signature-rejected sync surfaces as failure in BOTH `grok setup` and the
|
|
/// post-login sync — never as Installed/NoChange while nothing was persisted.
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn rejected_signature_surfaces_as_setup_and_login_failure() {
|
|
let home = test_home().clone();
|
|
reset(&home);
|
|
let (kp, _pubkey) = install_test_key();
|
|
|
|
let url = spawn_mock(forged_dk_body(&kp, "dep-42"));
|
|
write_dk_config(&home, &url, "dep-key-1");
|
|
|
|
let outcome = kigi_shell::managed_config::run_setup().await;
|
|
assert!(
|
|
matches!(
|
|
outcome,
|
|
kigi_shell::managed_config::SetupOutcome::Failed(
|
|
kigi_shell::managed_config::ManagedConfigError::SignatureRejected
|
|
)
|
|
),
|
|
"setup must surface the signature rejection, got {outcome:?}"
|
|
);
|
|
|
|
let login = kigi_shell::managed_config::post_login_sync(None).await;
|
|
assert_eq!(
|
|
login,
|
|
kigi_shell::managed_config::ManagedConfigSync::Failed,
|
|
"post-login sync must report Failed, not NoChange"
|
|
);
|
|
}
|
|
|
|
/// A response that stops serving requirements deletes the on-disk file, and the
|
|
/// NEW sidecar (written after the deletion) covers the absence — the converged cache
|
|
/// reads fresh and the gate allows.
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn withdrawn_requirements_is_deleted_and_covered_by_the_new_sidecar() {
|
|
let home = test_home().clone();
|
|
reset(&home);
|
|
let (kp, pubkey) = install_test_key();
|
|
|
|
sync_fail_closed_policy(&home, &kp).await;
|
|
assert!(home.join("requirements.toml").exists());
|
|
|
|
let url_partial = spawn_mock(signed_dk_body(&kp, "dep-42", Some(MANAGED), None));
|
|
write_dk_config(&home, &url_partial, "dep-key-1");
|
|
let wrote = kigi_shell::managed_config::sync()
|
|
.await
|
|
.expect("withdrawing sync should succeed");
|
|
assert!(wrote, "the deletion is a change");
|
|
|
|
assert!(
|
|
!home.join("requirements.toml").exists(),
|
|
"the withdrawn artifact is removed"
|
|
);
|
|
let sidecar: serde_json::Value = serde_json::from_str(
|
|
&std::fs::read_to_string(home.join("managed_config.sig.json")).unwrap(),
|
|
)
|
|
.unwrap();
|
|
let payload = signed_policy::verify_signed_payload(
|
|
sidecar["signed_payload"].as_str().unwrap(),
|
|
sidecar["signature"].as_str().unwrap(),
|
|
&[(TEST_KEY_ID, &pubkey)],
|
|
)
|
|
.expect("the refreshed sidecar must verify");
|
|
assert!(
|
|
payload.requirements.is_none(),
|
|
"the new sidecar covers the absence"
|
|
);
|
|
assert!(
|
|
!kigi_shell::config::is_managed_config_hard_stale_for(&dk_identity()),
|
|
"the converged, covered cache is not hard-stale"
|
|
);
|
|
assert!(kigi_shell::managed_config::managed_policy_gate().is_ok());
|
|
}
|
|
|
|
/// A directory squatting at a signed artifact path reads COMPROMISED at the gate
|
|
/// (not lenient-unreadable), and an online sync converges over it — clearing the
|
|
/// directory, rewriting the file, and restoring enforcement.
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn directory_squat_reads_compromised_and_online_sync_heals() {
|
|
let home = test_home().clone();
|
|
reset(&home);
|
|
let (kp, _pubkey) = install_test_key();
|
|
|
|
sync_fail_closed_policy(&home, &kp).await;
|
|
|
|
// Dir-squat the enforced artifact (with a child, like a real squat).
|
|
std::fs::remove_file(home.join("requirements.toml")).unwrap();
|
|
std::fs::create_dir(home.join("requirements.toml")).unwrap();
|
|
std::fs::write(home.join("requirements.toml").join("junk"), "x").unwrap();
|
|
|
|
let gate = kigi_shell::managed_config::managed_policy_gate();
|
|
assert!(
|
|
gate.is_err(),
|
|
"a directory squat on a fail-closed policy must refuse offline"
|
|
);
|
|
// The gate verdict, not an incidental error; classification is unit-pinned
|
|
// in signed_policy::directory_squat_is_tamper_not_unreadable.
|
|
assert!(
|
|
gate.unwrap_err()
|
|
.contains("Managed policy is required for this account"),
|
|
"the refusal is the managed-policy gate message"
|
|
);
|
|
assert!(
|
|
kigi_shell::config::is_managed_config_hard_stale_for(&dk_identity()),
|
|
"the squat must trigger the refetch"
|
|
);
|
|
|
|
let wrote = kigi_shell::managed_config::sync()
|
|
.await
|
|
.expect("healing sync should succeed");
|
|
assert!(wrote, "the healing sync must rewrite the squatted artifact");
|
|
assert_eq!(
|
|
std::fs::read_to_string(home.join("requirements.toml")).unwrap(),
|
|
REQUIREMENTS_FAIL_CLOSED,
|
|
"the served file replaces the squatting directory"
|
|
);
|
|
assert!(
|
|
kigi_shell::managed_config::managed_policy_gate().is_ok(),
|
|
"enforcement is restored after the heal"
|
|
);
|
|
}
|
|
|
|
/// A sidecar read blip (chmod 000) is not tamper: the gate allows while the
|
|
/// refetch trigger fires — mirroring the artifact-slot blip semantics.
|
|
#[cfg(unix)]
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn sidecar_read_blip_allows_session_and_triggers_refetch() {
|
|
use std::os::unix::fs::PermissionsExt;
|
|
let home = test_home().clone();
|
|
reset(&home);
|
|
let (kp, _pubkey) = install_test_key();
|
|
|
|
sync_fail_closed_policy(&home, &kp).await;
|
|
|
|
let sidecar_path = home.join("managed_config.sig.json");
|
|
std::fs::set_permissions(&sidecar_path, std::fs::Permissions::from_mode(0o000)).unwrap();
|
|
if skip_as_root(
|
|
&sidecar_path,
|
|
"sidecar_read_blip_allows_session_and_triggers_refetch",
|
|
) {
|
|
let _ = std::fs::set_permissions(&sidecar_path, std::fs::Permissions::from_mode(0o600));
|
|
return;
|
|
}
|
|
|
|
assert!(
|
|
kigi_shell::managed_config::managed_policy_gate().is_ok(),
|
|
"a transient sidecar read blip must not refuse the session"
|
|
);
|
|
assert!(
|
|
kigi_shell::config::is_managed_config_hard_stale_for(&dk_identity()),
|
|
"the blip must trigger the refetch so the self-heal runs"
|
|
);
|
|
// Restore so the tempdir (and later tests) stay clean.
|
|
std::fs::set_permissions(&sidecar_path, std::fs::Permissions::from_mode(0o600)).unwrap();
|
|
}
|
|
|
|
/// A directory squatting at the SIDECAR path refuses at the gate, and the online
|
|
/// sync clears it — a bare rename would error forever.
|
|
#[tokio::test]
|
|
#[serial]
|
|
async fn sidecar_directory_squat_refuses_then_online_sync_heals() {
|
|
let home = test_home().clone();
|
|
reset(&home);
|
|
let (kp, _pubkey) = install_test_key();
|
|
|
|
sync_fail_closed_policy(&home, &kp).await;
|
|
|
|
// Dir-squat the sidecar (with a child, like a real squat).
|
|
let sidecar_path = home.join("managed_config.sig.json");
|
|
std::fs::remove_file(&sidecar_path).unwrap();
|
|
std::fs::create_dir(&sidecar_path).unwrap();
|
|
std::fs::write(sidecar_path.join("junk"), "x").unwrap();
|
|
|
|
let gate = kigi_shell::managed_config::managed_policy_gate();
|
|
assert!(
|
|
gate.is_err(),
|
|
"an unreadable (squatted) sidecar under a fail-closed marker must refuse offline"
|
|
);
|
|
// The gate verdict, not an incidental error; classification is unit-pinned
|
|
// in signed_policy::sidecar_directory_squat_is_absence_not_a_blip.
|
|
assert!(
|
|
gate.unwrap_err()
|
|
.contains("Managed policy is required for this account"),
|
|
"the refusal is the managed-policy gate message"
|
|
);
|
|
assert!(
|
|
kigi_shell::config::is_managed_config_hard_stale_for(&dk_identity()),
|
|
"the squat must trigger the refetch"
|
|
);
|
|
|
|
kigi_shell::managed_config::sync()
|
|
.await
|
|
.expect("healing sync should succeed");
|
|
assert!(
|
|
sidecar_path.is_file(),
|
|
"the rewrite must replace the squatting directory with a sidecar FILE"
|
|
);
|
|
// Under a fail-closed marker the gate requires an authentic sidecar, so
|
|
// allowing here also pins that the healed sidecar verifies.
|
|
assert!(
|
|
kigi_shell::managed_config::managed_policy_gate().is_ok(),
|
|
"enforcement is restored after the heal"
|
|
);
|
|
}
|