diff --git a/detection-rules/ai_voice_fraud.kql b/detection-rules/ai_voice_fraud.kql new file mode 100644 index 0000000..a22c13d --- /dev/null +++ b/detection-rules/ai_voice_fraud.kql @@ -0,0 +1,151 @@ +// ============================================================ +// RetailShield — AI Voice Fraud Detection +// Rule ID : RS-VOI-001 +// MITRE ATT&CK : T1598 — Phishing for Information (Voice) +// Tactic : Reconnaissance +// Severity : High +// Frequency : Every 30 minutes | Lookback: 30 minutes +// Author : Tanvir Farhad — ShieldTech Ltd, London +// ============================================================ +// Detects AI-generated deepfake voice calls targeting retail +// finance and management staff. Covers high AI confidence score +// calls, urgent financial requests from unverified callers, +// caller ID spoofing patterns, and after-hours vishing attempts. +// Retail-specific: targets CFO/finance-role impersonation and +// urgent payment override requests unique to retail operations. +// ============================================================ + +let LookbackWindow = 30m; +let BusinessHoursStart = 7; // 07:00 UTC +let BusinessHoursEnd = 20; // 20:00 UTC +let AIConfidenceThreshold = 0.85; // AI voice deepfake confidence above this is high-risk +let HighRiskSuspicionScore = 85; + +// Roles that are high-value targets for voice fraud +let SensitiveTargetRoles = dynamic([ + "finance", "director", "manager", "cfo", "ceo", + "payment", "accounts", "payroll", "head" +]); + +// Impersonation personas used in retail voice fraud +let KnownImpersonationEntities = dynamic([ + "Area Manager", "Head Office", "CFO", "CEO", + "Finance Director", "Regional Director", "DHL", + "HMRC", "Bank", "IT Support" +]); + +// Keywords that signal urgency / bypass request — fraud indicators +let FraudKeywords = dynamic([ + "urgent", "immediate", "emergency", "override", + "bypass", "redirect", "transfer", "wire", "bacs", + "authorise", "approve now", "time sensitive", "confidential" +]); + +// ── Signal 1 — High AI confidence score on detected voice call ─────────────── +let HighConfidenceVoiceFraud = + RetailShield_Logs_CL + | where ingestion_time() > ago(LookbackWindow) + | where EventType_s == "AI_Voice_Fraud" + | where todouble(AIConfidenceScore_d) >= AIConfidenceThreshold + | project + TimeGenerated, + SignalType = "HighConfidenceVoiceFraud", + TargetEmployee = tostring(TargetEmployee_s), + AIConfidenceScore = AIConfidenceScore_d, + ImpersonatingEntity = tostring(ImpersonatingEntity_s), + RequestMade = tostring(RequestMade_s), + SuspicionScore = todouble(SuspicionScore_d); + +// ── Signal 2 — Urgent financial / bypass request on voice channel ──────────── +let UrgentFinancialRequest = + RetailShield_Logs_CL + | where ingestion_time() > ago(LookbackWindow) + | where EventType_s == "AI_Voice_Fraud" + | where tolower(tostring(RequestMade_s)) has_any (FraudKeywords) + | where tolower(tostring(TargetEmployee_s)) has_any (SensitiveTargetRoles) + or tostring(ImpersonatingEntity_s) in (KnownImpersonationEntities) + | project + TimeGenerated, + SignalType = "UrgentFinancialRequest", + TargetEmployee = tostring(TargetEmployee_s), + AIConfidenceScore = AIConfidenceScore_d, + ImpersonatingEntity = tostring(ImpersonatingEntity_s), + RequestMade = tostring(RequestMade_s), + SuspicionScore = todouble(SuspicionScore_d); + +// ── Signal 3 — Caller ID spoofing pattern detected ─────────────────────────── +let SpoofedCallerID = + RetailShield_Logs_CL + | where ingestion_time() > ago(LookbackWindow) + | where EventType_s == "AI_Voice_Fraud" + | where SuspicionScore_d >= HighRiskSuspicionScore + | where isnotempty(CallerID_s) + | where tostring(CallerID_s) matches regex @"^(\+44|0044|0)[0-9]{9,10}$" + | project + TimeGenerated, + SignalType = "SpoofedCallerID", + TargetEmployee = tostring(TargetEmployee_s), + AIConfidenceScore = AIConfidenceScore_d, + ImpersonatingEntity = tostring(ImpersonatingEntity_s), + RequestMade = tostring(RequestMade_s), + SuspicionScore = todouble(SuspicionScore_d); + +// ── Signal 4 — After-hours vishing attempt ─────────────────────────────────── +let AfterHoursVoiceFraud = + RetailShield_Logs_CL + | where ingestion_time() > ago(LookbackWindow) + | where EventType_s == "AI_Voice_Fraud" + | extend CallHour = hourofday(TimeGenerated) + | where CallHour < BusinessHoursStart or CallHour >= BusinessHoursEnd + | project + TimeGenerated, + SignalType = "AfterHoursVoiceFraud", + TargetEmployee = tostring(TargetEmployee_s), + AIConfidenceScore = AIConfidenceScore_d, + ImpersonatingEntity = tostring(ImpersonatingEntity_s), + RequestMade = tostring(RequestMade_s), + SuspicionScore = todouble(SuspicionScore_d); + +// ── Union all signals ───────────────────────────────────────────────────────── +union HighConfidenceVoiceFraud, UrgentFinancialRequest, SpoofedCallerID, AfterHoursVoiceFraud +| summarize + SignalCount = count(), + Signals = make_set(SignalType), + FirstSeen = min(TimeGenerated), + LastSeen = max(TimeGenerated), + MaxAIScore = max(todouble(AIConfidenceScore)), + MaxSuspicionScore = max(todouble(SuspicionScore)), + TargetEmployee = take_any(TargetEmployee), + ImpersonatingEntity = take_any(ImpersonatingEntity), + RequestMade = take_any(RequestMade) + by bin(TimeGenerated, LookbackWindow) +| where SignalCount >= 1 +| extend + RiskScore = case(SignalCount >= 3, 90, SignalCount == 2, 78, 65), + MitreTechnique = "T1598", + MitreTactic = "Reconnaissance", + AlertSeverity = case(SignalCount >= 3, "CRITICAL", "HIGH"), + Severity = "HIGH", + PlaybookTrigger = "notify_soc", + DeviceName = "VOIP-SYSTEM", + AlertTitle = strcat("AI Voice Fraud Detected — Target: ", TargetEmployee, + " | Impersonating: ", ImpersonatingEntity) +| project + AlertTitle, + DeviceName, + TargetEmployee, + ImpersonatingEntity, + RequestMade, + AlertSeverity, + Severity, + RiskScore, + SignalCount, + Signals, + MaxAIScore, + MaxSuspicionScore, + MitreTechnique, + MitreTactic, + PlaybookTrigger, + FirstSeen, + LastSeen +| sort by RiskScore desc diff --git a/detection-rules/pos_anomaly.kql b/detection-rules/pos_anomaly.kql new file mode 100644 index 0000000..ff52818 --- /dev/null +++ b/detection-rules/pos_anomaly.kql @@ -0,0 +1,154 @@ +// ============================================================ +// RetailShield — POS Anomaly Detection +// Rule ID : RS-POS-001 +// MITRE ATT&CK : T1056.001 — Keylogging / RAM Scraping +// Tactic : Collection +// Severity : High +// Frequency : Every 15 minutes | Lookback: 30 minutes +// Author : Tanvir Farhad — ShieldTech Ltd, London +// ============================================================ +// Detects RAM-scraping keyloggers and anomalous activity on +// retail POS terminals. Covers unknown DLL injection into POS +// processes, abnormal transaction volume spikes (statistical +// anomaly), process memory dumps, and suspicious outbound +// network connections from POS endpoints to threat IPs. +// ============================================================ + +let LookbackWindow = 30m; +let BaselineWindow = 30d; +let TransactionVolumeThreshold = 3.5; // std deviations above 30-day baseline + +// POS application process names +let POSProcessNames = dynamic([ + "xstore.exe", "aloha.exe", "tcxpos.exe", + "posready.exe", "retail.exe", "revel.exe", "squarepos.exe" +]); + +// Hostname prefixes that identify POS / till hardware +let RetailTerminalPrefix = dynamic([ + "pos-", "till-", "kiosk-", "ped-", "term-" +]); + +// Folders that are trusted for DLL loads — flag anything outside +let TrustedDLLPaths = dynamic([ + @"C:\Windows\System32", + @"C:\Windows\SysWOW64", + @"C:\Program Files", + @"C:\Program Files (x86)" +]); + +// ── Signal 1 — Unknown unsigned DLL injected into POS process ──────────────── +let UnknownDLLInjection = + DeviceEvents + | where ingestion_time() > ago(LookbackWindow) + | where ActionType == "ImageLoaded" + | where InitiatingProcessFileName has_any (POSProcessNames) + | where FileName endswith ".dll" or FileName endswith ".sys" + | where isempty(SHA1) or Signer == "" or isempty(Signer) + | where not(FolderPath has_any (TrustedDLLPaths)) + | extend DeviceLower = tolower(DeviceName) + | where DeviceLower has_any (RetailTerminalPrefix) + | project + TimeGenerated, DeviceName, DeviceId, + SignalType = "UnknownDLLInjection", + POSProcess = InitiatingProcessFileName, + SuspiciousDLL = FileName, + DLLPath = FolderPath, + SHA256 = InitiatingProcessSHA256; + +// ── Signal 2 — Abnormal transaction volume (statistical spike) ─────────────── +let AbnormalTransactionVolume = + RetailShield_Logs_CL + | where ingestion_time() > ago(BaselineWindow) + | where EventType_s == "POS_Transaction" + | summarize HourlyCount = count() by TerminalID_s, bin(TimeGenerated, 1h) + | summarize AvgCount = avg(HourlyCount), StdDev = stdev(HourlyCount) + by TerminalID_s + | join kind=inner ( + RetailShield_Logs_CL + | where ingestion_time() > ago(LookbackWindow) + | where EventType_s == "POS_Transaction" + | summarize RecentCount = count() by TerminalID_s + ) on TerminalID_s + | where RecentCount > AvgCount + (TransactionVolumeThreshold * StdDev) + | extend AnomalyScore = round((RecentCount - AvgCount) / StdDev, 2) + | project + TimeGenerated = now(), DeviceName = TerminalID_s, DeviceId = "", + SignalType = "AbnormalTransactionVolume", + POSProcess = "POS_Transaction", + SuspiciousDLL = "", + DLLPath = "", + SHA256 = ""; + +// ── Signal 3 — Process memory dump targeting a POS application ─────────────── +let ProcessMemoryDump = + DeviceEvents + | where ingestion_time() > ago(LookbackWindow) + | where ActionType in ("ProcessDumped", "MemoryDumpCreated") + or (ActionType == "ProcessAccessed" + and ProcessCommandLine has_any ("MiniDump", "ProcDump", "createdump", "procdump")) + | where tolower(TargetProcessFileName) has_any (POSProcessNames) + | project + TimeGenerated, DeviceName, DeviceId, + SignalType = "ProcessMemoryDump", + POSProcess = TargetProcessFileName, + SuspiciousDLL = InitiatingProcessFileName, + DLLPath = ProcessCommandLine, + SHA256 = InitiatingProcessSHA256; + +// ── Signal 4 — Suspicious outbound network from POS to threat IP ───────────── +let SuspiciousNetworkFromPOS = + DeviceNetworkEvents + | where ingestion_time() > ago(LookbackWindow) + | where tolower(DeviceName) has_any (RetailTerminalPrefix) + | where RemoteIPType == "Public" + | where not(RemotePort in (443, 80, 8443, 8080)) + | join kind=inner ( + _GetWatchlist("RetailIOCWatchlist") + | project ThreatIP = tostring(column_ifexists("SearchKey", "")) + ) on $left.RemoteIP == $right.ThreatIP + | project + TimeGenerated, DeviceName, DeviceId, + SignalType = "SuspiciousNetworkFromPOS", + POSProcess = InitiatingProcessFileName, + SuspiciousDLL = RemoteIP, + DLLPath = tostring(RemotePort), + SHA256 = ""; + +// ── Union all signals ───────────────────────────────────────────────────────── +union UnknownDLLInjection, AbnormalTransactionVolume, ProcessMemoryDump, SuspiciousNetworkFromPOS +| summarize + SignalCount = count(), + Signals = make_set(SignalType), + FirstSeen = min(TimeGenerated), + LastSeen = max(TimeGenerated), + SuspiciousDLL = take_any(SuspiciousDLL), + POSProcess = take_any(POSProcess), + DeviceId = take_any(DeviceId) + by DeviceName +| where SignalCount >= 1 +| extend + RiskScore = case(SignalCount >= 3, 95, SignalCount == 2, 82, 65), + MitreTechnique = "T1056.001", + MitreTactic = "Collection", + Severity = case(SignalCount >= 2, "HIGH", "HIGH"), + AlertSeverity = case(SignalCount >= 3, "CRITICAL", "HIGH"), + PlaybookTrigger = "suspend_terminal", + AlertTitle = strcat("POS Anomaly — Potential RAM Scraping on ", DeviceName) +| project + AlertTitle, + DeviceName, + DeviceId, + AlertSeverity, + Severity, + RiskScore, + SignalCount, + Signals, + SuspiciousDLL, + POSProcess, + MitreTechnique, + MitreTactic, + PlaybookTrigger, + FirstSeen, + LastSeen +| sort by RiskScore desc diff --git a/detection-rules/supply_chain_anomaly.kql b/detection-rules/supply_chain_anomaly.kql new file mode 100644 index 0000000..2bf4f47 --- /dev/null +++ b/detection-rules/supply_chain_anomaly.kql @@ -0,0 +1,158 @@ +// ============================================================ +// RetailShield — Supply Chain Anomaly Detection +// Rule ID : RS-SUP-001 +// MITRE ATT&CK : T1195 — Supply Chain Compromise +// Tactic : Initial Access +// Severity : High +// Frequency : Every 30 minutes | Lookback: 30 minutes +// Author : Tanvir Farhad — ShieldTech Ltd, London +// ============================================================ +// Detects compromised or malicious supplier API behaviour +// against the retail platform. Covers supplier keys accessing +// admin endpoints, new service principals created by supplier +// accounts, out-of-spec endpoint access, and mass data export. +// Retail-specific: tracks supplier_api_key patterns tied to +// logistics, payment, and stock management vendors. +// ============================================================ + +let LookbackWindow = 30m; +let BulkExportThreshold = 1000; // records exported above this is suspicious +let MinAnomalousRequests = 3; // minimum out-of-spec requests before alert + +// Admin / privileged endpoints suppliers must never call +let AdminEndpoints = dynamic([ + "/admin", "/management", "/config", "/users", + "/audit", "/security", "/settings", "/permissions" +]); + +// Sensitive data endpoints — acceptable only for authorised internal accounts +let SensitiveEndpoints = dynamic([ + "/customers", "/export", "/payment", "/finance", + "/employees", "/cardholder", "/transactions" +]); + +// Endpoints that are part of the agreed supplier integration spec +let AgreeableEndpoints = dynamic([ + "/inventory", "/stock", "/orders", "/delivery", + "/products", "/shipment", "/tracking", "/catalogue" +]); + +// ── Signal 1 — Supplier API key accessing admin / privileged endpoints ──────── +let SupplierAdminAccess = + AzureDiagnostics + | where ingestion_time() > ago(LookbackWindow) + | where ResourceType == "APIMANAGEMENT/SERVICE" + | extend APIKey = tostring(column_ifexists("apimSubscriptionId_s", "")) + | extend Endpoint = tostring(column_ifexists("requestPath_s", "")) + | where APIKey startswith "supplier_api_key" + | where Endpoint has_any (AdminEndpoints) + | project + TimeGenerated, + SignalType = "SupplierAdminAccess", + SupplierKey = APIKey, + Endpoint, + Method = tostring(column_ifexists("requestMethod_s", "")), + StatusCode = tostring(column_ifexists("responseCode_d", "")); + +// ── Signal 2 — New Azure AD service principal created by supplier account ────── +let NewServicePrincipal = + AuditLogs + | where ingestion_time() > ago(LookbackWindow) + | where OperationName has_any ("Add service principal", "Add application") + | extend InitiatedBy = tostring(InitiatedBy.user.userPrincipalName) + | where InitiatedBy has_any ("supplier", "vendor", "partner", "api", "3rd-party") + or InitiatedBy matches regex @"svc_[a-z0-9]+@" + | extend NewPrincipal = tostring(TargetResources[0].displayName) + | project + TimeGenerated, + SignalType = "NewServicePrincipal", + SupplierKey = InitiatedBy, + Endpoint = NewPrincipal, + Method = CorrelationId, + StatusCode = ""; + +// ── Signal 3 — Supplier accessing endpoints outside agreed integration spec ──── +let UnauthorisedEndpointAccess = + AzureDiagnostics + | where ingestion_time() > ago(LookbackWindow) + | where ResourceType == "APIMANAGEMENT/SERVICE" + | extend APIKey = tostring(column_ifexists("apimSubscriptionId_s", "")) + | extend Endpoint = tostring(column_ifexists("requestPath_s", "")) + | where APIKey startswith "supplier_api_key" + | where not(Endpoint has_any (AgreeableEndpoints)) + | where Endpoint has_any (SensitiveEndpoints) + | summarize + AccessCount = count(), + Endpoints = make_set(Endpoint), + FirstSeen = min(TimeGenerated), + LastSeen = max(TimeGenerated) + by APIKey + | where AccessCount >= MinAnomalousRequests + | project + TimeGenerated = FirstSeen, + SignalType = "UnauthorisedEndpointAccess", + SupplierKey = APIKey, + Endpoint = tostring(Endpoints), + Method = "", + StatusCode = tostring(AccessCount); + +// ── Signal 4 — Mass data export by supplier API key ────────────────────────── +let MassDataExport = + AzureDiagnostics + | where ingestion_time() > ago(LookbackWindow) + | where ResourceType == "APIMANAGEMENT/SERVICE" + | extend APIKey = tostring(column_ifexists("apimSubscriptionId_s", "")) + | extend Endpoint = tostring(column_ifexists("requestPath_s", "")) + | extend RecordsServed = toint(column_ifexists("responseBodyLength_d", 0)) + | where APIKey startswith "supplier_api_key" + | where Endpoint has_any (SensitiveEndpoints) + | summarize + TotalRecords = sum(RecordsServed), + RequestCount = count(), + Endpoints = make_set(Endpoint) + by APIKey, bin(TimeGenerated, LookbackWindow) + | where TotalRecords >= BulkExportThreshold + | project + TimeGenerated, + SignalType = "MassDataExport", + SupplierKey = APIKey, + Endpoint = tostring(Endpoints), + Method = "", + StatusCode = tostring(TotalRecords); + +// ── Union all signals ───────────────────────────────────────────────────────── +union SupplierAdminAccess, NewServicePrincipal, UnauthorisedEndpointAccess, MassDataExport +| summarize + SignalCount = count(), + Signals = make_set(SignalType), + FirstSeen = min(TimeGenerated), + LastSeen = max(TimeGenerated), + SupplierKey = take_any(SupplierKey), + Endpoints = make_set(Endpoint) + by bin(TimeGenerated, LookbackWindow) +| where SignalCount >= 1 +| extend + RiskScore = case(SignalCount >= 3, 90, SignalCount == 2, 78, 60), + MitreTechnique = "T1195", + MitreTactic = "Initial Access", + AlertSeverity = case(SignalCount >= 3, "CRITICAL", SignalCount == 2, "HIGH", "MEDIUM"), + Severity = case(SignalCount >= 2, "HIGH", "MEDIUM"), + PlaybookTrigger = "notify_soc", + DeviceName = "API-GATEWAY", + AlertTitle = strcat("Supply Chain Anomaly — Supplier Behavioural Deviation: ", SupplierKey) +| project + AlertTitle, + DeviceName, + SupplierKey, + AlertSeverity, + Severity, + RiskScore, + SignalCount, + Signals, + Endpoints, + MitreTechnique, + MitreTactic, + PlaybookTrigger, + FirstSeen, + LastSeen +| sort by RiskScore desc diff --git a/docs/mitre-mapping.md b/docs/mitre-mapping.md index 494c71b..dcfc86c 100644 --- a/docs/mitre-mapping.md +++ b/docs/mitre-mapping.md @@ -5,10 +5,62 @@ Full cross-reference of RetailShield detection rules against MITRE ATT&CK Enterp | Status | Tactic | Technique ID | Technique Name | Rule File | Severity | Playbook Trigger | |---|---|---|---|---|---|---| | ✅ Done | Initial Access | T1566.001 | Spearphishing Attachment | `phishing_detection.kql` | High | `quarantine_email` | -| 🔲 Planned | Collection | T1056.001 | Keylogging (POS) | `pos_anomaly.kql` | High | `suspend_terminal` | -| 🔲 Planned | Persistence | T1078 | Valid Accounts | `after_hours_access.kql` | Medium | `notify-soc` | -| 🔲 Planned | Credential Access | T1110.004 | Credential Stuffing | `credential_stuffing.kql` | High | `block_ip` | -| 🔲 Planned | Exfiltration | T1048 | Exfiltration Over Alt. Protocol | `data_exfiltration.kql` | Critical | `isolate_endpoint` | +| ✅ Done | Collection | T1056.001 | Keylogging / RAM Scraping | `pos_anomaly.kql` | High | `suspend_terminal` | +| ✅ Done | Persistence | T1078 | Valid Accounts (After-Hours) | `after_hours_access.kql` | Medium | `notify_soc` | +| ✅ Done | Credential Access | T1110.004 | Credential Stuffing | `credential_stuffing.kql` | High | `block_ip` | +| ✅ Done | Exfiltration | T1048 | Exfiltration Over Alt. Protocol | `data_exfiltration.kql` | Critical | `data_exfil_contain` | | ✅ Done | Impact | T1486 | Data Encrypted for Impact | `ransomware_indicator.kql` | Critical | `isolate_endpoint` | -| 🔲 Planned | Reconnaissance | T1598 | Phishing for Information | `ai_voice_fraud.kql` | High | `notify-soc` | -| 🔲 Planned | Initial Access | T1195 | Supply Chain Compromise | `supply_chain_anomaly.kql` | High | `block_ip` | +| ✅ Done | Reconnaissance | T1598 | Phishing for Information (Voice) | `ai_voice_fraud.kql` | High | `notify_soc` | +| ✅ Done | Initial Access | T1195 | Supply Chain Compromise | `supply_chain_anomaly.kql` | High | `notify_soc` | + +--- + +## Coverage Summary + +| Category | Count | Status | +|---|---|---| +| Techniques Monitored | 8 / 8 | ✅ Full Coverage | +| Rules Implemented | 8 | ✅ Complete | +| Playbooks Wired | 4 | `quarantine_email`, `block_ip`, `isolate_endpoint`, `notify_soc` | +| MITRE Tactics Covered | 6 | Initial Access, Persistence, Credential Access, Collection, Exfiltration, Impact | + +--- + +## Retail-Specific Threat Mapping + +| Retail Threat | MITRE Technique | RetailShield Rule | Priority | +|---|---|---|---| +| Phishing targeting finance/HR staff | T1566.001 | `phishing_detection.kql` | Critical | +| POS RAM scraping / keylogger | T1056.001 | `pos_anomaly.kql` | Critical | +| Service account misuse (off-hours) | T1078 | `after_hours_access.kql` | High | +| Distributed credential stuffing | T1110.004 | `credential_stuffing.kql` | High | +| DNS exfiltration from ERP/DB servers | T1048 | `data_exfiltration.kql` | Critical | +| Ransomware staging on POS network | T1486 | `ransomware_indicator.kql` | Critical | +| AI deepfake voice fraud (CFO fraud) | T1598 | `ai_voice_fraud.kql` | High | +| Compromised supplier API access | T1195 | `supply_chain_anomaly.kql` | High | + +--- + +## PCI DSS v4.0 Alignment + +| PCI DSS Requirement | Description | Covered By | Status | +|---|---|---|---| +| Req 1 | Network security controls | `after_hours_access.kql`, `supply_chain_anomaly.kql` | ✅ Covered | +| Req 2 | Secure configurations | CVE Scanner (`cve_scanner.py`) | ✅ Covered | +| Req 3 | Protect stored cardholder data | `data_exfiltration.kql`, `pos_anomaly.kql` | ✅ Covered | +| Req 4 | Cryptography in transit | CVE Scanner (Verifone TLS CVEs) | ✅ Covered | +| Req 5 | Anti-malware | `ransomware_indicator.kql` | ✅ Covered | +| Req 6 | Secure software development | CVE Scanner (patching status) | ✅ Covered | +| Req 7 | Need-to-know access control | `after_hours_access.kql` | 🔶 Partial | +| Req 8 | Identity and authentication | `credential_stuffing.kql` | ✅ Covered | +| Req 9 | Physical access controls | `pos_anomaly.kql` (terminal tampering) | 🔶 Partial | +| Req 10 | Logging and monitoring | All 8 KQL rules | ✅ Covered | +| Req 11 | Security testing | CVE Scanner (`cve_scanner.py`) | ✅ Covered | +| Req 12 | Organisational security policy | 🔲 Planned (v3.0) | 🔲 Planned | + +--- + +## Author + +**Tanvir Farhad** — ShieldTech Ltd, London +[github.com/TFT444/RetailShield](https://github.com/TFT444/RetailShield) diff --git a/frontend/api/brief.js b/frontend/api/brief.js new file mode 100644 index 0000000..a026449 --- /dev/null +++ b/frontend/api/brief.js @@ -0,0 +1,101 @@ +/** + * RetailShield — AI Threat Briefing Endpoint + * Vercel Serverless Function — POST /api/brief + * Calls the Anthropic Claude API to generate an executive-ready + * threat summary from the current dashboard state. + * + * Requires ANTHROPIC_API_KEY env var set in Vercel project settings. + * Falls back to a simulated briefing when the key is absent. + */ + +const FALLBACK_BRIEFING = (threats, score, vulnCount) => ` +RETAILSHIELD EXECUTIVE THREAT BRIEFING — ${new Date().toUTCString()} + +Security Score: ${score}/100 | Active Threats: ${threats.critical} Critical, ${threats.high} High | CVEs Outstanding: ${vulnCount} + +IMMEDIATE ACTIONS REQUIRED: +${threats.critical > 0 ? `• ${threats.critical} CRITICAL threat(s) detected — incident response team should be engaged immediately.` : ''} +${threats.active > 0 ? `• ${threats.active} active threat(s) currently uncontained — review live feed for isolation status.` : ''} +${vulnCount > 0 ? `• ${vulnCount} unpatched CVEs identified across retail infrastructure — prioritise critical severity patches within 48 hours.` : ''} + +Automated playbooks have been triggered for all active incidents. Manual analyst review required for items marked ACTIVE in the threat feed. + +This briefing was generated by RetailShield v2.0 — ShieldTech Ltd. +`.trim() + +export default async function handler(req, res) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }) + } + + const { threats = {}, score = 0, vulnSummary = {}, topThreats = [] } = req.body || {} + + const apiKey = process.env.ANTHROPIC_API_KEY + if (!apiKey) { + return res.status(200).json({ + briefing: FALLBACK_BRIEFING(threats, score, Object.values(vulnSummary).reduce((a, b) => a + b, 0)), + source: 'fallback', + }) + } + + const threatLines = topThreats.slice(0, 5).map( + (t, i) => `${i + 1}. ${t.name} (${t.mitre} — ${t.tactic}, ${t.severity.toUpperCase()}, Status: ${t.status})` + ).join('\n') + + const vulnTotal = Object.values(vulnSummary).reduce((a, b) => a + b, 0) + + const prompt = `You are a senior SOC analyst writing a concise executive briefing for a UK retail CISO. + +Current RetailShield dashboard state: +- Security Score: ${score}/100 +- Active Threats: ${threats.critical || 0} Critical, ${threats.high || 0} High, ${threats.active || 0} Active, ${threats.blocked || 0} Blocked +- CVE Vulnerability Summary: ${vulnSummary.critical || 0} Critical, ${vulnSummary.high || 0} High, ${vulnSummary.medium || 0} Medium +- Total unpatched CVEs: ${vulnTotal} + +Top active threats: +${threatLines || 'No active threats at this time.'} + +Write a professional executive briefing in exactly this format: +1. One sentence overall status (use plain English, not jargon) +2. Top 2-3 priority actions the business must take RIGHT NOW +3. Regulatory exposure (PCI DSS, UK GDPR Art.33 if relevant) +4. One closing sentence on what automated playbooks have already done + +Keep it under 200 words. Be direct and authoritative. Do not use bullet points — use numbered lists only for the priority actions. Do not start with "I" or "As an AI".` + + try { + const response = await fetch('https://api.anthropic.com/v1/messages', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': apiKey, + 'anthropic-version': '2023-06-01', + }, + body: JSON.stringify({ + model: 'claude-sonnet-4-6', + max_tokens: 400, + messages: [{ role: 'user', content: prompt }], + }), + }) + + if (!response.ok) { + const err = await response.text() + console.error('Anthropic API error:', err) + return res.status(200).json({ + briefing: FALLBACK_BRIEFING(threats, score, vulnTotal), + source: 'fallback', + }) + } + + const data = await response.json() + const briefing = data.content?.[0]?.text || FALLBACK_BRIEFING(threats, score, vulnTotal) + + return res.status(200).json({ briefing, source: 'claude' }) + } catch (err) { + console.error('Brief generation error:', err) + return res.status(200).json({ + briefing: FALLBACK_BRIEFING(threats, score, vulnTotal), + source: 'fallback', + }) + } +} diff --git a/frontend/src/RetailShield.jsx b/frontend/src/RetailShield.jsx index b6f31ad..3448ce4 100644 --- a/frontend/src/RetailShield.jsx +++ b/frontend/src/RetailShield.jsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from 'react' -// ── Design tokens ───────────────────────────────────────────────────────────── +// ── Design tokens ─────────────────────────────────────────────────────────────────────────── const C = { bg: '#080808', surface: '#0f0f0f', @@ -22,7 +22,7 @@ const C = { const SEV = { critical: C.red, high: C.orange, medium: C.yellow, low: C.blue } const STA = { active: C.red, blocked: C.green, pending: C.orange } -// ── Threat data ─────────────────────────────────────────────────────────────── +// ── Threat data ─────────────────────────────────────────────────────────────────────────── const BASE_THREATS = [ { id: 1, name: 'Phishing Email — Finance Team', mitre: 'T1566.001', @@ -214,7 +214,6 @@ const BASE_THREATS = [ }, ] -// New threats injected during live simulation const LIVE_THREATS = [ { name: 'Brute Force — Admin Console', mitre: 'T1110.001', @@ -246,7 +245,6 @@ const LIVE_THREATS = [ }, ] -// ── Attack simulation events (5 threats, one per MITRE technique) ──────────── const ATTACK_SIM_EVENTS = [ { name: 'Spearphishing — CFO Impersonation [SIM]', mitre: 'T1566.001', @@ -255,20 +253,8 @@ const ATTACK_SIM_EVENTS = [ desc: '[SIMULATION] Macro-enabled .docm attachment delivered to CFO inbox from invoice-urgent@dr4gonm4il.com. SHA256 matches RetailIOCWatchlist entry added 6 minutes ago.', playbook: { trigger: 'quarantine_email', - auto: [ - 'Email quarantined across all mailboxes via Defender for O365', - 'Sender domain dr4gonm4il.com added to tenant block list', - 'SHA256 hash pushed to RetailIOCWatchlist', - 'CFO and EA notified via Teams', - 'Incident ticket raised: #SIM-001', - ], - manual: [ - 'Confirm CFO did not open attachment before quarantine', - 'Check for related campaign in last 72h mail gateway logs', - 'Review sender domain registration — likely <7 days old', - 'Escalate to CISO if attachment was executed', - 'Submit to Microsoft MSRC threat intelligence', - ], + auto: ['Email quarantined across all mailboxes via Defender for O365','Sender domain dr4gonm4il.com added to tenant block list','SHA256 hash pushed to RetailIOCWatchlist','CFO and EA notified via Teams','Incident ticket raised: #SIM-001'], + manual: ['Confirm CFO did not open attachment before quarantine','Check for related campaign in last 72h mail gateway logs','Review sender domain registration — likely <7 days old','Escalate to CISO if attachment was executed','Submit to Microsoft MSRC threat intelligence'], }, }, { @@ -278,20 +264,8 @@ const ATTACK_SIM_EVENTS = [ desc: '[SIMULATION] 1,247 failed logins across 31 accounts from 89 distinct IPs in 3 minutes. Credential pairs match leaked Retail Sector dump (Collection #7, 2025). 4 accounts compromised.', playbook: { trigger: 'block_ip', - auto: [ - '89 source IPs added to Azure WAF deny rules', - 'CAPTCHA enforcement enabled on /pos-admin/login', - '4 compromised accounts locked and password reset triggered', - 'Rate limiting set to 3 attempts / 5 min per account', - 'Indicators submitted to AbuseIPDB', - ], - manual: [ - 'Force reset all 31 targeted accounts preventatively', - 'Check if compromised accounts accessed transaction data', - 'Assess PCI DSS notification requirement', - 'Tune WAF rules for similar User-Agent rotation patterns', - 'Monitor for continued campaign over next 48h', - ], + auto: ['89 source IPs added to Azure WAF deny rules','CAPTCHA enforcement enabled on /pos-admin/login','4 compromised accounts locked and password reset triggered','Rate limiting set to 3 attempts / 5 min per account','Indicators submitted to AbuseIPDB'], + manual: ['Force reset all 31 targeted accounts preventatively','Check if compromised accounts accessed transaction data','Assess PCI DSS notification requirement','Tune WAF rules for similar User-Agent rotation patterns','Monitor for continued campaign over next 48h'], }, }, { @@ -301,20 +275,8 @@ const ATTACK_SIM_EVENTS = [ desc: '[SIMULATION] 847 files renamed with .dragon3 extension in 4 minutes. vssadmin delete shadows confirmed. C2 beacon to 91.234.55.12 (DragonForce infrastructure). Endpoint being isolated.', playbook: { trigger: 'isolate_endpoint', - auto: [ - 'WORKSTATION-SIM-07 isolated via Defender for Endpoint API', - 'Network access revoked at Azure Firewall', - 'C2 IP 91.234.55.12 added to block list across all firewalls', - 'Forensic memory snapshot and disk image initiated', - 'P1 incident raised — on-call SOC paged via PagerDuty', - ], - manual: [ - 'Confirm no lateral movement to adjacent workstations', - 'Scope shadow copy destruction across all network shares', - 'Engage IR retainer if spread confirmed to >3 hosts', - 'Start GDPR Art.33 72h notification clock', - 'Prepare operational comms for store management', - ], + auto: ['WORKSTATION-SIM-07 isolated via Defender for Endpoint API','Network access revoked at Azure Firewall','C2 IP 91.234.55.12 added to block list across all firewalls','Forensic memory snapshot and disk image initiated','P1 incident raised — on-call SOC paged via PagerDuty'], + manual: ['Confirm no lateral movement to adjacent workstations','Scope shadow copy destruction across all network shares','Engage IR retainer if spread confirmed to >3 hosts','Start GDPR Art.33 72h notification clock','Prepare operational comms for store management'], }, }, { @@ -324,20 +286,8 @@ const ATTACK_SIM_EVENTS = [ desc: '[SIMULATION] 3,847 DNS queries to exfil.d4t4pipe[.]xyz with 64-char base64 subdomains over 12 minutes. 2.1 GB staged from \\\\DB-SERVER\\customers\\. DNS blocked at resolver.', playbook: { trigger: 'data_exfil_contain', - auto: [ - 'DNS to d4t4pipe[.]xyz blocked at Sentinel-managed resolver', - 'Domain added to RetailIOCWatchlist and sinkholes', - 'db_svc_account password reset and sessions revoked', - 'Network flow evidence preserved in Log Analytics', - 'DLP alert raised for \\customers\\ folder bulk read', - ], - manual: [ - 'Determine initial access vector to DB-SERVER', - 'Assess what customer PII was staged — GDPR impact assessment', - 'Engage Data Protection Officer immediately', - 'Determine if exfiltration completed before DNS block', - 'Audit db_svc_account activity for prior 72h', - ], + auto: ['DNS to d4t4pipe[.]xyz blocked at Sentinel-managed resolver','Domain added to RetailIOCWatchlist and sinkholes','db_svc_account password reset and sessions revoked','Network flow evidence preserved in Log Analytics','DLP alert raised for \\customers\\ folder bulk read'], + manual: ['Determine initial access vector to DB-SERVER','Assess what customer PII was staged — GDPR impact assessment','Engage Data Protection Officer immediately','Determine if exfiltration completed before DNS block','Audit db_svc_account activity for prior 72h'], }, }, { @@ -347,19 +297,8 @@ const ATTACK_SIM_EVENTS = [ desc: '[SIMULATION] £125,000 BACS transfer request via AI voice call impersonating Regional MD. Caller asked to bypass dual-approval. AI confidence score 0.97. Payment blocked by finance team.', playbook: { trigger: 'notify_soc', - auto: [ - 'Call recording preserved under legal hold', - 'Spoofed number added to VOIP block list', - 'Finance team fraud awareness alert issued', - 'Report filed with Action Fraud (ref: SIM-NCSC-2026)', - ], - manual: [ - 'Verify with Regional MD via known direct mobile', - 'Issue mandatory voice fraud refresher to finance team', - 'Confirm dual-approval protocol was followed correctly', - 'Submit deepfake audio to NCSC AI threat analysis team', - 'Update payment policy: add video call re-confirmation step', - ], + auto: ['Call recording preserved under legal hold','Spoofed number added to VOIP block list','Finance team fraud awareness alert issued','Report filed with Action Fraud (ref: SIM-NCSC-2026)'], + manual: ['Verify with Regional MD via known direct mobile','Issue mandatory voice fraud refresher to finance team','Confirm dual-approval protocol was followed correctly','Submit deepfake audio to NCSC AI threat analysis team','Update payment policy: add video call re-confirmation step'], }, }, { @@ -369,19 +308,8 @@ const ATTACK_SIM_EVENTS = [ desc: '[SIMULATION] Interactive RDP login to domain controller at 02:58 UTC from 185.220.101.47 (Tor exit node). Service account svc_backup_sim — interactive sessions are outside baseline.', playbook: { trigger: 'notify_soc', - auto: [ - 'svc_backup_sim flagged for elevated monitoring', - 'Risky sign-in logged in Azure AD Identity Protection', - 'SOC Teams channel alerted with full session context', - 'Session commands captured to forensic audit trail', - ], - manual: [ - 'Verify whether svc_backup_sim ever logs in interactively', - 'Cross-reference IP against Tor exit node list', - 'Review all AD changes made during this session', - 'Disable account immediately if compromise confirmed', - 'Audit all accounts from same /24 IP range', - ], + auto: ['svc_backup_sim flagged for elevated monitoring','Risky sign-in logged in Azure AD Identity Protection','SOC Teams channel alerted with full session context','Session commands captured to forensic audit trail'], + manual: ['Verify whether svc_backup_sim ever logs in interactively','Cross-reference IP against Tor exit node list','Review all AD changes made during this session','Disable account immediately if compromise confirmed','Audit all accounts from same /24 IP range'], }, }, { @@ -391,24 +319,84 @@ const ATTACK_SIM_EVENTS = [ desc: '[SIMULATION] Unknown unsigned DLL (xf99ab.dll) injected into POS process on TILL-09. Transaction volume 5.1σ above 30-day baseline. RAM scraping pattern consistent with known POS malware.', playbook: { trigger: 'suspend_terminal', - auto: [ - 'POS-SIM-TILL-09 suspended via retail management API', - 'Store manager notified via Teams', - 'Memory dump initiated for DLL forensic analysis', - 'ServiceNow ticket raised with PCI DSS flag', - ], - manual: [ - 'Physically inspect TILL-09 for hardware skimmer', - 'Pull network capture from last 4 hours on switch port', - 'Identify origin of xf99ab.dll via software deployment logs', - 'Assess payment card data exposure window for PCI notification', - 'Re-image terminal before returning to service', - ], + auto: ['POS-SIM-TILL-09 suspended via retail management API','Store manager notified via Teams','Memory dump initiated for DLL forensic analysis','ServiceNow ticket raised with PCI DSS flag'], + manual: ['Physically inspect TILL-09 for hardware skimmer','Pull network capture from last 4 hours on switch port','Identify origin of xf99ab.dll via software deployment logs','Assess payment card data exposure window for PCI notification','Re-image terminal before returning to service'], }, }, ] -// ── SVG Security Posture Gauge ──────────────────────────────────────────────── +const VULN_INITIAL = { + lastScan: '2026-05-28T06:00:00Z', + totalAssets: 18, + summary: { critical: 8, high: 22, medium: 4, low: 0 }, + findings: [ + { id:'POS-TILL-01', product:'Oracle Xstore POS', version:'8.1', location:'Hounslow Branch', cat:'POS System', vulns:[ + { cve:'CVE-2025-44123', cvss:9.8, sev:'critical', title:'Unauthenticated RCE via Java deserialization in config sync', patch:true, exploit:true, mitre:'T1190' }, + { cve:'CVE-2025-31847', cvss:8.8, sev:'high', title:'SQL injection in transaction reporting module', patch:true, exploit:false, mitre:'T1190' }, + ]}, + { id:'POS-TILL-03', product:'NCR Aloha POS', version:'12.3', location:'Hammersmith Branch', cat:'POS System', vulns:[ + { cve:'CVE-2025-19284', cvss:9.1, sev:'critical', title:'Authentication bypass via malformed session token', patch:true, exploit:true, mitre:'T1078' }, + { cve:'CVE-2024-52891', cvss:7.8, sev:'high', title:'Local privilege escalation via insecure service dir', patch:true, exploit:false, mitre:'T1068' }, + ]}, + { id:'POS-TILL-05', product:'Toshiba TCx POS', version:'5.2', location:'Ealing Branch', cat:'POS System', vulns:[ + { cve:'CVE-2025-08374', cvss:8.1, sev:'high', title:'Stack buffer overflow in EMV payment parsing library', patch:true, exploit:false, mitre:'T1203' }, + { cve:'CVE-2024-47203', cvss:6.5, sev:'medium', title:'Plaintext credentials in world-readable config files', patch:true, exploit:false, mitre:'T1083' }, + ]}, + { id:'POS-TILL-06', product:'Verifone POS', version:'3.4', location:'Ealing Branch', cat:'POS System', vulns:[ + { cve:'CVE-2025-22916', cvss:9.1, sev:'critical', title:'Hardcoded admin credentials in management daemon', patch:true, exploit:true, mitre:'T1078' }, + { cve:'CVE-2024-38847', cvss:7.5, sev:'high', title:"IDOR allows access to other terminals' transactions", patch:true, exploit:false, mitre:'T1083' }, + ]}, + { id:'ERP-DYN-01', product:'Microsoft Dynamics Retail', version:'10.0.28', location:'Head Office', cat:'Stock Management', vulns:[ + { cve:'CVE-2024-61834', cvss:9.3, sev:'critical', title:'Authentication bypass via malformed OAuth token', patch:true, exploit:true, mitre:'T1078' }, + { cve:'CVE-2025-17293', cvss:8.2, sev:'high', title:'XXE injection in product import — reads server files', patch:true, exploit:false, mitre:'T1190' }, + ]}, + { id:'ERP-ORA-01', product:'Oracle Retail Merchandising', version:'21.0', location:'Head Office', cat:'Stock Management', vulns:[ + { cve:'CVE-2025-29481', cvss:9.8, sev:'critical', title:'RCE via deserialization in merchandise planning API', patch:true, exploit:true, mitre:'T1190' }, + { cve:'CVE-2024-73921', cvss:7.5, sev:'high', title:'Path traversal in report download endpoint', patch:true, exploit:false, mitre:'T1083' }, + ]}, + { id:'ERP-SAP-01', product:'SAP Retail', version:'S/4HANA 2021', location:'Head Office', cat:'Stock Management', vulns:[ + { cve:'CVE-2025-55921', cvss:8.6, sev:'high', title:'SSRF in integration layer exposes internal metadata', patch:true, exploit:false, mitre:'T1190' }, + { cve:'CVE-2025-43782', cvss:7.4, sev:'high', title:'Stored XSS in product catalogue via supplier input', patch:true, exploit:false, mitre:'T1059.007' }, + ]}, + { id:'ERP-JDA-01', product:'JDA Supply Chain', version:'9.2', location:'Head Office', cat:'Stock Management', vulns:[ + { cve:'CVE-2024-44918', cvss:8.1, sev:'high', title:'SQL injection in order management module', patch:true, exploit:false, mitre:'T1190' }, + { cve:'CVE-2025-61204', cvss:7.5, sev:'high', title:'IDOR exposes competitor purchase orders', patch:true, exploit:false, mitre:'T1083' }, + ]}, + { id:'TERM-VFN-01', product:'Verifone VX520', version:'2.1.0', location:'Hounslow Branch', cat:'Payment Terminal', vulns:[ + { cve:'CVE-2025-33741', cvss:9.4, sev:'critical', title:'PIN block extraction via differential power analysis', patch:true, exploit:false, mitre:'T1056.001' }, + { cve:'CVE-2024-55847', cvss:7.8, sev:'high', title:'Unsigned firmware accepted via ARP-spoofed update server', patch:true, exploit:false, mitre:'T1542' }, + ]}, + { id:'TERM-PAX-01', product:'PAX S920', version:'1.2', location:'Hounslow Branch', cat:'Payment Terminal', vulns:[ + { cve:'CVE-2025-37482', cvss:9.8, sev:'critical', title:'RCE via malformed EMV TLV packet over merchant LAN', patch:true, exploit:true, mitre:'T1190' }, + { cve:'CVE-2025-21847', cvss:8.4, sev:'high', title:'Root shell exposed via USB diagnostic interface', patch:true, exploit:true, mitre:'T1059' }, + ]}, + { id:'TERM-ING-01', product:'Ingenico iCT250', version:'6.0.0', location:'Ealing Branch', cat:'Payment Terminal', vulns:[ + { cve:'CVE-2025-48293', cvss:8.8, sev:'high', title:'Memory corruption in NFC NDEF handler — RCE via card', patch:true, exploit:false, mitre:'T1203' }, + { cve:'CVE-2024-92841', cvss:7.6, sev:'high', title:'Static Bluetooth PIN 0000 — relay attack possible', patch:true, exploit:true, mitre:'T1557' }, + ]}, + { id:'TERM-VFN-02', product:'Verifone P400', version:'3.0.1', location:'Hammersmith Branch', cat:'Payment Terminal', vulns:[ + { cve:'CVE-2025-12847', cvss:8.0, sev:'high', title:'Firmware downgrade attack via USB maintenance port', patch:true, exploit:false, mitre:'T1542.001' }, + { cve:'CVE-2024-38291', cvss:6.8, sev:'medium', title:'Weak TLS cipher suites (RC4/3DES) accepted in payment comms', patch:true, exploit:false, mitre:'T1600' }, + ]}, + { id:'PLAT-SHP-01', product:'Shopify POS', version:'9.1.0', location:'Online', cat:'Retail Platform', vulns:[ + { cve:'CVE-2025-28473', cvss:8.1, sev:'high', title:'Certificate pinning bypass enables on-path MITM attack', patch:true, exploit:false, mitre:'T1557' }, + { cve:'CVE-2025-41928', cvss:7.5, sev:'high', title:'API secret key exposed in iOS app bundle plist', patch:true, exploit:false, mitre:'T1552.001' }, + ]}, + { id:'PLAT-SQR-01', product:'Square POS', version:'5.28', location:'Online', cat:'Retail Platform', vulns:[ + { cve:'CVE-2025-53847', cvss:8.8, sev:'high', title:'No TLS cert validation — accepts self-signed certs', patch:true, exploit:false, mitre:'T1557.001' }, + { cve:'CVE-2024-82941', cvss:7.4, sev:'high', title:'Bluetooth pairing replay attack on card reader', patch:true, exploit:false, mitre:'T1557' }, + ]}, + { id:'PLAT-LSP-01', product:'Lightspeed Retail', version:'2024.1', location:'Online', cat:'Retail Platform', vulns:[ + { cve:'CVE-2025-44821', cvss:8.6, sev:'high', title:'Path traversal in file export reads server credentials', patch:true, exploit:false, mitre:'T1083' }, + { cve:'CVE-2024-63947', cvss:7.1, sev:'medium', title:'CSRF token reuse allows unauthorised admin actions', patch:true, exploit:false, mitre:'T1059.007' }, + ]}, + { id:'PLAT-RVL-01', product:'Revel POS', version:'4.7', location:'Online', cat:'Retail Platform', vulns:[ + { cve:'CVE-2025-17482', cvss:9.1, sev:'critical', title:'Hardcoded default admin credentials in iPad management', patch:true, exploit:true, mitre:'T1078' }, + { cve:'CVE-2024-57291', cvss:6.8, sev:'medium', title:'Card tokens stored in unencrypted local SQLite DB', patch:true, exploit:false, mitre:'T1005' }, + ]}, + ], +} + function SecurityGauge({ score }) { const cx = 100, cy = 95, r = 72 const startDeg = -210, endDeg = 30, totalArc = endDeg - startDeg @@ -423,152 +411,95 @@ function SecurityGauge({ score }) { return ( - - {score} - SECURITY SCORE + + {score} + SECURITY SCORE 0 100 ) } -// ── Attack timeline bar chart ───────────────────────────────────────────────── function AttackTimeline({ tick }) { - const labels = ['18h', '19h', '20h', '21h', '22h', '23h', '00h', '01h', '02h', '03h', '04h', '05h'] - const base = [1, 0, 2, 1, 0, 3, 2, 1, 4, 3, 2, 1] - const counts = base.map((v, i) => i === labels.length - 1 ? Math.min(6, v + (tick % 3)) : v) + const labels = ['18h','19h','20h','21h','22h','23h','00h','01h','02h','03h','04h','05h'] + const base = [1,0,2,1,0,3,2,1,4,3,2,1] + const counts = base.map((v,i) => i === labels.length-1 ? Math.min(6, v+(tick%3)) : v) const max = Math.max(...counts, 1) return (
-
- {counts.map((n, i) => ( -
-
= 4 ? C.red : n >= 3 ? C.orange : n >= 1 ? C.blue : C.border, - borderRadius: '2px 2px 0 0', - transition: 'height 0.5s ease, background 0.3s', - boxShadow: n >= 3 ? `0 0 6px ${n >= 4 ? C.red : C.orange}` : 'none', - }} /> +
+ {counts.map((n,i) => ( +
+
=4?C.red:n>=3?C.orange:n>=1?C.blue:C.border, borderRadius:'2px 2px 0 0', transition:'height 0.5s ease, background 0.3s', boxShadow:n>=3?`0 0 6px ${n>=4?C.red:C.orange}`:'none' }} />
))}
-
- {labels.map((l, i) => ( -
{l}
- ))} +
+ {labels.map((l,i) =>
{l}
)}
) } -// ── MITRE coverage bar ──────────────────────────────────────────────────────── function MitreBar({ technique, tactic, pct, color }) { return ( -
-
- {technique} - {tactic} · {pct}% +
+
+ {technique} + {tactic} · {pct}%
-
-
+
+
) } -// ── Incident playbook modal ─────────────────────────────────────────────────── function PlaybookModal({ threat, onClose }) { if (!threat) return null return ( -
-
e.stopPropagation()} style={{ - background: C.card, border: `1px solid ${C.border}`, borderRadius: 14, - padding: 28, maxWidth: 660, width: '100%', maxHeight: '88vh', - overflowY: 'auto', - }}> - {/* Title bar */} -
+
+
e.stopPropagation()} style={{ background:C.card, border:`1px solid ${C.border}`, borderRadius:14, padding:28, maxWidth:660, width:'100%', maxHeight:'88vh', overflowY:'auto' }}> +
-
- {threat.mitre} - {threat.status.toUpperCase()} - {threat.severity.toUpperCase()} +
+ {threat.mitre} + {threat.status.toUpperCase()} + {threat.severity.toUpperCase()}
-

{threat.name}

-

{threat.tactic} · {threat.device} · {threat.user} · {threat.time}

+

{threat.name}

+

{threat.tactic} · {threat.device} · {threat.user} · {threat.time}

- +
- - {/* Description */} -
-

{threat.desc}

+
+

{threat.desc}

- - {/* Trigger */} -
- Playbook trigger: - {threat.playbook.trigger} +
+ Playbook trigger: + {threat.playbook.trigger}
- - {/* AUTO steps */} -
-

- AUTO +
+

+ AUTO Automated Response — Executed by Logic App

- {threat.playbook.auto.map((step, i) => ( -
-
- {step} + {threat.playbook.auto.map((step,i) => ( +
+
+ {step}
))}
- - {/* MANUAL steps */}
-

- MANUAL +

+ MANUAL Analyst Actions Required

- {threat.playbook.manual.map((step, i) => ( -
-
{i + 1}
- {step} + {threat.playbook.manual.map((step,i) => ( +
+
{i+1}
+ {step}
))}
@@ -577,7 +508,6 @@ function PlaybookModal({ threat, onClose }) { ) } -// ── Main dashboard ──────────────────────────────────────────────────────────── export default function RetailShield() { const [threats, setThreats] = useState(BASE_THREATS) const [filter, setFilter] = useState('all') @@ -586,15 +516,15 @@ export default function RetailShield() { const [score, setScore] = useState(73) const [tick, setTick] = useState(0) const [clock, setClock] = useState(new Date()) - const [simStatus, setSimStatus] = useState(null) // null | 'running' | 'complete' + const [simStatus, setSimStatus] = useState(null) + const [vulnData, setVulnData] = useState(VULN_INITIAL) + const [vulnScan, setVulnScan] = useState(null) + const [vulnProg, setVulnProg] = useState(0) + const [briefing, setBriefing] = useState(null) + const [briefStatus, setBriefStatus] = useState(null) - // Live clock - useEffect(() => { - const id = setInterval(() => setClock(new Date()), 1000) - return () => clearInterval(id) - }, []) + useEffect(() => { const id = setInterval(() => setClock(new Date()), 1000); return () => clearInterval(id) }, []) - // Live feed — inject new threat every 6 seconds useEffect(() => { let idx = 0 const id = setInterval(() => { @@ -610,391 +540,378 @@ export default function RetailShield() { return () => clearInterval(id) }, []) - // Attack simulation — injects 5 threats one per second const runAttackSimulation = useCallback(() => { if (simStatus === 'running') return setSimStatus('running') setScore(s => Math.max(10, s - 20)) - ATTACK_SIM_EVENTS.forEach((event, i) => { setTimeout(() => { const threat = { ...event, id: Date.now() + i, ts: Date.now() } setThreats(prev => [threat, ...prev.slice(0, 14)]) setBanner(threat) setTimeout(() => setBanner(null), 3500) - if (i === ATTACK_SIM_EVENTS.length - 1) { - setTimeout(() => { - setSimStatus('complete') - setTimeout(() => setSimStatus(null), 6000) - }, 800) + setTimeout(() => { setSimStatus('complete'); setTimeout(() => setSimStatus(null), 6000) }, 800) } }, i * 800) }) }, [simStatus]) + const runVulnScan = useCallback(() => { + if (vulnScan === 'scanning') return + setVulnScan('scanning'); setVulnProg(0) + let p = 0 + const step = setInterval(() => { + p += Math.random() * 14 + 4 + if (p >= 100) { + clearInterval(step); setVulnProg(100) + setTimeout(() => { setVulnData({ ...VULN_INITIAL, lastScan: new Date().toISOString() }); setVulnScan('done'); setTimeout(() => setVulnScan(null), 4000) }, 400) + } else { setVulnProg(Math.round(p)) } + }, 200) + }, [vulnScan]) + const counts = { total: threats.length, critical: threats.filter(t => t.severity === 'critical').length, active: threats.filter(t => t.status === 'active').length, blocked: threats.filter(t => t.status === 'blocked').length, + high: threats.filter(t => t.severity === 'high').length, } - const filtered = threats.filter(t => - filter === 'all' ? true : - filter === 'critical' ? t.severity === 'critical' : - filter === 'active' ? t.status === 'active' : - filter === 'blocked' ? t.status === 'blocked' : true - ) + const generateBriefing = useCallback(async () => { + if (briefStatus === 'loading') return + setBriefStatus('loading'); setBriefing(null) + const topThreats = threats.slice(0, 5).map(t => ({ name:t.name, mitre:t.mitre, tactic:t.tactic, severity:t.severity, status:t.status })) + const crit = counts.critical, act = counts.active, blk = counts.blocked + try { + const res = await fetch('/api/brief', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ threats:counts, score:Math.round(score), vulnSummary:vulnData.summary, topThreats }) }) + const data = await res.json() + setBriefing(data.briefing) + } catch { + setBriefing(`RETAILSHIELD EXECUTIVE BRIEFING — ${new Date().toUTCString()}\n\nSecurity Score: ${Math.round(score)}/100. ${crit} critical and ${act} active threats currently detected across monitored retail infrastructure. ${vulnData.summary.critical + vulnData.summary.high} high-severity CVEs require patching within 48 hours. Automated playbooks have contained ${blk} incidents — no further manual action required for those. Immediate analyst review is required for all ACTIVE items in the threat feed. If any cardholder data was accessed, notify your DPO — UK GDPR Art.33 72-hour notification clock applies.\n\nThis briefing was generated by RetailShield v2.0 — ShieldTech Ltd.`) + } + setBriefStatus('done') + }, [briefStatus, threats, counts, score, vulnData]) + + const filtered = threats.filter(t => filter==='all'?true:filter==='critical'?t.severity==='critical':filter==='active'?t.status==='active':filter==='blocked'?t.status==='blocked':true) const mitre = [ - { technique: 'T1566.001 — Spearphishing', tactic: 'Initial Access', pct: 95, color: C.green }, - { technique: 'T1486 — Ransomware Impact', tactic: 'Impact', pct: 90, color: C.red }, - { technique: 'T1048 — Data Exfiltration', tactic: 'Exfiltration', pct: 88, color: C.orange }, - { technique: 'T1078 — Valid Accounts', tactic: 'Persistence', pct: 82, color: C.yellow }, - { technique: 'T1110.004 — Cred Stuffing', tactic: 'Credential Access', pct: 85, color: C.blue }, - { technique: 'T1056.001 — POS Keylog', tactic: 'Collection', pct: 78, color: C.orange }, - { technique: 'T1195 — Supply Chain', tactic: 'Initial Access', pct: 70, color: C.yellow }, - { technique: 'T1598 — Voice Fraud', tactic: 'Reconnaissance', pct: 65, color: C.blue }, + { technique:'T1566.001 — Spearphishing', tactic:'Initial Access', pct:95, color:C.green }, + { technique:'T1486 — Ransomware Impact', tactic:'Impact', pct:90, color:C.red }, + { technique:'T1048 — Data Exfiltration', tactic:'Exfiltration', pct:88, color:C.orange }, + { technique:'T1078 — Valid Accounts', tactic:'Persistence', pct:82, color:C.yellow }, + { technique:'T1110.004 — Cred Stuffing', tactic:'Credential Access', pct:85, color:C.blue }, + { technique:'T1056.001 — POS Keylog', tactic:'Collection', pct:78, color:C.orange }, + { technique:'T1195 — Supply Chain', tactic:'Initial Access', pct:70, color:C.yellow }, + { technique:'T1598 — Voice Fraud', tactic:'Reconnaissance', pct:65, color:C.blue }, ] - const s = { // shared styles - card: { background: C.card, border: `1px solid ${C.border}`, borderRadius: 10 }, - } + const s = { card: { background:C.card, border:`1px solid ${C.border}`, borderRadius:10 } } return ( -
+
- {/* ── Simulation status banner ── */} {simStatus && ( -
- {simStatus === 'running' ? '⚡' : '✅'} - - {simStatus === 'running' ? 'ATTACK SIMULATION RUNNING' : 'SIMULATION COMPLETE — 7 threats injected'} - - {simStatus === 'running' && ( - - — injecting threats into live feed... - - )} -
- {simStatus === 'running' && ['T1566.001','T1110.004','T1486','T1048','T1598'].map(t => ( - {t} - ))} +
+ {simStatus==='running'?'⚡':'✅'} + {simStatus==='running'?'ATTACK SIMULATION RUNNING':'SIMULATION COMPLETE — 7 threats injected'} + {simStatus==='running' && — injecting threats into live feed...} +
+ {simStatus==='running' && ['T1566.001','T1110.004','T1486','T1048','T1598'].map(t => {t})}
)} - {/* ── Live alert banner ── */} {banner && ( -
- 🚨 - - New Threat Detected - - — {banner.name} - - {banner.mitre} - - - {banner.severity.toUpperCase()} - +
+ 🚨 + New Threat Detected + — {banner.name} + {banner.mitre} + {banner.severity.toUpperCase()}
)} - {/* ── Header ── */} -
- {/* Logo */} -
-
🛡
+
+
+
🛡
-
RetailShield
-
by ShieldTech Ltd · Tanvir Farhad
+
RetailShield
+
by ShieldTech Ltd · Tanvir Farhad
- - {/* Live indicator */} -
- - Live Monitoring +
+ + Live Monitoring
- - {/* Right side */} -
- Microsoft Sentinel · Azure · UK Retail SOC - - {clock.toLocaleTimeString('en-GB')} - -
- - {counts.critical} CRITICAL - - - {counts.active} ACTIVE - +
+ Microsoft Sentinel · Azure · UK Retail SOC + {clock.toLocaleTimeString('en-GB')} +
+ {counts.critical} CRITICAL + {counts.active} ACTIVE
-
- {/* ── Main content ── */} -
+
- {/* KPI row */} -
+
{[ - { label: 'Total Threats', value: counts.total, color: C.text, icon: '⚡', sub: 'last 24 hours' }, - { label: 'Critical', value: counts.critical, color: C.red, icon: '🔴', sub: 'immediate action' }, - { label: 'Active', value: counts.active, color: C.orange, icon: '🟠', sub: 'in-progress' }, - { label: 'Blocked', value: counts.blocked, color: C.green, icon: '🟢', sub: 'auto-contained' }, + { label:'Total Threats', value:counts.total, color:C.text, icon:'⚡', sub:'last 24 hours' }, + { label:'Critical', value:counts.critical, color:C.red, icon:'🔴', sub:'immediate action' }, + { label:'Active', value:counts.active, color:C.orange, icon:'🟠', sub:'in-progress' }, + { label:'Blocked', value:counts.blocked, color:C.green, icon:'🟢', sub:'auto-contained' }, ].map(({ label, value, color, icon, sub }) => ( -
-
- {label} - {icon} +
+
+ {label} + {icon}
-
{value}
-
{sub}
+
{value}
+
{sub}
))}
- {/* Filter bar */} -
- {['all', 'critical', 'active', 'blocked'].map(f => ( - ))} -
- - {filtered.length} alerts · refresh every 6s +
+ + {filtered.length} alerts · refresh every 6s
- {/* Two-column layout: feed + sidebar */} -
- - {/* Threat feed */} +
-
- Live Threat Feed - Click any row to open incident playbook → +
+ Live Threat Feed + Click any row to open incident playbook →
-
- {filtered.length === 0 && ( -
No threats match this filter.
- )} +
+ {filtered.length===0 &&
No threats match this filter.
} {filtered.map(threat => ( -
setSelected(threat)} - style={{ - padding: '13px 20px', - borderBottom: `1px solid ${C.border}`, - borderLeft: `3px solid ${SEV[threat.severity]}`, - cursor: 'pointer', - transition: 'background 0.15s', - }} - onMouseEnter={e => e.currentTarget.style.background = C.cardHover} - onMouseLeave={e => e.currentTarget.style.background = 'transparent'} - > -
- {threat.name} -
- - {threat.severity.toUpperCase()} - - - {threat.status} - +
setSelected(threat)} style={{ padding:'13px 20px', borderBottom:`1px solid ${C.border}`, borderLeft:`3px solid ${SEV[threat.severity]}`, cursor:'pointer', transition:'background 0.15s' }} + onMouseEnter={e => e.currentTarget.style.background=C.cardHover} + onMouseLeave={e => e.currentTarget.style.background='transparent'}> +
+ {threat.name} +
+ {threat.severity.toUpperCase()} + {threat.status}
-
- {threat.mitre} +
+ {threat.mitre} 📍 {threat.device} 👤 {threat.user} - 🕐 {threat.time} + 🕐 {threat.time}
))}
- {/* Right sidebar */} -
- - {/* Security posture gauge */} -
-
Security Posture
-
- -
-
- {[ - { label: 'Rules Active', val: '8 / 8', color: C.green }, - { label: 'MTTD', val: '4.2 min', color: C.text }, - { label: 'MTTR', val: '18 min', color: C.text }, - { label: 'FP Rate', val: '2.1%', color: C.green }, - ].map(({ label, val, color }) => ( -
-
{label}
-
{val}
+
+
+
Security Posture
+
+
+ {[{label:'Rules Active',val:'8 / 8',color:C.green},{label:'MTTD',val:'4.2 min',color:C.text},{label:'MTTR',val:'18 min',color:C.text},{label:'FP Rate',val:'2.1%',color:C.green}].map(({label,val,color}) => ( +
+
{label}
+
{val}
))}
- - {/* Attack timeline */} -
-
Attack Timeline — 12h
+
+
Attack Timeline — 12h
- {/* MITRE ATT&CK coverage */} -
-
- MITRE ATT&CK Coverage — Retail TTP Matrix - 8 / 8 Techniques Monitored +
+
+ MITRE ATT&CK Coverage — Retail TTP Matrix + 8 / 8 Techniques Monitored
-
+
{mitre.map(m => )}
- {/* Emergency contacts */} -
-
🆘 Emergency Contacts
-
+
+ {vulnScan==='scanning' && ( +
+
Scanning Retail Infrastructure...
+
+
+
+
Checking {vulnData.totalAssets} assets against 32 retail CVEs — {vulnProg}%
+
+ )} +
+
+ CVE Vulnerability Scanner — Retail Stack +
Last scan: {new Date(vulnData.lastScan).toLocaleString('en-GB')} · {vulnData.totalAssets} assets · {vulnData.findings.length} vulnerable{vulnScan==='done'&&✓ Scan complete}
+
+
+ {[{label:'CRITICAL',val:vulnData.summary.critical,color:C.red},{label:'HIGH',val:vulnData.summary.high,color:C.orange},{label:'MEDIUM',val:vulnData.summary.medium,color:C.yellow},{label:'LOW',val:vulnData.summary.low,color:C.blue}].map(({label,val,color}) => ( +
+
{val}
+
{label}
+
+ ))} + +
+
+
+
+ {['ASSET ID','CVE / DESCRIPTION','CVSS','PRODUCT','VERSION','SEVERITY','PATCH'].map(h => {h})} +
+
+ {vulnData.findings.flatMap(f => f.vulns.map((v,vi) => { + const sevColor = SEV[v.sev]||C.muted + return ( +
e.currentTarget.style.background=C.cardHover} + onMouseLeave={e => e.currentTarget.style.background='transparent'}> +
{vi===0?f.id:''}
+
+
{v.cve}{v.exploit&&EXPLOIT}
+
{v.title}
+
MITRE: {v.mitre}
+
+
{v.cvss}
+
{vi===0?f.product:''}
+
{vi===0&&v{f.version}}
+
{v.sev}
+
{v.patch?'✓ Available':'✗ No patch'}
+
+ ) + }))} +
+
+
{[ - { - org: 'NCSC', - full: 'National Cyber Security Centre', - tel: '0300 020 0973', - email: 'report@ncsc.gov.uk', - note: 'Critical infrastructure & national-level cyber incidents', - color: C.blue, - icon: '🏛', - }, - { - org: 'ICO', - full: "Information Commissioner's Office", - tel: '0303 123 1113', - email: 'casework@ico.org.uk', - note: 'GDPR breach notification — 72-hour statutory window', - color: C.yellow, - icon: '⚖️', - }, - { - org: 'Action Fraud', - full: 'UK National Fraud & Cybercrime', - tel: '0300 123 2040', - email: 'actionfraud.police.uk', - note: 'Report financial fraud, voice fraud, and cybercrime', - color: C.orange, - icon: '🚔', - }, - ].map(({ org, full, tel, email, note, color, icon }) => ( -
-
- {icon} - {org} + {label:'Assets Scanned',val:vulnData.totalAssets,color:C.text}, + {label:'Total CVEs',val:vulnData.summary.critical+vulnData.summary.high+vulnData.summary.medium+vulnData.summary.low,color:C.text}, + {label:'Patch Available',val:vulnData.findings.reduce((n,f)=>n+f.vulns.filter(v=>v.patch).length,0),color:C.green}, + {label:'Public Exploits',val:vulnData.findings.reduce((n,f)=>n+f.vulns.filter(v=>v.exploit).length,0),color:C.red}, + ].map(({label,val,color}) =>
{val}{label}
)} +
+
+ +
+
+
+ PCI DSS v4.0 Compliance Scorecard +
Retail payment card security — 12 requirements · Auto-mapped from RetailShield detection rules & CVE scanner
+
+
+ {[{label:'COVERED',n:8,color:C.green},{label:'PARTIAL',n:2,color:C.yellow},{label:'PLANNED',n:1,color:C.dim}].map(({label,n,color}) => ( +
+
{n}
+
{label}
-
{full}
-
📞 {tel}
-
✉ {email}
-
{note}
+ ))} +
+
+
+ {[ + {req:'Req 1', label:'Network Security Controls', status:'covered', rule:'supply_chain_anomaly.kql', color:C.green }, + {req:'Req 2', label:'Secure System Configurations', status:'covered', rule:'cve_scanner.py', color:C.green }, + {req:'Req 3', label:'Protect Stored Cardholder Data', status:'covered', rule:'data_exfiltration.kql', color:C.green }, + {req:'Req 4', label:'Encrypt Cardholder Data in Transit', status:'covered', rule:'CVE-2024-38291 (TLS)', color:C.green }, + {req:'Req 5', label:'Anti-Malware Protection', status:'covered', rule:'ransomware_indicator.kql', color:C.green }, + {req:'Req 6', label:'Secure Software Development', status:'covered', rule:'cve_scanner.py (patches)', color:C.green }, + {req:'Req 7', label:'Restrict Access by Business Need', status:'partial', rule:'after_hours_access.kql', color:C.yellow}, + {req:'Req 8', label:'Identify Users & Auth Access', status:'covered', rule:'credential_stuffing.kql', color:C.green }, + {req:'Req 9', label:'Restrict Physical Access', status:'partial', rule:'pos_anomaly.kql', color:C.yellow}, + {req:'Req 10', label:'Log & Monitor All Access', status:'covered', rule:'All 8 KQL rules', color:C.green }, + {req:'Req 11', label:'Test Security Systems Regularly', status:'covered', rule:'cve_scanner.py (scans)', color:C.green }, + {req:'Req 12', label:'Organisational Security Policies', status:'planned', rule:'v3.0 — Planned', color:C.dim }, + ].map(({req,label,status,rule,color}) => ( +
+
+ {req} + {status} +
+
{label}
+
{rule}
))}
- {/* Footer */} -
- RetailShield v1.0 · ShieldTech Ltd · Tanvir Farhad · Microsoft Sentinel · MITRE ATT&CK Enterprise +
+
+
+ AI Executive Threat Briefing +
Claude AI generates a board-ready summary from live dashboard state · Powered by Anthropic
+
+ +
+ {briefing && ( +
+
+ Claude AI — Executive Briefing +
+ {new Date().toLocaleString('en-GB')} + +
+
+
{briefing}
+
+ )} + {!briefing && ( +
+
🤖
+
Click GENERATE AI BRIEFING to produce a board-ready executive summary
+
Analyses active threats · CVE severity · PCI DSS exposure · Regulatory obligations
+
+ )} +
+ +
+
🆘 Emergency Contacts
+
+ {[ + {org:'NCSC',full:'National Cyber Security Centre',tel:'0300 020 0973',email:'report@ncsc.gov.uk',note:'Critical infrastructure & national-level cyber incidents',color:C.blue,icon:'🏗'}, + {org:'ICO',full:"Information Commissioner's Office",tel:'0303 123 1113',email:'casework@ico.org.uk',note:'GDPR breach notification — 72-hour statutory window',color:C.yellow,icon:'⚖️'}, + {org:'Action Fraud',full:'UK National Fraud & Cybercrime',tel:'0300 123 2040',email:'actionfraud.police.uk',note:'Report financial fraud, voice fraud, and cybercrime',color:C.orange,icon:'🚔'}, + ].map(({org,full,tel,email,note,color,icon}) => ( +
+
{icon}{org}
+
{full}
+
📞 {tel}
+
✉ {email}
+
{note}
+
+ ))} +
+ +
RetailShield v1.0 · ShieldTech Ltd · Tanvir Farhad · Microsoft Sentinel · MITRE ATT&CK Enterprise
- {/* Playbook modal */} setSelected(null)} /> - {/* Global styles */}