diff --git a/package/secubox/luci-app-ai-insights/htdocs/luci-static/resources/ai-insights/api.js b/package/secubox/luci-app-ai-insights/htdocs/luci-static/resources/ai-insights/api.js index bb025d29..b2e33811 100644 --- a/package/secubox/luci-app-ai-insights/htdocs/luci-static/resources/ai-insights/api.js +++ b/package/secubox/luci-app-ai-insights/htdocs/luci-static/resources/ai-insights/api.js @@ -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, diff --git a/package/secubox/luci-app-ai-insights/htdocs/luci-static/resources/view/ai-insights/dashboard.js b/package/secubox/luci-app-ai-insights/htdocs/luci-static/resources/view/ai-insights/dashboard.js index 175a1c82..6b6fbfcf 100644 --- a/package/secubox/luci-app-ai-insights/htdocs/luci-static/resources/view/ai-insights/dashboard.js +++ b/package/secubox/luci-app-ai-insights/htdocs/luci-static/resources/view/ai-insights/dashboard.js @@ -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...') diff --git a/package/secubox/luci-app-ai-insights/root/usr/libexec/rpcd/luci.ai-insights b/package/secubox/luci-app-ai-insights/root/usr/libexec/rpcd/luci.ai-insights index dd1ef0ae..acb4db4c 100644 --- a/package/secubox/luci-app-ai-insights/root/usr/libexec/rpcd/luci.ai-insights +++ b/package/secubox/luci-app-ai-insights/root/usr/libexec/rpcd/luci.ai-insights @@ -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") diff --git a/package/secubox/luci-app-ai-insights/root/usr/share/rpcd/acl.d/luci-app-ai-insights.json b/package/secubox/luci-app-ai-insights/root/usr/share/rpcd/acl.d/luci-app-ai-insights.json index 6c3c601e..be240370 100644 --- a/package/secubox/luci-app-ai-insights/root/usr/share/rpcd/acl.d/luci-app-ai-insights.json +++ b/package/secubox/luci-app-ai-insights/root/usr/share/rpcd/acl.d/luci-app-ai-insights.json @@ -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": {