fix(web): finding modal — dedupe repeated attribution text, prose vs code sections

impact and business_impact often carry identical text (both ending with the
reporter's 'Identified and validated by NeuroSploit...' footer), and the
modal concatenated them verbatim — the boilerplate line rendered twice, and
whenever the two fields matched, so did the whole paragraph.

- Strip the attribution sentence out of impact/business_impact/remediation/
  evidence wherever it appears; surface it once, at the bottom of the modal,
  instead of embedded per field.
- Skip business_impact entirely when it's identical to impact (the common
  case) instead of printing the same paragraph twice.
- Split rendering into codeBlock() (endpoint/payload — monospace, looks like
  what it is: a request/curl) and proseBlock() (description/impact/
  remediation — a readable paragraph, not a code box).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0129WdYHccPsH27k5GGuwijd
This commit is contained in:
CyberSecurityUP
2026-08-23 15:35:21 -03:00
co-authored by Claude Sonnet 5
parent a083990ce4
commit 8047c66e8f
2 changed files with 31 additions and 8 deletions
+30 -8
View File
@@ -554,15 +554,37 @@ function openFindingModal(f, pocs, runId) {
$('#fmMeta').innerHTML = meta.map(([k, v]) =>
`<div class="review-item"><div class="k">${esc(k)}</div><div class="v mono">${esc(v || '—')}</div></div>`).join('');
const section = (label, text) => text
? `<div class="field-group"><label class="field-label">${esc(label)}</label><div class="poc-pre">${esc(text)}</div></div>`
: '';
// The report footer ("Identified and validated by NeuroSploit...") gets
// baked into impact/business_impact by the reporter — strip it from every
// field so it doesn't repeat per-section, and surface it once at the
// bottom of the modal instead.
const ATTRIBUTION_RE = /Identified and validated by NeuroSploit[\s\S]*?Red Team Leaders\.?/i;
let attributed = false;
const clean = (text) => {
if (!text) return '';
const stripped = text.replace(ATTRIBUTION_RE, () => { attributed = true; return ''; });
return stripped.split(/\n\n+/).map((p) => p.trim()).filter(Boolean).join('\n\n');
};
// Technical evidence (endpoint/payload/curl) reads as code; prose
// (description/impact/remediation) reads as a paragraph, not a code block.
const codeBlock = (label, text) => text
? `<div class="field-group"><label class="field-label">${esc(label)}</label><pre class="poc-pre">${esc(text)}</pre></div>` : '';
const proseBlock = (label, text) => text
? `<div class="field-group"><label class="field-label">${esc(label)}</label><div class="fm-prose">${esc(text)}</div></div>` : '';
const impactText = clean(f.impact);
const bizText = clean(f.business_impact);
const impactCombined = bizText && bizText !== impactText
? [impactText, bizText].filter(Boolean).join('\n\n') : impactText;
$('#fmSection-evidence').innerHTML =
section('Endpoint / payload', [f.endpoint, f.payload].filter(Boolean).join('\n\n')) + section('Evidence', f.evidence);
$('#fmSection-impact').innerHTML = section('Impact', [f.impact, f.business_impact].filter(Boolean).join('\n\n'));
$('#fmSection-remediation').innerHTML = section('Remediation', f.remediation);
$('#fmSection-chains').innerHTML = (f.chains_from || []).length
? `<div class="field-help">Chains from: ${esc(f.chains_from.join(', '))}</div>` : '';
proseBlock('Description', clean(f.evidence)) +
codeBlock('Technical evidence', [f.endpoint, f.payload].filter(Boolean).join('\n\n'));
$('#fmSection-impact').innerHTML = proseBlock('Impact', impactCombined);
$('#fmSection-remediation').innerHTML = proseBlock('Remediation', clean(f.remediation));
$('#fmSection-chains').innerHTML =
((f.chains_from || []).length ? `<div class="field-help">Chains from: ${esc(f.chains_from.join(', '))}</div>` : '') +
(attributed ? '<div class="field-help" style="margin-top:6px;">Identified and validated by NeuroSploit (multi-model adversarial validation) — full methodology in the generated report.</div>' : '');
// Proof of concept — doctrine tells agents to cite the PoC's file name in
// `evidence` (see pocs_line() in pipeline.rs), so match on that text first;
+1
View File
@@ -325,6 +325,7 @@ textarea { resize: vertical; min-height: 72px; }
.poc-file { display: flex; align-items: center; gap: var(--sp-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-2); }
.poc-file .fn { font-family: var(--mono); font-size: 12px; flex: 1; }
.poc-pre { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: var(--sp-3); font-family: var(--mono); font-size: 11.5px; max-height: 220px; overflow: auto; white-space: pre-wrap; word-break: break-word; margin-top: var(--sp-2); }
.fm-prose { font-family: var(--sans); font-size: 12.5px; line-height: 1.6; color: var(--text-dim); white-space: pre-wrap; padding: var(--sp-1) 0; }
.data-table .col-endpoint { font-family: var(--mono); font-size: 11.5px; color: var(--text-dim); max-width: 260px; overflow: hidden; text-overflow: ellipsis; }
.data-table .col-conf { font-family: var(--mono); text-align: right; }
.empty-state { padding: var(--sp-7) var(--sp-5); text-align: center; color: var(--text-faint); font-size: 12.5px; }