ailint_core/rules/security/
tool_confirmation.rs1use crate::parser::{DocumentContent, ParsedDocument};
6use crate::rules::security::{line_of_offset, AIL203};
7use crate::rules::{Rule, RuleContext, RuleId, Severity, Violation};
8
9#[derive(Debug, Default)]
11pub struct ToolConfirmationRequiredRule;
12
13impl Rule for ToolConfirmationRequiredRule {
14 fn id(&self) -> RuleId {
15 AIL203
16 }
17
18 fn default_severity(&self) -> Severity {
19 Severity::Error
20 }
21
22 fn description(&self) -> &'static str {
23 "File describes destructive actions but has no confirmation constraint."
24 }
25
26 fn fix_hint(&self) -> &'static str {
27 "Require explicit confirmation (\"wait for human confirmation\") before destructive actions."
28 }
29
30 fn run(&self, doc: &ParsedDocument, ctx: &RuleContext<'_>) -> Vec<Violation> {
31 match &doc.content {
32 DocumentContent::Markdown(_) | DocumentContent::Text => {}
33 _ => return Vec::new(),
34 };
35
36 let destructive_phrases = [
37 "delete data",
38 "rm -rf",
39 "drop table",
40 "truncate table",
41 "irreversible",
42 "destructive action",
43 ];
44 let confirmation_phrases = [
45 "ask",
46 "confirm",
47 "human",
48 "wait",
49 "permission",
50 "consent",
51 "approval",
52 ];
53
54 let lower_raw = doc.raw.to_lowercase();
55
56 let mut violations = Vec::new();
57 for d in &destructive_phrases {
58 if let Some(idx) = lower_raw.find(d) {
59 if !confirmation_phrases.iter().any(|c| lower_raw.contains(c)) {
60 let line = line_of_offset(&doc.raw, idx);
61 let v = Violation::new(
62 AIL203,
63 ctx.severity,
64 doc.path.clone(),
65 "destructive action without confirmation constraint",
66 )
67 .at(line, 1)
68 .with_detail((*d).to_string());
69 violations.push(v);
70
71 break;
72 }
73 }
74 }
75
76 violations
77 }
78}