myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

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 }