<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: GT</title>
    <description>The latest articles on DEV Community by GT (@gt_0e9845b9b3a41f1b75b5de).</description>
    <link>https://dev.to/gt_0e9845b9b3a41f1b75b5de</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3790709%2F0f4c4087-e0a1-4c5e-9d7b-1756c0002286.jpg</url>
      <title>DEV Community: GT</title>
      <link>https://dev.to/gt_0e9845b9b3a41f1b75b5de</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gt_0e9845b9b3a41f1b75b5de"/>
    <language>en</language>
    <item>
      <title>How I Built a Live Cybersecurity Intelligence Dashboard on a Raspberry Pi 5</title>
      <dc:creator>GT</dc:creator>
      <pubDate>Wed, 25 Feb 2026 02:48:18 +0000</pubDate>
      <link>https://dev.to/gt_0e9845b9b3a41f1b75b5de/how-i-built-a-live-cybersecurity-intelligence-dashboard-on-a-raspberry-pi-5-231j</link>
      <guid>https://dev.to/gt_0e9845b9b3a41f1b75b5de/how-i-built-a-live-cybersecurity-intelligence-dashboard-on-a-raspberry-pi-5-231j</guid>
      <description>&lt;p&gt;A few weeks ago I got tired of manually trawling through 15+ security blogs, RSS feeds, and Twitter/X accounts every morning. So I built an automated cybersecurity intelligence dashboard that runs 24/7 on a Raspberry Pi 5 sitting on my desk.&lt;/p&gt;

&lt;p&gt;Here's what it does, how I built it, and every mistake I made along the way.&lt;/p&gt;

&lt;p&gt;What it does&lt;br&gt;
signal-noise.tech is a live threat intelligence feed that:&lt;/p&gt;

&lt;p&gt;Aggregates 18 cybersecurity RSS sources (The Hacker News, BleepingComputer, CISA advisories, Krebs, Unit 42, Cisco Talos, and more)&lt;br&gt;
Refreshes every 30 minutes&lt;br&gt;
Classifies stories by severity (Critical/Gov/Vendor/Media)&lt;br&gt;
Detects 30+ known threat actors in headlines (Lazarus, ALPHV, Volt Typhoon, etc.)&lt;br&gt;
Counts CVEs tracked, critical headlines, CISA advisories — live KPIs on the homepage&lt;br&gt;
Auto-posts the top stories to @SignalOverNoizX on X&lt;br&gt;
Everything runs on a Pi 5 behind a domestic broadband connection with port forwarding.&lt;/p&gt;

&lt;p&gt;The stack&lt;br&gt;
Hardware: Raspberry Pi 5, 8GB RAM, running Raspberry Pi OS Bookworm 64-bit&lt;br&gt;
Web server: Apache2 with SSL (Let's Encrypt), reverse proxy headers, security hardening&lt;br&gt;
Backend: Python 3 scripts, scheduled via cron.d&lt;br&gt;
AI: GPT-5-mini for tweet commentary generation&lt;br&gt;
Frontend: Vanilla HTML/CSS/JS (no frameworks — keeps it fast on a Pi)&lt;br&gt;
Feed data: news.json served as a static file, rebuilt every 30 minutes&lt;br&gt;
The feed pipeline&lt;br&gt;
The core of the system is update_news.py. Every 30 minutes it:&lt;/p&gt;

&lt;p&gt;Fetches all 18 RSS feeds concurrently with a thread pool&lt;br&gt;
Deduplicates by URL&lt;br&gt;
Attempts to pull og:image for each story (for the card thumbnails)&lt;br&gt;
Falls back to Bing image search if no OG image is found&lt;br&gt;
Generates a branded dark-themed placeholder card if all else fails&lt;br&gt;
Scores stories by recency and source tier&lt;br&gt;
Writes news.json to the web root&lt;/p&gt;

&lt;p&gt;with ThreadPoolExecutor(max_workers=10) as ex:&lt;br&gt;
    futures = {ex.submit(fetch_feed, src): src for src in SOURCES}&lt;br&gt;
    for f in as_completed(futures):&lt;br&gt;
        items.extend(f.result())&lt;br&gt;
The JSON structure is dead simple:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "generated_at": "2026-02-25T14:30:00Z",&lt;br&gt;
  "items": [&lt;br&gt;
    {&lt;br&gt;
      "title": "Critical RCE in Ivanti products under active exploitation",&lt;br&gt;
      "url": "https://...",&lt;br&gt;
      "source": "BleepingComputer",&lt;br&gt;
      "published": "2026-02-25T12:00:00Z",&lt;br&gt;
      "image": "https://..."&lt;br&gt;
    }&lt;br&gt;
  ]&lt;br&gt;
}&lt;br&gt;
The frontend is a single JS file that fetches news.json and renders everything client-side. No database, no API calls at page load — just a static JSON file. The Pi barely breaks a sweat.&lt;/p&gt;

&lt;p&gt;The hardest part: getting decent images&lt;br&gt;
Security articles are plagued with terrible default images — the CISA US flag, padlock stock photos, vendor logos. I built a three-level fallback:&lt;/p&gt;

&lt;p&gt;Parse og:image from the article HTML&lt;br&gt;
Query Bing Image Search API for the article title&lt;br&gt;
Generate a branded dark-themed card with PIL (Python Imaging Library)&lt;br&gt;
The generated cards look surprisingly good — dark cyber aesthetic, company name, colour-coded by source type. Here's the basic approach:&lt;/p&gt;

&lt;p&gt;img = Image.new('RGB', (800, 400), color='#0a0f1c')&lt;br&gt;
draw = ImageDraw.Draw(img)&lt;/p&gt;

&lt;h1&gt;
  
  
  dot grid background
&lt;/h1&gt;

&lt;p&gt;for y in range(0, 400, 28):&lt;br&gt;
    for x in range(0, 800, 28):&lt;br&gt;
        draw.ellipse([x-1, y-1, x+1, y+1], fill='#1a2744')&lt;/p&gt;

&lt;h1&gt;
  
  
  company name centred
&lt;/h1&gt;

&lt;p&gt;draw.text((400, 200), company_name, font=font, fill=accent_colour, anchor='mm')&lt;br&gt;
The X automation&lt;br&gt;
Four times a day, GPT-5-mini reads the top story and generates commentary in the voice of a sharp, opinionated security analyst. Not "Here's the latest news from BleepingComputer" — actual takes:&lt;/p&gt;

&lt;p&gt;System: You are a sharp, opinionated cybersecurity analyst with dry humour.&lt;br&gt;
        Rules: max 220 chars, no hashtags, no sycophancy, never promotional.&lt;br&gt;
        Sound human. Sound confident. Make people want to follow you.&lt;/p&gt;

&lt;p&gt;User: Tweet about: Critical RCE in Palo Alto GlobalProtect...&lt;br&gt;
The model is a reasoning model, which means it burns internal "thinking tokens" before producing output. Important gotchas I learned the hard way:&lt;/p&gt;

&lt;p&gt;Use max_completion_tokens, NOT max_tokens (different parameter name)&lt;br&gt;
Set it to at least 4000 — at 1000 it often burns everything on reasoning and returns empty output&lt;br&gt;
Minimum 60s timeout — reasoning can take 30-45 seconds&lt;br&gt;
Temperature parameter is NOT supported (reasoning models ignore it)&lt;br&gt;
Security hardening the Pi&lt;br&gt;
Exposing a Pi to the internet requires some care. Things I did:&lt;/p&gt;

&lt;p&gt;ServerTokens Prod + ServerSignature Off — hides Apache version&lt;br&gt;
Options -Indexes — no directory listings&lt;br&gt;
/scripts/ blocked in Apache config + robots.txt&lt;br&gt;
Full security header suite: X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Content-Security-Policy&lt;br&gt;
GoAccess analytics behind basic auth, LAN IP restriction only&lt;br&gt;
Credentials in /etc/openclaw/ (root-owned, 600 perms), never in code&lt;br&gt;
The RSS feed&lt;br&gt;
Generating RSS 2.0 from news.json is surprisingly straightforward. The feed is at /feed.xml and updates every 30 minutes. Already picked up by some RSS readers.&lt;/p&gt;

&lt;p&gt;The key is correct RFC-822 date formatting — RSS validators are picky:&lt;/p&gt;

&lt;p&gt;from email.utils import format_datetime&lt;br&gt;
pub_date = format_datetime(dt)  # "Tue, 25 Feb 2026 14:30:00 +0000"&lt;br&gt;
Lessons learned&lt;br&gt;
Static files beat databases for this use case. No query latency, trivially cacheable, survives traffic spikes on a Pi.&lt;/p&gt;

&lt;p&gt;Reasoning models need breathing room. Set max_completion_tokens to 4000 even if your output is 50 characters. The model burns tokens thinking before it writes.&lt;/p&gt;

&lt;p&gt;Image quality matters more than you'd think. The first version showed every article with a padlock clipart or the CISA flag. It looked terrible. The three-level fallback (OG → Bing → generated card) made a massive difference.&lt;/p&gt;

&lt;p&gt;Deduplication by URL, not title. The same CVE advisory gets published by 8 sources simultaneously. Title dedup gives false positives; URL dedup is clean.&lt;/p&gt;

&lt;p&gt;Apache's mod_headers is your friend. Adding security headers takes 10 minutes and dramatically improves security posture.&lt;/p&gt;

&lt;p&gt;What's next&lt;br&gt;
Email newsletter (subscribers get a weekly "Week in Cyber" digest)&lt;br&gt;
Reddit integration for /r/netsec and /r/cybersecurity posting&lt;br&gt;
Mastodon cross-posting to infosec.exchange&lt;br&gt;
The site is live at signal-noise.tech and the X account is @SignalOverNoizX. RSS feed at /feed.xml if that's your thing.&lt;/p&gt;

&lt;p&gt;Happy to answer questions in the comments — especially around the image pipeline and the GPT-5-mini quirks, which took the most debugging.&lt;/p&gt;

</description>
      <category>python</category>
      <category>cybersecurity</category>
      <category>showdev</category>
      <category>raspberrypi</category>
    </item>
  </channel>
</rss>
