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
- Login as auditor in /auditor → Sidebar → Audit Packs
- Download .jsonl.gz + .pdf + .sig for the month
- Click ‘Download verify-CLI’ to grab the offline verifier
- 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 pages2026-04.sig— Ed25519 signature overmanifest_hash
Verify locally (no network round-trip to monsys):
Or via API (advanced — for automation)
wget https://get.monsys.ai/monsys-verify-eat-linux-x64chmod +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.pubWhat the CLI checks:
- Computes
manifest_hash = sha256(manifest)— must match.sigcontents - Walks every line in JSONL.gz, chains sha256 → must end at
hash_chain_rootin the manifest - 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 tampered2. Prove CVE-2026-XXXX was patched within 7 days
In the dashboard
- Sidebar → Kernel CVEs → search CVE-2026-XXXX
- ‘Timeline’ tab shows per host: detected, patched, operator
- Filter ‘Time to patch > 7 days’ isolates SLA breaches
- ‘Export for audit’ button → CSV with all columns
Three auditor questions:
- When was the CVE first detected?
- When was it patched per host?
- 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
- Sidebar → Audit log (under MANAGE)
- Filter: event_type = emergency_token_issued + Q1 date range
- Per row: operator, target, actions JSON, exit_code
- ‘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:
./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
- Sidebar → Compliance → pick framework NIS2
- Matrix shows per control: coverage + evidence count + reviewed status
- Filter reviewed_status=‘draft’ to see outstanding review work
- ‘Export as CSV’ button for your quarterly report
/compliance/NIS2 shows a matrix:
| Control | Coverage | Evidence count | Reviewed |
|---|---|---|---|
| NIS2-Art21-2-a | automatic | 247 | ✓ |
| NIS2-Art21-2-b | partial | 12 | draft |
| NIS2-Art21-2-c | manual | 0 | draft |
| … |
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)
curl 'https://app.monsys.ai/api/v1/compliance/coverage?framework=NIS2&format=csv' \ -H "Authorization: Bearer $TOKEN" > nis2-coverage-2026-Q1.csv5. Verify cryptographic integrity of one specific evidence row
In the dashboard
- Sidebar → Audit Packs → pick the month
- Click ‘Tamper check’ button in download view
- Paste the suspect JSONL line + line number
- 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)
zcat 2026-04.jsonl.gz | sed -n '847p' > suspicious-line.jsonsha256sum suspicious-line.json# 7c34a9e2b1f0…Compare against the hash_chain in the manifest — every JSONL line
adds its sha256 to the chain:
zcat 2026-04.jsonl.gz | ./monsys-verify-eat-linux-x64 chain-position --line 847# expected_position: 7c34a9e2b1f0…# computed_position: 7c34a9e2b1f0…# ✓ line 847 matches the chainMismatch = 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
- Sidebar → RBAC → ‘Admin overview’ tab
- Per user: all hosts + EATs executed last 90d
- Sort by eats_last_90d ascending — admins who never use
- 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
- Sidebar → Inventory → Backups tab → filter tag ‘production’
- Per host: successful_runs_90d + failed_runs_90d
- Sort ascending on successful — 0 on prod = audit finding
- 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
- Sidebar → Trust Score → ‘Evidence continuity’ component
- Table shows per evidence-table oldest row + newest
- At a glance: green >12m, red <12m (NIS2 minimum)
- 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::UUIDUNION ALLSELECT 'emergency_tokens', MIN(issued_at), MAX(issued_at), COUNT(*) FROM emergency_tokens WHERE tenant_id=$1::UUIDUNION ALLSELECT 'transparency_log', MIN(appended_at), MAX(appended_at), COUNT(*) FROM transparency_log WHERE tenant_id=$1::UUIDUNION ALLSELECT '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.