pub struct AgentError { /* private fields */ }Expand description
Main error type with security-conscious design.
§Key Properties
- All context is zeroized on drop (including source errors)
- External display reveals minimal information
- Internal logging uses structured data with explicit lifetimes
- No implicit conversions from stdlib errors
- Hot paths are zero-allocation where possible
- Built-in constant-time error generation to reduce timing side-channels
- Always-on error code obfuscation to resist fingerprinting
§Design Rationale - Error Constructors
We provide convenience constructors like config(), telemetry(), etc.
even though ErrorCode already contains the category. This is intentional:
- Ergonomics:
AgentError::config(code, ...)is clearer thanAgentError::new(code, ...) - Future extensibility: Different subsystems may need different context types
- Type safety: Prevents mixing categories (compile-time check vs runtime enum match)
- Grep-ability: Engineers can search for “::telemetry(” to find all telemetry errors
The redundancy is acceptable because it improves maintainability and reduces the chance of errors being created with mismatched code/category pairs.
Implementations§
Source§impl AgentError
impl AgentError
Sourcepub fn with_retry(self) -> Self
pub fn with_retry(self) -> Self
Mark this error as retryable (transient failure)
Sourcepub fn with_metadata(
self,
key: &'static str,
value: impl Into<Cow<'static, str>>,
) -> Self
pub fn with_metadata( self, key: &'static str, value: impl Into<Cow<'static, str>>, ) -> Self
Add tracking metadata (correlation IDs, session tokens, etc.)
MUTATES IN PLACE - no cloning, no wasted allocations.
Sourcepub fn with_timing_normalization(self, target_duration: Duration) -> Self
pub fn with_timing_normalization(self, target_duration: Duration) -> Self
Normalize timing to prevent timing side-channel attacks.
This sleeps until target_duration has elapsed since error creation,
ensuring that error responses take a consistent amount of time regardless
of which code path failed.
§Use Cases
- Authentication failures (prevent user enumeration)
- Sensitive file operations (prevent path existence probing)
- Cryptographic operations (prevent timing attacks on key material)
§Example
use palisade_errors::{AgentError, definitions};
use std::time::Duration;
fn authenticate(user: &str, pass: &str) -> palisade_errors::Result<()> {
// Fast path: invalid username
if !user_exists(user) {
return Err(
AgentError::config(definitions::CFG_VALIDATION_FAILED, "auth", "Invalid credentials")
.with_timing_normalization(Duration::from_millis(100))
);
}
// Slow path: password hash check
if !check_password(user, pass) {
return Err(
AgentError::config(definitions::CFG_VALIDATION_FAILED, "auth", "Invalid credentials")
.with_timing_normalization(Duration::from_millis(100))
);
}
Ok(())
}Both error paths now take at least 100ms, preventing attackers from distinguishing between “user doesn’t exist” and “wrong password”.
§Performance Note
This adds a sleep to the error path, which is acceptable since errors are not the hot path. The slight performance cost is worth the security benefit for sensitive operations.
§Limitations
- Not async-safe: Blocks the thread. Use in sync contexts only.
- Coarse precision: OS scheduling affects accuracy (1-15ms jitter).
- Partial protection: Only normalizes error return timing, not upstream operations.
- Observable side-channels: Network timing, cache behavior, DB queries remain.
This provides defense-in-depth against timing attacks but is not a complete solution.
Sourcepub const fn is_retryable(&self) -> bool
pub const fn is_retryable(&self) -> bool
Check if this error can be retried
Sourcepub const fn category(&self) -> OperationCategory
pub const fn category(&self) -> OperationCategory
Get operation category
Sourcepub fn age(&self) -> Duration
pub fn age(&self) -> Duration
Get the time elapsed since this error was created.
Useful for metrics and debugging, but should NOT be exposed externally as it could leak timing information.
Sourcepub fn internal_log(&self) -> InternalLog<'_>
pub fn internal_log(&self) -> InternalLog<'_>
Create structured internal log entry with explicit lifetime.
§Critical Security Property
The returned InternalLog borrows from self and CANNOT
outlive this error. This is intentional and enforces that sensitive
data is consumed immediately by the logger and cannot be retained.
§Usage Pattern
let err = AgentError::config(
definitions::CFG_PARSE_FAILED,
"test",
"test details"
);
let log = err.internal_log();
// logger.log_structured(log); // log dies here
// err is dropped, all data zeroizedThe short lifetime prevents accidental retention in log buffers, async contexts, or background threads.
Sourcepub fn with_internal_log<F, R>(&self, f: F) -> Rwhere
F: FnOnce(&InternalLog<'_>) -> R,
pub fn with_internal_log<F, R>(&self, f: F) -> Rwhere
F: FnOnce(&InternalLog<'_>) -> R,
Alternative logging pattern for frameworks that need callback-style.
This enforces immediate consumption and prevents accidental retention:
err.with_internal_log(|log| {
// logger.write(log.code(), log.operation());
// log is destroyed when this closure returns
});Sourcepub fn config(
code: ErrorCode,
operation: impl Into<Cow<'static, str>>,
details: impl Into<Cow<'static, str>>,
) -> Self
pub fn config( code: ErrorCode, operation: impl Into<Cow<'static, str>>, details: impl Into<Cow<'static, str>>, ) -> Self
Create a configuration error
Sourcepub fn config_sensitive(
code: ErrorCode,
operation: impl Into<Cow<'static, str>>,
details: impl Into<Cow<'static, str>>,
sensitive: impl Into<Cow<'static, str>>,
) -> Self
pub fn config_sensitive( code: ErrorCode, operation: impl Into<Cow<'static, str>>, details: impl Into<Cow<'static, str>>, sensitive: impl Into<Cow<'static, str>>, ) -> Self
Create a configuration error with sensitive context
Sourcepub fn deployment(
code: ErrorCode,
operation: impl Into<Cow<'static, str>>,
details: impl Into<Cow<'static, str>>,
) -> Self
pub fn deployment( code: ErrorCode, operation: impl Into<Cow<'static, str>>, details: impl Into<Cow<'static, str>>, ) -> Self
Create a deployment error
Sourcepub fn telemetry(
code: ErrorCode,
operation: impl Into<Cow<'static, str>>,
details: impl Into<Cow<'static, str>>,
) -> Self
pub fn telemetry( code: ErrorCode, operation: impl Into<Cow<'static, str>>, details: impl Into<Cow<'static, str>>, ) -> Self
Create a telemetry error
Sourcepub fn correlation(
code: ErrorCode,
operation: impl Into<Cow<'static, str>>,
details: impl Into<Cow<'static, str>>,
) -> Self
pub fn correlation( code: ErrorCode, operation: impl Into<Cow<'static, str>>, details: impl Into<Cow<'static, str>>, ) -> Self
Create a correlation error
Sourcepub fn response(
code: ErrorCode,
operation: impl Into<Cow<'static, str>>,
details: impl Into<Cow<'static, str>>,
) -> Self
pub fn response( code: ErrorCode, operation: impl Into<Cow<'static, str>>, details: impl Into<Cow<'static, str>>, ) -> Self
Create a response error
Sourcepub fn logging(
code: ErrorCode,
operation: impl Into<Cow<'static, str>>,
details: impl Into<Cow<'static, str>>,
) -> Self
pub fn logging( code: ErrorCode, operation: impl Into<Cow<'static, str>>, details: impl Into<Cow<'static, str>>, ) -> Self
Create a logging error
Sourcepub fn platform(
code: ErrorCode,
operation: impl Into<Cow<'static, str>>,
details: impl Into<Cow<'static, str>>,
) -> Self
pub fn platform( code: ErrorCode, operation: impl Into<Cow<'static, str>>, details: impl Into<Cow<'static, str>>, ) -> Self
Create a platform error
Sourcepub fn io_operation(
code: ErrorCode,
operation: impl Into<Cow<'static, str>>,
details: impl Into<Cow<'static, str>>,
) -> Self
pub fn io_operation( code: ErrorCode, operation: impl Into<Cow<'static, str>>, details: impl Into<Cow<'static, str>>, ) -> Self
Create an I/O operation error
Sourcepub fn from_io_path(
code: ErrorCode,
operation: impl Into<Cow<'static, str>>,
path: impl Into<Cow<'static, str>>,
error: Error,
) -> Self
pub fn from_io_path( code: ErrorCode, operation: impl Into<Cow<'static, str>>, path: impl Into<Cow<'static, str>>, error: Error, ) -> Self
Wrap io::Error with explicit context, keeping path and error separate.
This prevents conflation of:
- Error kind (semi-sensitive, reveals error type)
- Path (sensitive, reveals filesystem structure)
By keeping them separate, logging systems can choose to handle them differently (e.g., hash paths but log error kinds).
Sourcepub async fn with_timing_normalization_async(
self,
target_duration: Duration,
) -> Self
pub async fn with_timing_normalization_async( self, target_duration: Duration, ) -> Self
Async-safe timing normalization for non-blocking contexts.
Unlike with_timing_normalization, this uses async sleep primitives
and won’t block the executor thread. Essential for Tokio/async-std runtimes.
§Example
async fn authenticate(user: &str, pass: &str) -> Result<Session> {
let result = check_credentials(user, pass).await;
if let Err(e) = result {
// Normalize timing without blocking executor
return Err(
e.with_timing_normalization_async(Duration::from_millis(100)).await
);
}
result
}§Runtime Support
- Requires either
tokioorasync-stdfeature - Tokio takes precedence if both are enabled
- Will not compile without at least one async runtime feature
Trait Implementations§
Source§impl Debug for AgentError
impl Debug for AgentError
Source§impl Display for AgentError
impl Display for AgentError
Source§fn fmt(&self, f: &mut Formatter<'_>) -> Result
fn fmt(&self, f: &mut Formatter<'_>) -> Result
External display - sanitized for untrusted viewers. Zero-allocation formatting.
Format: “{Category} operation failed [{permanence}] ({ERROR-CODE})”
Example: “Configuration operation failed [permanent] (E-CFG-100)”
This provides:
- Operation domain (for troubleshooting)
- Retry semantics (for automation)
- Error code (for tracking)
Without revealing:
- Internal paths or structure
- Validation logic
- User identifiers
- Configuration values
- Timing information