@@ -1177,6 +1177,28 @@ <h2>Select Analysis Method</h2>
11771177
11781178 <!-- Results -->
11791179 < div id ="results " class ="results ">
1180+ <!-- Pwned Results Detection (before Analysis Results) -->
1181+ < div class ="section " id ="detection-section ">
1182+ < h2 > 🔍 Pwned Results Detection</ h2 >
1183+ < div class ="subsection " style ="margin-top: 0.75rem; ">
1184+ < h4 style ="font-size: 0.95rem; font-weight: 600; margin: 0 0 0.5rem 0; color: var(--text-secondary); "> Sigma</ h4 >
1185+ < div style ="display: flex; align-items: center; justify-content: space-between; margin-top: 0.5rem; gap: 0.5rem; ">
1186+ < span style ="font-size: 0.9rem; font-weight: 500; color: var(--text-primary); "> Rules</ span >
1187+ < button class ="section-toggle collapsed " onclick ="toggleSection('sigma-rules') "> ▼</ button >
1188+ </ div >
1189+ < div class ="section-content collapsed " id ="sigma-rules-content " style ="margin-top: 0.5rem; ">
1190+ < div id ="sigma-rules-container " style ="font-size: 0.875rem; color: var(--text-primary); "> </ div >
1191+ </ div >
1192+ < div style ="display: flex; align-items: center; justify-content: space-between; margin-top: 0.75rem; gap: 0.5rem; ">
1193+ < span style ="font-size: 0.9rem; font-weight: 500; color: var(--text-primary); "> Matches</ span >
1194+ < button class ="section-toggle " onclick ="toggleSection('sigma-matches') "> ▼</ button >
1195+ </ div >
1196+ < div class ="section-content " id ="sigma-matches-content " style ="margin-top: 0.5rem; ">
1197+ < div id ="sigma-matches-container " style ="font-size: 0.875rem; color: var(--text-primary); "> </ div >
1198+ </ div >
1199+ </ div >
1200+ </ div >
1201+
11801202 < div class ="section ">
11811203 < h2 > 📊 Analysis Results</ h2 >
11821204 < div id ="analysis-timestamp " style ="color: var(--text-secondary); font-size: 0.875rem; margin-bottom: 1rem; display: none; ">
@@ -1572,7 +1594,70 @@ <h3>📋 Full Data (JSON)</h3>
15721594 return 'Error calculating hash' ;
15731595 }
15741596 }
1575-
1597+
1598+ // Parse Sigma rule YAML for title, id, description, status, level (best-effort, no external deps)
1599+ function parseSigmaRuleMeta ( yamlText ) {
1600+ const o = { title : null , id : null , description : null , status : null , level : null } ;
1601+ if ( ! yamlText || typeof yamlText !== 'string' ) return o ;
1602+ const m = ( name , pat ) => { const r = yamlText . match ( pat ) ; if ( r ) o [ name ] = r [ 1 ] . trim ( ) ; } ;
1603+ m ( 'title' , / ^ t i t l e : \s * ( .+ ) $ / m) ;
1604+ m ( 'id' , / ^ i d : \s * ( .+ ) $ / m) ;
1605+ m ( 'status' , / ^ s t a t u s : \s * ( .+ ) $ / m) ;
1606+ m ( 'level' , / ^ l e v e l : \s * ( .+ ) $ / m) ;
1607+ const desc = yamlText . match ( / ^ d e s c r i p t i o n : \s * ( .+ ) $ / m) ;
1608+ if ( desc ) o . description = desc [ 1 ] . replace ( / \s + / g, ' ' ) . trim ( ) ;
1609+ return o ;
1610+ }
1611+
1612+ // Fetch Sigma rules from server
1613+ async function fetchSigmaRules ( ) {
1614+ const urls = [ '/rules/novispy.yml' , '/rules/cellebrite.yml' ] ;
1615+ console . log ( '[fetchSigmaRules] Fetching Sigma rules:' , urls . join ( ', ' ) ) ;
1616+ try {
1617+ const fetchOne = async ( url ) => {
1618+ try {
1619+ const res = await fetch ( url ) ;
1620+ const text = res . ok ? await res . text ( ) : '' ;
1621+ const success = ! ! ( res . ok && ( text || '' ) . length > 0 ) ;
1622+ if ( success ) {
1623+ console . log ( `[fetchSigmaRules] ✓ ${ url } : ${ res . status } , ${ ( text || '' ) . length } chars` ) ;
1624+ } else {
1625+ console . warn ( `[fetchSigmaRules] ✗ ${ url } : ${ res . status } ${ res . statusText } ` ) ;
1626+ }
1627+ return { text, success, httpStatus : res . status , fetchError : success ? null : ( res . status + ' ' + ( res . statusText || '' ) ) , url } ;
1628+ } catch ( e ) {
1629+ console . warn ( `[fetchSigmaRules] ✗ ${ url } :` , e ) ;
1630+ return { text : '' , success : false , httpStatus : null , fetchError : 'Network error: ' + ( e . message || String ( e ) ) , url } ;
1631+ }
1632+ } ;
1633+ const results = await Promise . all ( urls . map ( u => fetchOne ( u ) ) ) ;
1634+ const combined = results . filter ( r => ( r . text || '' ) . length > 0 ) . map ( r => r . text ) . join ( '\n---\n' ) ;
1635+ const rulesInfo = results . map ( r => {
1636+ const meta = r . success ? parseSigmaRuleMeta ( r . text ) : { } ;
1637+ return {
1638+ url : r . url ,
1639+ success : r . success ,
1640+ fetchError : r . fetchError || null ,
1641+ title : meta . title ?? null ,
1642+ id : meta . id ?? null ,
1643+ description : meta . description ?? null ,
1644+ status : meta . status ?? null ,
1645+ level : meta . level ?? null
1646+ } ;
1647+ } ) ;
1648+ const count = rulesInfo . filter ( r => r . success ) . length ;
1649+ if ( count > 0 ) {
1650+ console . log ( `[fetchSigmaRules] Loaded ${ count } rule file(s), ${ combined . length } chars total` ) ;
1651+ } else {
1652+ console . warn ( '[fetchSigmaRules] No rules loaded (all fetches failed or empty)' ) ;
1653+ }
1654+ return { rules : combined , rulesInfo } ;
1655+ } catch ( e ) {
1656+ console . warn ( '[fetchSigmaRules] Could not fetch Sigma rules:' , e ) ;
1657+ return { rules : '' , rulesInfo : [ ] } ;
1658+ }
1659+ }
1660+
15761661 // Download results
15771662 window . downloadResults = function ( ) {
15781663 if ( ! currentResults ) {
@@ -1587,6 +1672,8 @@ <h3>📋 Full Data (JSON)</h3>
15871672 battery_info : currentResults . battery_info ,
15881673 process_count : currentResults . process_count ,
15891674 package_count : currentResults . package_count ,
1675+ sigma_matches : currentResults . sigma_matches || null ,
1676+ sigma_rules_info : currentResults . sigma_rules_info || [ ] ,
15901677 packages : currentResults . packages ,
15911678 processes : currentResults . processes ,
15921679 battery_apps : currentResults . battery_apps ,
@@ -1625,6 +1712,8 @@ <h3>📋 Full Data (JSON)</h3>
16251712 battery_info : currentResults . battery_info ,
16261713 process_count : currentResults . process_count ,
16271714 package_count : currentResults . package_count ,
1715+ sigma_matches : currentResults . sigma_matches || null ,
1716+ sigma_rules_info : currentResults . sigma_rules_info || [ ] ,
16281717 logcat_lines : currentResults . logcat_lines ,
16291718 has_security_analysis : currentResults . has_security_analysis ,
16301719 packages : currentResults . packages ,
@@ -1993,6 +2082,52 @@ <h3>📋 Full Data (JSON)</h3>
19932082 document . getElementById ( 'stats-processes' ) . textContent = data . process_count || 0 ;
19942083 document . getElementById ( 'stats-packages' ) . textContent = data . package_count || 0 ;
19952084
2085+ // Detection / Sigma rules (title, id, description, status, level, fetch success)
2086+ const sigmaEl = document . getElementById ( 'sigma-rules-container' ) ;
2087+ const info = data . sigma_rules_info ;
2088+ if ( info && Array . isArray ( info ) && info . length > 0 ) {
2089+ sigmaEl . innerHTML = info . map ( r => {
2090+ const fetchBadge = r . success
2091+ ? '<span style="color: var(--success);">✓ Fetched</span>'
2092+ : '<span style="color: var(--error);">✗ ' + escapeHtml ( r . fetchError || 'Failed' ) + '</span>' ;
2093+ const rows = [
2094+ [ 'URL' , escapeHtml ( r . url ) ] ,
2095+ [ 'Server' , fetchBadge ] ,
2096+ [ 'Title' , r . title ? escapeHtml ( r . title ) : '-' ] ,
2097+ [ 'ID' , r . id ? escapeHtml ( r . id ) : '-' ] ,
2098+ [ 'Description' , r . description ? escapeHtml ( r . description ) : '-' ] ,
2099+ [ 'Status' , r . status ? escapeHtml ( r . status ) : '-' ] ,
2100+ [ 'Level' , r . level ? escapeHtml ( r . level ) : '-' ]
2101+ ] ;
2102+ const body = rows . map ( ( [ l , v ] ) => `<div style="margin-bottom: 0.35rem;"><span style="color: var(--text-secondary); font-weight: 500;">${ escapeHtml ( l ) } :</span> ${ v } </div>` ) . join ( '' ) ;
2103+ return `<div class="result-card" style="margin-bottom: 0.75rem; padding: 1rem;"><div style="font-size: 0.875rem;">${ body } </div></div>` ;
2104+ } ) . join ( '' ) ;
2105+ } else {
2106+ sigmaEl . textContent = 'No rules loaded.' ;
2107+ }
2108+
2109+ // Sigma matches (from match_with_log_to_value: rule_id, rule_title, level, log_entry)
2110+ const matchesEl = document . getElementById ( 'sigma-matches-container' ) ;
2111+ const matches = data . sigma_matches ;
2112+ if ( matches && Array . isArray ( matches ) && matches . length > 0 ) {
2113+ matchesEl . innerHTML = matches . map ( m => {
2114+ const id = ( m . rule_id && m . rule_id . trim ( ) ) ? escapeHtml ( m . rule_id ) : '-' ;
2115+ const title = ( m . rule_title && m . rule_title . trim ( ) ) ? escapeHtml ( m . rule_title ) : '-' ;
2116+ const level = ( m . level && m . level . trim ( ) ) ? escapeHtml ( m . level ) : '-' ;
2117+ const l = ( m . level || '' ) . toLowerCase ( ) ;
2118+ const levelColor = l === 'critical' ? 'var(--error)' : l === 'high' ? 'var(--warning)' : 'var(--text-secondary)' ;
2119+ let matchedLogHtml = '' ;
2120+ const ml = m . log_entry ?? m . matched_log ;
2121+ if ( ml && typeof ml === 'object' && ! Array . isArray ( ml ) && Object . keys ( ml ) . length > 0 ) {
2122+ const fmt = ( v ) => typeof v === 'object' && v !== null ? escapeHtml ( JSON . stringify ( v ) ) : escapeHtml ( String ( v ?? '' ) ) ;
2123+ matchedLogHtml = '<div style="margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid var(--border);"><span style="color: var(--text-secondary); font-weight: 500;">Matched log:</span><div style="margin-top: 0.35rem; font-family: monospace; font-size: 0.8rem;">' + Object . entries ( ml ) . map ( ( [ k , v ] ) => '<div style="margin-bottom: 0.2rem;"><span style="color: var(--text-secondary);">' + escapeHtml ( k ) + ':</span> ' + fmt ( v ) + '</div>' ) . join ( '' ) + '</div></div>' ;
2124+ }
2125+ return `<div class="result-card" style="margin-bottom: 0.5rem; padding: 0.75rem 1rem;"><div style="font-size: 0.875rem;"><div style="margin-bottom: 0.25rem;"><span style="color: var(--text-secondary); font-weight: 500;">Rule:</span> ${ title } </div><div style="display: flex; gap: 1rem; flex-wrap: wrap;"><span><span style="color: var(--text-secondary);">ID:</span> ${ id } </span><span style="color: ${ levelColor } ; font-weight: 500;">${ level } </span></div>${ matchedLogHtml } </div></div>` ;
2126+ } ) . join ( '' ) ;
2127+ } else {
2128+ matchesEl . textContent = 'No matches.' ;
2129+ }
2130+
19962131 // File info
19972132 const fileInfoCard = document . getElementById ( 'file-info-card' ) ;
19982133 if ( data . file_info ) {
@@ -3433,8 +3568,10 @@ <h3 style="margin-bottom: 1rem;">Socket Details</h3>
34333568
34343569 showStatus ( 'Hash calculated! Analyzing...' , 'info' ) ;
34353570
3436- const result = await adb . analyze_bugreport ( bugreportData ) ;
3437-
3571+ const { rules, rulesInfo } = await fetchSigmaRules ( ) ;
3572+ const result = await adb . analyze_bugreport ( bugreportData , rules ) ;
3573+ result . sigma_rules_info = rulesInfo ;
3574+
34383575 result . file_info = {
34393576 path : 'Generated on device' ,
34403577 size_bytes : bugreportData . length ,
@@ -3502,10 +3639,12 @@ <h3 style="margin-bottom: 1rem;">Socket Details</h3>
35023639
35033640 showStatus ( `✓ Hash calculated in ${ formatDuration ( hashDurationMs ) } ! Now analyzing...` , 'info' ) ;
35043641 showLoadingSpinner ( 'Analyzing bugreport... This may take a minute' ) ;
3505-
3642+
3643+ const { rules, rulesInfo } = await fetchSigmaRules ( ) ;
35063644 const startAnalysis = Date . now ( ) ;
3507- const result = await adb . analyze_bugreport ( bugreportData ) ;
3508-
3645+ const result = await adb . analyze_bugreport ( bugreportData , rules ) ;
3646+ result . sigma_rules_info = rulesInfo ;
3647+
35093648 const analysisDurationMs = Date . now ( ) - startAnalysis ;
35103649 const totalDurationMs = Date . now ( ) - startDownload ;
35113650
@@ -3635,8 +3774,10 @@ <h3 style="margin-bottom: 1rem;">Socket Details</h3>
36353774 window . adb = adb ;
36363775 }
36373776
3638- const result = await adb . analyze_bugreport ( uint8Array ) ;
3639-
3777+ const { rules, rulesInfo } = await fetchSigmaRules ( ) ;
3778+ const result = await adb . analyze_bugreport ( uint8Array , rules ) ;
3779+ result . sigma_rules_info = rulesInfo ;
3780+
36403781 result . file_info = {
36413782 path : uploadedFile . name ,
36423783 size_bytes : uploadedFile . size ,
0 commit comments