services_hardening_legacy_removal.rs (9071B)
1 #![forbid(unsafe_code)] 2 3 use std::path::Path; 4 use std::process::Command; 5 6 use myc::{MycLogRecord, MycProcessResult}; 7 8 const LIB_SOURCE: &str = include_str!("../src/lib.rs"); 9 const MAIN_SOURCE: &str = include_str!("../src/main.rs"); 10 const MANIFEST: &str = include_str!("../Cargo.toml"); 11 const ACTIVE_STATE_SOURCES: &[&str] = &[ 12 include_str!("../src/state_admin.rs"), 13 include_str!("../src/state_catalog.rs"), 14 include_str!("../src/state_completion.rs"), 15 include_str!("../src/state_config.rs"), 16 include_str!("../src/state_connection.rs"), 17 include_str!("../src/state_delivery.rs"), 18 include_str!("../src/state_discovery.rs"), 19 include_str!("../src/state_governance.rs"), 20 include_str!("../src/state_host.rs"), 21 include_str!("../src/state_maintenance.rs"), 22 include_str!("../src/state_metadata.rs"), 23 include_str!("../src/state_recovery.rs"), 24 include_str!("../src/state_repository.rs"), 25 include_str!("../src/state_request.rs"), 26 include_str!("../src/state_response.rs"), 27 ]; 28 29 fn process_diagnostic(result: MycProcessResult) -> String { 30 format!("{}\n", MycLogRecord::process_result(result)) 31 } 32 33 #[test] 34 fn prototype_environment_and_cli_sources_are_absent() { 35 let root = Path::new(env!("CARGO_MANIFEST_DIR")); 36 for relative in [ 37 ".env.example", 38 "src/cli.rs", 39 "src/bin/myc_repo_local_identity_bootstrap.rs", 40 "tests/discovery_cli.rs", 41 "tests/logging_run.rs", 42 "tests/operability_cli.rs", 43 "tests/persistence_cli.rs", 44 "tests/nip46_e2e.rs", 45 "tests/operability_e2e.rs", 46 "tests/operability_server.rs", 47 "src/app/mod.rs", 48 "src/app/backend.rs", 49 "src/app/runtime.rs", 50 "src/audit.rs", 51 "src/audit_sqlite.rs", 52 "src/config.rs", 53 "src/control.rs", 54 "src/discovery.rs", 55 "src/operability/mod.rs", 56 "src/operability/server.rs", 57 "src/outbox.rs", 58 "src/outbox_sqlite.rs", 59 "src/paths.rs", 60 "src/persistence.rs", 61 "src/sql.rs", 62 "src/signer/migrations.rs", 63 "src/signer/sqlite.rs", 64 "src/signer/store.rs", 65 "src/transport.rs", 66 "src/transport/nip46.rs", 67 "migrations/0000_delivery_outbox_init.up.sql", 68 "migrations/0000_delivery_outbox_init.down.sql", 69 "migrations/0000_runtime_audit_init.up.sql", 70 "migrations/0000_runtime_audit_init.down.sql", 71 "migrations/signer/0000_init.up.sql", 72 "migrations/signer/0000_init.down.sql", 73 "migrations/signer/0001_publish_workflows.up.sql", 74 "migrations/signer/0001_publish_workflows.down.sql", 75 "migrations/signer/0002_client_metadata.up.sql", 76 "migrations/signer/0002_client_metadata.down.sql", 77 ] { 78 assert!( 79 !root.join(relative).exists(), 80 "legacy source remains: {relative}" 81 ); 82 } 83 84 for forbidden in [ 85 "pub mod cli;", 86 "run_from_env", 87 "load_from_default_env_path", 88 "load_from_env_path", 89 "from_env_str", 90 "to_env_string", 91 "DEFAULT_ENV_PATH", 92 "config_env_path", 93 "process_path_selection", 94 "path_selection_from_entries", 95 "parse_path_profile_env", 96 "\"MYC_", 97 "pub mod app;", 98 "pub mod audit;", 99 "mod audit_sqlite;", 100 "pub mod config;", 101 "pub mod outbox;", 102 "mod outbox_sqlite;", 103 "pub mod persistence;", 104 "pub mod sql;", 105 "pub mod transport;", 106 "pub mod operability;", 107 "MycSignerStateBackend", 108 "MycRuntimeAuditBackend", 109 "import_json_to_sqlite", 110 "MycJsonlOperationAuditStore", 111 "MycSqliteOperationAuditStore", 112 "MycSqliteDeliveryOutboxStore", 113 "RadrootsNostrFileSignerStore", 114 "RadrootsNostrSqliteSignerStore", 115 ] { 116 assert!( 117 !LIB_SOURCE.contains(forbidden), 118 "legacy selector remains: {forbidden}" 119 ); 120 } 121 } 122 123 #[test] 124 fn active_state_tree_has_one_shared_database_and_no_legacy_backend() { 125 assert_eq!(LIB_SOURCE.matches("mod state_").count(), 15); 126 assert!(!LIB_SOURCE.contains("pub mod state_")); 127 let active_state = ACTIVE_STATE_SOURCES.join("\n"); 128 for forbidden in [ 129 "signer-state.json", 130 "signer-state.sqlite", 131 "operations.jsonl", 132 "operations.sqlite", 133 "delivery-outbox.sqlite", 134 "manifest.json", 135 "backend selection", 136 "SqlitePool", 137 "Pool<Sqlite>", 138 "import_json_to_sqlite", 139 "MycSignerStateBackend", 140 "MycRuntimeAuditBackend", 141 ] { 142 assert!( 143 !active_state.contains(forbidden), 144 "legacy state authority remains: {forbidden}" 145 ); 146 } 147 } 148 149 #[test] 150 fn obsolete_provider_sources_and_dependencies_are_absent() { 151 let root = Path::new(env!("CARGO_MANIFEST_DIR")); 152 for relative in [ 153 "src/accounts.rs", 154 "src/custody.rs", 155 "src/error.rs", 156 "src/host_identity.rs", 157 "src/identity_files.rs", 158 "src/logging.rs", 159 "src/nostr_contract.rs", 160 "src/policy.rs", 161 "src/signing_adapter.rs", 162 "src/signer/backend.rs", 163 "src/signer/capability.rs", 164 "src/signer/error.rs", 165 "src/signer/evaluation.rs", 166 "src/signer/manager.rs", 167 "src/signer/mod.rs", 168 "src/signer/model.rs", 169 "src/signer/nip46.rs", 170 "src/signer/test_fixtures.rs", 171 "src/signer/test_support.rs", 172 ] { 173 assert!( 174 !root.join(relative).exists(), 175 "obsolete provider source remains: {relative}" 176 ); 177 } 178 179 let manifest: toml::Value = toml::from_str(MANIFEST).expect("Cargo manifest"); 180 let dependencies = manifest["dependencies"] 181 .as_table() 182 .expect("dependencies table"); 183 for forbidden in [ 184 "axum", 185 "keyring", 186 "nostr-sdk", 187 "radroots_event", 188 "radroots_signing", 189 "rand", 190 "thiserror", 191 "tracing", 192 "tracing-appender", 193 "tracing-subscriber", 194 "uuid", 195 ] { 196 assert!( 197 !dependencies.contains_key(forbidden), 198 "obsolete provider dependency remains: {forbidden}" 199 ); 200 } 201 let secrets_features = dependencies["radroots_secrets"]["features"] 202 .as_array() 203 .expect("radroots_secrets features"); 204 assert_eq!(secrets_features, &[toml::Value::String("std".to_owned())]); 205 let tokio_features = dependencies["tokio"]["features"] 206 .as_array() 207 .expect("Tokio features"); 208 assert!( 209 !tokio_features 210 .iter() 211 .any(|feature| feature.as_str() == Some("process")) 212 ); 213 assert!(!dependencies.contains_key("tempfile")); 214 215 let dev_dependencies = manifest["dev-dependencies"] 216 .as_table() 217 .expect("dev-dependencies table"); 218 assert!(dev_dependencies.contains_key("tempfile")); 219 for forbidden in ["futures-util", "serial_test", "tokio-tungstenite"] { 220 assert!( 221 !dev_dependencies.contains_key(forbidden), 222 "obsolete development dependency remains: {forbidden}" 223 ); 224 } 225 } 226 227 #[test] 228 fn binary_uses_only_the_hardened_parser_and_fails_closed_before_dispatch() { 229 assert!(MAIN_SOURCE.contains("parse_myc_cli_v1_from(std::env::args_os())")); 230 assert!(!MAIN_SOURCE.contains("MycConfig")); 231 assert!(!MAIN_SOURCE.contains("MycRuntime")); 232 233 let missing = Command::new(env!("CARGO_BIN_EXE_myc")) 234 .env("MYC_PATHS_PROFILE", "service_host") 235 .env("MYC_SERVICE_INSTANCE_NAME", "implicit") 236 .output() 237 .expect("run missing-selector case"); 238 assert_eq!(missing.status.code(), Some(2)); 239 assert_eq!( 240 String::from_utf8(missing.stderr).expect("utf8 stderr"), 241 process_diagnostic(MycProcessResult::InputOrConfiguration) 242 ); 243 244 let admitted = Command::new(env!("CARGO_BIN_EXE_myc")) 245 .args(["--profile", "service-host", "--instance", "primary", "run"]) 246 .output() 247 .expect("run admitted command"); 248 assert_eq!(admitted.status.code(), Some(2)); 249 assert_eq!( 250 String::from_utf8(admitted.stderr).expect("utf8 stderr"), 251 process_diagnostic(MycProcessResult::InputOrConfiguration) 252 ); 253 } 254 255 #[test] 256 fn removed_alias_and_leaf_arguments_fail_without_echoing_values() { 257 for arguments in [ 258 vec!["--env-file", "/sensitive/config.env", "run"], 259 vec!["metrics"], 260 vec!["run", "--relay-url", "wss://sensitive.example"], 261 ] { 262 let output = Command::new(env!("CARGO_BIN_EXE_myc")) 263 .args(["--profile", "service-host", "--instance", "primary"]) 264 .args(arguments) 265 .output() 266 .expect("run forbidden command"); 267 assert_eq!(output.status.code(), Some(2)); 268 let stderr = String::from_utf8(output.stderr).expect("utf8 stderr"); 269 assert_eq!( 270 stderr, 271 process_diagnostic(MycProcessResult::InputOrConfiguration) 272 ); 273 assert!(!stderr.contains("sensitive")); 274 } 275 }