mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
+1








14fba21e57
Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> Signed-off-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Mari <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Quinn <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Dawn <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Co-authored-by: Sami <sami@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
109 lines
4.3 KiB
Rust
109 lines
4.3 KiB
Rust
use thiserror::Error;
|
|
|
|
/// Errors that can occur during audit log operations.
|
|
///
|
|
/// These are **operator-internal** diagnostics (logged by the audit worker, or
|
|
/// returned to an operator-scoped verification call) — they are never relayed to
|
|
/// a client on the wire. Even so, no variant embeds a `community_id` or any
|
|
/// cross-community object identifier: a `seq` is per-community and meaningless
|
|
/// without its chain, and hashes are opaque. An error raised while verifying
|
|
/// community A's chain therefore cannot reveal a fact about community B.
|
|
#[derive(Debug, Error)]
|
|
pub enum AuditError {
|
|
/// A database operation failed.
|
|
#[error("database error: {0}")]
|
|
Database(#[from] sqlx::Error),
|
|
|
|
/// The `prev_hash` of an entry does not match the hash of the preceding
|
|
/// entry in the same community's chain.
|
|
#[error(
|
|
"hash chain integrity violation at seq {seq}: prev_hash does not match preceding entry"
|
|
)]
|
|
ChainViolation {
|
|
/// Per-community sequence number of the offending entry.
|
|
seq: i64,
|
|
},
|
|
|
|
/// The stored hash of an entry does not match the recomputed hash.
|
|
#[error("hash mismatch at seq {seq}: stored hash does not match recomputed hash")]
|
|
HashMismatch {
|
|
/// Per-community sequence number of the offending entry.
|
|
seq: i64,
|
|
},
|
|
|
|
/// An unrecognised action string was found in the database.
|
|
#[error("unknown audit action in database")]
|
|
UnknownAction,
|
|
|
|
/// A JSON serialization error occurred (e.g. while canonicalising `detail`).
|
|
#[error("serialization error: {0}")]
|
|
Serialization(#[from] serde_json::Error),
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
/// The sanitization obligation for the conformance `audit_log` row: an error
|
|
/// raised while verifying or appending to one community's chain must not let
|
|
/// its rendered text become a cross-community identifier — no `community_id`,
|
|
/// no constraint name. Only `seq` may appear, and `seq` is per-community and
|
|
/// meaningless without the chain it indexes.
|
|
///
|
|
/// This is the *complement* to the structural fence in the variant
|
|
/// definitions above: those variants simply have no `community_id` field, so
|
|
/// there is no slot to leak one from. This test pins the observable form —
|
|
/// if anyone adds a `community_id` to a variant and threads it into the
|
|
/// `#[error(...)]` format string, the assertion below reds.
|
|
#[test]
|
|
fn audit_error_text_carries_no_community_id_or_constraint() {
|
|
// A concrete community whose chain is "being verified" when these errors
|
|
// fire. If its id leaked into any error text, the error would identify a
|
|
// specific tenant.
|
|
let community = uuid::Uuid::new_v4();
|
|
let community_str = community.to_string();
|
|
let community_simple = community.simple().to_string();
|
|
|
|
// The variants the audit crate constructs itself with chain-derived data.
|
|
let domain_errors = [
|
|
AuditError::ChainViolation { seq: 7 },
|
|
AuditError::HashMismatch { seq: 42 },
|
|
AuditError::UnknownAction,
|
|
];
|
|
|
|
for err in &domain_errors {
|
|
let text = err.to_string();
|
|
|
|
// No form of the community id may appear.
|
|
assert!(
|
|
!text.contains(&community_str) && !text.contains(&community_simple),
|
|
"audit error text leaked a community_id: {text:?}"
|
|
);
|
|
|
|
// No Postgres constraint/PK names that would reveal schema shape or
|
|
// the existence of a cross-community key.
|
|
for needle in [
|
|
"community_id",
|
|
"audit_log_pkey",
|
|
"constraint",
|
|
"communities",
|
|
] {
|
|
assert!(
|
|
!text.to_ascii_lowercase().contains(needle),
|
|
"audit error text leaked a constraint/identifier '{needle}': {text:?}"
|
|
);
|
|
}
|
|
}
|
|
|
|
// The two chain-integrity variants must still carry their per-community
|
|
// `seq` (the diagnostic is useless without it) — proves the assertion
|
|
// above isn't vacuously passing on empty strings.
|
|
assert!(AuditError::ChainViolation { seq: 7 }
|
|
.to_string()
|
|
.contains('7'));
|
|
assert!(AuditError::HashMismatch { seq: 42 }
|
|
.to_string()
|
|
.contains("42"));
|
|
}
|
|
}
|