// file: crates/ksp-app-wallet-desk/tests/desktop_security.rs // version: 14 //! Static desktop security contracts for the Wallet Desk pre.002 shell. fn app_root() -> std::path::PathBuf { return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); } fn read_text(path: &std::path::Path) -> String { let source = std::fs::read_to_string(path); assert!(source.is_ok(), "unable to read {}", path.display()); return match source { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => String::new(), }; } #[test] fn initial_capability_surface_is_core_plus_tracing_only() { let root = app_root(); let capability = read_text(root.join("capabilities/default.json").as_path()); assert!(capability.contains("\"core:default\"")); assert!(capability.contains("\"tracing:default\"")); assert!(!capability.contains("dialog:")); assert!(!capability.contains("fs:")); } #[test] fn frontend_shell_uses_no_browser_native_dialog_or_secret_storage() { let root = app_root(); let main = read_text(root.join("frontend/ts/main.ts").as_path()); let html = read_text(root.join("frontend/main.html").as_path()); for forbidden in ["window.alert", "window.confirm", "window.prompt", "localStorage", "sessionStorage"] { assert!(!main.contains(forbidden), "forbidden frontend primitive {forbidden}"); assert!(!html.contains(forbidden), "forbidden frontend primitive {forbidden}"); } } #[test] fn locked_inventory_frontend_never_receives_protected_wallet_identity_fields() { let root = app_root(); let inventory = read_text(root.join("src/wallet_inventory.rs").as_path()); assert!(!inventory.contains("pubkey:")); assert!(!inventory.contains("alias:")); assert!(!inventory.contains("notes:")); assert!(!inventory.contains("password:")); } #[test] fn create_passwords_are_request_only_and_protected_projection_is_purged() { let root = app_root(); let session = read_text(root.join("src/wallet_session.rs").as_path()); let main = read_text(root.join("frontend/ts/main.ts").as_path()); let authorized = session.split("pub(crate) struct WalletAuthorizedDto").nth(1); assert!(authorized.is_some()); let authorized = authorized.unwrap_or_default().split("}\n").next().unwrap_or_default(); let authorized_fields = authorized.lines().filter(|line| return line.trim_start().starts_with("pub(crate) ")).collect::>().join("\n"); assert!(!authorized_fields.contains("password")); let request_start = session.find("pub(crate) struct WalletCreateRequestDto"); assert!(request_start.is_some()); let request_start = request_start.unwrap_or_default(); assert!(session[request_start..].contains("owner_password")); assert!(session[request_start..].contains("view_password")); assert!(main.contains("clearAuthorizedProjection")); assert!(main.contains("clearCreateFormSensitiveInputs")); assert!(!main.contains("frontendDebug(\"main\", request.ownerPassword")); assert!(!main.contains("frontendTrace(\"main\", request.viewPassword")); } #[test] fn unlock_passwords_and_config_secret_names_never_cross_rust_to_frontend() { let root = app_root(); let session = read_text(root.join("src/wallet_session.rs").as_path()); let main = read_text(root.join("frontend/ts/main.ts").as_path()); let html = read_text(root.join("frontend/main.html").as_path()); let authorized = session.split("pub(crate) struct WalletAuthorizedDto").nth(1); assert!(authorized.is_some()); let authorized = authorized.unwrap_or_default().split("}\n").next().unwrap_or_default(); let authorized_fields = authorized.lines().filter(|line| return line.trim_start().starts_with("pub(crate) ")).collect::>().join("\n"); assert!(!authorized_fields.contains("password")); let unlock_request = session.find("pub(crate) struct WalletUnlockRequestDto"); assert!(unlock_request.is_some()); assert!(session[unlock_request.unwrap_or_default()..].contains("password: String")); assert!(main.contains("clearUnlockSensitiveInputs")); assert!(!main.contains("KSP_SECRET_WALLET_PASS_")); assert!(!html.contains("KSP_SECRET_WALLET_PASS_")); assert!(!main.contains("variableName")); assert!(!main.contains("effectiveSource")); } #[test] fn wallet_selection_does_not_auto_unlock_configured_secrets() { let root = app_root(); let main = read_text(root.join("frontend/ts/main.ts").as_path()); let selection_start = main.find("async function selectWallet"); let selection_end = main.find("function bindWalletTableSelection"); assert!(selection_start.is_some()); assert!(selection_end.is_some()); let selection = &main[selection_start.unwrap_or_default()..selection_end.unwrap_or(main.len())]; assert!(selection.contains("select_wallet")); assert!(!selection.contains("unlock_wallet_view_configured")); assert!(!selection.contains("unlock_wallet_owner_configured")); } #[test] fn balance_refresh_never_accepts_pubkey_or_endpoint_url_from_frontend() { let root = app_root(); let state = read_text(root.join("src/app_state.rs").as_path()); let tauri = read_text(root.join("src/tauri.rs").as_path()); let main = read_text(root.join("frontend/ts/main.ts").as_path()); let command_start = tauri.find("async fn refresh_wallet_balance"); assert!(command_start.is_some()); let command = &tauri[command_start.unwrap_or_default()..]; let command = command.split("#[tauri::command]").next().unwrap_or_default(); assert!(!command.contains("request:")); assert!(!command.contains("pubkey")); let refresh_start = main.find("async function refreshWalletBalance"); let refresh_end = main.find("function bindBalanceActions"); assert!(refresh_start.is_some()); assert!(refresh_end.is_some()); let refresh = &main[refresh_start.unwrap_or_default()..refresh_end.unwrap_or(main.len())]; assert!(refresh.contains("refresh_wallet_balance")); assert!(!refresh.contains("pubkey")); assert!(!main.contains("transportUrl")); assert!(!main.contains("endpointUrl")); assert!(state.contains("wallet.pubkey()")); } #[test] fn import_picker_path_and_transfer_secret_never_cross_into_frontend() { let root = app_root(); let package = read_text(root.join("package.json").as_path()); let capability = read_text(root.join("capabilities/default.json").as_path()); let import = read_text(root.join("src/wallet_import.rs").as_path()); let tauri = read_text(root.join("src/tauri.rs").as_path()); let main = read_text(root.join("frontend/ts/main.ts").as_path()); assert!(!package.contains("@tauri-apps/plugin-dialog")); assert!(!capability.contains("dialog:")); assert!(!capability.contains("fs:")); assert!(!main.contains("@tauri-apps/plugin-dialog")); assert!(!main.contains("sourcePath")); assert!(!main.contains("source_path")); assert!(import.contains("zeroize::Zeroizing")); let inspection = import.split("pub(crate) struct WalletTransferInspectionDto").nth(1); assert!(inspection.is_some()); let inspection = inspection.unwrap_or_default().split("}\n").next().unwrap_or_default(); let inspection_fields = inspection.lines().filter(|line| return line.trim_start().starts_with("pub(crate) ")).collect::>().join("\n"); assert!(!inspection_fields.contains("path")); assert!(!inspection_fields.contains("password")); let request = import.split("pub(crate) struct WalletImportRequestDto").nth(1); assert!(request.is_some()); let request = request.unwrap_or_default().split("}\n").next().unwrap_or_default(); let request_fields = request.lines().filter(|line| return line.trim_start().starts_with("pub(crate) ")).collect::>().join("\n"); assert!(request_fields.contains("owner_password")); assert!(request_fields.contains("view_password")); assert!(!request_fields.contains("path")); let command_start = tauri.find("async fn inspect_import_source"); assert!(command_start.is_some()); let command = &tauri[command_start.unwrap_or_default()..]; let command = command.split("#[tauri::command]").next().unwrap_or_default(); assert!(command.contains("WalletImportSourceRequestDto")); assert!(!command.contains("source_path")); } #[test] fn owner_metadata_values_are_request_only_and_state_conflict_forces_reauthorization() { let root = app_root(); let metadata = read_text(root.join("src/wallet_metadata.rs").as_path()); let state = read_text(root.join("src/app_state.rs").as_path()); let main = read_text(root.join("frontend/ts/main.ts").as_path()); assert!(!metadata.contains("derive(Clone")); assert!(!metadata.contains("derive(Debug")); assert!(state.contains("recover_owner_state_conflict")); assert!(state.contains("inspect_locked_wallet_file")); assert!(state.contains("ERROR_CODE_STATE_CONFLICT")); assert!(main.contains("isWalletStateConflict")); assert!(main.contains("select_wallet")); assert!(main.contains("clearAuthorizedProjection")); assert!(!main.contains("frontendDebug(\"main\", request.text")); assert!(!main.contains("frontendTrace(\"main\", request.text")); assert!(!main.contains("frontendDebug(\"main\", request.alias")); assert!(!main.contains("window.confirm")); assert!(main.contains("deleteNoteModal = new Modal(modalElement)")); } #[test] fn rotation_passwords_are_request_only_confirmed_frontend_side_and_never_logged() { let root = app_root(); let security = read_text(root.join("src/wallet_security.rs").as_path()); let state = read_text(root.join("src/app_state.rs").as_path()); let main = read_text(root.join("frontend/ts/main.ts").as_path()); assert!(!security.contains("derive(Clone")); assert!(!security.contains("derive(Debug")); let request = security.split("pub(crate) struct WalletPasswordRotationRequestDto").nth(1); assert!(request.is_some()); let fields = request .unwrap_or_default() .split("}\n") .next() .unwrap_or_default() .lines() .filter(|line| return line.trim_start().starts_with("pub(crate) ")) .collect::>() .join("\n"); assert_eq!(fields.matches("password").count(), 1); assert!(!fields.contains("confirm")); assert!(state.contains("OwnerPassword::new(request.password)")); assert!(state.contains("ViewPassword::new(request.password)")); assert!(state.contains("view.rotate_view_password")); assert!(state.contains("owner.rotate_view_password")); assert!(main.contains("password.value !== confirmation.value")); assert!(main.contains("clearRotationSensitiveInputs")); assert!(main.contains("viewAuthorized")); assert!(main.contains("recoverRotationStateConflict")); assert!(!main.contains("frontendDebug(\"main\", request.password")); assert!(!main.contains("frontendTrace(\"main\", request.password")); assert!(!main.contains("KSP_SECRET_WALLET_PASS_")); } #[test] fn strong_view_administration_is_owner_only_uses_bootstrap_modals_and_returns_no_secret() { let root = app_root(); let security = read_text(root.join("src/wallet_security.rs").as_path()); let state = read_text(root.join("src/app_state.rs").as_path()); let tauri = read_text(root.join("src/tauri.rs").as_path()); let main = read_text(root.join("frontend/ts/main.ts").as_path()); let status = security.split("pub(crate) struct WalletViewSecurityStatusDto").nth(1); assert!(status.is_some()); let fields = status .unwrap_or_default() .split("}\n") .next() .unwrap_or_default() .lines() .filter(|line| return line.trim_start().starts_with("pub(crate) ")) .collect::>() .join("\n"); assert!(!fields.contains("password")); assert!(!fields.contains("pubkey")); assert!(!fields.contains("alias")); assert!(!fields.contains("note")); assert!(state.contains("begin_owner_operation(\"view_disable\")")); assert!(state.contains("begin_owner_operation(\"view_recreate\")")); assert!(!state.contains("begin_view_operation(\"view_disable\")")); assert!(!state.contains("begin_view_operation(\"view_recreate\")")); let disable = tauri.find("async fn disable_wallet_view"); assert!(disable.is_some()); let disable = &tauri[disable.unwrap_or_default()..]; let disable = disable.split("#[tauri::command]").next().unwrap_or_default(); assert!(!disable.contains("request:")); let recreate = tauri.find("async fn recreate_wallet_view"); assert!(recreate.is_some()); let recreate = &tauri[recreate.unwrap_or_default()..]; let recreate = recreate.split("#[tauri::command]").next().unwrap_or_default(); assert!(recreate.contains("WalletPasswordRotationRequestDto")); assert!(main.contains("new Modal(disableModalElement)")); assert!(main.contains("new Modal(recreateModalElement)")); assert!(main.contains("clearStrongViewSensitiveInputs")); assert!(main.contains("hidden.bs.modal")); assert!(!main.contains("window.confirm")); assert!(!main.contains("frontendDebug(\"main\", request.password")); assert!(!main.contains("frontendTrace(\"main\", request.password")); } #[test] fn owner_export_never_projects_key_material_or_full_destination_path_to_frontend() { let root = app_root(); let export = read_text(root.join("src/wallet_export.rs").as_path()); let state = read_text(root.join("src/app_state.rs").as_path()); let tauri = read_text(root.join("src/tauri.rs").as_path()); let main = read_text(root.join("frontend/ts/main.ts").as_path()); let request = export.split("pub(crate) struct WalletExportRequestDto").nth(1); assert!(request.is_some()); let request_fields = request .unwrap_or_default() .split("}\n") .next() .unwrap_or_default() .lines() .filter(|line| return line.trim_start().starts_with("pub(crate) ")) .collect::>() .join("\n"); assert!(request_fields.contains("format")); assert!(!export.contains("#[derive(Clone, serde::Deserialize")); assert!(!export.contains("#[derive(Debug, serde::Deserialize")); assert!(!request_fields.contains("path")); assert!(!request_fields.contains("password")); assert!(!request_fields.contains("secret")); let result = export.split("pub(crate) struct WalletExportResultDto").nth(1); assert!(result.is_some()); let result_fields = result .unwrap_or_default() .split("}\n") .next() .unwrap_or_default() .lines() .filter(|line| return line.trim_start().starts_with("pub(crate) ")) .collect::>() .join("\n"); assert!(result_fields.contains("destination_name")); assert!(result_fields.contains("format")); assert!(result_fields.contains("wallet_id")); assert!(!result_fields.contains("path")); assert!(!result_fields.contains("pubkey")); assert!(!result_fields.contains("password")); assert!(!result_fields.contains("secret")); assert!(!result_fields.contains("content")); assert!(!result_fields.contains("bytes")); assert!(state.contains("owner.export_transfer_file")); assert!(!state.contains("owner.export_transfer(format")); let command_start = tauri.find("async fn export_wallet_transfer"); assert!(command_start.is_some()); let command = &tauri[command_start.unwrap_or_default()..]; let command = command.split("#[tauri::command]").next().unwrap_or_default(); assert!(command.contains("WalletExportRequestDto")); assert!(command.contains("WalletExportResultDto")); assert!(!main.contains("navigator.clipboard")); assert!(!main.contains("destinationPath")); assert!(!main.contains("destination_path")); assert!(!main.contains("keypairBytes")); assert!(!main.contains("secretKey")); assert!(main.contains("exportWalletModal = new Modal(modalElement)")); }