Archived
Merge worktree-agent: add struct literals, generics, print/log builtins
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
//! 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![]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user