This guide explains how to properly log security-relevant operations in StarForge to maintain reliable audit trails and debugging capabilities.
- Philosophy
- Sensitivity Levels
- What to Log
- What NOT to Log
- Structured Logging Format
- Security-Relevant Operations
- Audit Trail Requirements
- Code Examples
- Verification and Testing
Security and auditability are critical in blockchain tooling where users interact with real assets and sensitive keys. Every security-relevant operation should:
- Be logged - Operations that affect security state must be recorded
- Be traceable - Include enough context to understand what happened
- Be sanitized - Remove sensitive data from logs
- Be useful - Provide actionable information for debugging and auditing
StarForge uses structured logging (JSON format available) with the tracing crate to ensure logs are machine-parseable and can be aggregated for analysis.
Safe to log in all contexts:
- User-provided wallet/contract names
- Public keys (Stellar G-accounts)
- Network names (testnet, mainnet)
- Operation types (create, deploy, inspect)
- Success/failure status
- Timestamps
Example:
{
"timestamp": "2024-01-15T10:30:45Z",
"operation": "wallet_create",
"wallet_name": "alice",
"network": "testnet",
"encrypted": true,
"success": true
}Log only at DEBUG/TRACE level or never:
- Account addresses in some contexts
- Gas costs and fees (operational)
- Algorithm parameters (KDF iterations, salt size)
- Error messages with technical details
Example (only at DEBUG level):
debug!(address = %redact_public_key(addr, Level::DEBUG), "Account balance query");NEVER log under any circumstances:
- Private keys / secret keys
- Passphrases
- Encryption keys or derived keys
- Signed transactions or XDR payloads
- Wallet backup data
- Plugin source code or binaries
- Authentication tokens
- Operation Type - What is being done (create, encrypt, deploy, load, execute)
- Operation Status - Success or failure
- Operation Context - Who/what triggered it, which account/resource
- Timestamp - When it happened
- Duration - How long it took (important for crypto operations)
- Error Details - If failed, what went wrong (without revealing secrets)
Example structure:
info!(
operation = "wallet_encrypt",
wallet = "alice",
status = "success",
duration_ms = 245,
"Wallet encryption completed"
);Operation: wallet_create
Logs:
- wallet name, network, encryption enabled, success
Operation: wallet_encrypt
Logs:
- wallet name, encryption algorithm, KDF iterations, success
Operation: wallet_fund
Logs:
- wallet name, network, requested amount, balance after, success
Operation: wallet_remove
Logs:
- wallet name, network, confirmation status, success
Operation: wallet_show
Logs:
- wallet name, reveal_requested, balance_query_status
Operation: plugin_load
Logs:
- plugin name, plugin version, source path, compatibility check result
Operation: plugin_execute
Logs:
- plugin name, command args (sanitized), execution status, duration
Operation: plugin_unload
Logs:
- plugin name, cleanup status, success
Operation: contract_deploy
Logs:
- contract path, network, account, wasm hash, deployment status, gas
Operation: contract_validate
Logs:
- contract path, validation status, file size, warnings/errors
Operation: contract_inspect
Logs:
- contract address, network, inspection status, metadata retrieved
Operation: network_switch
Logs:
- from_network, to_network, connectivity_check_status
Operation: network_test
Logs:
- network name, horizon_status, soroban_status, latency
// ❌ WRONG - Never log secret keys
warn!("Secret key: {}", secret_key);
// ❌ WRONG - Never log passphrases
info!("User entered passphrase: {}", passphrase);
// ❌ WRONG - Never log private/sensitive material
debug!("Encryption key: {}", derived_key);
// ❌ WRONG - Never log full XDR payloads
trace!("Signed transaction: {}", xdr_envelope);// ❌ Unnecessary - Don't log function entry/exit for every function
trace!("Entering validate_key()");
// ❌ Noisy - Don't log every iteration
for item in items {
trace!("Processing: {:?}", item); // Too verbose
}
// ❌ Sensitive context - Don't log full error messages with context
error!("Failed to decrypt wallet {} with passphrase {}: {}", name, pass, err);// ✅ Redact public keys at INFO level
info!(key = %redact_public_key(addr, Level::INFO), "Account queried");
// ✅ Never redact error messages (no secrets)
error!("Network connection failed: {}", err);
// ✅ Log operation metadata
info!(
wallet = "alice",
network = "testnet",
operation = "fund",
status = "success"
);StarForge uses structured logging for machine-parseability. Each log entry includes:
2024-01-15T10:30:45.123Z [INFO] starforge::commands::wallet: Wallet encrypted
wallet: alice
algorithm: aes-256-gcm
iterations: 100000
duration_ms: 245
{
"timestamp": "2024-01-15T10:30:45.123Z",
"level": "INFO",
"target": "starforge::commands::wallet",
"message": "Wallet encrypted",
"fields": {
"wallet": "alice",
"algorithm": "aes-256-gcm",
"iterations": 100000,
"duration_ms": 245
}
}- Use structured fields - Not free-text messages
- Use consistent names -
wallet,network,operation,status - Use appropriate types - Timestamps as ISO-8601, durations as milliseconds
- Be specific -
status: "success"notmessage: "OK"
| Operation | Logs | Sensitivity |
|---|---|---|
| Create | name, network, encryption enabled, result | Public |
| Encrypt | name, algorithm, KDF params (size, not value) | Public |
| Decrypt | name, attempt status | Public |
| Fund | name, network, amount, balance after | Public |
| Show | name, reveal_requested, balance_status | Public |
| Remove | name, confirmation, result | Public |
| Rotate | name, new_account_created, result | Public |
Never log: Secret keys, passphrases, private key material
| Operation | Logs | Sensitivity |
|---|---|---|
| Load | name, version, source_path, verify status | Public |
| Execute | name, args (sanitized), duration, result | Public |
| Unload | name, cleanup_status | Public |
| Compatibility Check | name, starforge_version, plugin_version, compatible | Public |
Never log: Plugin source code, loaded binaries, execution results with sensitive data
| Operation | Logs | Sensitivity |
|---|---|---|
| Validate | file_path, file_size, validation_result | Public |
| Deploy | network, account (redacted), hash, gas, result | Public |
| Inspect | address (redacted), network, inspection_result | Public |
| Invoke | address (redacted), function, param_count, result | Public |
Never log: Private contract state, function arguments with secrets, signed XDR
| Operation | Logs | Sensitivity |
|---|---|---|
| Switch | from_network, to_network, verify_status | Public |
| Test | network, endpoint_status, latency_ms | Public |
| Add Custom | network_name, urls (redacted), result | Public |
Never log: API keys for custom networks
All security operations must include precise timestamps for audit correlation:
use chrono::Utc;
info!(
timestamp = %Utc::now(),
operation = "wallet_create",
"New wallet created"
);Each security operation should include enough context to understand the full flow:
info!(
session_id = uuid,
user_action = "import_wallet",
wallet_name = "alice",
network = "testnet",
encrypted = true,
duration_ms = 150,
status = "success",
"Wallet import completed"
);When logging errors, include diagnostic information but not sensitive data:
// ✅ Good - Specific error type without secrets
error!(
wallet = "alice",
error_type = "DecryptionFailed",
error_code = "ERR_WRONG_PASSPHRASE",
attempts = 3,
"Failed to decrypt wallet"
);
// ❌ Bad - Too specific about secrets
error!(
wallet = "alice",
error = "Failed to decrypt with provided passphrase",
passphrase_length = 8,
"Decryption failed"
);Always log the result of security operations:
info!(
operation = "deploy",
account = redact_public_key(addr, Level::INFO),
network = "testnet",
wasm_hash = hash,
gas_used = 12345,
result = "success",
"Contract deployed"
);use tracing::info;
fn create_wallet(name: &str, network: &str, encrypt: bool) -> Result<Wallet> {
let start = std::time::Instant::now();
let wallet = Wallet::new(name, network)?;
if encrypt {
wallet.encrypt(passphrase)?;
}
let duration_ms = start.elapsed().as_millis() as u64;
info!(
wallet = name,
network = network,
encrypted = encrypt,
duration_ms = duration_ms,
status = "success",
"Wallet created"
);
Ok(wallet)
}use tracing::info;
fn load_plugin(plugin_path: &str, name: &str) -> Result<Plugin> {
let plugin = unsafe { pm.load_plugin(plugin_path)? };
let plugin_version = plugin.version();
let starforge_version = env!("CARGO_PKG_VERSION");
info!(
plugin_name = name,
plugin_version = plugin_version,
starforge_version = starforge_version,
source = plugin_path,
status = "success",
"Plugin loaded"
);
Ok(plugin)
}use tracing::info;
fn deploy_contract(wasm_path: &str, network: &str, account: &str) -> Result<String> {
let wasm_hash = calculate_wasm_hash(wasm_path)?;
let result = execute_deployment(wasm_path, network, account)?;
info!(
wasm_hash = %wasm_hash,
network = network,
account = %redact_public_key(account, Level::INFO),
contract_address = %result.address,
gas_used = result.gas_used,
status = "success",
"Contract deployed"
);
Ok(result.address)
}use tracing::{info, error};
fn fund_wallet(wallet: &str, network: &str, amount: u64) -> Result<()> {
match request_funds(wallet, network, amount) {
Ok(tx_hash) => {
info!(
wallet = wallet,
network = network,
amount = amount,
transaction_hash = tx_hash,
status = "success",
"Wallet funded"
);
Ok(())
}
Err(e) => {
error!(
wallet = wallet,
network = network,
error_type = "FundingFailed",
error_message = %e,
status = "failed",
"Failed to fund wallet"
);
Err(e)
}
}
}Always test that security operations log correctly:
#[test]
fn test_wallet_creation_is_logged() {
// Setup logging capture
let subscriber = tracing_subscriber::fmt()
.with_max_level(Level::INFO)
.finish();
let _guard = tracing::subscriber::set_default(subscriber);
// Perform security operation
let wallet = Wallet::new("test", "testnet").unwrap();
// Verify logs were emitted (integration test would check log output)
assert_eq!(wallet.name, "test");
}Enable JSON logging in CI for better parsing:
# In CI environment
cargo test --log-format json --log-dir ./logs
# Verify logs contain required fields
jq '.fields | has("wallet") and has("status")' logs/*.log | grep trueRegular review of security logs:
- Weekly - Check for failed authentication attempts
- On deployment - Verify deployment logs are complete
- On incidents - Full log trace of affected operations
- On access - Who accessed sensitive operations and when
- AUDIT_TRAIL_DOCUMENTATION.md - Detailed audit trail setup
- SECURITY_LOGGING_BEST_PRACTICES.md - Implementation patterns
- src/utils/logging.rs - Logging infrastructure
- tracing crate - Official tracing documentation
Last updated: 2026-06-01
Issue #223: Improve security logging and auditability