{% extends "base.html" %} {% block title %}Feeds · ai-protect{% endblock %} {% block autorefresh %}window.AI_PROTECT_NO_AUTOREFRESH = true;{% endblock %} {% block content %}

Intel feeds

External CVE / threat feeds. Background poller fetches at each feed's interval; items land in Intel. Force-fetch and validator are below each feed.

Additive, not a replacement. Feeds supplement the existing scanner pipeline (pip_audit, grype, osv_scanner, dependency_check, etc.) — they don't replace it. Scanner findings remain the source of truth for what's actually in your apps; intel items provide external context (severity scoring, disclosure timing, mitigation links) for the same CVEs. Cross-references are visible on each Finding detail page when a CVE id matches.

Add a feed

Short label shown in the feed list.
The HTTP(S) URL of the feed. Format is auto-detected on save — atom / rss / xml / json. The validator fetches once to preview what's detected.
Minimum 60s. 3600 (1h) is a reasonable default for CVE feeds.
Discover from a page →

Configured feeds

{% macro feed_row(f, show_history=True) -%} {{ f.name }}{% if not f.enabled %} paused{% endif %} {{ f.url }} {{ f.format }} {{ f.poll_seconds }}s {% if f.last_status == 'ok' %}ok {% elif f.last_status == 'http_error' %}http error {% elif f.last_status == 'translator_error' %}translator {% elif f.last_status %}{{ f.last_status }} {% else %}never {% endif %} {{ f.last_fetch_ts|localtime }} {{ f.last_item_count if f.last_item_count is not none else '—' }} / {{ f.last_new_count if f.last_new_count is not none else '—' }}
Edit
View intel → {% if show_history %} {% set hist = history_by_id.get(f.feed_id, []) %} {% if hist %}
Recent fetches ({{ hist|length }}) {% for h in hist %} {% endfor %}
WhenStatusHTTPItemsNewDurationError
{{ h.ts|localtime }} {{ h.status }} {{ h.http_status or '—' }} {{ h.items_count }} {{ h.new_count }} {{ h.duration_ms }}ms {{ h.error }}
{% endif %} {% endif %} {%- endmacro %} {% set total_feeds = ungrouped|length + groups|sum(attribute='count') %} {% if total_feeds == 0 %}
No feeds configured yet.
{% else %}
{{ total_feeds }} feed{{ 's' if total_feeds != 1 else '' }} total {%- if groups %} · {{ ungrouped|length }} individual · {{ groups|length }} group{{ 's' if groups|length != 1 else '' }} ({{ groups|sum(attribute='count') }} feeds collapsed){% endif %}
{# Ungrouped feeds — the user's hand-curated set sits in the main table. #} {% if ungrouped %} {% for f in ungrouped %} {{ feed_row(f, show_history=True) }} {% endfor %}
NameURLFormatPoll StatusLast fetchItems / newActions
{% endif %} {# Auto-collapsed groups — large clusters of feeds sharing a URL prefix (e.g. cvedaily.com/feed-tags/* with 800+ per-tag feeds) get hidden behind a
so the main view stays scannable. #} {% for g in groups %}
{{ g.prefix }} {{ g.count }} feeds {{ g.ok }} ok {% if g.errored %}{{ g.errored }} errored{% endif %} {% if g.never %}{{ g.never }} never fetched{% endif %} items in store: {{ g.total_items }} {% if g.last_fetch_ts %}last fetch: {{ g.last_fetch_ts|localtime }}{% endif %} {% for f in g.feeds %} {{ feed_row(f, show_history=False) }} {% endfor %}
NameURLFormatPoll StatusLast fetchItems / newActions
{% endfor %} {% endif %}
{% endblock %}