A real-time security intelligence feed for Linux, cloud, and Kubernetes security content: security advisories, CVEs, threats, exploits, and patches.
The UI mirrors the dark "Live Intelligence Feed" design with:
- relative timestamps (
6 hours,1 day) - colored topic/severity tags
- red "urgent" notification dots
- tag filters
- a
VIEW FULL LIVE FEED →footer
Iteration 5. The feed works end-to-end: live sources are fetched, normalized,
enriched (CISA KEV + EPSS + OSV.dev), deduplicated, prioritized, persisted to
PostgreSQL (or SQLite locally), searchable (/api/search), pushed to the
browser over SSE, rendered in a single-page frontend, and urgent items trigger
alerts via Slack/email/log channels.
| Layer | Technology |
|---|---|
| Backend | Python 3.13, FastAPI, httpx, feedparser |
| Frontend | Single-file HTML/CSS/JS (no build step, no CDN) |
| Storage | PostgreSQL primary store, SQLite fallback, in-memory cache |
| Live updates | Server-Sent Events (/api/events) with polling fallback |
| Enrichment | CISA Known Exploited Vulnerabilities + FIRST EPSS + OSV.dev |
| Malware | OpenSSF Malicious Packages (recent OSV reports) |
| Search | /api/search with SQLite fallback or optional OpenSearch |
| Alerting | Slack webhook / SMTP email / log for urgent items; Discord webhook planned as first option |
| Deployment | Docker/Podman compose + Kubernetes manifests |
Planned next: Discord webhook alerting as first alert option, deeper distro patch-status normalization, and OpenSearch auto-sync improvements.
| Source | Kind | Focus |
|---|---|---|
| Ubuntu Security Notices | RSS | Linux |
| Debian Security Advisories | RSS | Linux |
| Red Hat CVE Database | JSON API | Linux / cloud |
| Kubernetes Blog (security-filtered) | RSS | Kubernetes |
| AWS Security Bulletins | RSS | Cloud |
| CISA Cybersecurity Advisories (topic-filtered) | RSS | Threats |
NVD CVE 2.0 (linux kernel, kubernetes, cloud) |
JSON API | CVE |
| OpenSSF Malicious Packages (recent commits) | GitHub API | Supply-chain malware |
- The NVD keyword API returns oldest matches first, so the fetcher reads
totalResultsand requests the last page to obtain the newest CVEs. - CISA and the Kubernetes blog are broad feeds, so items are filtered for Linux/cloud/Kubernetes relevance before entering the feed.
- OpenSSF Malicious Packages uses the GitHub API and only processes new
commits (no 1 GB clone). Set
GITHUB_TOKENto avoid unauthenticated rate limits. By default only Go/git ecosystems or packages mentioning Linux/cloud/Kubernetes tooling are included. - If a source fails, the rest of the feed continues. If all live sources fail, the server serves realistic sample items so the UI is always usable.
.
├── app/
│ ├── main.py # FastAPI application and API routes
│ ├── sources.py # Source definitions
│ ├── fetcher.py # Fetching, normalization, caching
│ ├── enrich.py # CISA KEV + EPSS enrichment
│ ├── osv.py # OSV.dev enrichment (affected/fixed/severity)
│ ├── search.py # Search backend (OpenSearch + SQLite fallback)
│ ├── ossf.py # OpenSSF Malicious Packages GitHub-API source
│ ├── store.py # Storage facade (Postgres or SQLite)
│ └── postgres_store.py # PostgreSQL storage implementation
│ ├── store.py # SQLite persistence
│ ├── events.py # SSE pub/sub broker
│ └── alerts.py # Slack / email / log alerting
├── static/
│ └── index.html # Single-page frontend
├── tests/
│ ├── test_feed.py # Unit tests for feed logic
│ └── test_store.py # Unit tests for persistence
├── deploy/
│ └── k8s/ # Kubernetes manifests (Deployment, Service, PVC, ConfigMap)
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── README.md
└── AGENTS.md
cd /home/ngeorger/feeder
# Install dependencies into ./.pip-packages
python3 -m pip install --target ./.pip-packages -r requirements.txt
# Run the server
PYTHONPATH=./.pip-packages python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8000Open http://localhost:8000.
The first feed refresh runs in the background on startup. Subsequent requests are served from the configured store and refresh every 10 minutes; the browser updates via SSE (
/api/events) and falls back to polling every 5 minutes.
# Without Docker, point the app at any PostgreSQL database:
export DATABASE_URL=postgresql://feed:feed@localhost:5432/feed
PYTHONPATH=./.pip-packages python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8000When DATABASE_URL is unset, the app uses SQLite in ./data/feed.db.
docker compose up --build
# or, with rootless containers available:
podman-compose up --buildThe SQLite database is stored in the feed-data volume.
Current channels (opt-in):
| Variable | Channel |
|---|---|
SLACK_WEBHOOK_URL |
Slack incoming webhook |
ALERT_EMAIL_TO + SMTP_HOST |
SMTP email |
| none | Log-only fallback |
Planned next: DISCORD_WEBHOOK_URL will become the first alert option, with
Slack and email as secondary channels.
docker compose up --build starts the app plus PostgreSQL. The feed service
uses DATABASE_URL=postgresql://feed:feed@postgres:5432/feed.
To add OpenSearch search, run:
docker compose --profile search up --buildThen uncomment OPENSEARCH_URL=http://opensearch:9200 in the feed service
environment. Without OpenSearch, /api/search falls back to SQL (Postgres
ILIKE or SQLite LIKE).
kubectl apply -k deploy/k8sThe deployment uses a ReadWriteOnce PVC for the SQLite archive and exposes
the app as a ClusterIP service on port 80.
| Method | Path | Description |
|---|---|---|
GET |
/ |
Single-page frontend |
GET |
/api/feed |
Normalized feed JSON |
GET |
/api/items |
Search/filter the persistent archive |
GET |
/api/search?q=... |
Full-text search (OpenSearch or SQLite) |
GET |
/api/stats |
Counts by severity/tag |
GET |
/api/events |
Server-Sent Events stream |
GET |
/api/sources |
Configured sources |
GET |
/health |
Cache + DB health |
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
tag |
string | — | Filter by one tag, e.g. kubernetes |
severity |
string | — | Filter by severity, e.g. critical |
limit |
int | 50 |
Max items (1–200) |
Example:
curl 'http://localhost:8000/api/feed?tag=kubernetes&severity=critical&limit=20'{
"id": "a1b2c3d4e5f6a7b8",
"title": "CVE-2024-21626: runc container escape",
"summary": "runc before 1.1.12 contains a container escape…",
"url": "https://example.com/advisory",
"source": "Ubuntu Security Notices",
"source_url": "https://ubuntu.com/security/notices/rss.xml",
"published": "2025-01-01T12:00:00+00:00",
"time_ago": "6 hours",
"tags": ["linux", "kubernetes", "cve", "exploit", "patch"],
"cves": ["CVE-2024-21626"],
"severity": "critical",
"urgent": true,
"kev": true,
"epss_score": 0.97,
"osv_affected": ["Go:runc"],
"osv_fixed": ["1.1.12"],
"osv_severity": "high"
}- Tags are inferred from source scope plus title/summary keywords:
linux,cloud,kubernetes,cve,exploit,patch,threat. - Severity comes from CVSS when available, otherwise from textual heuristics.
- Urgent items are critical/high-severity and exploitation-related; they render the red dot in the UI.
- KEV items are in CISA's Known Exploited Vulnerabilities catalog.
- EPSS is fetched from FIRST when CVEs are present (best-effort).
- OSV.dev adds affected packages, fixed versions, and severity for CVEs (best-effort, capped per refresh).
- The feed is sorted by
urgentfirst, thenpublisheddescending. - Sample/fallback rows are only shown while no live rows are available.
cd /home/ngeorger/feeder
PYTHONPATH=./.pip-packages python3 -m pytest -q- Persistent store (SQLite) and search/filter endpoints
- Enrichment: EPSS, CISA KEV, OSV.dev
- SSE live updates
- Slack / email / log alerts for
urgentitems - Discord webhook alerting as first alert option
- Docker/Podman compose + Kubernetes manifests
- OpenSearch search backend (optional) with SQL fallback
- OpenSSF Malicious Packages source
- PostgreSQL primary store (SQLite fallback when
DATABASE_URLunset) - Distro patch-status normalization
- OpenSearch auto-sync improvements