doppel/lib.rs
1#![warn(missing_docs)]
2//! Secret swapping and streaming restoration for arbitrary byte payloads.
3//!
4//! `doppel` intercepts secrets in outbound payloads, replaces them with
5//! structurally-equivalent fakes, and restores the originals in streaming responses.
6//!
7//! Three operations form the core workflow:
8//!
9//! 1. **[`swap`]** — scan a payload for secrets matching the supplied [`Pattern`]s,
10//! replace each with a fake, and return the swapped payload, encrypted entries,
11//! and a session key.
12//! 2. **Transmit** — send the swapped payload to the external service. Hold the
13//! entries and session key locally.
14//! 3. **[`restore`]** — stream the response through the restore function, which
15//! replaces fakes with originals using the session key and entries.
16//!
17//! For repeated swap calls against the same fixed pattern set, use [`Detector`] to
18//! pre-build the Aho-Corasick automaton once and reuse it across calls; this avoids
19//! rebuilding the automaton on every request.
20//!
21//! # Quick start
22//!
23//! ```rust
24//! use doppel::{swap, restore, patterns};
25//!
26//! // NOT real credentials — synthetic key matching the Anthropic structural pattern
27//! let payload = b"Authorization: sk-ant-api03-w8bVJRHra9S96i3ios_XhbLgzEBjS6qjPUEgiPrWjN2OeICCY1lwhK3Z35Z_jM89STjqSOxHh6GWGkG2R7uv-AohQLmK9AA";
28//!
29//! // 1. Swap: detect and replace the key before sending to an external service
30//! // Note: `patterns::all()` uses ephemeral salts — two separate calls to `swap` with
31//! // the same secret will produce different fakes. For stable fakes across calls,
32//! // use `SecretsFile::to_patterns()`. The `restore` call always works correctly because
33//! // fakes are embedded in the encrypted entries, independent of pattern salts.
34//! let result = swap(payload, &patterns::all()).unwrap();
35//! assert_eq!(result.entries.len(), 1); // one secret detected
36//! assert_ne!(result.payload.as_slice(), payload as &[u8]); // key replaced with a fake
37//!
38//! // result.payload — send to external service (key replaced with a fake)
39//! // result.entries — keep locally; needed to restore secrets in the response
40//! // result.session_key — keep locally; zeroized on drop
41//!
42//! // 2. Restore: recover the original secret from the response stream
43//! let mut response = result.payload.as_slice();
44//! let mut restored = Vec::new();
45//! restore(
46//! &mut response,
47//! &mut restored,
48//! &result.entries,
49//! &result.session_key,
50//! )
51//! .unwrap();
52//! assert_eq!(restored, payload.as_slice());
53//! ```
54
55pub(crate) mod crypto;
56pub mod detector;
57pub(crate) mod fake;
58pub mod patterns;
59pub(crate) mod restore;
60pub(crate) mod restore_core;
61#[cfg(feature = "async")]
62pub(crate) mod restore_stream;
63pub(crate) mod secrets;
64pub mod secrets_file;
65pub mod segment;
66pub(crate) mod serde_helpers;
67pub(crate) mod swap;
68pub mod types;
69
70pub use detector::Detector;
71pub use patterns::Pattern;
72pub use restore::{RestoreError, restore};
73#[cfg(feature = "async")]
74pub use restore_stream::{RestoreStream, restore_stream};
75pub use secrets::{SecretError, SecretOptions, register, register_with_options};
76pub use secrets_file::{PatternEntry, SecretsFile, SecretsFileError};
77pub use swap::swap;
78pub use types::{Entry, SessionKey, SwapError, SwapResult};