commit ae95e9c5c397c6a68e7d19c555cfece6ab80a93f
parent a5566584db9b4d4a858ae66e44ea704680c7c22f
Author: triesap <tyson@radroots.org>
Date: Sat, 25 Apr 2026 04:31:06 +0000
runtime: remove memory cli secret backend
- reject memory for persistent account backend config
- keep fallback diagnostics limited to host_vault encrypted_file or none
- update runtime and render allowed-backend evidence
- prove rejected account creation writes no store state
Diffstat:
4 files changed, 61 insertions(+), 25 deletions(-)
diff --git a/src/render/mod.rs b/src/render/mod.rs
@@ -4345,11 +4345,7 @@ mod tests {
account_secret_contract: AccountSecretContractConfig {
default_backend: "host_vault".into(),
default_fallback: Some("encrypted_file".into()),
- allowed_backends: vec![
- "host_vault".into(),
- "encrypted_file".into(),
- "memory".into(),
- ],
+ allowed_backends: vec!["host_vault".into(), "encrypted_file".into()],
host_vault_policy: Some("desktop".into()),
uses_protected_store: true,
},
@@ -4506,11 +4502,7 @@ mod tests {
account_secret_contract: AccountSecretContractConfig {
default_backend: "host_vault".into(),
default_fallback: Some("encrypted_file".into()),
- allowed_backends: vec![
- "host_vault".into(),
- "encrypted_file".into(),
- "memory".into(),
- ],
+ allowed_backends: vec!["host_vault".into(), "encrypted_file".into()],
host_vault_policy: Some("desktop".into()),
uses_protected_store: true,
},
diff --git a/src/runtime/config.rs b/src/runtime/config.rs
@@ -29,7 +29,7 @@ const DEFAULT_RPC_URL: &str = "http://127.0.0.1:7070";
const CLI_HOST_VAULT_POLICY: &str = "desktop";
const CLI_DEFAULT_SECRET_BACKEND: &str = "host_vault";
const CLI_DEFAULT_SECRET_FALLBACK: &str = "encrypted_file";
-const CLI_ALLOWED_SHARED_SECRET_BACKENDS: &[&str] = &["host_vault", "encrypted_file", "memory"];
+const CLI_ALLOWED_SHARED_SECRET_BACKENDS: &[&str] = &["host_vault", "encrypted_file"];
const CLI_USES_PROTECTED_STORE: bool = true;
const ENV_FILE_PATH: &str = "RADROOTS_ENV_FILE";
const ENV_OUTPUT: &str = "RADROOTS_OUTPUT";
@@ -1332,11 +1332,16 @@ fn parse_account_secret_fallback(
key: &str,
value: &str,
) -> Result<Option<RadrootsSecretBackend>, RuntimeError> {
- if value.trim().eq_ignore_ascii_case("none") {
- return Ok(None);
+ match value.trim().to_ascii_lowercase().as_str() {
+ "none" => Ok(None),
+ "host_vault" => Ok(Some(RadrootsSecretBackend::HostVault(
+ RadrootsHostVaultPolicy::desktop(),
+ ))),
+ "encrypted_file" => Ok(Some(RadrootsSecretBackend::EncryptedFile)),
+ other => Err(RuntimeError::Config(format!(
+ "{key} must be `host_vault`, `encrypted_file`, or `none`, got `{other}`"
+ ))),
}
-
- parse_account_secret_backend(key, value).map(Some)
}
fn parse_account_secret_backend(
@@ -1348,9 +1353,8 @@ fn parse_account_secret_backend(
RadrootsHostVaultPolicy::desktop(),
)),
"encrypted_file" => Ok(RadrootsSecretBackend::EncryptedFile),
- "memory" => Ok(RadrootsSecretBackend::Memory),
other => Err(RuntimeError::Config(format!(
- "{key} must be `host_vault`, `encrypted_file`, `memory`, or `none` for fallback, got `{other}`"
+ "{key} must be `host_vault` or `encrypted_file`, got `{other}`"
))),
}
}
@@ -1556,11 +1560,7 @@ mod tests {
AccountSecretContractConfig {
default_backend: "host_vault".to_owned(),
default_fallback: Some("encrypted_file".to_owned()),
- allowed_backends: vec![
- "host_vault".to_owned(),
- "encrypted_file".to_owned(),
- "memory".to_owned(),
- ],
+ allowed_backends: vec!["host_vault".to_owned(), "encrypted_file".to_owned(),],
host_vault_policy: Some("desktop".to_owned()),
uses_protected_store: true,
}
diff --git a/tests/identity_commands.rs b/tests/identity_commands.rs
@@ -211,9 +211,46 @@ fn account_new_rejects_plaintext_fallback_backend() {
assert_eq!(output.status.code(), Some(2));
let stderr = String::from_utf8(output.stderr).expect("utf8 stderr");
- assert!(
- stderr.contains("must be `host_vault`, `encrypted_file`, `memory`, or `none` for fallback")
- );
+ assert!(stderr.contains("must be `host_vault`, `encrypted_file`, or `none`"));
+}
+
+#[test]
+fn account_new_rejects_memory_secret_backend_without_creating_store_state() {
+ let dir = tempdir().expect("tempdir");
+ let store_path = data_root(dir.path()).join("shared/accounts/store.json");
+
+ let output = cli_command_in(dir.path())
+ .env("RADROOTS_ACCOUNT_SECRET_BACKEND", "memory")
+ .args(["account", "new"])
+ .output()
+ .expect("run account new");
+
+ assert_eq!(output.status.code(), Some(2));
+ assert!(!store_path.exists());
+ assert!(output.stdout.is_empty());
+ let stderr = String::from_utf8(output.stderr).expect("utf8 stderr");
+ assert!(stderr.contains("RADROOTS_ACCOUNT_SECRET_BACKEND"));
+ assert!(stderr.contains("must be `host_vault` or `encrypted_file`"));
+}
+
+#[test]
+fn account_new_rejects_memory_secret_fallback_without_creating_store_state() {
+ let dir = tempdir().expect("tempdir");
+ let store_path = data_root(dir.path()).join("shared/accounts/store.json");
+
+ let output = cli_command_in(dir.path())
+ .env("RADROOTS_ACCOUNT_SECRET_BACKEND", "host_vault")
+ .env("RADROOTS_ACCOUNT_SECRET_FALLBACK", "memory")
+ .args(["account", "new"])
+ .output()
+ .expect("run account new");
+
+ assert_eq!(output.status.code(), Some(2));
+ assert!(!store_path.exists());
+ assert!(output.stdout.is_empty());
+ let stderr = String::from_utf8(output.stderr).expect("utf8 stderr");
+ assert!(stderr.contains("RADROOTS_ACCOUNT_SECRET_FALLBACK"));
+ assert!(stderr.contains("must be `host_vault`, `encrypted_file`, or `none`"));
}
#[test]
diff --git a/tests/runtime_show.rs b/tests/runtime_show.rs
@@ -237,6 +237,13 @@ fn config_show_json_reports_default_bootstrap_state() {
"encrypted_file"
);
assert_eq!(
+ json["account"]["secret_backend"]["allowed_backends"]
+ .as_array()
+ .expect("allowed backends")
+ .len(),
+ 2
+ );
+ assert_eq!(
json["account"]["secret_backend"]["allowed_backends"][0],
"host_vault"
);