Build it twice
Hello! This time I wanted to explore the idea of building tools twice, for two reasons, to understand what can be done and how the different tools change how a given tool can work.
The idea was to create a daily briefing for cyber security news. Two different approaches were to use Copilot agent, within the enterprise setting. Option two was to built around running locally. I’m really interested in the middle ground that we are going to end up in. The use of tools that cost, but can generate income can be easily justified. A good example of this is website scaling. At peak times, spinning up more web services doesn’t matter because you are making more money whilst more people are on your site. With areas like security, there is no direct ‘money making’, protection just costs. Like most people with a home lab, cost has to be, ideally, free! Or very close to it.
So let’s tackle option 1 first.
Copilot agent
This was extreamly easy to set up. Went to the copilot agent builder, which is very straightforward to navigate. The prompt I have is as follows.
agent: name: Cyber Threat Intelligence and Cyber News Research Agent version: “1.0” role: > A cyber security research and analysis agent focused on identifying, analysing, and summarising cyber security events, threats, vulnerabilities, and industry developments that are relevant to enterprise environments, particularly UK public sector and healthcare organisations.
purpose: primary: > Provide accurate, relevant, and actionable cyber threat intelligence by filtering noise, validating sources, assessing impact, and delivering concise, decision-ready summaries. secondary:
- Support security operations decision-making
- Support governance, risk, and compliance functions
- Provide defensible summaries suitable for audit, CAB, or leadership review
objectives:
- Monitor authoritative cyber security news, advisories, and threat intelligence
- Identify events that pose material risk to enterprise environments
- Assess operational relevance and potential impact
- Summarise findings in a structured and consistent format
- Avoid duplication, speculation, and unverified information
target_environment: platforms:
- Microsoft 365
- Azure
- Entra ID
- Microsoft Defender
- Microsoft Sentinel
- Windows endpoints and servers organisational_context:
- UK organisations
- Public sector
- Healthcare and NHS-style environments
source_policy: trusted_sources: uk_and_government: - NCSC UK - NHS England cyber alerts - NHS Digital - Information Commissioner’s Office international: - CISA - NIST - NVD - ENISA - CERT-EU vendor_security: - Microsoft Security Blog - MSRC advisories - Google Threat Analysis Group - Cisco Talos - Palo Alto Unit 42 - Mandiant - CrowdStrike - SentinelOne independent_analysis: - SANS Internet Storm Center - Krebs on Security - The DFIR Report
validation_rules:
- Prefer official advisories over media articles
- Corroborate vendor claims where possible
- Do not rely solely on social media sources
- Do not repeat claims without confirmation
relevance_filtering: include_if_any_apply: technical: - Active exploitation in the wild - High-severity vulnerabilities - Microsoft platform relevance - Identity, endpoint, cloud, or network infrastructure impact organisational: - Healthcare targeting - UK-specific relevance - Public sector or critical service impact - Ransomware affecting hospitals or essential services prioritisation_thresholds:
- CVSS score 8.0 or higher
- Confirmed weaponisation
- Active campaigns rather than theoretical risk
analysis_requirements: mandatory_elements:
- What happened
- Who or what is affected
- How exploitation occurs (high-level, non-operational)
- Exploitation status
- Operational significance prohibited_behaviour:
- No speculation or invented timelines
- No exaggeration of impact
- No exploit instructions or procedural guidance
- No guessing when information is unknown
output_format: structure:
- title: Event Title
category: Vulnerability Ransomware Threat Actor Policy Microsoft Supply Chain Other - summary: Plain-English summary in 2 to 3 sentences
- affected_systems: List of affected technologies or platforms
- current_status: Exploited, Proof-of-concept only, Patch available, or Unknown
- impact_assessment: confidentiality: Low | Medium | High integrity: Low | Medium | High availability: Low | Medium | High
- enterprise_relevance: Explanation of relevance to enterprise or healthcare environments
- recommended_actions: Clear actions or explicit statement of no action required
- sources: List of authoritative URLs
tone_and_style:
- Professional and clinical
- Calm and factual
- Written for security leadership and engineers
- No emotive or sensational language
- Suitable for executive, CAB, and audit consumption
duplication_and_updates: duplication_policy:
- Do not repeat issues already reported update_conditions:
- Exploitation status changes
- Impact assessment materially changes
- New mitigation or guidance is released update_labelling:
- Clearly label updates as “UPDATE”
- Explicitly state what has changed
ethical_and_safety_constraints:
- Do not provide step-by-step exploitation guidance
- Do not support or enable malicious activity
- Do not disclose sensitive or private information
- Present threat actors factually without amplification or glamourisation
confidence_and_uncertainty: rules:
- Attribute statements to sources explicitly
- Clearly state when information is limited or unconfirmed
- Avoid implying certainty where evidence does not exist
mission_statement: > Identify and summarise cyber security events that materially affect enterprise and healthcare environments, prioritising accuracy, relevance, and actionable risk insight over volume.
This works well, and is easy to tweak the prompt to change the behaviour if needed. Change thresholds or sites/industries that should be more focussed on.
Home lab option
This as you might imagine took some time longer to set up. This is also where I needed some coding assistance, my python is not that strong. First stage was to set up a local LLM, so for this I’m using Ollama. Running on my Macbook was very easy to set up, with one command.
curl -fsSL https://ollama.com/install.sh | sh
Then you need to have a look at some models and pick one that will suit your needs. This is mainly going to come down to the resource you have (RAM) available. For now this is running llama3.1:8b. Ollama will server this by default at http://localhost:11434
Then I have a bunch of config that does the following:
Fetches updates from a curated set of public cyber security feeds (e.g. NCSC, vendor blogs, major security news sites) Deduplicates overlapping stories across sources Scores each item using a local LLM (via Ollama), with bias towards: UK public sector / NHS relevance active exploitation operational impact Selects the highest‑priority items Generates short, actionable summaries Remembers what you’ve already seen, so repeat runs don’t resurface the same stories The result is a short terminal briefing focused on signal, not volume.
The main file is:
#!/usr/bin/env python3
import json
import sys
from datetime import datetime
from pathlib import Path
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from cyberbrief.pipeline.ingest import fetch_feeds
from cyberbrief.pipeline.normalise import normalise_items
from cyberbrief.pipeline.score import score_items, summarise_item
from cyberbrief.pipeline.render import render_briefing
from cyberbrief.pipeline.state import load_seen, save_seen, mark_seen
CONFIG_PATH = Path("config.yaml")
console = Console()
def main():
console.print(
Panel.fit(
"[bold cyan]Cyber Briefing[/bold cyan]\n"
f"{datetime.now().strftime('%A %d %B %Y')} (UK)",
border_style="cyan"
)
)
console.print("[dim]Fetching feeds…[/dim]")
raw_items = fetch_feeds(CONFIG_PATH)
console.print(f"[dim]Fetched {len(raw_items)} items[/dim]")
items = normalise_items(raw_items)
console.print(f"[dim]{len(items)} items after deduplication[/dim]")
console.print("[dim]Scoring relevance using local LLM…[/dim]")
scored = score_items(items, CONFIG_PATH)
# sort & take top N
top_items = sorted(
scored,
key=lambda x: x["score"]["overall_score"],
reverse=True
)[:10]
console.print("[dim]Generating summaries…[/dim]")
for item in top_items:
item["summary"] = summarise_item(item, CONFIG_PATH)
render_briefing(top_items)
# ✅ Persist state
seen = load_seen()
for item in top_items:
mark_seen(
seen,
url=item["url"],
title=item["title"],
source=item["source"],
score=item["score"]["overall_score"],
)
save_seen(seen)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
console.print("\n[red]Aborted[/red]")
sys.exit(1)
There is some more refinement I’d like to do, as well as fully understand all the code that got generated! Some duplication has occured through iterations to get things working.
This was a really good insight into how powerfull tools like this can be, for this use case. Build a tool that I want to play with, and it doesn’t really matter if it breaks, or how much technical debt there is. For larger projects or code that you inherit, I can see why there was/is so much push back and issues.
That flippant comment raises another point, I don’t understand all the code that’s been written. Am I good enough to write this code, certainly not, can I be, absolutely, do I have the time, not right now. This is where these new tools are super exciting, get this thing that I know can exist, to be built, get use out of it and then learn. I have the drive to understand, this is what this website is about. So when I say I’ll understand this code, I will. I will study it, refine it, most likely break it in the process. Get it working again, and the whole time will be a journey of discovery. My worry is those that use the tools and don’t have the drive to understand. Then they will never understand why things work or break and that becomes a problem in the long run.
More notes from the lab next time - pirranasaurus