feat(ai-insights): Add CVE feed panel to dashboard

- Add get_cve_feed RPCD method fetching from NVD API
- Add CVE feed panel showing recent vulnerabilities with CVSS scores
- Cache CVE feed for 30 minutes to reduce API calls
- Link CVE IDs to NVD detail pages
- Color-code severity (critical/high/medium/low)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-12 09:43:41 +01:00
parent 4f0e7d7cc4
commit 040b69ad1d
4 changed files with 109 additions and 2 deletions

View File

@ -47,6 +47,13 @@ var callAnalyze = rpc.declare({
expect: { }
});
var callGetCVEFeed = rpc.declare({
object: 'luci.ai-insights',
method: 'get_cve_feed',
params: ['limit'],
expect: { }
});
function getPostureColor(score) {
if (score >= 80) return 'success';
if (score >= 60) return 'warning';
@ -104,6 +111,7 @@ return baseclass.extend({
getTimeline: callGetTimeline,
runAll: callRunAll,
analyze: callAnalyze,
getCVEFeed: callGetCVEFeed,
getPostureColor: getPostureColor,
getPostureLabel: getPostureLabel,

View File

@ -12,12 +12,21 @@
*/
return view.extend({
cveCache: [],
load: function() {
var self = this;
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = L.resource('ai-insights/dashboard.css');
document.head.appendChild(link);
return api.getOverview().catch(function() { return {}; });
return Promise.all([
api.getOverview().catch(function() { return {}; }),
api.getCVEFeed(10).catch(function() { return { cves: [] }; })
]).then(function(results) {
self.cveCache = (results[1] || {}).cves || [];
return results[0];
});
},
render: function(data) {
@ -68,6 +77,15 @@ return view.extend({
E('span', { 'class': 'ai-badge' }, String(alerts.length))
]),
E('div', { 'class': 'ai-card-body', 'id': 'ai-alerts' }, this.renderAlerts(alerts))
]),
// CVE Feed card
E('div', { 'class': 'ai-card' }, [
E('div', { 'class': 'ai-card-header' }, [
'⚠️ CVE Feed (Recent)',
E('span', { 'class': 'ai-badge warning' }, String(this.cveCache.length))
]),
E('div', { 'class': 'ai-card-body', 'id': 'ai-cves' }, this.renderCVEs(this.cveCache))
])
]);
@ -159,6 +177,32 @@ return view.extend({
}));
},
renderCVEs: function(cves) {
if (!cves || !cves.length) {
return E('div', { 'class': 'ai-empty' }, 'Loading CVE feed...');
}
return E('div', { 'class': 'ai-cve-list' }, cves.slice(0, 10).map(function(cve) {
var score = cve.score || 0;
var severity = score >= 9.0 ? 'critical' : score >= 7.0 ? 'high' : score >= 4.0 ? 'medium' : 'low';
var severityClass = score >= 9.0 ? 'danger' : score >= 7.0 ? 'warning' : score >= 4.0 ? 'caution' : 'success';
return E('div', { 'class': 'ai-cve-item' }, [
E('div', { 'class': 'ai-cve-score ' + severityClass }, [
E('span', { 'class': 'score-value' }, score.toFixed(1)),
E('span', { 'class': 'score-label' }, severity.toUpperCase())
]),
E('div', { 'class': 'ai-cve-content' }, [
E('div', { 'class': 'ai-cve-id' }, [
E('a', { 'href': 'https://nvd.nist.gov/vuln/detail/' + cve.id, 'target': '_blank' }, cve.id)
]),
E('div', { 'class': 'ai-cve-desc' }, (cve.description || '').substring(0, 120) + '...')
]),
E('div', { 'class': 'ai-cve-date' }, cve.published || '')
]);
}));
},
runAllAgents: function() {
ui.showModal('Running Agents', [
E('p', { 'class': 'spinning' }, 'Starting all AI agents...')

View File

@ -104,6 +104,7 @@ case "$1" in
"get_alerts": {"limit": 50},
"get_posture": {},
"get_timeline": {"hours": 24},
"get_cve_feed": {"limit": 10},
"run_all": {},
"analyze": {}
}
@ -268,6 +269,60 @@ EOF
echo "$results"
;;
get_cve_feed)
read -r input
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null)
[ -z "$limit" ] && limit=10
# Cache file for CVE feed (refresh every 30 min)
cache_file="/tmp/cve_feed_cache.json"
cache_age=1800
# Check cache
if [ -f "$cache_file" ]; then
age=$(($(date +%s) - $(stat -c %Y "$cache_file" 2>/dev/null || echo 0)))
if [ "$age" -lt "$cache_age" ]; then
cat "$cache_file"
exit 0
fi
fi
# Fetch from NVD API (last 3 days)
start_date=$(date -d "3 days ago" +%Y-%m-%dT00:00:00.000 2>/dev/null || date -u +%Y-%m-%dT00:00:00.000)
end_date=$(date +%Y-%m-%dT23:59:59.999 2>/dev/null || date -u +%Y-%m-%dT23:59:59.999)
response=$(curl -s --max-time 15 \
"https://services.nvd.nist.gov/rest/json/cves/2.0?pubStartDate=${start_date}&pubEndDate=${end_date}&resultsPerPage=${limit}" 2>/dev/null)
if [ -n "$response" ]; then
# Parse and format CVEs
json_init
json_add_array "cves"
echo "$response" | jsonfilter -e '@.vulnerabilities[*]' 2>/dev/null | while read -r vuln; do
cve_id=$(echo "$vuln" | jsonfilter -e '@.cve.id' 2>/dev/null)
desc=$(echo "$vuln" | jsonfilter -e '@.cve.descriptions[0].value' 2>/dev/null | head -c 200 | sed 's/"/\\"/g')
score=$(echo "$vuln" | jsonfilter -e '@.cve.metrics.cvssMetricV31[0].cvssData.baseScore' 2>/dev/null)
[ -z "$score" ] && score=$(echo "$vuln" | jsonfilter -e '@.cve.metrics.cvssMetricV2[0].cvssData.baseScore' 2>/dev/null)
[ -z "$score" ] && score="0"
published=$(echo "$vuln" | jsonfilter -e '@.cve.published' 2>/dev/null | cut -c1-10)
json_add_object ""
json_add_string "id" "$cve_id"
json_add_string "description" "$desc"
json_add_double "score" "$score"
json_add_string "published" "$published"
json_close_object
done
json_close_array
json_dump > "$cache_file"
cat "$cache_file"
else
echo '{"cves":[],"error":"Failed to fetch CVE feed"}'
fi
;;
analyze)
# Get AI security analysis
localai_url=$(uci -q get localrecall.main.localai_url || echo "http://127.0.0.1:8091")

View File

@ -3,7 +3,7 @@
"description": "Grant access to AI Insights Dashboard",
"read": {
"ubus": {
"luci.ai-insights": ["status", "get_alerts", "get_posture", "get_timeline"]
"luci.ai-insights": ["status", "get_alerts", "get_posture", "get_timeline", "get_cve_feed"]
}
},
"write": {