updated the new feature

This commit is contained in:
rarebuffalo
2026-05-07 10:35:36 +05:30
parent aec30fb42f
commit 8e45532240
10 changed files with 162 additions and 32 deletions

View File

@@ -170,3 +170,40 @@ async def generate_threat_narrative(context_data: dict) -> str:
result = await call_ai(prompt, temperature=0.7)
return result or "Could not generate threat narrative."
async def generate_diff_narrative(diff_data: dict) -> str:
"""
Generates a plain-English summary of the changes between two scans.
Given the resolved, new, and persisting issues plus the score change,
the model writes a short paragraph explaining what improved, what
regressed, and what still needs attention — written for a developer
who wants to understand progress at a glance.
"""
if not settings.effective_ai_key:
return "AI narration is disabled because no AI API key is configured."
score_change = diff_data.get("score_change", 0)
resolved = diff_data.get("resolved_issues", [])
new_issues = diff_data.get("new_issues", [])
persisting = diff_data.get("persisting_issues", [])
prompt = (
"You are SecureLens AI, a cybersecurity assistant. "
"A developer has run two security scans on the same URL at different points in time. "
"Here is the comparison between the two scans:\n\n"
f"Score change: {score_change:+d} points\n"
f"Issues resolved since last scan ({len(resolved)}): "
f"{json.dumps([i.get('issue') for i in resolved])}\n"
f"New issues found ({len(new_issues)}): "
f"{json.dumps([i.get('issue') for i in new_issues])}\n"
f"Issues still present ({len(persisting)}): "
f"{json.dumps([i.get('issue') for i in persisting])}\n\n"
"Write a short, plain-English summary (2-4 sentences) for the developer. "
"Mention what improved, flag any new regressions if they exist, and note what still needs work. "
"Be direct and practical — no fluff."
)
result = await call_ai(prompt, temperature=0.4)
return result or "Could not generate diff narrative."

View File

@@ -0,0 +1,64 @@
"""
Webhook Dispatcher
==================
Shared utility for firing HMAC-signed webhook POST requests.
Previously the dispatch logic lived inline inside scan.py. Moving it here
means both the scan router and the background scheduler can call the same
function without creating a circular import.
"""
import hashlib
import hmac
import json
import logging
import httpx
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.webhook import Webhook
logger = logging.getLogger(__name__)
async def dispatch_webhooks(user_id: str, scan_data: dict, db: AsyncSession) -> None:
"""
Fetch all active webhooks for a user and POST the scan_data payload to each.
The payload is JSON-encoded and signed with HMAC-SHA256 if the webhook has
a secret key set. The signature is sent in the X-SecureLens-Signature header
so the receiving server can verify the request is genuine.
Failures are logged but never re-raised — a broken webhook should never
crash or block the scan response.
"""
result = await db.execute(
select(Webhook).where(
Webhook.user_id == user_id,
Webhook.is_active == True, # noqa: E712
)
)
hooks = result.scalars().all()
if not hooks:
return
payload = json.dumps(scan_data).encode("utf-8")
async with httpx.AsyncClient() as client:
for hook in hooks:
headers = {"Content-Type": "application/json"}
if hook.secret_key:
sig = hmac.new(
hook.secret_key.encode(), payload, hashlib.sha256
).hexdigest()
headers["X-SecureLens-Signature"] = sig
try:
await client.post(
hook.target_url, content=payload, headers=headers, timeout=5.0
)
logger.debug(f"Webhook fired: {hook.target_url}")
except Exception as e:
logger.warning(f"Webhook {hook.target_url} failed: {e}")