feat: browser session persistence, auto-screenshots, context optimization, user cleanup
Some checks failed
CI / Check (pull_request) Failing after 5m55s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped
Some checks failed
CI / Check (pull_request) Failing after 5m55s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped
Browser tool: - Session-persistent Chrome tab (same tab reused across all calls in a pentest) - Auto-screenshot on every navigate and click (stored in attack chain for report) - Fill uses CDP Input.insertText (fixes WebSocket corruption on special chars) - Switched from browserless/chromium to chromedp/headless-shell (stable WS) Context window optimization: - Strip screenshot_base64 from LLM conversation (kept in DB for report) - Truncate HTML to 2KB, page text to 1.5KB in LLM messages - Cap element/link arrays at 15 items - SAST triage: batch 30 findings per LLM call instead of all at once Report improvements: - Auto-embed screenshots in attack chain timeline (navigate + click nodes) - Cover page shows best app screenshot - Attack chain phases capped at 8 (no more 20x "Final") User cleanup: - TestUserRecord model tracks created test users per session - cleanup.rs: Keycloak (Admin REST API), Auth0 (Management API), Okta (Users API) - Auto-cleanup on session completion when cleanup_test_user is enabled - Env vars: KEYCLOAK_ADMIN_USERNAME, KEYCLOAK_ADMIN_PASSWORD System prompt: - Explicit browser usage instructions (navigate → get_content → click → fill) - SPA auth bypass guidance (check page content, not HTTP status) - Screenshot instructions for evidence collection Other: - Pin mongo:7 in docker-compose (mongo:latest/8 segfaults on kernel 6.19) - Add deploy/docker-compose.mailserver.yml for Postfix + Dovecot Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -149,6 +149,23 @@ fn build_chain_html(chain: &[AttackChainNode]) -> String {
|
||||
)
|
||||
};
|
||||
|
||||
// Render inline screenshot if this is a browser screenshot action
|
||||
let screenshot_html = if node.tool_name == "browser" {
|
||||
node.tool_output
|
||||
.as_ref()
|
||||
.and_then(|out| out.get("screenshot_base64"))
|
||||
.and_then(|v| v.as_str())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|b64| {
|
||||
format!(
|
||||
r#"<div class="step-screenshot"><img src="data:image/png;base64,{b64}" alt="Browser screenshot" style="max-width:100%;border:1px solid #e2e8f0;border-radius:6px;margin-top:8px;"/></div>"#
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
chain_html.push_str(&format!(
|
||||
r#"<div class="step-row">
|
||||
<div class="step-num">{num}</div>
|
||||
@@ -161,6 +178,7 @@ fn build_chain_html(chain: &[AttackChainNode]) -> String {
|
||||
{risk_badge}
|
||||
</div>
|
||||
{reasoning_html}
|
||||
{screenshot_html}
|
||||
</div>
|
||||
</div>"#,
|
||||
num = i + 1,
|
||||
|
||||
@@ -7,7 +7,18 @@ pub(super) fn cover(
|
||||
target_url: &str,
|
||||
requester_name: &str,
|
||||
requester_email: &str,
|
||||
app_screenshot_b64: Option<&str>,
|
||||
) -> String {
|
||||
let screenshot_html = app_screenshot_b64
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|b64| {
|
||||
format!(
|
||||
r#"<div style="margin: 20px auto; max-width: 560px; border: 1px solid #cbd5e1; border-radius: 8px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.08);">
|
||||
<img src="data:image/png;base64,{b64}" alt="Application screenshot" style="width:100%;display:block;"/>
|
||||
</div>"#
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
format!(
|
||||
r##"<!-- ═══════════════ COVER PAGE ═══════════════ -->
|
||||
<div class="cover">
|
||||
@@ -42,6 +53,8 @@ pub(super) fn cover(
|
||||
<strong>Prepared for:</strong> {requester_name} ({requester_email})
|
||||
</div>
|
||||
|
||||
{screenshot_html}
|
||||
|
||||
<div class="cover-footer">
|
||||
Compliance Scanner — AI-Powered Security Assessment Platform
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,50 @@ pub(super) fn build_html_report(ctx: &ReportContext) -> String {
|
||||
names
|
||||
};
|
||||
|
||||
// Find the best app screenshot for the cover page:
|
||||
// prefer the first navigate to the target URL that has a screenshot,
|
||||
// falling back to any navigate with a screenshot
|
||||
let app_screenshot: Option<String> = ctx
|
||||
.attack_chain
|
||||
.iter()
|
||||
.filter(|n| n.tool_name == "browser")
|
||||
.filter_map(|n| {
|
||||
n.tool_output
|
||||
.as_ref()?
|
||||
.get("screenshot_base64")?
|
||||
.as_str()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
})
|
||||
// Skip the Keycloak login page screenshots — prefer one that shows the actual app
|
||||
.find(|_| {
|
||||
ctx.attack_chain
|
||||
.iter()
|
||||
.filter(|n| n.tool_name == "browser")
|
||||
.any(|n| {
|
||||
n.tool_output
|
||||
.as_ref()
|
||||
.and_then(|o| o.get("title"))
|
||||
.and_then(|t| t.as_str())
|
||||
.is_some_and(|t| t.contains("Compliance") || t.contains("Dashboard"))
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
// Fallback: any screenshot
|
||||
ctx.attack_chain
|
||||
.iter()
|
||||
.filter(|n| n.tool_name == "browser")
|
||||
.filter_map(|n| {
|
||||
n.tool_output
|
||||
.as_ref()?
|
||||
.get("screenshot_base64")?
|
||||
.as_str()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
})
|
||||
.next()
|
||||
});
|
||||
|
||||
let styles_html = styles::styles();
|
||||
let cover_html = cover::cover(
|
||||
&ctx.target_name,
|
||||
@@ -45,6 +89,7 @@ pub(super) fn build_html_report(ctx: &ReportContext) -> String {
|
||||
&ctx.target_url,
|
||||
&ctx.requester_name,
|
||||
&ctx.requester_email,
|
||||
app_screenshot.as_deref(),
|
||||
);
|
||||
let exec_html = executive_summary::executive_summary(
|
||||
&ctx.findings,
|
||||
|
||||
Reference in New Issue
Block a user