Most security appliances detect attacks. Far fewer can prove they did — in a way an auditor will accept months or years later. Regulations like the EU’s NIS2 Directive (Article 21), SOC 2, ISO 27001 and Cyber Essentials all require evidence that is signed, timestamped against a trusted time source, and tamper-evident.
The Defend-O-Tron records that evidence continuously while it protects your network. Every day, the device signs a manifest of the day’s logs with an Ed25519 key anchored to NTP-attested time. Every minute, the collector tick adds new events to a hash-chained store. Every hour, the device re-verifies its own chain of custody and surfaces the result to your dashboards. When an auditor asks for proof, you produce a single signed ZIP covering whatever period they ask about — events, threat decisions, configuration snapshot, NTP attestation, and the daily chain of signed manifests, all in one package.
This is the kind of capability you’d typically pay extra for as a SIEM add-on on Cisco, Fortinet or Sophos. On the Defend-O-Tron it’s built in and runs automatically.
The part that matters most when someone is actually checking your work: your auditor can verify the evidence without the device, without an internet connection, and without us. The verification tool and the device’s signing-key ring both travel inside the evidence pack. Nobody has to take a vendor’s word that the logs are genuine, call a support line, or log into anyone’s cloud portal. They run one command against the ZIP and get a yes or a no.
That is a meaningfully different claim from “our platform stores your logs securely.” It means the evidence keeps its value even if the device is later compromised, replaced, or retired — and even if Awesome-O Gadgets is no longer around to vouch for it.
Many small businesses assume evidence requirements only apply to large regulated companies. Increasingly they don’t.
The audit subsystem is built to produce evidence that maps cleanly onto the major cybersecurity frameworks. The tables below show which framework outcomes the device itself addresses, and which are operator or organizational responsibilities that the device’s evidence supports.
What “addressed” means here. Frameworks describe outcomes, not products. Saying the device addresses a control means it implements the technical capability the control calls for. Achieving full conformance is always an organizational matter — the device gives you the evidence; you and your auditor make the determination.
Every status on this page is machine-checked against the code. The firmware carries a control map naming, for each control, the specific code, metric, unit or configuration key that produces its evidence. A build-time check verifies that each one still exists, refuses any
AddressedorPartially addressedstatus that has no code behind it, and fails if this page and the map disagree. We added it after finding four claims here that nothing in the code supported — so if a status looks conservative, that is deliberate.
The NIST Cybersecurity Framework 2.0 (NIST CSWP 29, February 2024) organizes cybersecurity outcomes into six Functions — GOVERN, IDENTIFY, PROTECT, DETECT, RESPOND, RECOVER — each broken down into Categories and Subcategories. The audit subsystem most directly addresses the PROTECT, DETECT and RESPOND Subcategories that concern log generation, continuous monitoring, adverse-event analysis, and incident-data preservation.
| Subcategory | Outcome | Defend-O-Tron status |
|---|---|---|
| PR.PS-04 | Log records are generated and made available for continuous monitoring | Addressed. Suricata IDS alerts, CrowdSec decisions, nftables packet counters, and Linux auditd events all flow into the events store and a Prometheus textfile that node-exporter scrapes for Grafana. The daily Ed25519-signed manifest hashes every rotated log file. |
| DE.CM-01 | Networks and network services are monitored to find potentially adverse events | Addressed. Continuous Suricata IDS + CrowdSec community blocklists + nftables named counters cover network monitoring end-to-end. |
| DE.CM-09 | Computing hardware and software, runtime environments, and their data are monitored | Partially addressed. Linux auditd rules ship enabled (file access, perm_mod, execve, modules, network, time-change); the rotated audit.log is hashed into the daily manifest. Parsing auditd events into the queryable event store is on the roadmap. |
| DE.AE-02 | Potentially adverse events are analyzed to better understand associated activities | Partially addressed. The Audit Evidence dashboard surfaces events for operator analysis; automated correlation is operator-side. |
| DE.AE-03 | Information is correlated from multiple sources | Addressed. A single unified events store stitches Suricata + CrowdSec + nftables into one queryable timeline per device. |
| DE.AE-06 | Information on adverse events is provided to authorized staff and tools | Addressed. Prometheus textfile → node-exporter → Grafana for operators; signed evidence ZIP for auditors. |
| DE.AE-07 | Cyber threat intelligence and other contextual information are integrated into the analysis | Partially addressed. CrowdSec community blocklists and any curated lists you subscribe from the console are enforced inline. File hashes seen on the wire are captured and stored, so they can be cross-referenced against an IOC feed off the device — the device does no matching itself. Note the recording scope: the decisions table holds decisions this device or its operator authored — its own detections, your console actions, your manual bans. Blocks that a threat feed prevented are counted in aggregate and enforced, but not journaled individually, because a reputation block records that nothing happened: an address someone else flagged was stopped before it attempted anything here. Recording millions of those would bury the attempts that carried real risk. Aggregate proof that feed enforcement occurred is in the signed nftables counter rollup. |
| DE.AE-08 | Incidents are declared when adverse events meet the defined incident criteria | Operator responsibility. The device surfaces severity-classified events; declaration thresholds are an organizational policy decision. |
| RS.AN-06 | Actions performed during an investigation are recorded, and the records’ integrity and provenance are preserved | Partially addressed. The evidence itself is signed and bundled — daily Ed25519-signed manifest plus a chain-of-custody signed evidence ZIP, verifiable offline using the embedded pubring. What is not yet recorded is the investigator’s own activity: running a verification, re-extracting a pack or rotating a key leaves no entry anywhere. The operator_actions table is built and hash-chained for exactly this and has no writer yet. |
| RS.AN-07 | Incident data and metadata are collected, and their integrity and provenance are preserved | Addressed. A per-row hash chain on the events and decisions tables: each row’s hash incorporates the previous row’s, and as of audit-deb 2.3.4 the device walks that chain and recomputes every row hash — hourly over recent history, and completely on demand with defend-audit verify-chain. An edit to a covered field is reported with the row that failed; a deleted row is reported as a break. The chain covers the immutable facts of a record; fields that legitimately change later, such as a decision’s active-to-expired status, sit outside it by design (see Tamper Evidence below). Each day’s chain tip is recorded in that day’s signed manifest, and the manifest series is verified hourly too, so a removed day is detected. Every signed artifact carries the device serial, hostname, and image variant. |
| RS.MA-03 | Incidents are categorized and prioritized | Partially addressed. Suricata signature severity + CrowdSec scenario provide raw classification; full prioritization is operator-side. |
| ID.AM-08 | Systems, hardware, software, services, and data are managed throughout their life cycles | Partially addressed (device contribution). Every signed artifact embeds the device identity record (serial number, hostname, image variant) for fleet life-cycle correlation. |
ISO/IEC 27001:2022 is the cybersecurity management-system standard; ISO/IEC 27002:2022 is its controls catalog (published February 2022). The audit subsystem maps to the following 27002:2022 controls.
| Control | Title (27002:2022) | Defend-O-Tron status |
|---|---|---|
| 5.25 | Assessment and decision on information security events | Partially addressed. Events are surfaced for assessment via dashboards and the evidence ZIP; the decision step is operator-side. |
| 5.28 | Collection of evidence | Addressed. The defend-audit report evidence ZIP is purpose-built as the chain-of-custody pack — events, decisions, configuration snapshot, daily signed manifests, NTP attestation, all signed end-to-end. |
| 5.33 | Protection of records | Addressed. The signing key is protected (per-device generation, mode 0400, redundant copy on SD card, pubring rotation history), and daily manifests carry an Ed25519 signature with key continuity across rotations. Each manifest also names the previous one and its SHA-256 inside the signed payload, so the series cannot be thinned: deleting a day leaves a dangling pointer the hourly self-check reports. The live events store is covered too — the per-row chain is verified, so an edit made directly in the database is detected rather than merely recorded. Be clear on what this is: detection, not prevention. The SQLite file remains writable by root, which is precisely why the evidence of record is the signed manifest rather than the database. Mutable bookkeeping fields and rows pruned past the retention window are outside the chain’s coverage by design (see Tamper Evidence). |
| 8.10 | Information deletion | Addressed. Retention is declared in audit.conf (events_retention_days, default 365; nft_retention_days, default 7) and enforced by the hourly maintenance pass. The prune predicate is decidable from each row itself rather than from a status another process has to report, so no row can become unprunable and the store cannot grow without bound. Deletion is deliberate about the hash chain: pruning removes chained rows, so the chain is continuous from the retention boundary forward — and because the chain tip is stored rather than derived, a prune that empties a table does not restart the chain. |
| 8.15 | Logging | Addressed. Activities, exceptions, faults, and other relevant events are produced (multi-source ingest), stored (SQLite WAL + 365-day default retention), protected (Ed25519-signed daily manifest covering log-file hashes, per-table chain tips, the nftables counter rollup and a signed pointer to the previous manifest, plus a per-row hash chain and RFC 3161 trusted timestamps), and analysed (Grafana Audit Evidence dashboard). |
| 8.16 | Monitoring activities | Addressed. 60-second collector tick into the events store; node-exporter scrape; live Grafana surfacing. |
| 8.17 | Clock synchronization | Addressed. Chrony NTP discipline is mandatory; chrony logs are included in every evidence ZIP as time attestation. As of audit-deb 2.1.0, each daily manifest’s signature is also anchored to an external clock via an RFC 3161 trusted timestamp from a pinned Time-Stamp Authority — see Trusted Timestamps below. |
Directive (EU) 2022/2555 — the NIS2 Directive — entered into force in January 2023 with member-state transposition by October 2024. Article 21(2) lists ten cybersecurity risk-management measures essential entities must implement.
| Art 21(2) | Measure | Defend-O-Tron status |
|---|---|---|
| 21(2)(a) | Policies on risk analysis and information system security | Organizational responsibility. The device’s evidence stream is an input to those policies. |
| 21(2)(b) | Incident handling | Partially addressed. Detection and evidence preservation are device-side; the notification workflow per Article 23 (24-hour early warning, 72-hour notification, 1-month final report) is operator-side. |
| 21(2)© | Business continuity, including backup management and disaster recovery, and crisis management | Out of scope at audit-deb. Handled at the appliance level via the A/B image-update system and the LUKS-backed data partition. |
| 21(2)(d) | Supply chain security | Organizational responsibility. |
| 21(2)(e) | Security in network and information systems acquisition, development and maintenance, including vulnerability handling | Organizational responsibility. |
| 21(2)(f) | Policies and procedures to assess the effectiveness of cybersecurity risk-management measures | Partially addressed (device contribution). Periodic evidence ZIPs are the data backbone for the effectiveness review the operator runs. |
| 21(2)(g) | Basic cyber hygiene practices and cybersecurity training | Organizational responsibility. |
| 21(2)(h) | Policies and procedures regarding the use of cryptography and, where appropriate, encryption | Addressed for audit signing. Per-device Ed25519 keypair (no shared fleet key), rotation supported and audited via the pubring, retired keys retained so historical evidence remains verifiable. |
| 21(2)(i) | Human resources security, access control policies and asset management | Partially addressed (device contribution). Each signed artifact carries the device’s asset record; HR and access control are out of scope. |
| 21(2)(j) | Multi-factor authentication or continuous authentication and secured communications | Out of scope at audit-deb. Provided by the appliance’s authentication layer. |
NIST SP 800-61 Rev 3 (April 2025) is the Incident Response Recommendations and Considerations for Cybersecurity Risk Management. It restructured the older Rev 2 four-phase model (Preparation → Detection & Analysis → Containment/Eradication/Recovery → Post-Incident Activity) to align with the CSF 2.0 functions. Rev 2 was withdrawn on 3 April 2025; this page cites Rev 3.
| Topic | Defend-O-Tron status |
|---|---|
| Log sources as primary precursors and indicators | Addressed. Suricata, CrowdSec, nftables, and Linux auditd are all in the baseline log set the document recommends. |
| Evidence preservation and chain of custody | Addressed. Daily Ed25519-signed manifest plus chain-of-custody-signed evidence ZIP, and since 2.3.3 the manifest series is chained so a removed day is detectable. Actions taken against the evidence — verification, re-extraction, key rotation — are not themselves logged yet; see RS.AN-06. |
| Time correlation across events | Addressed. All timestamps are UTC; chrony NTP discipline is mandatory; chrony logs ship in every evidence pack. |
| Provenance of incident data and metadata | Addressed. Device serial + hostname + image variant are embedded in every signed artifact, the manifest series is chained and verified, the per-row chain is walked and re-hashed, and the nftables counter rollup carries aggregate enforcement provenance in the signed manifest itself. Surviving a device wipe is a separate outcome — see the forensic-preservation row below. |
| Forensic preservation against device compromise | Partially addressed. Tampering on the device is detected: the per-row chain is verified and the manifest series is chained, so neither an edit nor a deletion passes silently. What is still missing is survival — there is no off-device forwarder, so a wipe takes the store with it. On the roadmap. |
The audit subsystem has a published roadmap of features that close remaining technical gaps in the matrix above. Most of these raise the assurance ceiling rather than meet a requirement — none is needed for a framework determination today. Two are different in kind: the first two open items below close limits this page states explicitly, so they are listed first and described in the same terms used where the limit is admitted.
This list is held one-to-one against the firmware’s own control map by a build-time check, in both directions. A roadmap item here with no engineering record behind it fails the build, and so does a gap recorded there but never published here. That is the same mechanism that holds every status above to the code.
.tsr sibling is verified hourly by the self-check. See Trusted Timestamps.defend-audit verify-chain, with a bounded version in the hourly self-check. See Tamper Evidence..tsr’s SHA-256 in the signed manifest payload beside the previous JSON’s, so a deleted trusted timestamp becomes a dangling pointer instead of an apparent TSA outage (closes the gap described under Trusted Timestamps).operator_actions rows (lifts RS.AN-06 and ISO 5.28). The table is built and hash-chained; it has no writer.The audit subsystem aggregates evidence from every defense component on the device:
Every event is timestamped against the device’s NTP-attested clock. The daily manifest hashes every log file the device ships and signs the hash list, so any tampering after-the-fact is detectable.
Inside the events store, the events and decisions tables carry a per-row hash chain (audit-deb 2.0.0+). Each new row’s hash incorporates the previous row’s hash, so a retroactive insert, deletion, or edit of a covered field breaks the chain at that row and every row thereafter.
As of audit-deb 2.3.4 the device checks this itself. It walks the chain backward from the signed tip and recomputes every row’s hash from the row’s own columns, reporting an edited field with the row that failed and a deleted row as a break. The hourly self-check covers recent history (5,000 rows per table by default, tunable); defend-audit verify-chain walks the whole chain on demand and reads the store read-only, so it is safe to run on a device that is actively collecting.
Closing that gap exposed a deeper one worth stating plainly, because it changes what earlier releases could honestly claim: the original hash recipe covered values before they were converted for storage, so a row with an empty or zero column — a port 0, an absent flow id, a decision with no parseable duration — hashed a value the column does not actually contain. Those hashes could never have been recomputed from the table. The chain was unverifiable in principle, not merely unverified. The recipe now covers each value exactly as stored, and the set of covered columns is declared in one place and checked on every insert, so the two halves cannot drift apart again.
Which means there is a boundary, and you should know where it is. A row can only be re-hashed by the recipe that produced it, and the recipe has changed twice — once when a mutable bookkeeping field was removed from the hash, and once in 2.3.5 when the values were corrected to match what is stored. Rows written before that boundary are not re-hashable, and the device does not pretend otherwise: it records the boundary (the hash-recipe epoch) and verifies forward from it, reporting older rows as out of scope rather than as tampered. Those rows keep the integrity they always had — the chain tip for their day was signed into that day’s manifest, and that manifest is Ed25519-signed and externally timestamped — which is a weaker guarantee than per-row recomputation, and is the reason this is said out loud. The device deliberately does not re-hash old rows under the new recipe: that would make them verify and destroy their value as evidence in the same step, since anyone able to tamper would also be able to re-chain.
The chain’s order is stored rather than inferred from any column (audit-deb 2.3.2+). That matters because one of the two chained tables has no column carrying insert order: a decision’s id comes from CrowdSec and can arrive out of sequence. Storing the tip has a second benefit worth knowing — a retention prune that empties a table does not restart the chain, because the next row still links to the last row that existed.
The daily manifests are themselves chained (audit-deb 2.3.3+). Each one names the previous manifest and its SHA-256 inside the signed payload. This closes a gap that signatures alone could not: a signature proves a manifest was not edited, but says nothing about a manifest that is no longer there. Before this, deleting a day’s manifest and its timestamp together left every remaining signature valid and nothing flagged the hole. Now the next day’s manifest points at a file that must exist and must hash to the recorded value, and the hourly self-check reports any pointer that does not resolve.
Two things are worth being precise about, because an auditor will ask.
What the chain covers is the immutable facts of a record — for an event: when, source and destination, port, signature, severity, and whether the attempt reached a live service. Fields that legitimately change after a row is written are deliberately outside the hash, because an append-only chain cannot survive a covered field being rewritten in place: a decision’s status moves from active to expired when the decision lapses, and that transition is bookkeeping rather than evidence. Covering it would have meant either a chain that breaks during normal operation or a row that gets silently rewritten — neither is worth having.
Retention deletes chained rows by design. Pruning past the retention window removes the oldest rows, so the chain is continuous from the retention boundary forward rather than back to first boot. That is the intended behaviour of a rolling window, not a defect — but it does mean the chain evidences the retained period, and the daily signed manifests are what carry continuity beyond it.
Not every table is chained, and the ones that aren’t are covered differently:
| Table | How its integrity is established |
|---|---|
events, decisions |
Per-row hash chain, plus the chain tip recorded in each daily signed manifest. The chain is written and its tip signed; a tool that walks the chain is not yet shipped |
nft_counters |
Rolled up per counter into the daily manifest and signed — stronger than the in-row chain, since the manifest carries an Ed25519 signature and an RFC 3161 trusted timestamp |
dns_events, file_events, tls_events |
Not chained. These hold supporting detection detail; the daily manifest hashes the underlying Suricata log files they were parsed from |
| The manifest series itself | Each daily manifest names the previous one and its SHA-256 inside the signed payload, so a removed day leaves a dangling pointer. Verified hourly |
In addition to the daily manifest signing, the device runs an hourly self-check that re-verifies every signed manifest against the on-device pubring and writes the outcome to a Prometheus textfile. If a manifest is tampered with, its signature fails verification; the failure shows up on your dashboard within an hour without anyone having to download a verification ZIP.
Since audit-deb 2.3.3 it also checks the series, which signatures alone cannot: each manifest’s signed pointer to the previous one must resolve to a file that exists and hashes to the recorded value. Deleting a day’s evidence used to leave every remaining signature valid, with nothing anywhere to show a day was missing. It is run by do-audit-housekeeping.timer, which consolidated the former per-task timers.
The metrics exposed are:
| Metric | What it tells you |
|---|---|
do_audit_selfcheck_signatures_ok |
Manifest signatures that verified at the last self-check run |
do_audit_selfcheck_signatures_bad |
Manifest signatures that failed verification — should always be 0 |
do_audit_selfcheck_files_mismatched |
Files referenced by a manifest whose SHA-256 has since changed (only checked when [selfcheck] rehash_files = true in audit.conf) |
do_audit_selfcheck_files_missing |
Files referenced by a manifest that no longer exist on disk |
do_audit_selfcheck_links_ok |
Manifests whose signed previous-manifest pointer resolved |
do_audit_selfcheck_links_bad |
Manifests whose pointer is dangling or hash-mismatched — a manifest the series committed to is gone or altered. Should always be 0 |
do_audit_selfcheck_links_absent |
Manifests carrying no pointer: every manifest written before 2.3.3, plus the device’s first. Not a fault, but while it is large the series is only as protected as a directory listing |
do_audit_selfcheck_manifests_missing |
Calendar days between the first and last manifest that have none. Not automatically a fault — a device that was powered off has legitimate gaps — but each one is a day with no evidence |
do_audit_selfcheck_chain_tables_ok |
Chained tables whose per-row chain verified |
do_audit_selfcheck_chain_tables_bad |
Chained tables with an edited row, a deleted row, or a missing signed tip. Should always be 0 |
do_audit_selfcheck_chain_rows_walked |
How many rows were actually re-hashed. This is bounded, so it tells you how much of the chain the hourly pass covered |
do_audit_selfcheck_last_run_ts |
Unix timestamp of the most recent self-check run |
A non-zero value on signatures_bad, files_mismatched, links_bad or chain_tables_bad is a tamper signal — investigate immediately. manifests_missing is a coverage signal rather than a tamper signal: it tells you a day has no evidence, not that anyone removed it. Note that a clean chain_tables_bad means the rows the hourly pass walked verified; run defend-audit verify-chain for the whole chain.
Ed25519 signatures prove who signed a manifest. They do not prove when it was signed — the device’s clock is the only witness, and an attacker who roots the device can backdate it. RFC 3161 trusted timestamps close that gap by anchoring each signature to an external clock maintained by a Time-Stamp Authority (TSA).
As of audit-deb 2.1.0, every daily manifest’s Ed25519 signature is timestamped by an external TSA, and the resulting TimeStampResp is stored as a sibling .tsr file next to the manifest.
After do-audit-manifest produces and signs MANIFEST-YYYY-MM-DD.json:
SHA-256(base64-decode(signature.value)) — a 32-byte message imprint over the signature bytes.TimeStampReq containing that imprint to the configured TSA.(imprint, current TSA time) with its own key and returns a TimeStampResp.MANIFEST-YYYY-MM-DD.tsr next to the manifest (binary DER).This follows the standard PAdES / CAdES encrypted-data timestamp pattern. The imprint is over the signature bytes (not the manifest content), so the TSR can be reproduced from the manifest’s on-disk form alone — no re-canonicalisation needed at verify time.
Without a TSR, an attacker who roots the device could:
With a TSR, that attack fails — the TSA’s signature is over the original manifest signature bytes at the time it was issued. Any subsequent re-signing produces different signature bytes, so the imprint won’t match. The auditor sees the discrepancy on verification.
The .tsr siblings are not themselves chained, so a removed timestamp reads as an outage. The signed previous-manifest pointer covers the manifest JSON — its filename and SHA-256 — and the timestamp deliberately lands in a sibling file that nothing commits to. The consequence is worth stating plainly, because it is the one move left to an attacker who has already rooted the device: rewriting a day’s evidence forces them to delete that day’s .tsr, and deleting it only raises do_audit_selfcheck_tsr_missing — which is also what a TSA outage looks like while the hourly backfill catches up. So the series of manifests cannot be thinned, but the series of timestamp tokens can.
Two things bound that in practice. The manifest JSON for the rewritten day still has to chain correctly to its neighbours, and its signature still has to verify, so the attacker is removing an anchor rather than forging one. And TSR coverage is on the dashboard: a figure that was at 100% and drops for a historical day is not what a current outage looks like, since backfill works forward from the gap. But “an operator would notice the shape of it” is not the same as a check that fails, and this page does not pretend otherwise.
Closing it means a second signed pointer — the previous .tsr’s SHA-256 alongside the JSON’s — which is on the roadmap below. It inherits the same two honest limits the manifest chain has: the newest token is unprotected because nothing points at it yet, and a day whose TSA was genuinely unreachable has no token to point at, so “none” has to be recorded as a signed fact rather than read as a break.
The default audit.conf ships with FreeTSA (http://freetsa.org/tsr) — a free, public RFC 3161 service that does not require an account. The FreeTSA root CA is pinned in the deb at /usr/share/awesome-o-audit/tsa-roots/freetsa.crt.
To use a different TSA (e.g. a commercial one from DigiCert, Sectigo, or GlobalSign, or a self-hosted one):
/usr/share/awesome-o-audit/tsa-roots/yourtsa.crt (PEM format)./etc/awesome-o/audit.conf [timestamp]:
urls to your TSA’s endpoint (comma-separated for fallback).roots to your PEM bundle path.sudo systemctl restart do-audit-manifest.timer do-audit-tsr-retry.timer.Removing the [timestamp] section entirely disables the feature cleanly — manifests still ship signed by the Ed25519 key alone, and verification still succeeds for manifests that have no .tsr sibling.
If the TSA is unreachable when a manifest is signed, the manifest still ships on time. do-audit-tsr-retry.timer fires hourly and walks the manifests directory for any MANIFEST-*.json without a matching .tsr, attempting to backfill them. Capped per run via [timestamp] retry_max_per_run (default 30) so the device doesn’t pound a recovering TSA.
defend-audit verify automatically validates a sibling .tsr when present. The Ed25519 signature is verified first; then the TSR is checked against the pinned TSA roots and its message imprint compared to the manifest’s signature. The TSA’s reported time is also compared to the manifest’s generated_utc and must fall within [timestamp] verify_tolerance_seconds (default 1 hour).
defend-audit verify manifests/MANIFEST-2026-04-15.json
What success looks like for a v2 manifest with a TSR:
signature OK (ed25519:abc1234...)
files: 142 listed, 0 mismatched, 0 missing
tsr OK (TSA=CN=www.freetsa.org,OU=Root CA,O=Free TSA at 2026-04-16T00:10:43Z)
Skip the TSR check with --skip-tsr if you’re verifying an older v2 manifest that didn’t get a TSR (or if you don’t have the TSA root cert handy).
The Audit Evidence dashboard’s integrity row shows:
| Metric | What it tells you |
|---|---|
do_audit_selfcheck_tsr_ok |
Manifests whose .tsr sibling verified at the last self-check |
do_audit_selfcheck_tsr_bad |
TSRs that failed verification — should be 0; non-zero = tamper OR stale TSA root |
do_audit_selfcheck_tsr_missing |
Manifests without a .tsr sibling. Normally the TSA was unreachable at sign time and backfill is in progress — but a deleted token is counted here too and the two are not currently distinguishable, so treat a rise affecting older manifests differently from one affecting the newest (see What it does not yet cover) |
| TSR coverage | 100 * tsr_ok / signatures_ok — climbs toward 100% as backfill catches up |
The Defend-O-Tron ships a pre-built Grafana dashboard that summarizes the audit evidence in real time. It’s titled Audit Evidence (folder audit in Grafana) and reads from a read-only snapshot of the audit event store — so your auditor can sit beside you and watch the same numbers go in.
Where to find it:

What you’ll see, top to bottom:
When an auditor asks for evidence covering a period — say last quarter — you produce a single signed ZIP with the defend-audit report command from the admin terminal.
The admin terminal is built into the Cockpit interface — open the admin web UI, click Tools → Terminal, then enter the command below. You do not need to SSH separately.
sudo defend-audit report --from 2026-04-01 --to 2026-06-30 --out /home/admin/q2-evidence.zip
--from and --to are inclusive UTC dates in YYYY-MM-DD format.--out is the destination path. If you omit it, the ZIP is written next to where you ran the command and named defend-o-tron-audit-FROM-to-TO.zip.Useful flags:
--no-pdf — skip the rendered PDF of the NIS2 dashboard. Use this if grafana-image-renderer isn’t deployed on your fleet (it’s optional).--no-config-snapshot — skip the configuration archive. Useful if your auditor only needs the events.--json — emit a stable JSON envelope on stdout instead of human-readable text, for use by other tooling.| File | What it is |
|---|---|
events.csv |
Every IDS event in the period |
decisions.csv |
Every CrowdSec decision in the period |
dns_events.csv / file_events.csv / tls_events.csv |
Ransomware indicators (DGA, transferred-file hashes, suspicious TLS) |
config-snapshot.tar.gz |
The device’s Suricata, CrowdSec, nftables, ulogd, chrony, and node-exporter configuration as it was at report time |
manifests/MANIFEST-YYYY-MM-DD.json |
The daily signed manifest for every day in the period (the chain-of-custody anchor) |
chrony-logs/ |
NTP attestation — proof the device clock was disciplined |
report.pdf (optional) |
Rendered snapshot of the NIS2 dashboard |
manifest.pubring.json |
The device’s signing-key ring, so the auditor can verify everything offline |
chain-of-custody.json |
Ed25519-signed SHA-256 manifest of every other file in the ZIP |
The ZIP itself is written with mode 0640, root-owned. It’s safe to copy to a USB drive, attach to a ticket, or upload to your compliance portal.
Anyone with the ZIP can verify it — your auditor doesn’t need access to the device. The signing-key ring travels inside the ZIP, so verification works completely offline.
The verification tool is a self-contained static Linux binary (defend-audit) shipped inside the deb — no Python, no cryptography library, no other runtime dependencies. Auditors can copy it onto any modern Linux system and run it directly.
From an extracted ZIP, run:
defend-audit verify manifests/MANIFEST-2026-04-15.json --pubring manifest.pubring.json
What success looks like:
signature OK (ed25519:abc1234...)
files: 142 listed, 0 mismatched, 0 missing
What a tampered manifest looks like:
defend-audit verify: BAD signature (key ed25519:abc1234...)
What a tampered log file looks like:
signature OK (ed25519:abc1234...)
MISMATCH /var/log/suricata/eve-ids-2026-04-15.json
files: 142 listed, 1 mismatched, 0 missing
Useful flags:
--skip-files — verify the signature only, skip the per-file re-hashing. Faster for spot checks.--pubring /path/to/manifest.pubring.json — verify against the pubring shipped inside this ZIP instead of the one on a live device. Auditors should always use this option when working from an evidence pack on a separate system.--json — machine-readable envelope output for automated verification pipelines.Manifests written by audit-deb 1.x (Python era, manifest schema version 1) and 2.x (Go era, manifest schema version 2 with
chain_tips) both verify with the 2.xdefend-audit verifybinary. The verifier dispatches on the manifest’sversionfield; older evidence stays verifiable forever.
The signing-key ring keeps every key the device has ever used, each marked with valid_from and retired_at timestamps. Verifying any manifest looks up the key that was active when it was signed — not the key active today. So manifests signed before a compromise can still be proven authentic after the compromise, as long as you have a copy of the pubring from before the incident (which you do, in every evidence pack you’ve ever produced).
The audit signing key can be rotated at any time. Reasons you might rotate:
sudo defend-audit key-rotate
What you’ll see:
rotated: retired ed25519:Y8nM3R9..., active is now ed25519:A4kP7w2... (at 2026-05-15T18:00:00Z)
What happens behind the scenes:
retired_at timestamp in the pubring.valid_from = now and retired_at = null./boot/defend-o-tron-auditor.key.bak).Key rotation does not invalidate past evidence. Every retired key stays in the pubring forever. Old manifests remain verifiable — they reference the key they were signed with, and that key entry never disappears.
The next daily manifest (and every one after) is signed with the new key. The pubring travels inside every evidence pack, so auditors verifying a multi-year period transparently see all the rotations in sequence.
The audit subsystem keeps 365 days of detailed events by default. The daily signed manifests, signing keys, and the underlying event store all live under a single directory:
/opt/data/auditor/
├── keys/
│ ├── manifest.key ← current private signing key (0400 root)
│ └── manifest.pubring.json ← public ring of every key ever used
├── manifests/
│ └── MANIFEST-YYYY-MM-DD.json ← daily signed manifest, one per day
├── events.sqlite ← live event store (WAL mode, with per-row hash chain)
└── share/
└── events.sqlite ← read-only snapshot for Grafana
A redundant copy of the signing private key lives at /boot/defend-o-tron-auditor.key.bak so an NVMe replacement does not lose the device’s identity.
For a full backup of the audit history, copy /opt/data/auditor/ and its contents. Everything you need to produce or verify evidence packs lives under that one tree.
The audit state lives on the data partition (
/opt/data), separate from the OS image. Firmware updates do not affect audit history.
All operational values — retention windows, log glob patterns, LAPI URL, Grafana URL, ransomware-indicator filtering knobs, self-check options — live in a single dpkg-managed conffile:
/etc/awesome-o/audit.conf
Operator edits to this file survive package upgrades (it is a Debian conffile, so dpkg prompts before overwriting). The shipped defaults are sensible for a typical deployment; review and adjust for your environment.
grafana-image-renderer. If your deployment doesn’t include it, use defend-audit report --no-pdf.