feat(phase-38): automatic intelligence — OSINT enrichment + stale coverage detection

Tarea 4.1 — OSINT Enrichment:
- Add OsintItem model with source_type, severity, CVSS metadata, review flag
- Add Alembic migration b022 with osint_items table and optimized indexes
- Add osint_enrichment_service with NVD API integration, deduplication, rate limiting
- Add OSINT router: GET /osint/items, /osint/summary, /osint/technique/{id}
- Add POST /osint/items/{id}/review to mark items as reviewed
- Add POST /osint/enrich/{technique_id} for manual single-technique enrichment
- Techniques with new CVEs are automatically flagged review_required=True
- Register weekly enrichment job in APScheduler
- Add NVD_API_KEY config setting for optional increased rate limits

Tarea 4.2 — Stale Coverage Detection:
- Add stale_detection_service that flags techniques with no validated test
  in the last N days, or never-validated but with a coverage status
- Configurable threshold via STALE_THRESHOLD_DAYS setting (default 365)
- Register daily stale detection job in APScheduler
- Only flags techniques not already marked review_required
This commit is contained in:
2026-02-17 17:47:47 +01:00
parent 31e116b4ba
commit 222979574a
9 changed files with 607 additions and 2 deletions

View File

@@ -21,6 +21,8 @@ from app.services.notification_service import cleanup_old_notifications
from app.services.snapshot_service import create_snapshot, cleanup_old_snapshots
from app.services.campaign_scheduler_service import check_and_run_recurring_campaigns
from app.jobs.jira_sync_job import sync_all_jira_links
from app.services.osint_enrichment_service import enrich_all_techniques
from app.services.stale_detection_service import detect_stale_coverage
logger = logging.getLogger(__name__)
@@ -108,6 +110,32 @@ def _run_intel_scan() -> None:
db.close()
def _run_osint_enrichment() -> None:
"""Execute weekly OSINT enrichment inside its own DB session."""
logger.info("Scheduled OSINT enrichment job starting...")
db = SessionLocal()
try:
total = enrich_all_techniques(db)
logger.info("OSINT enrichment finished — %d new items", total)
except Exception:
logger.exception("OSINT enrichment job failed")
finally:
db.close()
def _run_stale_detection() -> None:
"""Execute daily stale coverage detection inside its own DB session."""
logger.info("Scheduled stale coverage detection starting...")
db = SessionLocal()
try:
count = detect_stale_coverage(db)
logger.info("Stale detection finished — %d techniques flagged", count)
except Exception:
logger.exception("Stale coverage detection job failed")
finally:
db.close()
# ---------------------------------------------------------------------------
# Scheduler bootstrap
# ---------------------------------------------------------------------------
@@ -173,9 +201,26 @@ def start_scheduler() -> None:
name="Jira link sync (hourly)",
replace_existing=True,
)
scheduler.add_job(
_run_osint_enrichment,
trigger="interval",
weeks=1,
id="osint_enrichment",
name="OSINT enrichment (weekly)",
replace_existing=True,
)
scheduler.add_job(
_run_stale_detection,
trigger="interval",
hours=24,
id="stale_detection",
name="Stale coverage detection (daily)",
replace_existing=True,
)
scheduler.start()
logger.info(
"Background scheduler started — mitre_sync (24h), intel_scan (7d), "
"notification_cleanup (24h), weekly_snapshot (Sundays 00:00), "
"recurring_campaigns (daily), jira_sync (1h)"
"recurring_campaigns (daily), jira_sync (1h), "
"osint_enrichment (weekly), stale_detection (daily)"
)