From one hand-edited page to 810 signed threat-intel dossiers
Blog · turning-points

From one hand-edited page to 810 signed threat-intel dossiers

Lesson from:

How a single HTML file that grew to 1.5 MB became a pipeline that can refuse to publish

On 26 April 2026 one HTML file on my website grew from 896 lines to 3,233 lines in a single day. It was called threat-intel.html, and it held everything I knew about the people attacking my home lab: five investigations in the morning, seven by the evening, each one typed into the same page, styled by hand, checked by eye and copied onto the server.

Six months later the same site carries 810 threat-intelligence dossiers. Nobody edits their HTML. Each one is built from a structured investigation, refused by a machine if it is incomplete or leaks something it should not, rendered, signed, timestamped, committed to Git and re-sealed every day.

This is the story of how I got from the first to the second, and why I had to.

Contents · 10 sections

In one minute #

  • I run an SSH honeypot and web-traffic analysis at home, on one machine in one apartment in Iași. Attackers find it within minutes. I write up what they do.
  • At first, every write-up was a hand-made section in one long web page. It worked for five reports. By forty-three it was a 1.5 MB file that nobody, human or AI, could safely edit.
  • In June 2026 I split the work into a pipeline: a database of investigations, a search API, an indexer, a publishing service and a set of tools that AI agents can call.
  • The turning point was not speed. It was that the pipeline can say no: no hypothesis, no publish; an internal address in the text, no publish.
  • I still publish mistakes. The difference is that corrections are now appended, dated and signed, never quietly overwritten.

The before: a page that grew like a diary #

The honeypot started in March. By 24 April I had enough to show: a page about collective defence that counted 331,000 events and 3,182 attacking addresses, and explained how every attack on my network turns into a block list that protects other people. I wrote about that idea in Every attack here makes others safer.

Two days later I started the dossiers. The roadmap entries from 26 April read like a lab notebook kept by someone who could not stop:

  • three new investigations added, the page grows from 896 to 1,210 lines;
  • the same three rewritten as “deep investigative-quality OSINT reports”, the page reaches 1,800 lines;
  • the whole page rebuilt as a book with a sidebar and print buttons, 2,440 lines, “all 5 dossier contents preserved byte-for-byte”;
  • a seventh dossier mapping 6 crime networks from a single honeypot over 36 days, and the page reaches 3,233 lines.

The record describes the method in plain words: “edited threat-intel.html with 3 new dossier sections matching existing CSS/HTML patterns, updated all stat references, copied to [the] git source”. Every new investigation meant opening a file thousands of lines long, finding the right place, matching the styles of the ones before it, updating the counters at the top, updating the sidebar, and copying the result to two places. Then checking that the <div> tags still balanced.

If you have ever kept a document that started as a page of notes and became the only copy of something important, you know the feeling. It works. You are proud of it. And every edit is a small act of nerve.

The trigger: they came for my own name #

On 8 May the honeypot showed something different. Not the usual background noise of the internet, but two coordinated SSH campaigns aimed at my domain specifically.

One of them had generated its password list from the name of my own domain: different capitalisations, separators, numbers, keyboard patterns appended. Somebody had sat down, or more likely set a tool running, with my site’s name as the seed.

The honeypot did its job. The record ends calmly: “All 42 IPs banned via CrowdSec. Attackers never reached real infrastructure.” The public dossier went live the same day, 721 lines with personal data redacted, and its conclusion was titled “We See Everything”.

But the way it was published says a lot about where I was. It was written as a separate HTML file, then inserted after the previous dossier in the big page, with new CSS rules “to force it visible”, new print rules, a new sidebar link and new counters. By the end of that day there were 11 investigations on one page.

That was the moment the page stopped being a diary and became a liability. The work had real stakes now. A wrong edit could hide a dossier, break the page or, worse, publish something unredacted.

The build: from a page to a pipeline #

A long May #

For most of May I kept going by hand, and kept improving the page instead of the process. On 11 May I turned the long scroll into one-dossier-at-a-time pagination, with keyboard navigation and deep links. On 25 May a dossier about a 326-node botnet was published like this: “Created 311-line dossier HTML, inserted into threat-intel.html, updated CSS/nav/counters, deployed via hostpath copy.”

That sentence is the whole problem. Five manual steps, every time, and no step that could tell me I had done one of them wrong.

Meanwhile the analysis behind the dossiers was getting serious. The honeypot data moved from a single SQLite file into PostgreSQL. Enrichment jobs pulled in registry records, reputation feeds and routing data. A graph started to link addresses to networks, networks to organisations, organisations to people. The investigations were becoming data. The publishing was still typing.

Three services in one morning #

On 16 June, between 10:12 and 10:32, three new projects appear in the roadmap:

  • a dossier search API, a single place to ask questions across the investigations and the honeypot data, using keyword search, semantic search and SQL together;
  • an indexer, a scheduled job that turns investigations, actors, findings and evidence into searchable chunks every 30 minutes;
  • MCP dossier tools, so an AI agent can search, read, and later create investigations through typed calls instead of editing files.

The idea was simple: an investigation is not a web page. It is a hypothesis, a list of actors, findings, evidence, infrastructure and connections between them. The page is just one way to show it.

Splitting the monolith #

The next day, 17 June, I finally cut the big file apart. By then it was 1.5 MB with 43 dossiers inside it. A small Python script extracted every article from a backup of the monolith and rebuilt each one as its own page under /threat-intel/, with shared navigation, previous and next links, a polished index, a sitemap and a small redirect stub so that old links kept working.

On 18 June the publishing service went live and imported 48 dossiers. From then on, a dossier lived in a database row with a slug, a title, a series, an order and its content, and the service generated the static pages from it.

The bugs that only a pipeline could see #

Moving to a pipeline did not make the work clean. It made the dirt visible.

On 23 June a check found 111 broken internal links across the published dossiers. The cause is worth remembering: an AI coding assistant had generated series navigation using slugs that “never existed as actual pages”, and the publisher had renamed slugs over time without updating cross-references. In the hand-made page nobody would have noticed until a reader clicked. Now a crawler was hitting more than 30 old slug variants, and I could count them.

On 24 June I disabled the bulk-import tool and rewrote the agent guide with mandatory rules for doing investigations one step at a time, because bulk imports by agents were producing investigations that looked complete and were not.

On 1 July an observation, marked critical, records that an AI assistant had published a dossier outside the range its own plan covered. The fix was not to trust agents less. It was to make the system check what they had done.

The technical heart: a publisher that can say no #

The real turning point is a set of refusals. Here is the shape of the publishing path today, simplified and with generic names:

investigation (database)
  hypothesis, actors[], findings[], evidence[], infrastructure[], connections[]
        |
        v
completeness gate  -> 422 "investigation missing hypothesis"
        |              422 "no actors" / "no findings"
        v
leak gate          -> 422 leaks[]: internal addresses, private hostnames
        |
        v
render page + verify page + evidence JSON
        |
        v
sign (Merkle root, Ed25519) -> timestamp -> commit only these paths -> deploy

Each gate was added after a real failure.

The completeness gate came first. Since July a publish fails with HTTP 422 unless the investigation behind it exists, is marked complete, has a hypothesis and has at least one actor and one finding. An agent that writes a beautiful article without the structured investigation underneath simply cannot publish it.

The leak gate was added in August. The publisher enforces what the security world calls TLP:WHITE, meaning the content is safe for anyone to read, and refuses any page that contains internal addresses from my own network. The response lists every offending address, so the fix is obvious.

Signing came in July. Every dossier’s evidence is hashed into a Merkle tree and signed, and the root is anchored with OpenTimestamps in the Bitcoin blockchain. On 19 July all 699 of 699 published dossiers carried a verifiable seal. I wrote the details, including a float that made three honest reports look tampered, in Signing threat intelligence.

Scoped commits came last. Until late July the publisher ran a repository-wide git add -A on every deploy, which quietly swept unrelated work into commits called “deploy: publish”. Now each publish commits only the paths it wrote. The day after that fix, on 27 July, I found it had also been pushing a Git branch that was not checked out, and reporting it as success.

None of this is exotic. It is the same idea as a type checker or a CI pipeline: make the wrong thing fail loudly, early, and with a reason.

Corrections that stay visible #

The gates stop bad pages. They do not stop wrong conclusions. I published some.

On 8 August a dossier said, in the present tense, that my site “is serving 403s” to a well-known AI crawler, and that nobody had decided it, that it was emergent complexity. Both were false. The block was a deliberately named rule, and it had ended a month earlier. The lesson in the record is blunt: “Never attribute a cause to ‘emergent complexity’ while a field naming the responsible component is sitting unqueried in the data.”

The next day another dossier claimed, at high confidence, that 85 records “never traversed a network”. The real explanation was a forwarded-address header, and it had been documented in my own roadmap since 31 July, down to the exact count. The record’s verdict: “The dossier documented the failure mode of confident reasoning over unverified inputs, and then committed exactly that failure one layer up.”

That is the kind of mistake worth reading twice.

What changed is how a mistake is fixed. On 4 July I set the rule that still holds: append the corrected values, the date and the reason to the exact published dossier, and never delete. A correction is new evidence and a new finding that supersedes the old one. The page is republished, re-signed and re-anchored, and the old seal still verifies against the old version. A reader can see what I said, when I changed my mind and why.

In September a citation audit found 118 evidence rows, across 78 investigations, citing 23 files in my offline library that were mislabelled: the file name said one source, the content was another. They were re-sourced one by one, the affected dossiers re-signed. Nobody had complained. The pipeline made it possible to look.

The after: what changed in how I work #

The obvious change is scale. Two complete 26-part series were finished on 10 July alone, each letter at least 18,000 characters, with a structure an agent can follow. The search index holds every published dossier exactly once; on 5 August it was 804 documents and 8,827 chunks, with zero orphans against the publishing registry. The site now shows 810.

The less obvious change is in what I do. My role moved from typesetter towards editor: deciding what is true enough to sign. Most of the writing and enrichment is done by AI agents through typed tools. The investigation is the product. The page is a view of it.

And the work became searchable by its own authors. Before writing a new dossier, an agent asks the search API whether the subject is already covered. That sounds trivial. On 26 July it was not: grepping the site’s title index for an entity returned zero hits while the entity was covered extensively in the article bodies. Novelty has to be checked against the content, not the titles.

What it cost #

It cost some character. The hand-made page had a personality, every dossier shaped a little differently. The pipeline has a house style.

It cost complexity. Five services, two search backends that must agree with each other, a signing chain, daily reseal jobs. In August the two search backends disagreed by 330 stale documents, and my own cleanup code had three silent-skip bugs in a row before they agreed exactly. One of my agents wrote in the record: “I manufactured a decay finding by comparing two different metrics.” Machines that measure can be wrong in very convincing ways.

And it cost one comfortable illusion: that a careful person typing HTML is a quality process. It is not. It is a person hoping.

What changed #

  • Before: one file, edited by hand, 1.5 MB at the end, no checks, no history of corrections.
  • After: structured investigations, a search API, an indexer, a publisher with completeness and leak gates, signatures anchored in Bitcoin, scoped Git commits.
  • Speed: from five dossiers in a day of careful editing to two 26-part series finished in one day.
  • Trust: from “I checked it” to “anyone can check it”.
  • Mistakes: still made, now appended, dated and signed instead of overwritten.

Questions people ask me #

Why not keep writing dossiers by hand? #

Because a hand-made process has no way to refuse. Every rule lived in my head: redact personal data, update the counters, link the next part. A pipeline turns those rules into checks that fail with a reason. It also lets AI agents do the slow parts without letting them publish anything that does not pass.

What is a threat-intelligence dossier? #

A write-up of who attacked, how, from which infrastructure, and what it means for defenders. Mine are built from my own honeypot and web logs, cross-checked against public registries and reports, and published so others can block the same infrastructure. Most of them include indicators a defender can use directly.

Can AI agents write threat intelligence safely? #

They can do most of the research and drafting, if the system around them is strict. In my case agents work through typed tools, the publisher refuses incomplete investigations and anything that leaks internal addresses, and every correction is public. My own worst mistakes in the record were confident reasoning over data nobody had queried, and an agent can make that mistake as easily as I can.

What would you do differently? #

Start with the data model on day one. The moment a report has a hypothesis, actors and evidence, store those as data, even if the first “publisher” is a 50-line script. Everything I built in June was a way of recovering that structure from pages that never had it.

The closing #

The most important line in the whole pipeline is not clever. It is an HTTP 422 with a sentence in it: investigation missing hypothesis. It means that before anything I write can reach the public, I have to say what I think is true, and show what supports it.

I spent April and May making a beautiful page. I spent June learning that the page was never the point. If you keep something that matters in one hand-edited file, a notebook, a spreadsheet, a wiki page that only you understand, the time to give it structure is before the day someone comes looking for your name.