use url::Url; use better_twttr::{policy::*, site::SITE, theme::{x_default, XTheme, X_THEMES}}; #[test] fn exact_origin_and_window_scope_is_enforced() { let x = Url::parse("https://x.com/home").unwrap(); assert!(require_capture("tab-1", &x).is_ok()); assert!(require_capture("shell", &x).is_err()); assert!(require_local( "tab-1", &Url::parse("tauri://localhost/index.html").unwrap(), false ) .is_err()); for value in [ "https://evil.x.com/home", "http://x.com/home", "https://x.com:8443/home", "https://x.com.evil.test/home", "https://x.com@evil.test/home", "https://evil@x.com/home", "file:///tmp/index.html", "https://twitter.com/home", ] { assert!( require_capture("tab-1", &Url::parse(value).unwrap()).is_err(), "accepted {value}" ); } } #[test] fn local_origin_has_no_production_localhost_backdoor() { for value in [ "tauri://localhost/index.html", "http://tauri.localhost/index.html", "https://tauri.localhost/index.html", ] { assert!(require_local("shell", &Url::parse(value).unwrap(), false).is_ok()); } assert!(require_local("shell", &Url::parse("http://127.0.0.1:1420/").unwrap(), true).is_ok()); for value in [ "https://x.com/", "http://127.0.0.1:1420/", "http://localhost:1420/", "https://tauri.localhost.evil.test/", "tauri://evil/", ] { assert!( require_local("shell", &Url::parse(value).unwrap(), false).is_err(), "accepted {value}" ); } } #[test] fn navigation_allows_only_x_and_specific_legacy_login_routes() { for value in [ "https://x.com/home", "https://twitter.com/login", "https://twitter.com/i/flow/login", "https://twitter.com/i/flow/login/step", ] { assert!(x_navigation_allowed(&Url::parse(value).unwrap())); } for value in [ "https://twitter.com/home", "https://t.co/test", "https://accounts.google.com/", "javascript:alert(1)", "mailto:test@example.com", "https://twitter.com/login/evil", ] { assert!( !x_navigation_allowed(&Url::parse(value).unwrap()), "accepted {value}" ); } } #[test] fn remote_capabilities_cannot_reach_the_vault() { let read = |json: &str| serde_json::from_str::(json).unwrap(); let x = read(include_str!("../capabilities/x-capture.json")); // Drafts popover (2026-09-27): X may only open/close it about itself and ask one yes/no hint question. // Chat+ link previews (owner-approved, 2026-10-10): one link at a time, from a chat page only. assert_eq!(x["permissions"], serde_json::json!(["allow-capture-draft", "allow-open-panel", "allow-drafts-picker-open", "allow-drafts-popover-dismiss", "allow-drafts-hint", "allow-keyword-alert", "allow-link-preview"])); let desktop = include_str!("../src/desktop.rs"); waybetter_host::testkit::assert_guarded(desktop, "link_preview", "policy::require_capture(webview.label(), &webview.url()"); assert!(desktop.contains("link_preview::is_chat_page(page.path())"), "link previews answer chat pages only"); for vault in ["allow-popover-drafts", "allow-popover-insert", "allow-popover-state", "allow-draft-list"] { assert!(!x["permissions"].as_array().unwrap().iter().any(|p| p == vault), "X tabs have {vault}"); } // The drafts popover is local-only and gets nothing beyond its own five commands. let popover = read(include_str!("../capabilities/drafts-popover.json")); assert_eq!(popover["webviews"], serde_json::json!(["popover-*"])); assert_eq!(popover["local"], true); assert!(popover.get("remote").is_none()); assert_eq!(popover["permissions"], serde_json::json!(["allow-popover-state", "allow-popover-drafts", "allow-popover-insert", "allow-popover-close", "allow-popover-open-vault"])); assert_eq!(x["webviews"], serde_json::json!(["tab-*"])); assert_eq!(x["remote"]["urls"], serde_json::json!(["https://x.com/*"])); assert_eq!(x["local"], false); // Linux joined with the same permissions (owner request, 2026-09-28): platforms only, no new grants. assert_eq!(x["platforms"], serde_json::json!(["macOS", "windows", "linux"])); let tabs = read(include_str!("../capabilities/tab-chrome.json")); assert_eq!(tabs["permissions"], serde_json::json!(["allow-tab-report", "allow-tab-shortcut", "allow-tab-link-drag"])); assert_eq!(tabs["webviews"], serde_json::json!(["tab-*"])); assert_eq!(tabs["local"], false); let local = read(include_str!("../capabilities/workspace-local.json")); assert!(local.get("remote").is_none()); // My Space (profile customization) is removed: its commands do not exist, so no capability // file grants them and neither the command manifest nor the handler names them. let (build, desktop) = (include_str!("../build.rs"), include_str!("../src/desktop.rs")); for gone in [ "profile_owner_get", "profile_create", "profile_api", "profile_media_pick", "profile_media_still", "profile_media_upload", "profile_still_to_x", "profile_view_get", "profile_view_set", "bt_profiles_resolve", "bt_media_get", "bt_profile_link_seen", ] { let permission = format!("allow-{}", gone.replace('_', "-")); for (name, capability) in [("x-capture", &x), ("drafts-popover", &popover), ("tab-chrome", &tabs), ("workspace-local", &local)] { assert!(!capability["permissions"].as_array().unwrap().iter().any(|p| p == permission.as_str()), "{name} grants {permission}"); } assert!(!build.contains(&format!("\"{gone}\"")), "build.rs still lists {gone}"); assert!(!desktop.contains(gone), "desktop.rs still names {gone}"); } assert_eq!(local["webviews"], serde_json::json!(["shell"])); // Update commands are shell-only; no capability exposes the updater/process plugin JS APIs. let perms = |v: &serde_json::Value| v["permissions"].as_array().unwrap().iter().map(|p| p.as_str().unwrap().to_owned()).collect::>(); // Account profiles (separate X logins) are managed only by the shell. // Drafts are inserted and alert keywords edited only by the shell; X tabs only ask/report. for name in [ "allow-update-status", "allow-apply-update", "allow-app-ready", "allow-profiles-get", "allow-profiles-set", "allow-tab-insert-text", "allow-alerts-get", "allow-alerts-set", "allow-draft-list", "allow-draft-revisions", // Google Fonts downloads (network), account/pane themes and the builder preview: shell only. "allow-gfonts-installed", "allow-gfonts-install", "allow-gfonts-remove", "allow-gfonts-faces", "allow-gfonts-preview", "allow-theme-scopes-get", "allow-theme-scopes-set", "allow-theme-preview", // Installing themes/plugins (links, dropped files, the signed store; owner-approved 2026-09-28): // only the shell, after its confirmation screen. X tabs and web pages cannot install anything. "allow-install-pending", "allow-install-stage-link", "allow-install-stage-file", "allow-install-cancel", "allow-install-commit", "allow-install-records", "allow-install-forget", "allow-plugin-remove", "allow-store-catalog", "allow-store-stage", ] { assert!(perms(&local).iter().any(|p| p == name), "shell lacks {name}"); assert!(!perms(&x).iter().chain(perms(&tabs).iter()).any(|p| p == name), "tab has {name}"); } for p in perms(&local).iter().chain(perms(&x).iter()).chain(perms(&tabs).iter()) { assert!( !p.starts_with("updater:") && !p.starts_with("process:") && !p.starts_with("notification:") && !p.starts_with("deep-link:"), "plugin API exposed: {p}" ); } } #[test] fn theme_accepts_only_known_presets_and_hex_accents() { let script = |theme: XTheme| theme.script(&X_THEMES, &SITE); let valid = XTheme { preset: "night-ink".into(), accent: Some("#1d9bf0".into()), ..x_default() }; let text = script(valid).unwrap(); assert!(text.contains("location.origin!=='https://x.com'")); assert!(text.contains(r#""preset":"night-ink""#)); assert!(x_default().is_default(&X_THEMES)); for preset in ["x", "classic", "night-ink", "terminal", "paper"] { assert!(script(XTheme { preset: preset.into(), ..x_default() }).is_ok()); } for hostile in ["evil", "x;display:none", "", ""] { assert!(script(XTheme { preset: hostile.into(), ..x_default() }).is_err(), "accepted {hostile}"); } for hostile in ["red", "#fff", "url(https://evil.test)", "#000000;display:none", "