This repository has been archived on 2026-08-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
el-retired/crates/el-arch/src/rules/security.rs
T

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![]
}
}