From 424002c0f5260d5e8cb5f13a7951622d6a44a3ac Mon Sep 17 00:00:00 2001 From: Sirius DevOps Date: Thu, 10 Sep 2026 20:30:18 -0400 Subject: [PATCH] [verified] fix(hs): redact onion in publish logs; cache 0700 wait_until_published now logs HsId via safelog, not the locator. Store and client_config mkdir Arti cache/state 0700. Arti dangerously_trust_everyone stays on storage only. --- src/hs.rs | 24 ++++++++++++++++++++---- src/node.rs | 4 ++-- src/store.rs | 1 + tests/hs.rs | 28 ++++++++++++++++++++++++++++ tests/store.rs | 3 +++ tests/tor_hs.rs | 4 ++-- 6 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/hs.rs b/src/hs.rs index 019f311..159e53d 100644 --- a/src/hs.rs +++ b/src/hs.rs @@ -1,5 +1,6 @@ //! In-process Arti onion-service helpers (no C-tor). +use std::os::unix::fs::PermissionsExt; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -8,7 +9,7 @@ use arti_client::{TorClient, TorClientConfig}; use futures::StreamExt; use safelog::DisplayRedacted; use tor_hsservice::status::State; -use tor_hsservice::{HsNickname, OnionServiceConfig, RunningOnionService}; +use tor_hsservice::{HsId, HsNickname, OnionServiceConfig, RunningOnionService}; use tor_rtcompat::PreferredRuntime; pub const HS_PORT: u16 = 80; @@ -28,9 +29,24 @@ const PROBE_TIMEOUT: Duration = Duration::from_secs(12); pub type Client = Arc>; +fn mkdir_700(path: &std::path::Path) { + std::fs::create_dir_all(path).expect("mkdir"); + let mut perms = std::fs::metadata(path).expect("metadata").permissions(); + perms.set_mode(0o700); + std::fs::set_permissions(path, perms).expect("chmod 0700"); +} + +/// Status/probe log label: safelog-redacted v3 onion, never the locator. +pub fn log_label(onion: &str) -> String { + match onion.parse::() { + Ok(id) => id.display_redacted().to_string(), + Err(_) => safelog::sensitive(onion).to_string(), + } +} + pub fn client_config(state_dir: &std::path::Path, cache_dir: &std::path::Path) -> TorClientConfig { - std::fs::create_dir_all(state_dir).expect("state dir"); - std::fs::create_dir_all(cache_dir).expect("cache dir"); + mkdir_700(state_dir); + mkdir_700(cache_dir); let mut builder = TorClientConfigBuilder::from_directories(state_dir, cache_dir); builder.storage().permissions().dangerously_trust_everyone(); builder @@ -84,8 +100,8 @@ pub async fn wait_until_published( client: &Client, svc: &RunningOnionService, onion: &str, - label: &str, ) -> Result<(), String> { + let label = log_label(onion); let deadline = Instant::now() + PUBLISH_WAIT; let mut events = svc.status_events(); let mut next_probe = Instant::now() + PROBE_EVERY; diff --git a/src/node.rs b/src/node.rs index 92c49c5..4a8efac 100644 --- a/src/node.rs +++ b/src/node.rs @@ -92,7 +92,7 @@ impl Node { _svc: Arc::clone(&svc), rend, }); - hs::wait_until_published(&node.client, &svc, &onion, &onion).await?; + hs::wait_until_published(&node.client, &svc, &onion).await?; node.store .lock() .map_err(|e| e.to_string())? @@ -444,7 +444,7 @@ impl Node { _svc: Arc::clone(&svc), rend, }); - hs::wait_until_published(&self.client, &svc, &onion, &onion).await?; + hs::wait_until_published(&self.client, &svc, &onion).await?; { let store = self.store.lock().map_err(|e| e.to_string())?; store.set_onion(&onion).map_err(|e| e.to_string())?; diff --git a/src/store.rs b/src/store.rs index adc1228..19d1594 100644 --- a/src/store.rs +++ b/src/store.rs @@ -117,6 +117,7 @@ impl Store { } mkdir_700(home)?; mkdir_700(&home.join("arti"))?; + mkdir_700(&home.join("cache"))?; let db_path = home.join("onionwire.db"); let conn = Connection::open(&db_path)?; let journal: String = conn.query_row("PRAGMA journal_mode = WAL", [], |row| row.get(0))?; diff --git a/tests/hs.rs b/tests/hs.rs index bfa70eb..334620c 100644 --- a/tests/hs.rs +++ b/tests/hs.rs @@ -1,5 +1,6 @@ //! HS publish wait and CBT floor — 180s fail-closed cuts a working HsDir upload. +use std::os::unix::fs::PermissionsExt; use std::time::Duration; use onionwire::hs; @@ -67,3 +68,30 @@ fn broken_or_shutdown_never_ready() { assert!(!hs::hs_is_ready(State::Broken, true)); assert!(!hs::hs_is_ready(State::Shutdown, true)); } + +#[test] +fn hs_log_label_is_not_the_full_v3_onion() { + // Public v3 address; checksum is valid so HsId::from_str works. + let onion = "facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion"; + let label = hs::log_label(onion); + assert_ne!(label, onion, "status/probe logs must not use the locator"); + assert!( + !label.contains("facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd"), + "log label leaked the onion body: {label}" + ); + assert!( + label.contains('…') || label.contains("[scrubbed]"), + "expected safelog redaction, got {label}" + ); +} + +#[test] +fn client_config_mkdirs_state_and_cache_0700() { + let root = tempfile::tempdir().expect("tempdir"); + let state = root.path().join("arti"); + let cache = root.path().join("cache"); + let _cfg = hs::client_config(&state, &cache); + let mode = |p: &std::path::Path| std::fs::metadata(p).unwrap().permissions().mode() & 0o777; + assert_eq!(mode(&state), 0o700); + assert_eq!(mode(&cache), 0o700); +} diff --git a/tests/store.rs b/tests/store.rs index 33f4a89..bf993eb 100644 --- a/tests/store.rs +++ b/tests/store.rs @@ -55,11 +55,14 @@ fn first_run_creates_0700_dirs_and_self_row() { let store = Store::open().expect("open"); let arti = home.path().join("arti"); + let cache = home.path().join("cache"); let db = home.path().join("onionwire.db"); assert!(arti.is_dir(), "arti dir"); + assert!(cache.is_dir(), "cache dir"); assert!(db.is_file(), "onionwire.db"); assert_eq!(mode(home.path()), 0o700); assert_eq!(mode(&arti), 0o700); + assert_eq!(mode(&cache), 0o700); let me = store.self_identity().expect("self"); assert_eq!(me.identity_pk.len(), 32); diff --git a/tests/tor_hs.rs b/tests/tor_hs.rs index 1851183..d5b12bc 100644 --- a/tests/tor_hs.rs +++ b/tests/tor_hs.rs @@ -124,10 +124,10 @@ async fn two_node_byte_pipe_restart_and_dormant() { eprintln!("bob onion={bob_onion}"); assert_ne!(alice_onion, bob_onion, "separate HS identities"); - hs::wait_until_published(&alice, &alice_svc, &alice_onion, "alice") + hs::wait_until_published(&alice, &alice_svc, &alice_onion) .await .expect("alice publish"); - hs::wait_until_published(&bob, &bob_svc, &bob_onion, "bob") + hs::wait_until_published(&bob, &bob_svc, &bob_onion) .await .expect("bob publish");