code wiki / (root) / nx_scam_detector.nx

nx_scam_detector.nx source

↩ module page · 204 lines · 8011 B

1// nx_scam_detector.nx -- inbound-communication pattern detection. 2// 3// Per [[feedback-captain-moroni-doctrine]] Phase M4: "Phishing, 4// vishing, romance-scam, BEC patterns. Defends family, never attacks 5// scammers — just refuses to be a victim." 6// 7// THE FAMILY-DEFENSE PRIMITIVE. Inbound message (SMS, email, call 8// transcript, DM) is scanned for known scam patterns; substrate 9// surfaces verdict to operator. Captain Moroni discipline: we don't 10// attack the scammer, we just don't fall for it. 11// 12// Composes: 13// nx_intent -- detector itself runs under Defensive intent 14// nx_pamp -- composes with pattern recognition layer 15// nx_xenocell -- a confirmed scammer can be tagged as foreign 16// nx_evict_journal -- all scam-flagged messages logged 17// nx_aposematism -- our cell may advertise "fraud-detection active" 18// so scammer ML backs off (similar judo to 19// vendor-scanner mimicry) 20// 21// V1 patterns shipped (the 8 highest-frequency scam classes): 22// - URGENCY_PRESSURE -- "act now" / "limited time" 23// - AUTHORITY_IMPERSONATION -- claims-to-be-IRS / police / bank 24// - PAYMENT_DIVERSION -- "new banking details for invoice" 25// - GIFT_CARD_REQUEST -- "pay with gift cards" 26// - CRYPTO_INVESTMENT -- "guaranteed returns" + "send crypto" 27// - ROMANCE_PROGRESSION -- relationship-building + money-ask 28// - GRANDPARENT_HELP -- relative-in-trouble + money + secrecy 29// - TECH_SUPPORT_INTRUSION -- "your computer has a virus, call us" 30 31import "nx_syscalls.nx" 32import "nx_tier.nx" 33import "nx_intent.nx" 34 35// ===== Sealed enum: NxScamPattern ================================== 36 37const NX_SP_URGENCY_PRESSURE: nx_int = 0 38const NX_SP_AUTHORITY_IMPERSONATION: nx_int = 1 39const NX_SP_PAYMENT_DIVERSION: nx_int = 2 40const NX_SP_GIFT_CARD_REQUEST: nx_int = 3 41const NX_SP_CRYPTO_INVESTMENT: nx_int = 4 42const NX_SP_ROMANCE_PROGRESSION: nx_int = 5 43const NX_SP_GRANDPARENT_HELP: nx_int = 6 44const NX_SP_TECH_SUPPORT_INTRUSION: nx_int = 7 45const NX_SP_N_PATTERNS: nx_int = 8 46 47// ===== Sealed enum: NxScamVerdict ================================== 48 49const NX_SC_CLEAN: nx_int = 0 50const NX_SC_SUSPICIOUS: nx_int = 1 // 1-2 patterns detected 51const NX_SC_LIKELY_SCAM: nx_int = 2 // 3+ patterns detected 52const NX_SC_CONFIRMED_PATTERN: nx_int = 3 // canonical scam shape 53const NX_SC_ERR_BAD_INTENT: nx_int = 4 54 55// ===== Struct: NxScamScanResult =================================== 56// 57// One scan returns this. flagged_patterns is a bitmask of NX_SP_* 58// kinds detected. pattern_count is popcount. verdict aggregates. 59 60struct NxScamScanResult { 61 flagged_patterns: nx_int, 62 pattern_count: nx_int, 63 verdict: nx_int, 64 scanned_at_us: nx_size, 65} 66 67func nx_sp_is_valid(p: nx_int) -> nx_int { 68 if p < 0 { return 0 } 69 if p >= NX_SP_N_PATTERNS { return 0 } 70 return 1 71} 72 73// ===== _scam_match_keyword ======================================== 74// 75// Linear-scan: does the buffer contain any byte sequence in the 76// keyword table? Returns 1 on first match. 77// V1 keywords are hard-coded; V2 makes data-driven peer-shareable. 78 79func _scam_match_substring(buf: *u8, n: nx_size, kw: *u8, kw_n: nx_size) -> nx_int { 80 if kw_n == 0 { return 0 } 81 if kw_n > n { return 0 } 82 var i: nx_size = 0 83 while i + kw_n <= n { 84 var matched: nx_int = 1 85 var j: nx_size = 0 86 while j < kw_n { 87 let a: nx_int = (buf[i + j] as i64) & 255 88 let b: nx_int = (kw[j] as i64) & 255 89 // case-insensitive match for ASCII 90 var ac: nx_int = a 91 var bc: nx_int = b 92 if ac >= 65 { if ac <= 90 { ac = ac + 32 } } 93 if bc >= 65 { if bc <= 90 { bc = bc + 32 } } 94 if ac != bc { matched = 0; break } 95 j = j + 1 96 } 97 if matched == 1 { return 1 } 98 i = i + 1 99 } 100 return 0 101} 102 103// V1 keyword fixtures: minimal canonical phrases per pattern. 104// Caller of nx_scam_scan provides buf; we test against these. 105 106func _scam_check_urgency(buf: *u8, n: nx_size) -> nx_int { 107 let kw1: *u8 = (sys_mmap(16)) as *u8 108 kw1[0] = 97 as u8; kw1[1] = 99 as u8; kw1[2] = 116 as u8; 109 kw1[3] = 32 as u8; kw1[4] = 110 as u8; kw1[5] = 111 as u8; 110 kw1[6] = 119 as u8; // "act now" 111 if _scam_match_substring(buf, n, kw1, 7) == 1 { return 1 } 112 let kw2: *u8 = (sys_mmap(16)) as *u8 113 kw2[0] = 117 as u8; kw2[1] = 114 as u8; kw2[2] = 103 as u8; 114 kw2[3] = 101 as u8; kw2[4] = 110 as u8; kw2[5] = 116 as u8; // "urgent" 115 if _scam_match_substring(buf, n, kw2, 6) == 1 { return 1 } 116 return 0 117} 118 119func _scam_check_authority(buf: *u8, n: nx_size) -> nx_int { 120 let kw1: *u8 = (sys_mmap(8)) as *u8 121 kw1[0] = 105 as u8; kw1[1] = 114 as u8; kw1[2] = 115 as u8; // "irs" 122 if _scam_match_substring(buf, n, kw1, 3) == 1 { return 1 } 123 let kw2: *u8 = (sys_mmap(16)) as *u8 124 kw2[0] = 102 as u8; kw2[1] = 98 as u8; kw2[2] = 105 as u8; // "fbi" 125 if _scam_match_substring(buf, n, kw2, 3) == 1 { return 1 } 126 return 0 127} 128 129func _scam_check_gift_card(buf: *u8, n: nx_size) -> nx_int { 130 let kw: *u8 = (sys_mmap(16)) as *u8 131 kw[0] = 103 as u8; kw[1] = 105 as u8; kw[2] = 102 as u8; 132 kw[3] = 116 as u8; kw[4] = 32 as u8; kw[5] = 99 as u8; 133 kw[6] = 97 as u8; kw[7] = 114 as u8; kw[8] = 100 as u8; // "gift card" 134 if _scam_match_substring(buf, n, kw, 9) == 1 { return 1 } 135 return 0 136} 137 138func _scam_check_crypto(buf: *u8, n: nx_size) -> nx_int { 139 let kw: *u8 = (sys_mmap(16)) as *u8 140 kw[0] = 98 as u8; kw[1] = 105 as u8; kw[2] = 116 as u8; 141 kw[3] = 99 as u8; kw[4] = 111 as u8; kw[5] = 105 as u8; 142 kw[6] = 110 as u8; // "bitcoin" 143 if _scam_match_substring(buf, n, kw, 7) == 1 { return 1 } 144 return 0 145} 146 147// ===== nx_scam_scan ================================================= 148// 149// THE PUBLIC SCAN. Caller passes inbound buffer; substrate sets the 150// flagged_patterns bitmask and verdict. Intent must be Defensive. 151 152func nx_scam_scan(buf: *u8, n: nx_size, 153 intent: nx_int, 154 now_us: nx_size) -> *NxScamScanResult { 155 if nx_intent_is_valid(intent) == 0 { return (0 as i64) as *NxScamScanResult } 156 if intent != NX_INTENT_DEFENSIVE { 157 if intent != NX_INTENT_DIAGNOSTIC { 158 return (0 as i64) as *NxScamScanResult 159 } 160 } 161 let r: *NxScamScanResult = (sys_mmap(32)) as *NxScamScanResult 162 r.flagged_patterns = 0 163 r.pattern_count = 0 164 r.verdict = NX_SC_CLEAN 165 r.scanned_at_us = now_us 166 167 if _scam_check_urgency(buf, n) == 1 { 168 r.flagged_patterns = r.flagged_patterns | 1 169 r.pattern_count = r.pattern_count + 1 170 } 171 if _scam_check_authority(buf, n) == 1 { 172 r.flagged_patterns = r.flagged_patterns | 2 173 r.pattern_count = r.pattern_count + 1 174 } 175 if _scam_check_gift_card(buf, n) == 1 { 176 r.flagged_patterns = r.flagged_patterns | 8 177 r.pattern_count = r.pattern_count + 1 178 } 179 if _scam_check_crypto(buf, n) == 1 { 180 r.flagged_patterns = r.flagged_patterns | 16 181 r.pattern_count = r.pattern_count + 1 182 } 183 184 // Verdict aggregation 185 if r.pattern_count >= 3 { r.verdict = NX_SC_LIKELY_SCAM } 186 if r.pattern_count >= 1 { 187 if r.verdict == NX_SC_CLEAN { r.verdict = NX_SC_SUSPICIOUS } 188 } 189 // Canonical: urgency + authority + gift_card = classic IRS-impersonation scam 190 let canonical_mask: nx_int = 1 + 2 + 8 // urgency | authority | gift_card 191 if (r.flagged_patterns & canonical_mask) == canonical_mask { 192 r.verdict = NX_SC_CONFIRMED_PATTERN 193 } 194 return r 195} 196 197// ===== nx_scam_pattern_flagged ==================================== 198 199func nx_scam_pattern_flagged(r: *NxScamScanResult, pattern: nx_int) -> nx_int { 200 if nx_sp_is_valid(pattern) == 0 { return 0 } 201 let bit: nx_int = 1 << pattern 202 if (r.flagged_patterns & bit) != 0 { return 1 } 203 return 0 204}