Archived
105 lines
3.6 KiB
Rust
105 lines
3.6 KiB
Rust
//! Security architectural rules.
|
|
|
|
use crate::error::ArchDiagnostic;
|
|
use crate::rule::{ArchRule, FnContext};
|
|
|
|
// ── SEC-001: Public function with activate inside ─────────────────────────────
|
|
|
|
/// SEC-001: A @public function must not contain activate expressions.
|
|
/// Unauthenticated callers could trigger graph reads, potentially leaking data.
|
|
pub struct PublicFnWithActivate;
|
|
|
|
impl ArchRule for PublicFnWithActivate {
|
|
fn name(&self) -> &str { "SEC-001" }
|
|
|
|
fn description(&self) -> &str {
|
|
"@public functions must not contain activate — unauthenticated callers could trigger data reads"
|
|
}
|
|
|
|
fn check(&self, ctx: &FnContext<'_>) -> Vec<ArchDiagnostic> {
|
|
if !ctx.has_annotation("public") {
|
|
return vec![];
|
|
}
|
|
if !ctx.activate_types.is_empty() {
|
|
return vec![ArchDiagnostic::error(
|
|
self.name(),
|
|
format!(
|
|
"@public function '{}' contains activate — data leak risk for unauthenticated callers (types: {})",
|
|
ctx.fn_name,
|
|
ctx.activate_types.join(", ")
|
|
),
|
|
Some(ctx.fn_name.to_string()),
|
|
)];
|
|
}
|
|
vec![]
|
|
}
|
|
}
|
|
|
|
// ── SEC-002: sealed block inside a loop ──────────────────────────────────────
|
|
|
|
/// SEC-002: A sealed block inside a loop incurs encryption overhead per iteration.
|
|
pub struct SealedInLoop;
|
|
|
|
impl ArchRule for SealedInLoop {
|
|
fn name(&self) -> &str { "SEC-002" }
|
|
|
|
fn description(&self) -> &str {
|
|
"sealed blocks inside loops cause encryption overhead on every iteration"
|
|
}
|
|
|
|
fn check(&self, ctx: &FnContext<'_>) -> Vec<ArchDiagnostic> {
|
|
if ctx.has_sealed_in_loop {
|
|
return vec![ArchDiagnostic::warning(
|
|
self.name(),
|
|
format!(
|
|
"function '{}' contains a sealed block inside a loop — encryption overhead in hot path",
|
|
ctx.fn_name
|
|
),
|
|
Some(ctx.fn_name.to_string()),
|
|
)];
|
|
}
|
|
vec![]
|
|
}
|
|
}
|
|
|
|
// ── SEC-003: @authenticate without @authorize on mutations ────────────────────
|
|
|
|
/// SEC-003: Functions whose name suggests mutation (create_*, update_*, delete_*)
|
|
/// and carry @authenticate should also carry @authorize, otherwise authn without authz.
|
|
pub struct AuthnWithoutAuthz;
|
|
|
|
impl ArchRule for AuthnWithoutAuthz {
|
|
fn name(&self) -> &str { "SEC-003" }
|
|
|
|
fn description(&self) -> &str {
|
|
"@authenticate without @authorize on mutation functions — authentication without authorization"
|
|
}
|
|
|
|
fn check(&self, ctx: &FnContext<'_>) -> Vec<ArchDiagnostic> {
|
|
if !ctx.has_annotation("authenticate") {
|
|
return vec![];
|
|
}
|
|
if ctx.has_annotation("authorize") {
|
|
return vec![];
|
|
}
|
|
// Heuristic: mutation function names
|
|
let is_mutation = ctx.fn_name.starts_with("create_")
|
|
|| ctx.fn_name.starts_with("update_")
|
|
|| ctx.fn_name.starts_with("delete_")
|
|
|| ctx.fn_name.starts_with("write_")
|
|
|| ctx.fn_name.starts_with("mutate_");
|
|
|
|
if is_mutation {
|
|
return vec![ArchDiagnostic::warning(
|
|
self.name(),
|
|
format!(
|
|
"function '{}' has @authenticate but not @authorize — authn without authz on a mutation",
|
|
ctx.fn_name
|
|
),
|
|
Some(ctx.fn_name.to_string()),
|
|
)];
|
|
}
|
|
vec![]
|
|
}
|
|
}
|