Skip to main content

skilllite_core/skill/
skill_md_security.rs

1//! SKILL.md security scanning for supply chain / agent-driven social engineering attacks.
2//! Detects patterns that may instruct users to run malicious commands (e.g. ClawHavoc-style).
3
4/// Alert for a suspicious pattern found in SKILL.md.
5#[derive(Debug, Clone, serde::Serialize)]
6pub struct SkillMdAlert {
7    pub pattern: String,
8    pub severity: String,
9    pub message: String,
10}
11
12/// Scan SKILL.md content for patterns that may indicate supply chain or agent-driven social engineering.
13/// Returns list of alerts (high + medium severity).
14pub fn scan_skill_md_suspicious_patterns(content: &str) -> Vec<SkillMdAlert> {
15    let mut alerts = Vec::new();
16    let lower = content.to_lowercase();
17
18    // High severity: direct pipe to shell
19    if lower.contains("| bash") || lower.contains("|bash") {
20        alerts.push(SkillMdAlert {
21            pattern: "| bash".to_string(),
22            severity: "high".to_string(),
23            message: "SKILL.md contains '| bash' - may instruct user to run remote script"
24                .to_string(),
25        });
26    }
27    if lower.contains("| sh") && !lower.contains("#!/bin/sh") {
28        alerts.push(SkillMdAlert {
29            pattern: "| sh".to_string(),
30            severity: "high".to_string(),
31            message: "SKILL.md contains '| sh' - may instruct user to run remote script"
32                .to_string(),
33        });
34    }
35    if lower.contains("base64 -d") || lower.contains("base64 -D") {
36        alerts.push(SkillMdAlert {
37            pattern: "base64 -d/-D".to_string(),
38            severity: "high".to_string(),
39            message: "SKILL.md contains base64 decode - common in obfuscated payload delivery"
40                .to_string(),
41        });
42    }
43
44    // High severity: pastebin / known malicious host patterns
45    if lower.contains("rentry.co") || lower.contains("pastebin.com") {
46        alerts.push(SkillMdAlert {
47            pattern: "pastebin/rentry".to_string(),
48            severity: "high".to_string(),
49            message: "SKILL.md links to pastebin/rentry - often used to host second-stage payloads"
50                .to_string(),
51        });
52    }
53
54    // Medium severity: instructions to run in terminal
55    for (pattern, msg) in [
56        (
57            "run in terminal",
58            "Instructions to run command in user terminal",
59        ),
60        (
61            "copy and paste",
62            "Instructions to copy-paste command (social engineering)",
63        ),
64        ("copy and run", "Instructions to copy and run command"),
65        ("run this command", "Direct instruction to run a command"),
66        (
67            "execute this command",
68            "Direct instruction to execute a command",
69        ),
70        ("在终端运行", "Instructions to run in terminal (Chinese)"),
71        ("复制并执行", "Instructions to copy and execute (Chinese)"),
72    ] {
73        if lower.contains(pattern) {
74            alerts.push(SkillMdAlert {
75                pattern: pattern.to_string(),
76                severity: "medium".to_string(),
77                message: format!("SKILL.md contains '{}' - {}", pattern, msg),
78            });
79        }
80    }
81
82    // Medium: curl/wget in Prerequisites/Setup context
83    let in_prereq_or_setup =
84        lower.contains("prerequisite") || lower.contains("setup") || lower.contains("install");
85    if in_prereq_or_setup && (lower.contains("curl ") || lower.contains("wget ")) {
86        alerts.push(SkillMdAlert {
87            pattern: "curl/wget in prereq".to_string(),
88            severity: "medium".to_string(),
89            message: "SKILL.md mentions curl/wget in prerequisites/setup - verify before following"
90                .to_string(),
91        });
92    }
93
94    alerts
95}
96
97/// Returns true if SKILL.md content contains high-severity suspicious patterns.
98pub fn has_skill_md_high_risk_patterns(content: &str) -> bool {
99    scan_skill_md_suspicious_patterns(content)
100        .iter()
101        .any(|a| a.severity == "high")
102}