@blamejs/exceptd-skills 0.16.13 → 0.16.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +4 -0
- package/README.md +5 -5
- package/bin/exceptd.js +2 -0
- package/data/_indexes/_meta.json +16 -15
- package/data/_indexes/activity-feed.json +9 -2
- package/data/_indexes/chains.json +13673 -818
- package/data/_indexes/currency.json +10 -1
- package/data/_indexes/frequency.json +58 -19
- package/data/_indexes/handoff-dag.json +5 -1
- package/data/_indexes/jurisdiction-map.json +2 -1
- package/data/_indexes/section-offsets.json +85 -0
- package/data/_indexes/stale-content.json +1 -1
- package/data/_indexes/summary-cards.json +38 -0
- package/data/_indexes/token-budget.json +53 -3
- package/data/_indexes/trigger-table.json +48 -0
- package/data/_indexes/xref.json +21 -0
- package/data/cwe-catalog.json +7 -1
- package/data/playbooks/audit-log-integrity.json +637 -0
- package/data/playbooks/cred-stores.json +1 -0
- package/data/playbooks/framework.json +1 -0
- package/data/playbooks/mail-server-hardening.json +3 -1
- package/data/playbooks/network-trust.json +2 -1
- package/data/playbooks/vc-wallet-trust.json +2 -1
- package/manifest-snapshot.json +53 -2
- package/manifest-snapshot.sha256 +1 -1
- package/manifest.json +102 -47
- package/package.json +2 -2
- package/sbom.cdx.json +66 -36
- package/skills/audit-log-integrity/skill.md +80 -0
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"id": "audit-log-integrity",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"last_threat_review": "2026-06-02",
|
|
6
|
+
"threat_currency_score": 92,
|
|
7
|
+
"changelog": [
|
|
8
|
+
{
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"date": "2026-06-02",
|
|
11
|
+
"summary": "Initial seven-phase audit-log integrity playbook — tamper-evidence, WORM immutability, and deception posture. Covers the controls that determine whether an audit trail survives the privileged attacker most likely to tamper with it: hash-chain continuity actually VERIFIED on read, audit entries independently signed with a key held off the log-writing host, WORM/Object-Lock in compliance (not governance/override) mode, legal holds that actually block the retention purge, honeytokens seeded AND their trips alerted/triaged, dual-control + audit on break-glass, and separation of the log writer from the log custodian. citation-hygiene covers CITATION integrity; this covers audit-LOG integrity. Maps to NIST 800-53 AU-9/AU-10 intent via SI-2, ISO 27001 A.8.15, NIS2 Art.21, and SOC 2 CC7."
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"owner": "@blamejs/platform-security",
|
|
15
|
+
"air_gap_mode": false,
|
|
16
|
+
"scope": "service",
|
|
17
|
+
"preconditions": [
|
|
18
|
+
{
|
|
19
|
+
"id": "audit-subsystem-source-or-config-read",
|
|
20
|
+
"description": "Agent must read the operator's audit-logging source and/or runtime configuration to inspect chaining, signing, WORM/retention, deception, and break-glass. A host with neither marks the playbook visibility_gap=no_audit_subsystem_inventory.",
|
|
21
|
+
"check": "agent_has_filesystem_read == true OR agent_has_audit_config == true",
|
|
22
|
+
"on_fail": "halt"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"mutex": [],
|
|
26
|
+
"feeds_into": [
|
|
27
|
+
{
|
|
28
|
+
"playbook_id": "cred-stores",
|
|
29
|
+
"condition": "finding.includes_signing_key_colocation == true"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"playbook_id": "framework",
|
|
33
|
+
"condition": "analyze.compliance_theater_check.verdict == 'theater'"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"domain": {
|
|
38
|
+
"name": "Audit-log integrity (tamper-evidence, WORM, deception)",
|
|
39
|
+
"attack_class": "cloud-misconfig",
|
|
40
|
+
"atlas_refs": [],
|
|
41
|
+
"attack_refs": [
|
|
42
|
+
"T1070",
|
|
43
|
+
"T1565.001",
|
|
44
|
+
"T1562.008"
|
|
45
|
+
],
|
|
46
|
+
"cve_refs": [],
|
|
47
|
+
"cwe_refs": [
|
|
48
|
+
"CWE-345",
|
|
49
|
+
"CWE-347",
|
|
50
|
+
"CWE-284",
|
|
51
|
+
"CWE-778"
|
|
52
|
+
],
|
|
53
|
+
"frameworks_in_scope": [
|
|
54
|
+
"nist-800-53",
|
|
55
|
+
"iso-27001-2022",
|
|
56
|
+
"nis2",
|
|
57
|
+
"soc2",
|
|
58
|
+
"uk-caf",
|
|
59
|
+
"au-ism"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"phases": {
|
|
63
|
+
"govern": {
|
|
64
|
+
"jurisdiction_obligations": [
|
|
65
|
+
{
|
|
66
|
+
"jurisdiction": "EU",
|
|
67
|
+
"regulation": "NIS2 Art.23",
|
|
68
|
+
"obligation": "notify_regulator",
|
|
69
|
+
"window_hours": 24,
|
|
70
|
+
"clock_starts": "detect_confirmed",
|
|
71
|
+
"evidence_required": [
|
|
72
|
+
"audit_subsystem_inventory",
|
|
73
|
+
"tamper_or_deletion_evidence"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"jurisdiction": "US",
|
|
78
|
+
"regulation": "spoliation / legal-hold duty",
|
|
79
|
+
"obligation": "preserve_evidence",
|
|
80
|
+
"window_hours": 0,
|
|
81
|
+
"clock_starts": "detect_confirmed",
|
|
82
|
+
"evidence_required": [
|
|
83
|
+
"legal_hold_state",
|
|
84
|
+
"records_at_risk_of_purge"
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"theater_fingerprints": [
|
|
89
|
+
{
|
|
90
|
+
"pattern_id": "we-log-everything",
|
|
91
|
+
"claim": "We log everything, so we have an audit trail.",
|
|
92
|
+
"fast_detection_test": "Logging volume is not integrity. Ask whether the chain is VERIFIED on read, signed with an off-host key, and stored compliance-WORM — a complete log a privileged attacker can rewrite is not a trail."
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"pattern_id": "immutable-storage-checkbox",
|
|
96
|
+
"claim": "Our log storage is immutable / WORM.",
|
|
97
|
+
"fast_detection_test": "Ask which MODE — governance/override (an admin can delete) or compliance (no one can until expiry). Governance-mode immutability falls to the compromised admin credential."
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"pattern_id": "legal-hold-flag",
|
|
101
|
+
"claim": "Records under legal hold are preserved.",
|
|
102
|
+
"fast_detection_test": "Confirm the PURGE job consults the hold and refuses deletion. A hold flag that does not gate the lifecycle job preserves nothing."
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"framework_context": {
|
|
106
|
+
"gap_summary": "Org logging controls require that events are recorded and monitored, equating log presence with an audit trail. None require the integrity model — verified hash-chain continuity, off-host signing, compliance-WORM, writer/custodian separation — that resists the privileged or insider attacker most likely to tamper with the trail.",
|
|
107
|
+
"lag_score": 73,
|
|
108
|
+
"per_framework_gaps": [
|
|
109
|
+
{
|
|
110
|
+
"framework": "iso-27001-2022",
|
|
111
|
+
"control_id": "A.8.15",
|
|
112
|
+
"designed_for": "logging of activities",
|
|
113
|
+
"insufficient_because": "attested by \"we log and protect logs\" without verifying hash-chain continuity, independent signing, or immutability against a privileged attacker."
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"framework": "soc2",
|
|
117
|
+
"control_id": "CC7.2",
|
|
118
|
+
"designed_for": "monitoring of controls",
|
|
119
|
+
"insufficient_because": "satisfied by logs + alerts; does not require the trail be immutable to the privileged identity most likely to tamper with it."
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
"skill_preload": [
|
|
124
|
+
"audit-log-integrity",
|
|
125
|
+
"incident-response-playbook",
|
|
126
|
+
"cloud-security",
|
|
127
|
+
"framework-gap-analysis",
|
|
128
|
+
"compliance-theater",
|
|
129
|
+
"policy-exception-gen"
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
"direct": {
|
|
133
|
+
"threat_context": "An audit trail is a control only if it survives the attacker who wants it gone. Anti-forensic tampering (T1070) and stored-data manipulation (T1565.001) target exactly the log that would expose the intrusion. The high-value gaps are not missing logs but missing INTEGRITY: a hash chain never verified, a signing key on the same host as the writer, governance-mode WORM a compromised admin can delete, a legal hold that does not block the purge, and no honeytoken to catch the access in the first place. These are posture gaps, not CVEs.",
|
|
134
|
+
"rwep_threshold": {
|
|
135
|
+
"escalate": 55,
|
|
136
|
+
"monitor": 35,
|
|
137
|
+
"close": 20
|
|
138
|
+
},
|
|
139
|
+
"framework_lag_declaration": "A clean \"we log and monitor\" audit is NON-EVIDENCE for audit-log integrity; it confirms log presence and alerting, not verified-chain continuity, off-host signing, compliance-WORM, or writer/custodian separation.",
|
|
140
|
+
"skill_chain": [
|
|
141
|
+
{
|
|
142
|
+
"skill": "audit-log-integrity",
|
|
143
|
+
"purpose": "enumerate the tamper-evidence / WORM / deception / separation checks"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"skill": "incident-response-playbook",
|
|
147
|
+
"purpose": "frame the evidence-preservation and spoliation obligations the trail must meet"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"skill": "compliance-theater",
|
|
151
|
+
"purpose": "separate \"we log everything / it is immutable\" claims from verified integrity"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"skill": "framework-gap-analysis",
|
|
155
|
+
"purpose": "map findings to the A.8.15 / CC7 gaps the controls do not cover"
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
"token_budget": {
|
|
159
|
+
"estimated_total": 12000,
|
|
160
|
+
"breakdown": {
|
|
161
|
+
"govern": 1200,
|
|
162
|
+
"direct": 800,
|
|
163
|
+
"look": 4000,
|
|
164
|
+
"detect": 3800,
|
|
165
|
+
"analyze": 1500,
|
|
166
|
+
"validate": 500,
|
|
167
|
+
"close": 200
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"look": {
|
|
172
|
+
"artifacts": [
|
|
173
|
+
{
|
|
174
|
+
"id": "audit-chain-and-signing-config",
|
|
175
|
+
"type": "audit_trail",
|
|
176
|
+
"source": "grep for audit-chain / chain-writer / audit-sign / prior_hash / Ed25519 / checkpoint across the audit subsystem and config.",
|
|
177
|
+
"description": "How the audit log is chained and signed, and where the signing key lives relative to the writer.",
|
|
178
|
+
"required": true,
|
|
179
|
+
"air_gap_alternative": "Inspect the audit-chain source for a verification path and the signing-key location."
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"id": "worm-and-retention-config",
|
|
183
|
+
"type": "audit_trail",
|
|
184
|
+
"source": "grep for worm / object-lock / immutab / retention / legal-hold / compliance-mode / governance-mode across the storage and lifecycle config.",
|
|
185
|
+
"description": "Whether the evidence store is compliance-mode immutable and whether legal holds block the purge job.",
|
|
186
|
+
"required": true,
|
|
187
|
+
"air_gap_alternative": "Inspect the storage lifecycle config for Object-Lock mode and the purge job for a legal-hold gate."
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"id": "deception-config",
|
|
191
|
+
"type": "audit_trail",
|
|
192
|
+
"source": "grep for honeytoken / canary / decoy / tripped across the deception layer and alert routing.",
|
|
193
|
+
"description": "Whether honeytokens are seeded on high-value surfaces and whether a trip is alerted + triaged.",
|
|
194
|
+
"required": false,
|
|
195
|
+
"air_gap_alternative": "Inspect the honeytoken module for seeded canaries and the alert-routing for trip handling."
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"id": "break-glass-and-duty-separation",
|
|
199
|
+
"type": "audit_trail",
|
|
200
|
+
"source": "grep for break-glass / dual-control / emergency access / separation of duties / append-only across the privileged-access and log-permission config.",
|
|
201
|
+
"description": "Whether break-glass requires dual control + audit, and whether the log writer is separated from the log custodian.",
|
|
202
|
+
"required": false,
|
|
203
|
+
"air_gap_alternative": "Inspect the break-glass + dual-control modules and the log-store permission model."
|
|
204
|
+
}
|
|
205
|
+
],
|
|
206
|
+
"collection_scope": {
|
|
207
|
+
"time_window": "current audit-subsystem configuration + source state (point-in-time posture audit)",
|
|
208
|
+
"asset_scope": "the system-of-record audit trail, its storage lifecycle, the deception layer, and the privileged-access (break-glass) path"
|
|
209
|
+
},
|
|
210
|
+
"environment_assumptions": [
|
|
211
|
+
{
|
|
212
|
+
"assumption": "The operator maintains a system-of-record audit trail (not only ephemeral operational logs).",
|
|
213
|
+
"if_false": "If only ephemeral operational logs exist with no audit/compliance obligation, the WORM/legal-hold/signing indicators are reduced-scope; focus on writer/custodian separation."
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"assumption": "Audit-subsystem source or config is readable.",
|
|
217
|
+
"if_false": "Mark visibility_gap=no_audit_subsystem_inventory and report only what the audit-write path reveals."
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
"fallback_if_unavailable": [
|
|
221
|
+
{
|
|
222
|
+
"artifact_id": "deception-config",
|
|
223
|
+
"fallback_action": "If no deception layer is intended, note honeytokens as a recommended addition rather than a finding-by-default on low-value systems.",
|
|
224
|
+
"confidence_impact": "low"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"artifact_id": "break-glass-and-duty-separation",
|
|
228
|
+
"fallback_action": "If no break-glass mechanism exists, skip that indicator and focus on writer/custodian separation.",
|
|
229
|
+
"confidence_impact": "none"
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
"detect": {
|
|
234
|
+
"indicators": [
|
|
235
|
+
{
|
|
236
|
+
"id": "audit-hash-chain-not-verified",
|
|
237
|
+
"type": "config_value",
|
|
238
|
+
"value": "The append-only audit log maintains a hash chain (each entry chained to the prior) but the chain is never VERIFIED on read/replay, so a break or rewrite is not detected.",
|
|
239
|
+
"description": "A hash chain only provides tamper-EVIDENCE if it is checked. An attacker who rewrites history and recomputes the chain — or simply truncates it — goes undetected if nothing verifies continuity.",
|
|
240
|
+
"confidence": "high",
|
|
241
|
+
"deterministic": false,
|
|
242
|
+
"attack_ref": "T1565.001",
|
|
243
|
+
"false_positive_checks_required": [
|
|
244
|
+
"Confirm a verification path actually walks the chain (entry N hash == prior-hash field of N+1) on read/replay/export and fails closed on a break — a chain that is written but never verified is the finding.",
|
|
245
|
+
"A log shipped to an external WORM/notarisation service that verifies continuity there is safe; the finding is no verification anywhere in the consumer path."
|
|
246
|
+
]
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"id": "audit-log-not-signed-or-key-colocated",
|
|
250
|
+
"type": "config_value",
|
|
251
|
+
"value": "Audit entries (or chain checkpoints) are not cryptographically signed, OR the signing key is stored on the same host/identity that writes the log it signs.",
|
|
252
|
+
"description": "Without an independent signature an insider or a host-compromise can forge or rewrite entries; a co-located signing key means whoever controls the log also controls its signature.",
|
|
253
|
+
"confidence": "high",
|
|
254
|
+
"deterministic": false,
|
|
255
|
+
"attack_ref": "T1565.001",
|
|
256
|
+
"false_positive_checks_required": [
|
|
257
|
+
"Confirm the audit chain is signed (e.g. Ed25519 checkpoints) AND the signing key is held by a separate identity/KMS/HSM, not the log-writing process — a self-signed log under the writer's own key is the finding.",
|
|
258
|
+
"A log whose integrity is anchored externally (transparency log / notary / external WORM) does not need a co-located key; verify the external anchor exists."
|
|
259
|
+
]
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"id": "worm-immutability-not-enforced",
|
|
263
|
+
"type": "config_value",
|
|
264
|
+
"value": "The audit/evidence store is configured WORM/immutable in a governance/override mode (deletable by a privileged role) rather than a compliance mode that even the root account cannot shorten or delete before the retention period.",
|
|
265
|
+
"description": "Governance-mode WORM is reversible by a sufficiently privileged compromised credential, defeating the immutability the audit trail depends on; only compliance-mode Object-Lock resists a privileged attacker.",
|
|
266
|
+
"confidence": "medium",
|
|
267
|
+
"deterministic": false,
|
|
268
|
+
"attack_ref": "T1070",
|
|
269
|
+
"false_positive_checks_required": [
|
|
270
|
+
"Confirm the store uses compliance-mode immutability (no role, including root/admin, can delete or shorten the retention before expiry) — governance-mode that an admin can override is the finding.",
|
|
271
|
+
"A store with no immutability requirement (ephemeral operational logs, not the system-of-record audit trail) is not in scope for this indicator."
|
|
272
|
+
]
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"id": "legal-hold-not-blocking-purge",
|
|
276
|
+
"type": "config_value",
|
|
277
|
+
"value": "A legal hold is advisory (a flag/annotation) and does not actually block the retention/lifecycle purge job from deleting held records.",
|
|
278
|
+
"description": "If the hold does not gate the purge, records under legal hold are deleted on the normal retention schedule — destroying evidence that must be preserved (spoliation) and removing audit history.",
|
|
279
|
+
"confidence": "medium",
|
|
280
|
+
"deterministic": false,
|
|
281
|
+
"attack_ref": "T1565.001",
|
|
282
|
+
"false_positive_checks_required": [
|
|
283
|
+
"Confirm the retention/purge job consults the legal-hold state and refuses to delete held records (fail-closed) — a hold that only annotates without blocking the purge is the finding.",
|
|
284
|
+
"A system with no legal-hold requirement (no regulatory preservation obligation) is not in scope."
|
|
285
|
+
]
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"id": "honeytoken-not-deployed",
|
|
289
|
+
"type": "config_value",
|
|
290
|
+
"value": "No honeytokens / canary credentials are seeded across the high-value surfaces (canary API keys, session tokens, decoy URLs, decoy row IDs) where an attacker would forage.",
|
|
291
|
+
"description": "Honeytokens are a high-fidelity detection control: a single read/use is near-zero-false-positive evidence of unauthorized access. Their absence removes the cheapest tripwire for exactly the access that precedes log tampering.",
|
|
292
|
+
"confidence": "low",
|
|
293
|
+
"deterministic": false,
|
|
294
|
+
"attack_ref": "T1070",
|
|
295
|
+
"false_positive_checks_required": [
|
|
296
|
+
"Confirm canary tokens are seeded in the credential stores / databases / object stores an intruder would touch — a deployment with zero honeytokens on its high-value surfaces is the finding.",
|
|
297
|
+
"A small, low-value system where deception adds no detection benefit may legitimately omit it; the finding is a high-value surface with no tripwire."
|
|
298
|
+
]
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"id": "honeytoken-trip-not-triaged",
|
|
302
|
+
"type": "config_value",
|
|
303
|
+
"value": "Honeytokens exist but a tripped canary does not emit a routed, alerted, triaged signal (it is logged silently or to a channel no one watches).",
|
|
304
|
+
"description": "A canary that fires into the void provides no detection. The tripwire only works if the trip is alerted and triaged with the observing actor's identity/context for a SOC pivot.",
|
|
305
|
+
"confidence": "low",
|
|
306
|
+
"deterministic": false,
|
|
307
|
+
"attack_ref": "T1070",
|
|
308
|
+
"false_positive_checks_required": [
|
|
309
|
+
"Confirm a honeytoken trip raises an alert routed to a monitored channel with the 5-W context (who/what/when/where/how) — a trip that only writes a log line is the finding.",
|
|
310
|
+
"A deployment with no honeytokens at all is covered by the prior indicator, not this one."
|
|
311
|
+
]
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"id": "break-glass-no-dual-control-or-audit",
|
|
315
|
+
"type": "config_value",
|
|
316
|
+
"value": "Break-glass / emergency-privileged access is not gated by dual control (two-person) and/or its use is not independently audited and alerted.",
|
|
317
|
+
"description": "Unwitnessed, unaudited break-glass access is the standing path to disable logging or tamper with the audit trail with legitimate-looking privilege.",
|
|
318
|
+
"confidence": "medium",
|
|
319
|
+
"deterministic": false,
|
|
320
|
+
"attack_ref": "T1562.008",
|
|
321
|
+
"false_positive_checks_required": [
|
|
322
|
+
"Confirm break-glass requires dual control (or at minimum independent approval) AND every use raises an immediate audited alert to a separate party — single-actor, unalerted break-glass is the finding.",
|
|
323
|
+
"A system with no privileged break-glass mechanism has no such path; not in scope."
|
|
324
|
+
]
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"id": "audit-log-deletable-by-writing-identity",
|
|
328
|
+
"type": "config_value",
|
|
329
|
+
"value": "The application identity/role that WRITES the audit log also has delete/rotate/truncate permission on the same log store (no separation of duties between writer and custodian).",
|
|
330
|
+
"description": "If the writer can also delete, a compromise of the application identity erases its own trail — there is no independent custodian to preserve the record.",
|
|
331
|
+
"confidence": "medium",
|
|
332
|
+
"deterministic": false,
|
|
333
|
+
"attack_ref": "T1070",
|
|
334
|
+
"false_positive_checks_required": [
|
|
335
|
+
"Confirm the log-writing identity is append-only and a SEPARATE custodian identity/role holds delete/rotate rights (or the store is compliance-WORM) — a writer that can also delete its own log is the finding.",
|
|
336
|
+
"A store that is compliance-mode immutable makes delete impossible for anyone, satisfying this regardless of the writer's nominal permission."
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
],
|
|
340
|
+
"false_positive_profile": [
|
|
341
|
+
{
|
|
342
|
+
"indicator_id": "audit-hash-chain-not-verified",
|
|
343
|
+
"benign_pattern": "A control enforced externally (external WORM/notary, KMS-held signing key, compliance-mode Object-Lock) or a surface where the control is genuinely not required.",
|
|
344
|
+
"distinguishing_test": "Confirm a verification path actually walks the chain (entry N hash == prior-hash field of N+1) on read/replay/export and fails closed on a break — a chain that is written but never verified is the finding."
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"indicator_id": "audit-log-not-signed-or-key-colocated",
|
|
348
|
+
"benign_pattern": "A control enforced externally (external WORM/notary, KMS-held signing key, compliance-mode Object-Lock) or a surface where the control is genuinely not required.",
|
|
349
|
+
"distinguishing_test": "Confirm the audit chain is signed (e.g. Ed25519 checkpoints) AND the signing key is held by a separate identity/KMS/HSM, not the log-writing process — a self-signed log under the writer's own key is the finding."
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"indicator_id": "worm-immutability-not-enforced",
|
|
353
|
+
"benign_pattern": "A control enforced externally (external WORM/notary, KMS-held signing key, compliance-mode Object-Lock) or a surface where the control is genuinely not required.",
|
|
354
|
+
"distinguishing_test": "Confirm the store uses compliance-mode immutability (no role, including root/admin, can delete or shorten the retention before expiry) — governance-mode that an admin can override is the finding."
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"indicator_id": "legal-hold-not-blocking-purge",
|
|
358
|
+
"benign_pattern": "A control enforced externally (external WORM/notary, KMS-held signing key, compliance-mode Object-Lock) or a surface where the control is genuinely not required.",
|
|
359
|
+
"distinguishing_test": "Confirm the retention/purge job consults the legal-hold state and refuses to delete held records (fail-closed) — a hold that only annotates without blocking the purge is the finding."
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"indicator_id": "honeytoken-not-deployed",
|
|
363
|
+
"benign_pattern": "A control enforced externally (external WORM/notary, KMS-held signing key, compliance-mode Object-Lock) or a surface where the control is genuinely not required.",
|
|
364
|
+
"distinguishing_test": "Confirm canary tokens are seeded in the credential stores / databases / object stores an intruder would touch — a deployment with zero honeytokens on its high-value surfaces is the finding."
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
"indicator_id": "honeytoken-trip-not-triaged",
|
|
368
|
+
"benign_pattern": "A control enforced externally (external WORM/notary, KMS-held signing key, compliance-mode Object-Lock) or a surface where the control is genuinely not required.",
|
|
369
|
+
"distinguishing_test": "Confirm a honeytoken trip raises an alert routed to a monitored channel with the 5-W context (who/what/when/where/how) — a trip that only writes a log line is the finding."
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"indicator_id": "break-glass-no-dual-control-or-audit",
|
|
373
|
+
"benign_pattern": "A control enforced externally (external WORM/notary, KMS-held signing key, compliance-mode Object-Lock) or a surface where the control is genuinely not required.",
|
|
374
|
+
"distinguishing_test": "Confirm break-glass requires dual control (or at minimum independent approval) AND every use raises an immediate audited alert to a separate party — single-actor, unalerted break-glass is the finding."
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"indicator_id": "audit-log-deletable-by-writing-identity",
|
|
378
|
+
"benign_pattern": "A control enforced externally (external WORM/notary, KMS-held signing key, compliance-mode Object-Lock) or a surface where the control is genuinely not required.",
|
|
379
|
+
"distinguishing_test": "Confirm the log-writing identity is append-only and a SEPARATE custodian identity/role holds delete/rotate rights (or the store is compliance-WORM) — a writer that can also delete its own log is the finding."
|
|
380
|
+
}
|
|
381
|
+
],
|
|
382
|
+
"minimum_signal": {
|
|
383
|
+
"detected": "At least one integrity control on the system-of-record trail is absent — chain unverified, log unsigned or key co-located, governance-mode (not compliance) WORM, legal hold not blocking purge, or the writer can delete its own log.",
|
|
384
|
+
"inconclusive": "A control is enforced by external infrastructure the audit could not read (external WORM/notary, KMS) — record as visibility_gap, not a clean result.",
|
|
385
|
+
"not_detected": "The trail verifies its hash chain on read, is signed with an off-host key, is stored compliance-WORM, blocks purge under legal hold, separates writer from custodian, and (on high-value surfaces) seeds + triages honeytokens."
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
"analyze": {
|
|
389
|
+
"rwep_inputs": [
|
|
390
|
+
{
|
|
391
|
+
"signal_id": "worm-immutability-not-enforced",
|
|
392
|
+
"rwep_factor": "blast_radius",
|
|
393
|
+
"weight": 20
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
"signal_id": "audit-hash-chain-not-verified",
|
|
397
|
+
"rwep_factor": "public_poc",
|
|
398
|
+
"weight": 20
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
"signal_id": "audit-log-deletable-by-writing-identity",
|
|
402
|
+
"rwep_factor": "active_exploitation",
|
|
403
|
+
"weight": 10
|
|
404
|
+
}
|
|
405
|
+
],
|
|
406
|
+
"blast_radius_model": {
|
|
407
|
+
"scope_question": "Does the gap let a single compromised privileged/application identity rewrite or delete the system-of-record trail without detection?",
|
|
408
|
+
"scoring_rubric": [
|
|
409
|
+
{
|
|
410
|
+
"condition": "A single compromised identity can rewrite/delete the trail AND there is no honeytoken or external anchor to catch it",
|
|
411
|
+
"blast_radius_score": 5,
|
|
412
|
+
"description": "Full anti-forensic erasure with no tripwire"
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
"condition": "Trail is tamperable but a partial control (external anchor / honeytoken / alerting) would surface it",
|
|
416
|
+
"blast_radius_score": 3,
|
|
417
|
+
"description": "Tampering is possible but detectable after the fact"
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
"condition": "Only low-value operational logs affected; system-of-record trail is protected",
|
|
421
|
+
"blast_radius_score": 2,
|
|
422
|
+
"description": "Limited forensic impact"
|
|
423
|
+
}
|
|
424
|
+
]
|
|
425
|
+
},
|
|
426
|
+
"compliance_theater_check": {
|
|
427
|
+
"claim": "We log everything to immutable storage with legal holds, so the audit trail is trustworthy.",
|
|
428
|
+
"audit_evidence": "Log volume dashboards, an \"immutable storage\" checkbox, a legal-hold flag.",
|
|
429
|
+
"reality_test": "Verify the chain is checked on read, the signing key is off-host, the WORM mode is compliance (admin cannot delete), and the purge job honors the hold. If a privileged identity can rewrite or delete the trail undetected, the logging is not an audit trail.",
|
|
430
|
+
"theater_verdict_if_gap": "theater"
|
|
431
|
+
},
|
|
432
|
+
"framework_gap_mapping": [
|
|
433
|
+
{
|
|
434
|
+
"finding_id": "worm-immutability-not-enforced",
|
|
435
|
+
"framework": "iso-27001-2022",
|
|
436
|
+
"claimed_control": "A.8.15 (logging)",
|
|
437
|
+
"actual_gap": "A.8.15 requires logs be protected, not that immutability resist the privileged admin via compliance-mode Object-Lock."
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
"finding_id": "audit-hash-chain-not-verified",
|
|
441
|
+
"framework": "soc2",
|
|
442
|
+
"claimed_control": "CC7.2 (monitoring)",
|
|
443
|
+
"actual_gap": "CC7 monitoring is satisfied by logs + alerts; it does not require the chain be verified for continuity."
|
|
444
|
+
}
|
|
445
|
+
],
|
|
446
|
+
"escalation_criteria": [
|
|
447
|
+
{
|
|
448
|
+
"condition": "A single compromised identity can rewrite/delete the system-of-record trail undetected",
|
|
449
|
+
"action": "raise_severity"
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"condition": "Records under legal hold are reachable by the retention purge",
|
|
453
|
+
"action": "trigger_playbook"
|
|
454
|
+
}
|
|
455
|
+
]
|
|
456
|
+
},
|
|
457
|
+
"validate": {
|
|
458
|
+
"remediation_paths": [
|
|
459
|
+
{
|
|
460
|
+
"id": "verify-chain-and-sign-off-host",
|
|
461
|
+
"description": "Verify the audit hash chain on every read/replay/export and fail closed on a break; sign chain checkpoints with a key held by a separate identity / KMS / HSM, not the log-writing process.",
|
|
462
|
+
"preconditions": [
|
|
463
|
+
"operator controls the audit subsystem"
|
|
464
|
+
],
|
|
465
|
+
"priority": 1,
|
|
466
|
+
"for_signals": [
|
|
467
|
+
"audit-hash-chain-not-verified",
|
|
468
|
+
"audit-log-not-signed-or-key-colocated"
|
|
469
|
+
]
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
"id": "compliance-worm-and-hold-gate",
|
|
473
|
+
"description": "Move the evidence store to compliance-mode immutability (no role can delete before expiry) and make the retention purge consult and honor legal holds (fail closed).",
|
|
474
|
+
"preconditions": [],
|
|
475
|
+
"priority": 1,
|
|
476
|
+
"for_signals": [
|
|
477
|
+
"worm-immutability-not-enforced",
|
|
478
|
+
"legal-hold-not-blocking-purge"
|
|
479
|
+
]
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"id": "separate-writer-from-custodian",
|
|
483
|
+
"description": "Make the log-writing identity append-only and assign delete/rotate rights to a separate custodian identity (or rely on compliance-WORM so deletion is impossible for anyone).",
|
|
484
|
+
"preconditions": [],
|
|
485
|
+
"priority": 2,
|
|
486
|
+
"for_signals": [
|
|
487
|
+
"audit-log-deletable-by-writing-identity"
|
|
488
|
+
]
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"id": "deploy-and-triage-honeytokens",
|
|
492
|
+
"description": "Seed honeytokens (canary keys/sessions/URLs/row IDs) across high-value surfaces and route a trip to an alerted, triaged channel with full actor context.",
|
|
493
|
+
"preconditions": [],
|
|
494
|
+
"priority": 2,
|
|
495
|
+
"for_signals": [
|
|
496
|
+
"honeytoken-not-deployed",
|
|
497
|
+
"honeytoken-trip-not-triaged"
|
|
498
|
+
]
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
"id": "dual-control-break-glass",
|
|
502
|
+
"description": "Gate break-glass / emergency access behind dual control and raise an immediate independent audited alert on every use.",
|
|
503
|
+
"preconditions": [],
|
|
504
|
+
"priority": 3,
|
|
505
|
+
"for_signals": [
|
|
506
|
+
"break-glass-no-dual-control-or-audit"
|
|
507
|
+
]
|
|
508
|
+
}
|
|
509
|
+
],
|
|
510
|
+
"validation_tests": [
|
|
511
|
+
{
|
|
512
|
+
"id": "chain-break-detected",
|
|
513
|
+
"test": "Rewrite or truncate an entry in a copy of the audit log and run the verification path.",
|
|
514
|
+
"expected_result": "Verification fails closed and flags the break; the rewritten log is not accepted as valid.",
|
|
515
|
+
"test_type": "negative"
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
"id": "privileged-delete-refused",
|
|
519
|
+
"test": "As the highest-privileged role, attempt to delete an in-retention record from the compliance-WORM store.",
|
|
520
|
+
"expected_result": "Deletion is refused — compliance-mode immutability holds against root/admin.",
|
|
521
|
+
"test_type": "negative"
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
"id": "hold-blocks-purge",
|
|
525
|
+
"test": "Place a record under legal hold and run the retention purge job past its normal expiry.",
|
|
526
|
+
"expected_result": "The held record is preserved; the purge skips it.",
|
|
527
|
+
"test_type": "negative"
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
"id": "legitimate-write-and-verify",
|
|
531
|
+
"test": "Append a normal audit entry and run the verification + signing path.",
|
|
532
|
+
"expected_result": "Entry is chained, signed, and verifies — no regression on the legitimate path.",
|
|
533
|
+
"test_type": "functional"
|
|
534
|
+
}
|
|
535
|
+
],
|
|
536
|
+
"residual_risk_statement": {
|
|
537
|
+
"risk": "Collusion between the separated writer and custodian, or compromise of the off-host signing key / WORM authority, can still defeat the trail.",
|
|
538
|
+
"why_remains": "Separation and immutability raise the bar to multi-party or anchor compromise; they do not eliminate a sufficiently privileged collusion, which is handled by key-management and oversight.",
|
|
539
|
+
"acceptance_level": "ciso"
|
|
540
|
+
},
|
|
541
|
+
"evidence_requirements": [
|
|
542
|
+
{
|
|
543
|
+
"evidence_type": "config_diff",
|
|
544
|
+
"description": "The chain-verification, signing-key location, WORM mode, legal-hold gate, deception, and writer/custodian permission configuration as audited.",
|
|
545
|
+
"retention_period": "1 year"
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
"evidence_type": "exploit_replay_negative",
|
|
549
|
+
"description": "Outcomes of the chain-break / privileged-delete / hold-blocks-purge negative tests plus the legitimate-write functional test.",
|
|
550
|
+
"retention_period": "1 year"
|
|
551
|
+
}
|
|
552
|
+
],
|
|
553
|
+
"regression_trigger": [
|
|
554
|
+
{
|
|
555
|
+
"condition": "A change to the audit store, signing key, retention policy, or privileged-access model",
|
|
556
|
+
"interval": "on change"
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
"condition": "Periodic re-attestation of audit-log integrity posture",
|
|
560
|
+
"interval": "quarterly"
|
|
561
|
+
}
|
|
562
|
+
]
|
|
563
|
+
},
|
|
564
|
+
"close": {
|
|
565
|
+
"evidence_package": {
|
|
566
|
+
"bundle_format": "csaf-2.0",
|
|
567
|
+
"contents": [
|
|
568
|
+
"audit-subsystem config snapshot",
|
|
569
|
+
"per-indicator findings + false-positive disposition",
|
|
570
|
+
"negative + functional test results",
|
|
571
|
+
"framework gap mapping"
|
|
572
|
+
],
|
|
573
|
+
"destination": ".exceptd/attestations/<session_id>/attestation.json"
|
|
574
|
+
},
|
|
575
|
+
"learning_loop": {
|
|
576
|
+
"enabled": true,
|
|
577
|
+
"lesson_template": {
|
|
578
|
+
"attack_vector": "Anti-forensic tampering or deletion of the system-of-record audit trail by a privileged/insider identity, undetected because the trail lacked verified integrity, compliance-WORM, or a honeytoken tripwire.",
|
|
579
|
+
"control_gap": "Audit trail not chain-verified / off-host-signed / compliance-WORM, or the writer can delete its own log.",
|
|
580
|
+
"framework_gap": "ISO A.8.15 + SOC 2 CC7 + NIS2 Art.21 require logging and monitoring but not integrity against the privileged attacker.",
|
|
581
|
+
"new_control_requirement": "The system-of-record trail MUST be chain-verified, signed off-host, compliance-WORM, and separated writer-from-custodian."
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
"notification_actions": [
|
|
585
|
+
{
|
|
586
|
+
"obligation_ref": "EU/NIS2 Art.23 24h",
|
|
587
|
+
"deadline": "24h from detect_confirmed",
|
|
588
|
+
"recipient": "national CSIRT / competent authority",
|
|
589
|
+
"evidence_attached": [
|
|
590
|
+
"attestation"
|
|
591
|
+
]
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
"obligation_ref": "US/spoliation / legal-hold duty 0h",
|
|
595
|
+
"deadline": "immediate on detect_confirmed",
|
|
596
|
+
"recipient": "legal / records custodian",
|
|
597
|
+
"evidence_attached": [
|
|
598
|
+
"attestation"
|
|
599
|
+
]
|
|
600
|
+
}
|
|
601
|
+
],
|
|
602
|
+
"exception_generation": {
|
|
603
|
+
"trigger_condition": "A legacy log store cannot adopt compliance-WORM immediately (e.g. a third-party SaaS log sink).",
|
|
604
|
+
"exception_template": {
|
|
605
|
+
"scope": "the specific log store",
|
|
606
|
+
"duration": "90 days",
|
|
607
|
+
"compensating_controls": [
|
|
608
|
+
"ship a signed copy to an external WORM/notary",
|
|
609
|
+
"separate the delete-capable custodian from the writer",
|
|
610
|
+
"honeytoken + alerting on the surface"
|
|
611
|
+
],
|
|
612
|
+
"risk_acceptance_owner": "ciso"
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
"regression_schedule": {
|
|
616
|
+
"next_run": "quarterly or on any audit-store / retention / privileged-access change",
|
|
617
|
+
"trigger": "change to the audit subsystem, or quarterly re-attestation"
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
"directives": [
|
|
622
|
+
{
|
|
623
|
+
"id": "all-audit-trail-integrity",
|
|
624
|
+
"title": "Inventory + integrity-test the system-of-record audit trail end to end",
|
|
625
|
+
"applies_to": {
|
|
626
|
+
"always": true
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
"id": "anti-forensic-tamper-sweep",
|
|
631
|
+
"title": "Targeted sweep for anti-forensic tamperability of the audit trail",
|
|
632
|
+
"applies_to": {
|
|
633
|
+
"attack_technique": "T1070"
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
]
|
|
637
|
+
}
|