santh-error 0.2.2

Actionable error primitives - stable error codes, fix hints, and built-in secret redaction
Documentation
1
2
3
4
5
6
7
8
9
10
# BACKLOG - santh-error
src/redact.rs:37 | bug | high | OpenAI API keys starting with 'sk-proj-' (which contain hyphens) fail to match the 'sk-[a-zA-Z0-9]{20,}' regex and leak credentials. | FIXED: body widened to 'sk-[a-zA-Z0-9_-]{20,}' so project keys and hyphen/underscore keys redact. Proving test adversarial::redact_openai_project_key. | status=done
src/redact.rs:31 | bug | high | Bearer token regex 'Bearer\s+[A-Za-z0-9_-]+' is case-sensitive and fails to redact lowercase 'bearer' or uppercase 'BEARER' tokens, leading to credential leaks. | FIXED: prepended '(?i)'. Proving test adversarial::redact_bearer_token_case_insensitive. | status=done
src/redact.rs:32 | bug | high | Password KV regexes using '\S+' leak multi-word or quoted secrets containing spaces by matching only up to the first space. | Update regexes to match quoted strings or handle spaces appropriately. | status=done | ALREADY-FIXED (stale-open row reconciled): shared const KV_VALUE = ("[^"]*"|'[^']*'|\S+) matches double/single-quoted OR unquoted values, used by every *_kv rule; the file comment states the exact fix. Verified by tests/adversarial.rs::redact_quoted_password_with_spaces (password="my secret pass", single-quoted, api_key with spaces) - 17 adversarial tests pass.
src/redact.rs:24 | capability | medium | AWS access key regex 'AKIA[0-9A-Z]{16}' does not redact temporary/session AWS access keys starting with 'ASIA'. | FIXED: 'A[KS]IA[0-9A-Z]{16}' covers AKIA + ASIA. Proving test adversarial::redact_aws_session_key. | status=done
src/redact.rs:76 | perf | medium | Sequential regex replacement loop calls '.into_owned()' on every pattern check, allocating a new String 12 times even when no secrets are present. | Only update output and allocate if the replace_all result is Cow::Owned | status=done | FIXED: replaced `output = pattern.replace_all(..).into_owned()` with `if let Cow::Owned(replaced) = pattern.replace_all(..) { output = replaced }`. On the common no-secret path replace_all returns Cow::Borrowed, so we skip the clone entirely (was 12+ needless String allocs per call; runs on every error/log line). Semantics identical: Owned only when a redaction happened.
src/redact.rs:82 | perf | medium | URL userinfo regex replacement always calls '.into_owned()', allocating a new String even if no URL userinfo is present. | Only allocate if the replace_all result is Cow::Owned | status=done | FIXED: same Cow::Owned guard applied to URL_USERINFO.replace_all; no allocation when no userinfo credentials present. Added `use std::borrow::Cow;`.
src/lib.rs:274 | dedup | low | Duplication of 'with_context', 'with_source', and 'with_location' builders between SanthError and SanthErrorBuilder. | Refactor to reuse common logic or keep builders on the builder type only. | status=done | FIXED (ONE-PLACE, kept both surfaces): the three mutators are legitimately needed on BOTH types (builder-side during construction, SanthError-side for post-build enrichment - removing either breaks the public contract, Law 3), so "builders on the builder type only" would be a functionality loss. Instead hoisted the byte-identical bodies (incl. doc comments + #[must_use]) into ONE `macro_rules! impl_diagnostic_mutators!` defined once; both `impl<State> SanthErrorBuilder<State>` and `impl SanthError` invoke it. Six method bodies collapse to one definitional source. Proving test contract::builder_and_built_mutators_render_identically enriches an error entirely via the builder vs entirely via the built value and asserts byte-identical actionable_message() (+ concrete Context/Location/Caused-by content so a shared-but-broken impl can't pass empty). Verified all-features green (53 tests + new parity test).
src/lib.rs:177 | bug | medium | Redundant debug_assert! check of 'fix.starts_with' inside the else branch of the same check always fails and panics in debug builds (contradicting the inline 'normalise' comment and the release auto-prefix path). | FIXED: removed the self-contradicting debug_assert so build() normalises (prefixes 'Fix: ') identically in debug and release; updated the # Panics doc to 'never panics'. | status=done
tests/adversarial.rs:141 | test-gap | low | Lack of test coverage for case-insensitive bearer tokens, temporary AWS credentials (ASIA), and quoted/spaced passwords. | Add test cases in adversarial.rs verifying redaction of these patterns. | status=done | STALE/ALREADY-COVERED (verified): case-insensitive bearer = redact_bearer_token_case_insensitive @adversarial.rs:75 (asserts `bearer`/`BEARER`); ASIA temp creds @adversarial.rs:86-89 (`ASIAJKLMNOPQRSTUVWXY`); quoted/spaced passwords = redact_quoted_password_with_spaces @adversarial.rs:171 (6 quoted/single-quoted/spaced cases). All three classes fully covered; no gap remains.