Skip to content

Auditor — pulling and verifying evidence

This page assumes the auditor has their own account in /auditor with read-only scope. The client gives no shell access and no dashboard admin — all evidence is signed and offline verifiable.

1. Quarterly NIS2 audit — what do I have

In the dashboard

  1. Login as auditor in /auditor → Sidebar → Audit Packs
  2. Download .jsonl.gz + .pdf + .sig for the month
  3. Click ‘Download verify-CLI’ to grab the offline verifier
  4. Run CLI against pubkey — four green checkmarks

Open /audit-packs. You see one row per month for the chosen tenant. Download:

  • 2026-04.jsonl.gz — all raw events (machine-readable)
  • 2026-04.pdf — human-readable report, 12-15 pages
  • 2026-04.sig — Ed25519 signature over manifest_hash

Verify locally (no network round-trip to monsys):

Or via API (advanced — for automation)

Terminal window
wget https://get.monsys.ai/monsys-verify-eat-linux-x64
chmod +x monsys-verify-eat-linux-x64
./monsys-verify-eat-linux-x64 verify-pack \
--pack 2026-04.jsonl.gz \
--sig 2026-04.sig \
--pubkey https://transparency.monsys.ai/pubkeys/hub.pub

What the CLI checks:

  1. Computes manifest_hash = sha256(manifest) — must match .sig contents
  2. Walks every line in JSONL.gz, chains sha256 → must end at hash_chain_root in the manifest
  3. Verifies Ed25519 signature against the published hub pubkey

Output on success:

✓ manifest_hash matches signature
✓ hash chain root matches manifest
✓ Ed25519 signature valid against pubkey 7c34a9e2b1f0…
✓ 1247 entries in pack, 0 tampered

2. Prove CVE-2026-XXXX was patched within 7 days

In the dashboard

  1. Sidebar → Kernel CVEs → search CVE-2026-XXXX
  2. ‘Timeline’ tab shows per host: detected, patched, operator
  3. Filter ‘Time to patch > 7 days’ isolates SLA breaches
  4. ‘Export for audit’ button → CSV with all columns

Three auditor questions:

  1. When was the CVE first detected?
  2. When was it patched per host?
  3. Who executed the action?

In one SQL report:

Or via API (advanced — for automation)

WITH first_detected AS (
SELECT agent_id, MIN(detected_at) AS first_seen
FROM kernel_cve_findings
WHERE tenant_id = $1::UUID AND cve_id = 'CVE-2026-XXXX'
GROUP BY agent_id
),
patched AS (
SELECT r.agent_id, MIN(r.boot_time) AS rebooted_at, r.eat_id
FROM kernel_reboot_history r
JOIN first_detected fd ON fd.agent_id = r.agent_id
WHERE r.tenant_id = $1::UUID
AND r.expected = true
AND r.boot_time > fd.first_seen
GROUP BY r.agent_id, r.eat_id
)
SELECT a.hostname,
fd.first_seen,
p.rebooted_at,
p.rebooted_at - fd.first_seen AS time_to_patch,
u.email AS operator
FROM first_detected fd
LEFT JOIN patched p ON p.agent_id = fd.agent_id
LEFT JOIN agents a ON a.id = fd.agent_id
LEFT JOIN emergency_tokens et ON et.id = p.eat_id
LEFT JOIN users u ON u.id = et.user_id
ORDER BY time_to_patch NULLS FIRST;

A row with time_to_patch IS NULL = host not patched yet. A row with operator IS NULL = reboot was not EAT-driven (manual) — weaker evidence, but still a row in kernel_reboot_history with expected=false.


3. Show me every privileged action last quarter

In the dashboard

  1. Sidebar → Audit log (under MANAGE)
  2. Filter: event_type = emergency_token_issued + Q1 date range
  3. Per row: operator, target, actions JSON, exit_code
  4. ‘Verify in transparency log’ button opens external verifier per nonce

Or via API (advanced — for automation)

SELECT et.issued_at,
u.email AS operator,
a.hostname AS target,
et.level AS escalation,
et.actions::TEXT AS actions,
et.reason AS reason,
(et.result->>'exit_code')::INT AS exit_code
FROM emergency_tokens et
LEFT JOIN users u ON u.id = et.user_id
LEFT JOIN agents a ON a.id = et.agent_id
WHERE et.tenant_id = $1::UUID
AND et.issued_at >= '2026-01-01' AND et.issued_at < '2026-04-01'
ORDER BY et.issued_at DESC;

For offline verification: every nonce is in the transparency log. The auditor can independently validate:

Terminal window
./monsys-verify-eat-linux-x64 verify-eat \
--nonce 7a3c… \
--log-endpoint https://transparency.monsys.ai/api/v1/transparency-log/entry \
--pubkey https://transparency.monsys.ai/pubkeys/hub.pub

→ Confirms the EAT was issued by the legitimate hub key AND that the log entry has not been retroactively tampered with (hash chain verify).


4. Which NIS2/CRA controls fall short

In the dashboard

  1. Sidebar → Compliance → pick framework NIS2
  2. Matrix shows per control: coverage + evidence count + reviewed status
  3. Filter reviewed_status=‘draft’ to see outstanding review work
  4. ‘Export as CSV’ button for your quarterly report

/compliance/NIS2 shows a matrix:

ControlCoverageEvidence countReviewed
NIS2-Art21-2-aautomatic247
NIS2-Art21-2-bpartial12draft
NIS2-Art21-2-cmanual0draft

coverage_level=automatic = monsys runs the evidence query nightly and stores results in compliance_evidence. partial = mix of automatic + manual attestation. manual = text field for human declaration only.

reviewed_status='draft' means: not legally validated yet. Until that flag is reviewed AND COMPLIANCE_PRODUCTION=1 on the hub, the PDF shows a warning banner “draft mapping — not suitable for legal claims”.

For the quarterly report, export the matrix as CSV via:

Or via API (advanced — for automation)

Terminal window
curl 'https://app.monsys.ai/api/v1/compliance/coverage?framework=NIS2&format=csv' \
-H "Authorization: Bearer $TOKEN" > nis2-coverage-2026-Q1.csv

5. Verify cryptographic integrity of one specific evidence row

In the dashboard

  1. Sidebar → Audit Packs → pick the month
  2. Click ‘Tamper check’ button in download view
  3. Paste the suspect JSONL line + line number
  4. UI shows expected vs computed hash + verdict

Say the auditor doubts whether line #847 of 2026-04.jsonl.gz really was as registered, or whether the operator changed it after the fact.

Or via API (advanced — for automation)

Terminal window
zcat 2026-04.jsonl.gz | sed -n '847p' > suspicious-line.json
sha256sum suspicious-line.json
# 7c34a9e2b1f0…

Compare against the hash_chain in the manifest — every JSONL line adds its sha256 to the chain:

Terminal window
zcat 2026-04.jsonl.gz | ./monsys-verify-eat-linux-x64 chain-position --line 847
# expected_position: 7c34a9e2b1f0…
# computed_position: 7c34a9e2b1f0…
# ✓ line 847 matches the chain

Mismatch = evidence of retroactive tampering. Match = the line is identical to when it was written at signed_at.


6. Which users have admin rights across systems

In the dashboard

  1. Sidebar → RBAC → ‘Admin overview’ tab
  2. Per user: all hosts + EATs executed last 90d
  3. Sort by eats_last_90d ascending — admins who never use
  4. Click ‘Downgrade to editor’ for least-privilege operationalisation

Or via API (advanced — for automation)

SELECT u.email,
ARRAY_AGG(DISTINCT a.hostname) FILTER (WHERE a.hostname IS NOT NULL) AS hosts,
ARRAY_AGG(DISTINCT iu.username) AS local_users,
COUNT(DISTINCT et.id) AS eats_last_90d
FROM users u
LEFT JOIN role_assignments r ON r.user_id = u.id
LEFT JOIN inventory_users iu ON iu.email = u.email -- explicit link, no auto-correlate
LEFT JOIN agents a ON a.id = iu.agent_id
LEFT JOIN emergency_tokens et ON et.user_id = u.id
AND et.issued_at >= NOW() - INTERVAL '90 days'
WHERE u.tenant_id = $1::UUID
AND r.role = 'admin'
GROUP BY u.email
ORDER BY eats_last_90d DESC NULLS LAST;

What you can spot:

  • Admins who never executed an EAT → downgrade candidate
  • Admins on more hosts than strictly needed → least-privilege check
  • Admins without a local-user link → dashboard only, no shell

7. Backup evidence per host, last 90 days

In the dashboard

  1. Sidebar → Inventory → Backups tab → filter tag ‘production’
  2. Per host: successful_runs_90d + failed_runs_90d
  3. Sort ascending on successful — 0 on prod = audit finding
  4. Click ‘Export evidence’ for your ISO 27001 A.8.13 report

Or via API (advanced — for automation)

SELECT a.hostname,
b.tool,
b.destination,
b.last_successful_run,
(SELECT COUNT(*) FROM backup_inventory bi
WHERE bi.agent_id = b.agent_id
AND bi.run_at >= NOW() - INTERVAL '90 days'
AND bi.success = true) AS successful_runs_90d,
(SELECT COUNT(*) FROM backup_inventory bi
WHERE bi.agent_id = b.agent_id
AND bi.run_at >= NOW() - INTERVAL '90 days'
AND bi.success = false) AS failed_runs_90d
FROM backup_configs b
JOIN agents a ON a.id = b.agent_id
WHERE b.tenant_id = $1::UUID
AND 'production' = ANY(a.tags)
ORDER BY successful_runs_90d ASC;

successful_runs_90d = 0 on a prod host is an audit finding (ISO 27001 A.8.13 / NIS2 §2(c) business continuity).


8. Retention check — how long do we keep evidence

In the dashboard

  1. Sidebar → Trust Score → ‘Evidence continuity’ component
  2. Table shows per evidence-table oldest row + newest
  3. At a glance: green >12m, red <12m (NIS2 minimum)
  4. Click red row → details + remediation suggestions

Or via API (advanced — for automation)

SELECT 'audit_log' AS table_name,
MIN(created_at) AS oldest, MAX(created_at) AS newest,
COUNT(*) AS rows
FROM audit_log WHERE tenant_id=$1::UUID
UNION ALL
SELECT 'emergency_tokens',
MIN(issued_at), MAX(issued_at), COUNT(*)
FROM emergency_tokens WHERE tenant_id=$1::UUID
UNION ALL
SELECT 'transparency_log',
MIN(appended_at), MAX(appended_at), COUNT(*)
FROM transparency_log WHERE tenant_id=$1::UUID
UNION ALL
SELECT 'audit_packs',
MIN(month_start), MAX(month_start), COUNT(*)
FROM audit_packs WHERE tenant_id=$1::UUID;

NIS2 requires (per the Belgian transposition): minimum 12 months for audit evidence. The Trust Score evidence_continuity component penalises tenants with less than 12 months continuum — visible in /trust-score/v12/tenant.