nautilus_core/string/secret.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3// https://nautechsystems.io
4//
5// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6// You may not use this file except in compliance with the License.
7// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Helpers for redacting and masking secrets in strings.
17
18/// Placeholder used in `Debug` impls to redact secret fields.
19pub const REDACTED: &str = "<redacted>";
20
21/// Masks an API key by showing only the first and last 4 characters.
22///
23/// For keys 8 characters or shorter, returns asterisks only.
24///
25/// # Examples
26///
27/// ```
28/// use nautilus_core::string::secret::mask_api_key;
29///
30/// assert_eq!(mask_api_key("abcdefghijklmnop"), "abcd...mnop");
31/// assert_eq!(mask_api_key("short"), "*****");
32/// ```
33#[must_use]
34pub fn mask_api_key(key: &str) -> String {
35 // Work with Unicode scalars to avoid panicking on multibyte characters.
36 let chars: Vec<char> = key.chars().collect();
37 let len = chars.len();
38
39 if len <= 8 {
40 return "*".repeat(len);
41 }
42
43 let first: String = chars[..4].iter().collect();
44 let last: String = chars[len - 4..].iter().collect();
45
46 format!("{first}...{last}")
47}
48
49#[cfg(test)]
50mod tests {
51 use rstest::rstest;
52
53 use super::*;
54
55 #[rstest]
56 #[case("", "")]
57 #[case("a", "*")]
58 #[case("abc", "***")]
59 #[case("abcdefgh", "********")]
60 #[case("abcdefghi", "abcd...fghi")]
61 #[case("abcdefghijklmnop", "abcd...mnop")]
62 #[case("VeryLongAPIKey123456789", "Very...6789")]
63 fn test_mask_api_key(#[case] input: &str, #[case] expected: &str) {
64 assert_eq!(mask_api_key(input), expected);
65 }
66}