@@ -4330,9 +4330,27 @@ function ensureGroupedReconObservation(map, attack, original, requestId) {
43304330 return map . get ( key )
43314331}
43324332
4333+ function scoreGroupedReconSampleAttack ( attack = null ) {
4334+ if ( ! attack || typeof attack !== 'object' ) return 0
4335+ let score = 0
4336+ if ( attack . findingId || attack ?. finding ?. id ) score += 8
4337+ if ( Number . isFinite ( Number ( attack ?. confidence ) ) || Number . isFinite ( Number ( attack ?. finding ?. confidence ) ) || Number . isFinite ( Number ( attack ?. metadata ?. confidence ) ) ) score += 4
4338+ if ( attack ?. finding ?. presentationAggregate || attack ?. presentationAggregate ) score += 2
4339+ if ( Array . isArray ( attack ?. finding ?. evidence ?. dast ?. samples ) && attack . finding . evidence . dast . samples . length ) score += 3
4340+ if ( attack ?. finding ?. description || attack ?. description || attack ?. metadata ?. description || attack ?. metadata ?. docs ?. description ) score += 1
4341+ if ( attack ?. finding ?. links || attack ?. links || attack ?. metadata ?. links || attack ?. metadata ?. docs ?. links ) score += 1
4342+ return score
4343+ }
4344+
4345+ function selectPreferredGroupedReconSampleAttack ( current = null , incoming = null ) {
4346+ return scoreGroupedReconSampleAttack ( incoming ) >= scoreGroupedReconSampleAttack ( current ) ? incoming : current
4347+ }
4348+
43334349function collectGroupedReconObservation ( map , attack , original , requestId ) {
43344350 const entry = ensureGroupedReconObservation ( map , attack , original , requestId )
43354351 if ( ! entry ) return
4352+ entry . sampleAttack = selectPreferredGroupedReconSampleAttack ( entry . sampleAttack , attack )
4353+ entry . sampleRequest = entry . sampleAttack ?. request || attack ?. request || original ?. request || original || entry . sampleRequest
43364354 const request = attack ?. request || original ?. request || original || { }
43374355 const method = String ( request ?. method || '' ) . trim ( ) . toUpperCase ( ) || 'GET'
43384356 const path = normalizeObservationRoutePath ( request ?. url || request ?. ui_url || request ?. target || '' )
@@ -4360,8 +4378,8 @@ function renderGroupedReconRoutes(entry) {
43604378 </div>
43614379 `
43624380 }
4363- const previewRoutes = routes . slice ( 0 , 6 )
4364- const remainingRoutes = routes . slice ( 6 )
4381+ const previewRoutes = routes . slice ( 0 , 3 )
4382+ const remainingRoutes = routes . slice ( 3 )
43654383 const renderRouteItem = ( route ) => {
43664384 const label = `${ route . method } ${ route . path } `
43674385 if ( route . url ) {
@@ -4370,19 +4388,18 @@ function renderGroupedReconRoutes(entry) {
43704388 return `<li>${ ptk_utils . escapeHtml ( label ) } </li>`
43714389 }
43724390 const preview = previewRoutes . map ( renderRouteItem ) . join ( '' )
4373- const remaining = remainingRoutes . length
43744391 const allRoutes = remainingRoutes . map ( renderRouteItem ) . join ( '' )
43754392 const toggleKey = ptk_utils . escapeHtml ( String ( entry ?. key || '' ) )
43764393 return `
43774394 <div class="description">
4378- <p>Affected routes: <b>${ routes . length } </b>${ entry . rawCount > routes . length ? `</p><p> Matched requests : <b>${ entry . rawCount } </b>` : '' } </p>
4395+ <p>Affected routes: <b>${ routes . length } </b>${ entry . rawCount > routes . length ? ` | Matched responses : <b>${ entry . rawCount } </b>` : '' } </p>
43794396 <ul style="margin: 6px 0 0 16px;">${ preview } </ul>
4380- ${ remaining > 0 ? `
4397+ ${ remainingRoutes . length > 0 ? `
43814398 <div class="grouped_recon_routes_all" data-grouped-routes-key="${ toggleKey } " style="display:none;">
43824399 <ul style="margin: 6px 0 0 16px;">${ allRoutes } </ul>
43834400 </div>
43844401 <p style="margin-top:6px;">
4385- <a href="#" class="toggle_grouped_recon_routes" data-grouped-routes-key="${ toggleKey } " data-expanded="0">Show all routes </a>
4402+ <a href="#" class="toggle_grouped_recon_routes" data-grouped-routes-key="${ toggleKey } " data-expanded="0">More </a>
43864403 </p>
43874404 ` : '' }
43884405 </div>
@@ -4420,6 +4437,7 @@ function buildGroupedReconDetailsPayload(entry, resolvedAttack = null) {
44204437function buildGroupedReconCardHtml ( entry ) {
44214438 if ( ! entry ?. sampleAttack ) return null
44224439 const meta = rutils . getMiscMeta ( entry . sampleAttack )
4440+ const confidence = rutils . resolveConfidence ( entry . sampleAttack )
44234441 const severityAttr = meta . severity || ''
44244442 const safeRequestId = entry . requestId === null || entry . requestId === undefined || entry . requestId === ''
44254443 ? '__ptk_grouped_recon__'
@@ -4439,12 +4457,7 @@ function buildGroupedReconCardHtml(entry) {
44394457 data-severity="${ ptk_utils . escapeHtml ( severityAttr ) } "
44404458 data-request-id="${ ptk_utils . escapeHtml ( safeRequestId ) } "
44414459 data-grouped-recon-key="${ keyAttr } ">
4442- <div class="ptk-finding-header">
4443- <div class="ptk-finding-header-main">${ titleHtml } </div>
4444- </div>
4445- <div class="description">
4446- <p>Grouped posture observation to reduce repeated low-signal route cards.</p>
4447- </div>
4460+ ${ rutils . renderFindingHeader ( titleHtml , confidence ) }
44484461 ${ targetUrl ? `<div class="description"><p>Sample URL: <a href="${ safeTarget } " target="_blank">${ safeTarget } </a></p></div>` : '' }
44494462 ${ routesHtml }
44504463 <div class="ui left floated">
@@ -5838,7 +5851,7 @@ jQuery(function () {
58385851 const expanded = String ( $ ( this ) . attr ( "data-expanded" ) || "0" ) === "1"
58395852 $allRoutes . toggle ( ! expanded )
58405853 $ ( this ) . attr ( "data-expanded" , expanded ? "0" : "1" )
5841- $ ( this ) . text ( expanded ? "Show all routes " : "Hide routes " )
5854+ $ ( this ) . text ( expanded ? "More " : "Less " )
58425855 return false
58435856 } )
58445857
@@ -6568,7 +6581,9 @@ function bindScanResult(result) {
65686581 const aggregateKey = getPassiveHeadersAggregateFindingKey ( enrichedAttack , original )
65696582 if ( aggregateKey ) {
65706583 const attrValue = ( window . CSS && typeof CSS . escape === 'function' ) ? CSS . escape ( aggregateKey ) : escAttrValue ( aggregateKey )
6571- if ( $ ( `#attacks_info .attack_info[data-aggregate-key="${ attrValue } "]` ) . length ) {
6584+ const $existing = $ ( `#attacks_info .attack_info[data-aggregate-key="${ attrValue } "]` )
6585+ if ( $existing . length ) {
6586+ $existing . replaceWith ( rutils . bindAttack ( enrichedAttack , original , attackKey , requestKey ) )
65726587 return
65736588 }
65746589 }
@@ -7142,7 +7157,9 @@ function flushDastQueue() {
71427157 const aggregateKey = getPassiveHeadersAggregateFindingKey ( enrichedAttack , original )
71437158 if ( aggregateKey ) {
71447159 const attrValue = ( window . CSS && typeof CSS . escape === 'function' ) ? CSS . escape ( aggregateKey ) : escAttrValue ( aggregateKey )
7145- if ( $ ( `#attacks_info .attack_info[data-aggregate-key="${ attrValue } "]` ) . length ) {
7160+ const $existing = $ ( `#attacks_info .attack_info[data-aggregate-key="${ attrValue } "]` )
7161+ if ( $existing . length ) {
7162+ $existing . replaceWith ( rutils . bindAttack ( enrichedAttack , original , attackId , requestId ) )
71467163 return
71477164 }
71487165 }
0 commit comments