Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions crates/starknet_transaction_prover/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,8 @@ docker run -e LOG_FORMAT=json ... <IMAGE>
and `span` keys. The `text` format may include ANSI colour codes. `json` never does, so use
`--log-format json` in containers and in production. A URL can carry credentials in its userinfo
component, so the service redacts every logged URL down to `scheme://host[:port]`. That covers
`rpc_node_url` in the startup logs and in its CLI-override message, and `blocking_check_url` in its
CLI-override message. The startup logs say only whether the blocking check is enabled, never its
URL.
`rpc_node_url` and `blocking_check_url` in the startup `config_resolved` log, and each of them
again in its own CLI-override message.

## Compression

Expand Down Expand Up @@ -371,6 +370,21 @@ there was never a status.
The logging layer never reads request bodies. Transaction calldata is private user data and stays
out of the logs.

### Startup logs

Startup emits two `info` logs, plus an `OHTTP envelope encryption enabled` line between them when
OHTTP is enabled. Before binding, an `event="config_resolved"` log records the build identity
(version, git SHA) together with the resolved service and prover settings, which are the merge of
the config file, environment variables, and CLI flags. Read that line to see the values the process
runs with, instead of working the precedence rules out by hand. The `contract_class_manager_config`
and `runner_config` sub-configs are not included. After the listener is bound, a second log records
what the server came up on: local address, scheme (`http` or `https`), `max_concurrent_requests`,
`max_connections`, `ohttp_enabled`, and the CORS mode and allowed origins.

Both lines are redacted. The service logs the RPC and blocking-check URLs host-only (see
[Logging](#logging)), never logs TLS certificate and key paths, and puts no transaction-scoped data
in either line.

### Shutdown

`SIGTERM` and `SIGINT` start a graceful shutdown. The server stops accepting new requests and lets
Expand Down
17 changes: 2 additions & 15 deletions crates/starknet_transaction_prover/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ async fn main() -> anyhow::Result<()> {
};
use starknet_transaction_prover::server::cors::{build_cors_layer, cors_mode};
use starknet_transaction_prover::server::health::HealthLayer;
use starknet_transaction_prover::server::log_redact::redact_url_host;
use starknet_transaction_prover::server::metrics::{install_exporter, spawn_upkeep};
use starknet_transaction_prover::server::panic::install_panic_hook;
use starknet_transaction_prover::server::rpc_api::ProvingRpcServer;
Expand Down Expand Up @@ -60,6 +59,8 @@ async fn main() -> anyhow::Result<()> {

let config = ServiceConfig::from_args(args)?;

config.log_startup_summary();

// Install the Prometheus exporter and emit `prover_build_info` before binding, so a scrape
// during a slow startup still returns the build identity.
let prometheus_handle =
Expand All @@ -68,20 +69,6 @@ async fn main() -> anyhow::Result<()> {
let metrics_layer = MetricsLayer::new(prometheus_handle.clone());
spawn_upkeep(prometheus_handle);

// Startup banner — version + chain id + redacted RPC host only. No URLs
// with userinfo, no fee token address, no TLS paths, no tx data.
info!(
version = env!("CARGO_PKG_VERSION"),
git_sha = option_env!("GIT_SHA").unwrap_or("unknown"),
chain_id = %config.prover_config.chain_id,
rpc_node_host = %redact_url_host(&config.prover_config.rpc_node_url),
validate_zero_fee_fields = config.prover_config.validate_zero_fee_fields,
blocking_check_enabled = config.prover_config.blocking_check_url.is_some(),
blocking_check_fail_open = config.prover_config.blocking_check_fail_open,
ohttp_enabled = config.ohttp_enabled,
"Starting Starknet transaction prover."
);

// Build and start the JSON-RPC server. The request path and the health probe share one
// saturation monitor. The request path records rejects and worker-slot progress, and the
// probe reads it.
Expand Down
42 changes: 42 additions & 0 deletions crates/starknet_transaction_prover/src/server/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,48 @@ impl ServiceConfig {
health_max_saturated_ms: config.health_max_saturated_ms,
})
}

/// Logs the resolved service and prover settings (file + env + CLI merge) once at startup.
/// The `contract_class_manager_config` and `runner_config` sub-configs are not included.
///
/// It logs the RPC and blocking-check URLs host-only, and never logs the TLS key and
/// certificate paths.
pub fn log_startup_summary(&self) {
let version = env!("CARGO_PKG_VERSION");
let git_sha = option_env!("GIT_SHA").unwrap_or("unknown");
let transport_label = match self.transport {
TransportMode::Http => "http",
TransportMode::Https { .. } => "https",
};
let blocking_check_host =
self.prover_config.blocking_check_url.as_deref().map(redact_url_host);
info!(
event = "config_resolved",
version,
git_sha,
ip = %self.ip,
port = self.port,
transport = transport_label,
max_concurrent_requests = self.max_concurrent_requests,
max_queued_requests = self.max_queued_requests,
queue_wait_timeout_millis = self.queue_wait_timeout_millis,
max_connections = self.max_connections,
max_request_body_size = self.max_request_body_size,
cors_allow_origin = ?self.cors_allow_origin,
ohttp_enabled = self.ohttp_enabled,
ohttp_key_cache_max_age_secs = self.ohttp_key_cache_max_age_secs,
health_max_saturated_ms = self.health_max_saturated_ms,
chain_id = %self.prover_config.chain_id,
rpc_node_host = %redact_url_host(&self.prover_config.rpc_node_url),
strk_fee_token_address = ?self.prover_config.strk_fee_token_address,
validate_zero_fee_fields = self.prover_config.validate_zero_fee_fields,
blocking_check_enabled = self.prover_config.blocking_check_url.is_some(),
blocking_check_host = ?blocking_check_host,
blocking_check_timeout_millis = self.prover_config.blocking_check_timeout_millis,
blocking_check_fail_open = self.prover_config.blocking_check_fail_open,
"Starting Starknet transaction prover."
);
}
}

/// CLI arguments for the proving service.
Expand Down
25 changes: 25 additions & 0 deletions crates/starknet_transaction_prover/src/server/config_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::sync::{Mutex, MutexGuard};
use clap::Parser;
use rstest::rstest;
use tempfile::NamedTempFile;
use tracing_test::traced_test;

use crate::errors::ConfigError;
use crate::server::config::{CliArgs, LogFormat, ServiceConfig, TransportMode};
Expand Down Expand Up @@ -344,3 +345,27 @@ fn env_var_sets_tls_key_file() {

assert_eq!(args.tls_key_file, Some(PathBuf::from("/etc/ssl/key.pem")));
}

/// The startup summary is the only place that writes the resolved service and
/// prover settings to the log stream, so its redaction is what keeps credentials
/// out of the operator's log aggregator.
#[test]
#[traced_test]
fn startup_summary_logs_hosts_and_redacts_url_credentials() {
let mut args = base_args();
args.rpc_url = Some("https://user:sekret@rpc.example.com:8545/v2/api-key".to_string());
args.blocking_check_url = Some("https://ops:hunter2@screen.example.com/check".to_string());
let config = ServiceConfig::from_args(args).unwrap();

config.log_startup_summary();

assert!(logs_contain("event=\"config_resolved\""), "must tag the event for log-based checks");
assert!(logs_contain("rpc.example.com"), "the host is operational context, so it is logged");
assert!(
logs_contain("screen.example.com"),
"the blocking-check host is operational context, safe to log"
);
for secret in ["sekret", "hunter2", "api-key"] {
assert!(!logs_contain(secret), "`{secret}` from a URL must never reach the log stream");
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weak startup redaction test isolation

Low Severity

startup_summary_logs_hosts_and_redacts_url_credentials calls from_args before log_startup_summary, and from_args already emits redacted CLI-override lines that include the same hosts. The positive logs_contain host checks can therefore pass even if log_startup_summary stops logging hosts. Secret-absence checks still cover the summary path, but host logging on the config_resolved line is not actually pinned.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8ccf362. Configure here.

Loading