package types // SkillFile is the parsed representation of a Claude Code skill markdown file. type SkillFile struct { Name string `yaml:"name"` Description string `yaml:"description"` Type string `yaml:"type,omitempty"` Triggers []string `yaml:"triggers,omitempty"` Body string // Markdown body after frontmatter RawContent string // Original full file content FilePath string BodyStartLine int // 1-based line number where Body begins in RawContent } // FeatureVector holds numeric features extracted from a SkillFile. type FeatureVector struct { // Structural (7 features) BodyLength float64 DescriptionLength float64 LineCount float64 CodeBlockCount float64 URLCount float64 AvgLineLength float64 ShannonEntropy float64 // Keyword presence scores (count-based per category) InjectionKeywordScore float64 ExfilKeywordScore float64 SystemManipScore float64 SocialEngineeringScore float64 // Regex pattern match counts NetworkCommandCount float64 EnvAccessCount float64 FilePathSensitiveCount float64 Base64PatternCount float64 DestructiveCommandCount float64 PrivEscalationCount float64 RoleOverrideCount float64 UrgencyLanguageCount float64 HiddenUnicodeCount float64 PackageLookalikeCount float64 PackageInstallLookalikeCount float64 PackageContextLookalikeCount float64 PackageRiskContextCount float64 SupplyChainCredentialFlowCount float64 PackageBootstrapHookCount float64 HiddenContainerCount float64 SensitiveCaptureCount float64 HeadSensitiveDirective float64 TailSensitiveDirective float64 CovertSensitiveDirective float64 HeadCovertSensitiveDirective float64 // Section-aware features (keywords in code blocks vs prose) ProseKeywordRatio float64 // suspicious keywords in prose / total suspicious keywords CodeBlockKeywordRatio float64 // suspicious keywords in code blocks / total suspicious keywords KeywordDensity float64 // suspicious keywords / total words // Bigram features (action+target vs educational patterns) AttackBigramCount float64 // "send credentials", "execute command", etc. EducationalBigramCount float64 // "prevent attack", "detect injection", etc. BigramRatio float64 // attack bigrams / (attack + educational + 1) // Verb intent features (prescriptive policy language vs imperative action commands) PrescriptiveVerbCount float64 // must/should/ensure/verify/check/enforce/validate/prevent ImperativeVerbCount float64 // send/post/gather/collect/execute/run/curl/wget VerbIntentRatio float64 // prescriptive / (prescriptive + imperative + 1) // Positional features FirstSuspiciousPosition float64 // normalized position (0-1) of first suspicious keyword SuspiciousInTail float64 // count of suspicious keywords in last 10% of document // TF-IDF features (variable length, flattened for XGBoost) TfidfFeatures []float64 } // ToSlice flattens the feature vector into a []float64 for model input. func (fv *FeatureVector) ToSlice() []float64 { base := []float64{ fv.BodyLength, fv.DescriptionLength, fv.LineCount, fv.CodeBlockCount, fv.URLCount, fv.AvgLineLength, fv.ShannonEntropy, fv.InjectionKeywordScore, fv.ExfilKeywordScore, fv.SystemManipScore, fv.SocialEngineeringScore, fv.NetworkCommandCount, fv.EnvAccessCount, fv.FilePathSensitiveCount, fv.Base64PatternCount, fv.DestructiveCommandCount, fv.PrivEscalationCount, fv.RoleOverrideCount, fv.UrgencyLanguageCount, fv.HiddenUnicodeCount, fv.PackageLookalikeCount, fv.PackageInstallLookalikeCount, fv.PackageContextLookalikeCount, fv.PackageRiskContextCount, fv.SupplyChainCredentialFlowCount, fv.PackageBootstrapHookCount, fv.HiddenContainerCount, fv.SensitiveCaptureCount, fv.HeadSensitiveDirective, fv.TailSensitiveDirective, fv.CovertSensitiveDirective, fv.HeadCovertSensitiveDirective, fv.ProseKeywordRatio, fv.CodeBlockKeywordRatio, fv.KeywordDensity, fv.AttackBigramCount, fv.EducationalBigramCount, fv.BigramRatio, fv.PrescriptiveVerbCount, fv.ImperativeVerbCount, fv.VerbIntentRatio, fv.FirstSuspiciousPosition, fv.SuspiciousInTail, } return append(base, fv.TfidfFeatures...) } // FeatureNames returns ordered names for the base features (excluding TF-IDF). func FeatureNames() []string { return []string{ "body_length", "description_length", "line_count", "code_block_count", "url_count", "avg_line_length", "shannon_entropy", "injection_keyword_score", "exfil_keyword_score", "system_manip_score", "social_engineering_score", "network_command_count", "env_access_count", "file_path_sensitive_count", "base64_pattern_count", "destructive_command_count", "priv_escalation_count", "role_override_count", "urgency_language_count", "hidden_unicode_count", "package_lookalike_count", "package_install_lookalike_count", "package_context_lookalike_count", "package_risk_context_count", "supply_chain_credential_flow_count", "package_bootstrap_hook_count", "hidden_container_count", "sensitive_capture_count", "head_sensitive_directive", "tail_sensitive_directive", "covert_sensitive_directive", "head_covert_sensitive_directive", "prose_keyword_ratio", "code_block_keyword_ratio", "keyword_density", "attack_bigram_count", "educational_bigram_count", "bigram_ratio", "prescriptive_verb_count", "imperative_verb_count", "verb_intent_ratio", "first_suspicious_position", "suspicious_in_tail", } } // RuleMatch represents a single line-level detection from a heuristic rule. type RuleMatch struct { RuleName string `json:"rule_name"` Line int `json:"line"` // 1-based line in the original file EndLine int `json:"end_line,omitempty"` // end line for multi-line matches (0 = same as Line) Text string `json:"text"` // matched text or indicator description // File is the originating sibling (RelPath) for cross-file/bundle findings. // Empty for single-file rules (omitted in JSON), so existing single-.md // output is byte-for-byte identical. Enables per-sibling suppression by // (RuleName + File) instead of only global-by-RuleName. Purely additive: // does NOT touch FeatureVector / ToSlice / FeatureNames / the frozen vector. File string `json:"file,omitempty"` } // CategoryScore represents the detection score for a single threat category. type CategoryScore struct { Category string `json:"category"` Score float64 `json:"score"` Triggered bool `json:"triggered"` Indicators []string `json:"indicators,omitempty"` RuleNames []string `json:"rule_names,omitempty"` Matches []RuleMatch `json:"matches,omitempty"` } // HeuristicResult is the output of the rule-based engine. type HeuristicResult struct { Flagged bool `json:"flagged"` Score float64 `json:"score"` Categories []CategoryScore `json:"categories"` VerbIntentRatio float64 `json:"verb_intent_ratio"` // prescriptive / (prescriptive + imperative + 1) } // Verdict is the final classification output. type Verdict struct { Label string `json:"label"` Confidence float64 `json:"confidence"` MLScore float64 `json:"ml_score"` BERTScore float64 `json:"bert_score,omitempty"` Heuristic HeuristicResult `json:"heuristic"` Categories []CategoryScore `json:"categories"` Reasons []string `json:"reasons"` }